qzdoom/src/g_shared/a_action.cpp
Christoph Oelckers 66d28a24b8 - disabled the scripted virtual function module after finding out that it only works if each single class that may serve as a parent for scripting is explicitly declared.
Needless to say, this is simply too volatile and would require constant active maintenance, not to mention a huge amount of work up front to get going.
It also hid a nasty problem with the Destroy method. Due to the way the garbage collector works, Destroy cannot be exposed to scripts as-is. It may be called from scripts but it may not be overridden from scripts because the garbage collector can call this function after all data needed for calling a scripted override has already been destroyed because if that data is also being collected there is no guarantee that proper order of destruction is observed. So for now Destroy is just a normal native method to scripted classes
2016-11-25 00:25:26 +01:00

457 lines
11 KiB
C++

#include "actor.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 "serializer.h"
#include "r_data/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, false, false)
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, false, false)
//----------------------------------------------------------------------------
//
// PROC A_NoBlocking
//
//----------------------------------------------------------------------------
void A_Unblock(AActor *self, bool drop)
{
// [RH] Andy Baker's stealth monsters
if (self->flags & MF_STEALTH)
{
self->Alpha = 1.;
self->visdir = 0;
}
self->flags &= ~MF_SOLID;
// If the actor 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 actor has attached metadata for items to drop, drop those.
if (drop && !self->IsKindOf (RUNTIME_CLASS (APlayerPawn))) // [GRB]
{
DDropItem *di = self->GetDropItems();
if (di != NULL)
{
while (di != NULL)
{
if (di->Name != NAME_None)
{
PClassActor *ti = PClass::FindActor(di->Name);
if (ti != NULL)
{
P_DropItem (self, ti, di->Amount, di->Probability);
}
}
di = di->Next;
}
}
}
}
DEFINE_ACTION_FUNCTION(AActor, A_NoBlocking)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_BOOL_DEF(drop);
A_Unblock(self, drop);
return 0;
}
//============================================================================
//
// A_FreezeDeath
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_FreezeDeath)
{
PARAM_SELF_PROLOGUE(AActor);
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;
// Remove fuzz effects from frozen actors.
if (self->RenderStyle.BlendOp >= STYLEOP_Fuzz && self->RenderStyle.BlendOp <= STYLEOP_FuzzOrRevSub)
{
self->RenderStyle = STYLE_Normal;
}
S_Sound (self, CHAN_BODY, "misc/freeze", 1, ATTN_NORM);
// [RH] Andy Baker's stealth monsters
if (self->flags & MF_STEALTH)
{
self->Alpha = 1;
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
P_ExecuteSpecial(self->special, NULL, self, false, self->args[0],
self->args[1], self->args[2], self->args[3], self->args[4]);
self->special = 0;
}
return 0;
}
//==========================================================================
//
// A_GenericFreezeDeath
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_GenericFreezeDeath)
{
PARAM_SELF_PROLOGUE(AActor);
self->Translation = TRANSLATION(TRANSLATION_Standard, 7);
CALL_ACTION(A_FreezeDeath, self);
return 0;
}
//============================================================================
//
// A_IceSetTics
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_IceSetTics)
{
PARAM_SELF_PROLOGUE(AActor);
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;
}
return 0;
}
//============================================================================
//
// A_FreezeDeathChunks
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_FreezeDeathChunks)
{
PARAM_SELF_PROLOGUE(AActor);
int i;
int numChunks;
AActor *mo;
if (!self->Vel.isZero() && !(self->flags6 & MF6_SHATTERING))
{
self->tics = 3*TICRATE;
return 0;
}
self->Vel.Zero();
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 actor with radius 20 and height 64 creates ~40 chunks.
numChunks = MAX<int>(4, int(self->radius * self->Height)/32);
i = (pr_freeze.Random2()) % (numChunks/4);
for (i = MAX (24, numChunks + i); i >= 0; i--)
{
double xo = (pr_freeze() - 128)*self->radius / 128;
double yo = (pr_freeze() - 128)*self->radius / 128;
double zo = (pr_freeze()*self->Height / 255);
mo = Spawn("IceChunk", self->Vec3Offset(xo, yo, zo), ALLOW_REPLACE);
if (mo)
{
mo->SetState (mo->SpawnState + (pr_freeze()%3));
mo->Vel.X = pr_freeze.Random2() / 128.;
mo->Vel.Y = pr_freeze.Random2() / 128.;
mo->Vel.Z = (mo->Z() - self->Z()) / self->Height * 4;
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->PosPlusZ(self->player->mo->ViewHeight), ALLOW_REPLACE);
if (head != NULL)
{
head->Vel.X = pr_freeze.Random2() / 128.;
head->Vel.Y = pr_freeze.Random2() / 128.;
head->Vel.Z = (mo->Z() - self->Z()) / self->Height * 4;
head->health = self->health;
head->Angles.Yaw = self->Angles.Yaw;
if (head->IsKindOf(RUNTIME_CLASS(APlayerPawn)))
{
head->player = self->player;
head->player->mo = static_cast<APlayerPawn*>(head);
self->player = NULL;
head->ObtainInventory (self);
}
head->Angles.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)
{
A_BossDeath(self);
}
A_Unblock(self, true);
self->SetState(self->FindState(NAME_Null));
return 0;
}
//----------------------------------------------------------------------------
//
// 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() override;
void Serialize(FSerializer &arc);
TObjPtr<AActor> Corpse;
DWORD Count; // Only the first corpse pointer's count is valid.
private:
DCorpsePointer () {}
};
IMPLEMENT_CLASS(DCorpsePointer, false, true)
IMPLEMENT_POINTERS_START(DCorpsePointer)
IMPLEMENT_POINTER(Corpse)
IMPLEMENT_POINTERS_END
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 ();
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 ();
first->Destroy ();
first = next;
}
}
++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(FSerializer &arc)
{
Super::Serialize(arc);
arc("corpse", Corpse)
("count", Count);
}
// throw another corpse on the queue
DEFINE_ACTION_FUNCTION(AActor, A_QueueCorpse)
{
PARAM_SELF_PROLOGUE(AActor);
if (sv_corpsequeuesize > 0)
{
new DCorpsePointer (self);
}
return 0;
}
// Remove an self from the queue (for resurrection)
DEFINE_ACTION_FUNCTION(AActor, A_DeQueueCorpse)
{
PARAM_SELF_PROLOGUE(AActor);
TThinkerIterator<DCorpsePointer> iterator (STAT_CORPSEPOINTER);
DCorpsePointer *corpsePtr;
while ((corpsePtr = iterator.Next()) != NULL)
{
if (corpsePtr->Corpse == self)
{
corpsePtr->Corpse = NULL;
corpsePtr->Destroy ();
return 0;
}
}
return 0;
}
//===========================================================================
//
// FaceMovementDirection
//
//===========================================================================
void FaceMovementDirection(AActor *actor)
{
switch (actor->movedir)
{
case DI_EAST:
actor->Angles.Yaw = 0.;
break;
case DI_NORTHEAST:
actor->Angles.Yaw = 45.;
break;
case DI_NORTH:
actor->Angles.Yaw = 90.;
break;
case DI_NORTHWEST:
actor->Angles.Yaw = 135.;
break;
case DI_WEST:
actor->Angles.Yaw = 180.;
break;
case DI_SOUTHWEST:
actor->Angles.Yaw = 225.;
break;
case DI_SOUTH:
actor->Angles.Yaw = 270.;
break;
case DI_SOUTHEAST:
actor->Angles.Yaw = 315.;
break;
}
}
DEFINE_ACTION_FUNCTION(AActor, FaceMovementDirection)
{
PARAM_SELF_PROLOGUE(AActor);
FaceMovementDirection(self);
return 0;
}