vkdoom_m/src/g_shared/a_action.cpp
Randy Heit e4af82ae96 - Enough with this "momentum" garbage. What Doom calls "momentum" is really
velocity, and now it's known as such. The actor variables momx/momy/momz
  are now known as velx/vely/velz, and the ACS functions GetActorMomX/Y/Z
  are now known as GetActorVelX/Y/Z. For compatibility, momx/momy/momz will
  continue to work as aliases from DECORATE. The ACS functions, however,
  require you to use the new name, since they never saw an official release
  yet.


SVN r1689 (trunk)
2009-06-30 20:57:51 +00:00

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->velx || self->vely || self->velz)
{
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->velz = FixedDiv(mo->z - self->z, self->height)<<2;
mo->velx = pr_freeze.Random2 () << (FRACBITS-7);
mo->vely = 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->velz = FixedDiv(head->z - self->z, self->height)<<2;
head->velx = pr_freeze.Random2 () << (FRACBITS-7);
head->vely = 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->SetState(self->FindState(NAME_Null));
}
//----------------------------------------------------------------------------
//
// 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;
}
}