mirror of
https://github.com/ZDoom/qzdoom-gpl.git
synced 2025-02-24 12:11:22 +00:00
some changes to the MAPINFO parser which tried to access the texture manager to check if the level name patches exist. That check had to be moved to where the intermission screen is set up. - Fixed: 'bloodcolor' ignored the first parameter value when given a list of integers. Please note that this creates an incompatibility between old and new versions so if you want to create something that works with both 2.2.0 and current versions better use the string format version for the color parameter! - Rewrote the DECORATE property parser so that the parser is completely separated from the property handlers. This should allow reuse of all the handler code for a new format if Doomscript requires one. - Fixed: PClass::InitializeActorInfo copied too many bytes if a subclass's defaults were larger than the parent's. - Moved A_ChangeFlag to thingdef_codeptr.cpp. - Moved translation related code from thingdef_properties.cpp to r_translate.cpp and rewrote the translation parser to use FScanner instead of strtol. - replaced DECORATE's 'alpha default' by 'defaultalpha' for consistency. Since this was never used outside zdoom.pk3 it's not critical. - Removed support for game specific pickup messages because the only thing this was ever used for - Raven's invulnerability item - has already been split up into a Heretic and Hexen version. SVN r1240 (trunk)
585 lines
15 KiB
C++
585 lines
15 KiB
C++
#include "actor.h"
|
|
#include "thingdef/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"
|
|
#include "r_translate.h"
|
|
|
|
static FRandom pr_freezedeath ("FreezeDeath");
|
|
static FRandom pr_icesettics ("IceSetTics");
|
|
static FRandom pr_freeze ("FreezeDeathChunks");
|
|
|
|
|
|
// SwitchableDecoration: Activate and Deactivate change state ---------------
|
|
|
|
class ASwitchableDecoration : public AActor
|
|
{
|
|
DECLARE_CLASS (ASwitchableDecoration, AActor)
|
|
public:
|
|
void Activate (AActor *activator);
|
|
void Deactivate (AActor *activator);
|
|
};
|
|
|
|
IMPLEMENT_CLASS (ASwitchableDecoration)
|
|
|
|
void ASwitchableDecoration::Activate (AActor *activator)
|
|
{
|
|
SetState (FindState(NAME_Active));
|
|
}
|
|
|
|
void ASwitchableDecoration::Deactivate (AActor *activator)
|
|
{
|
|
SetState (FindState(NAME_Inactive));
|
|
}
|
|
|
|
// SwitchingDecoration: Only Activate changes state -------------------------
|
|
|
|
class ASwitchingDecoration : public ASwitchableDecoration
|
|
{
|
|
DECLARE_CLASS (ASwitchingDecoration, ASwitchableDecoration)
|
|
public:
|
|
void Deactivate (AActor *activator) {}
|
|
};
|
|
|
|
IMPLEMENT_CLASS (ASwitchingDecoration)
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_NoBlocking
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_NoBlocking)
|
|
{
|
|
// [RH] Andy Baker's stealth monsters
|
|
if (self->flags & MF_STEALTH)
|
|
{
|
|
self->alpha = OPAQUE;
|
|
self->visdir = 0;
|
|
}
|
|
|
|
self->flags &= ~MF_SOLID;
|
|
|
|
// If the self has a conversation that sets an item to drop, drop that.
|
|
if (self->Conversation != NULL && self->Conversation->DropType != NULL)
|
|
{
|
|
P_DropItem (self, self->Conversation->DropType, -1, 256);
|
|
self->Conversation = NULL;
|
|
return;
|
|
}
|
|
|
|
self->Conversation = NULL;
|
|
|
|
// If the self has attached metadata for items to drop, drop those.
|
|
if (!self->IsKindOf (RUNTIME_CLASS (APlayerPawn))) // [GRB]
|
|
{
|
|
FDropItem *di = self->GetDropItems();
|
|
|
|
if (di != NULL)
|
|
{
|
|
while (di != NULL)
|
|
{
|
|
if (di->Name != NAME_None)
|
|
{
|
|
const PClass *ti = PClass::FindClass(di->Name);
|
|
if (ti) P_DropItem (self, ti, di->amount, di->probability);
|
|
}
|
|
di = di->Next;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_Fall)
|
|
{
|
|
CALL_ACTION(A_NoBlocking, self);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// A_SetFloorClip
|
|
//
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_SetFloorClip)
|
|
{
|
|
self->flags2 |= MF2_FLOORCLIP;
|
|
self->AdjustFloorClip ();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// A_UnSetFloorClip
|
|
//
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_UnSetFloorClip)
|
|
{
|
|
self->flags2 &= ~MF2_FLOORCLIP;
|
|
self->floorclip = 0;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// A_HideThing
|
|
//
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_HideThing)
|
|
{
|
|
self->renderflags |= RF_INVISIBLE;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// A_UnHideThing
|
|
//
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_UnHideThing)
|
|
{
|
|
self->renderflags &= ~RF_INVISIBLE;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_FreezeDeath
|
|
//
|
|
//============================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_FreezeDeath)
|
|
{
|
|
int t = pr_freezedeath();
|
|
self->tics = 75+t+pr_freezedeath();
|
|
self->flags |= MF_SOLID|MF_SHOOTABLE|MF_NOBLOOD|MF_ICECORPSE;
|
|
self->flags2 |= MF2_PUSHABLE|MF2_TELESTOMP|MF2_PASSMOBJ|MF2_SLIDE;
|
|
self->flags3 |= MF3_CRASHED;
|
|
self->height = self->GetDefault()->height;
|
|
S_Sound (self, CHAN_BODY, "misc/freeze", 1, ATTN_NORM);
|
|
|
|
// [RH] Andy Baker's stealth monsters
|
|
if (self->flags & MF_STEALTH)
|
|
{
|
|
self->alpha = OPAQUE;
|
|
self->visdir = 0;
|
|
}
|
|
|
|
if (self->player)
|
|
{
|
|
self->player->damagecount = 0;
|
|
self->player->poisoncount = 0;
|
|
self->player->bonuscount = 0;
|
|
}
|
|
else if (self->flags3&MF3_ISMONSTER && self->special)
|
|
{ // Initiate monster death actions
|
|
LineSpecials [self->special] (NULL, self, false, self->args[0],
|
|
self->args[1], self->args[2], self->args[3], self->args[4]);
|
|
self->special = 0;
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// A_GenericFreezeDeath
|
|
//
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_GenericFreezeDeath)
|
|
{
|
|
self->Translation = TRANSLATION(TRANSLATION_Standard, 7);
|
|
CALL_ACTION(A_FreezeDeath, self);
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_IceSetTics
|
|
//
|
|
//============================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_IceSetTics)
|
|
{
|
|
int floor;
|
|
|
|
self->tics = 70+(pr_icesettics()&63);
|
|
floor = P_GetThingFloorType (self);
|
|
if (Terrains[floor].DamageMOD == NAME_Fire)
|
|
{
|
|
self->tics >>= 2;
|
|
}
|
|
else if (Terrains[floor].DamageMOD == NAME_Ice)
|
|
{
|
|
self->tics <<= 1;
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_FreezeDeathChunks
|
|
//
|
|
//============================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_FreezeDeathChunks)
|
|
{
|
|
|
|
int i;
|
|
int numChunks;
|
|
AActor *mo;
|
|
|
|
if (self->momx || self->momy || self->momz)
|
|
{
|
|
self->tics = 3*TICRATE;
|
|
return;
|
|
}
|
|
S_Sound (self, 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 self 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 self with radius 20 and height 64 creates ~40 chunks.
|
|
numChunks = MAX<int> (4, (self->radius>>FRACBITS)*(self->height>>FRACBITS)/32);
|
|
i = (pr_freeze.Random2()) % (numChunks/4);
|
|
for (i = MAX (24, numChunks + i); i >= 0; i--)
|
|
{
|
|
mo = Spawn("IceChunk",
|
|
self->x + (((pr_freeze()-128)*self->radius)>>7),
|
|
self->y + (((pr_freeze()-128)*self->radius)>>7),
|
|
self->z + (pr_freeze()*self->height/255), ALLOW_REPLACE);
|
|
mo->SetState (mo->SpawnState + (pr_freeze()%3));
|
|
if (mo)
|
|
{
|
|
mo->momz = FixedDiv(mo->z-self->z, self->height)<<2;
|
|
mo->momx = pr_freeze.Random2 () << (FRACBITS-7);
|
|
mo->momy = pr_freeze.Random2 () << (FRACBITS-7);
|
|
CALL_ACTION(A_IceSetTics, mo); // set a random tic wait
|
|
mo->RenderStyle = self->RenderStyle;
|
|
mo->alpha = self->alpha;
|
|
}
|
|
}
|
|
if (self->player)
|
|
{ // attach the player's view to a chunk of ice
|
|
AActor *head = Spawn("IceChunkHead", self->x, self->y,
|
|
self->z + self->player->mo->ViewHeight, ALLOW_REPLACE);
|
|
head->momz = FixedDiv(head->z-self->z, self->height)<<2;
|
|
head->momx = pr_freeze.Random2 () << (FRACBITS-7);
|
|
head->momy = pr_freeze.Random2 () << (FRACBITS-7);
|
|
head->health = self->health;
|
|
head->angle = self->angle;
|
|
if (head->IsKindOf(RUNTIME_CLASS(APlayerPawn)))
|
|
{
|
|
head->player = self->player;
|
|
head->player->mo = static_cast<APlayerPawn*>(head);
|
|
self->player = NULL;
|
|
head->ObtainInventory (self);
|
|
}
|
|
head->pitch = 0;
|
|
head->RenderStyle = self->RenderStyle;
|
|
head->alpha = self->alpha;
|
|
if (head->player->camera == self)
|
|
{
|
|
head->player->camera = head;
|
|
}
|
|
}
|
|
|
|
// [RH] Do some stuff to make this more useful outside Hexen
|
|
if (self->flags4 & MF4_BOSSDEATH)
|
|
{
|
|
CALL_ACTION(A_BossDeath, self);
|
|
}
|
|
CALL_ACTION(A_NoBlocking, self);
|
|
|
|
self->Destroy ();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// CorpseQueue Routines (used by Hexen)
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
// Corpse queue for monsters - this should be saved out
|
|
|
|
class DCorpsePointer : public DThinker
|
|
{
|
|
DECLARE_CLASS (DCorpsePointer, DThinker)
|
|
HAS_OBJECT_POINTERS
|
|
public:
|
|
DCorpsePointer (AActor *ptr);
|
|
void Destroy ();
|
|
void Serialize (FArchive &arc);
|
|
TObjPtr<AActor> Corpse;
|
|
DWORD Count; // Only the first corpse pointer's count is valid.
|
|
private:
|
|
DCorpsePointer () {}
|
|
};
|
|
|
|
IMPLEMENT_POINTY_CLASS(DCorpsePointer)
|
|
DECLARE_POINTER(Corpse)
|
|
END_POINTERS
|
|
|
|
CUSTOM_CVAR(Int, sv_corpsequeuesize, 64, CVAR_ARCHIVE|CVAR_SERVERINFO)
|
|
{
|
|
if (self > 0)
|
|
{
|
|
TThinkerIterator<DCorpsePointer> iterator (STAT_CORPSEPOINTER);
|
|
DCorpsePointer *first = iterator.Next ();
|
|
while (first != NULL && first->Count > (DWORD)self)
|
|
{
|
|
DCorpsePointer *next = iterator.Next ();
|
|
next->Count = first->Count;
|
|
first->Destroy ();
|
|
first = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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 >= (DWORD)sv_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)
|
|
{
|
|
Super::Serialize(arc);
|
|
arc << Corpse << Count;
|
|
}
|
|
|
|
|
|
// throw another corpse on the queue
|
|
DEFINE_ACTION_FUNCTION(AActor, A_QueueCorpse)
|
|
{
|
|
if (sv_corpsequeuesize > 0)
|
|
new DCorpsePointer (self);
|
|
}
|
|
|
|
// Remove an self from the queue (for resurrection)
|
|
DEFINE_ACTION_FUNCTION(AActor, A_DeQueueCorpse)
|
|
{
|
|
TThinkerIterator<DCorpsePointer> iterator (STAT_CORPSEPOINTER);
|
|
DCorpsePointer *corpsePtr;
|
|
|
|
while ((corpsePtr = iterator.Next()) != NULL)
|
|
{
|
|
if (corpsePtr->Corpse == self)
|
|
{
|
|
corpsePtr->Corpse = NULL;
|
|
corpsePtr->Destroy ();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SetInvulnerable
|
|
//
|
|
//============================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_SetInvulnerable)
|
|
{
|
|
self->flags2 |= MF2_INVULNERABLE;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_UnSetInvulnerable
|
|
//
|
|
//============================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_UnSetInvulnerable)
|
|
{
|
|
self->flags2 &= ~MF2_INVULNERABLE;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SetReflective
|
|
//
|
|
//============================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_SetReflective)
|
|
{
|
|
self->flags2 |= MF2_REFLECTIVE;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_UnSetReflective
|
|
//
|
|
//============================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_UnSetReflective)
|
|
{
|
|
self->flags2 &= ~MF2_REFLECTIVE;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_SetReflectiveInvulnerable
|
|
//
|
|
//============================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_SetReflectiveInvulnerable)
|
|
{
|
|
self->flags2 |= MF2_REFLECTIVE|MF2_INVULNERABLE;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_UnSetReflectiveInvulnerable
|
|
//
|
|
//============================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_UnSetReflectiveInvulnerable)
|
|
{
|
|
self->flags2 &= ~(MF2_REFLECTIVE|MF2_INVULNERABLE);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// A_SetShootable
|
|
//
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_SetShootable)
|
|
{
|
|
self->flags2 &= ~MF2_NONSHOOTABLE;
|
|
self->flags |= MF_SHOOTABLE;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// A_UnSetShootable
|
|
//
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_UnSetShootable)
|
|
{
|
|
self->flags2 |= MF2_NONSHOOTABLE;
|
|
self->flags &= ~MF_SHOOTABLE;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_NoGravity
|
|
//
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_NoGravity)
|
|
{
|
|
self->flags |= MF_NOGRAVITY;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_Gravity
|
|
//
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_Gravity)
|
|
{
|
|
self->flags &= ~MF_NOGRAVITY;
|
|
self->gravity = FRACUNIT;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_LowGravity
|
|
//
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_LowGravity)
|
|
{
|
|
self->flags &= ~MF_NOGRAVITY;
|
|
self->gravity = FRACUNIT/8;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// 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;
|
|
}
|
|
}
|
|
|