#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 "farchive.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) 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 // //---------------------------------------------------------------------------- void A_Unblock(AActor *self, bool drop) { // [RH] Andy Baker's stealth monsters if (self->flags & MF_STEALTH) { self->alpha = OPAQUE; 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_ACTION_PROLOGUE; A_Unblock(self, true); return 0; } DEFINE_ACTION_FUNCTION(AActor, A_Fall) { PARAM_ACTION_PROLOGUE; A_Unblock(self, true); return 0; } //========================================================================== // // A_SetFloorClip // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SetFloorClip) { PARAM_ACTION_PROLOGUE; self->flags2 |= MF2_FLOORCLIP; self->AdjustFloorClip (); return 0; } //========================================================================== // // A_UnSetFloorClip // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_UnSetFloorClip) { PARAM_ACTION_PROLOGUE; self->flags2 &= ~MF2_FLOORCLIP; self->floorclip = 0; return 0; } //========================================================================== // // A_HideThing // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_HideThing) { PARAM_ACTION_PROLOGUE; self->renderflags |= RF_INVISIBLE; return 0; } //========================================================================== // // A_UnHideThing // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_UnHideThing) { PARAM_ACTION_PROLOGUE; self->renderflags &= ~RF_INVISIBLE; return 0; } //============================================================================ // // A_FreezeDeath // //============================================================================ DEFINE_ACTION_FUNCTION(AActor, A_FreezeDeath) { PARAM_ACTION_PROLOGUE; 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 = 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 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_ACTION_PROLOGUE; self->Translation = TRANSLATION(TRANSLATION_Standard, 7); CALL_ACTION(A_FreezeDeath, self); return 0; } //============================================================================ // // A_IceSetTics // //============================================================================ DEFINE_ACTION_FUNCTION(AActor, A_IceSetTics) { PARAM_ACTION_PROLOGUE; 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_ACTION_PROLOGUE; int i; int numChunks; AActor *mo; if ((self->vel.x || self->vel.y || self->vel.z) && !(self->flags6 & MF6_SHATTERING)) { self->tics = 3*TICRATE; return 0; } self->vel.x = self->vel.y = self->vel.z = 0; 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 (4, (self->radius>>FRACBITS)*(self->height>>FRACBITS)/32); i = (pr_freeze.Random2()) % (numChunks/4); for (i = MAX (24, numChunks + i); i >= 0; i--) { fixed_t xo = (((pr_freeze() - 128)*self->radius) >> 7); fixed_t yo = (((pr_freeze() - 128)*self->radius) >> 7); fixed_t 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.z = FixedDiv(mo->Z() - self->Z(), self->height)<<2; mo->vel.x = pr_freeze.Random2 () << (FRACBITS-7); mo->vel.y = 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->PosPlusZ(self->player->mo->ViewHeight), ALLOW_REPLACE); if (head != NULL) { head->vel.z = FixedDiv(head->Z() - self->Z(), self->height)<<2; head->vel.x = pr_freeze.Random2 () << (FRACBITS-7); head->vel.y = pr_freeze.Random2 () << (FRACBITS-7); 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(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 (); void Serialize (FArchive &arc); TObjPtr 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 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 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 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) { PARAM_ACTION_PROLOGUE; if (sv_corpsequeuesize > 0) { new DCorpsePointer (self); } return 0; } // Remove an self from the queue (for resurrection) DEFINE_ACTION_FUNCTION(AActor, A_DeQueueCorpse) { PARAM_ACTION_PROLOGUE; TThinkerIterator iterator (STAT_CORPSEPOINTER); DCorpsePointer *corpsePtr; while ((corpsePtr = iterator.Next()) != NULL) { if (corpsePtr->Corpse == self) { corpsePtr->Corpse = NULL; corpsePtr->Destroy (); return 0; } } return 0; } //============================================================================ // // A_SetInvulnerable // //============================================================================ DEFINE_ACTION_FUNCTION(AActor, A_SetInvulnerable) { PARAM_ACTION_PROLOGUE; self->flags2 |= MF2_INVULNERABLE; return 0; } //============================================================================ // // A_UnSetInvulnerable // //============================================================================ DEFINE_ACTION_FUNCTION(AActor, A_UnSetInvulnerable) { PARAM_ACTION_PROLOGUE; self->flags2 &= ~MF2_INVULNERABLE; return 0; } //============================================================================ // // A_SetReflective // //============================================================================ DEFINE_ACTION_FUNCTION(AActor, A_SetReflective) { PARAM_ACTION_PROLOGUE; self->flags2 |= MF2_REFLECTIVE; return 0; } //============================================================================ // // A_UnSetReflective // //============================================================================ DEFINE_ACTION_FUNCTION(AActor, A_UnSetReflective) { PARAM_ACTION_PROLOGUE; self->flags2 &= ~MF2_REFLECTIVE; return 0; } //============================================================================ // // A_SetReflectiveInvulnerable // //============================================================================ DEFINE_ACTION_FUNCTION(AActor, A_SetReflectiveInvulnerable) { PARAM_ACTION_PROLOGUE; self->flags2 |= MF2_REFLECTIVE|MF2_INVULNERABLE; return 0; } //============================================================================ // // A_UnSetReflectiveInvulnerable // //============================================================================ DEFINE_ACTION_FUNCTION(AActor, A_UnSetReflectiveInvulnerable) { PARAM_ACTION_PROLOGUE; self->flags2 &= ~(MF2_REFLECTIVE|MF2_INVULNERABLE); return 0; } //========================================================================== // // A_SetShootable // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SetShootable) { PARAM_ACTION_PROLOGUE; self->flags2 &= ~MF2_NONSHOOTABLE; self->flags |= MF_SHOOTABLE; return 0; } //========================================================================== // // A_UnSetShootable // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_UnSetShootable) { PARAM_ACTION_PROLOGUE; self->flags2 |= MF2_NONSHOOTABLE; self->flags &= ~MF_SHOOTABLE; return 0; } //=========================================================================== // // A_NoGravity // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_NoGravity) { PARAM_ACTION_PROLOGUE; self->flags |= MF_NOGRAVITY; return 0; } //=========================================================================== // // A_Gravity // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_Gravity) { PARAM_ACTION_PROLOGUE; self->flags &= ~MF_NOGRAVITY; self->gravity = FRACUNIT; return 0; } //=========================================================================== // // A_LowGravity // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_LowGravity) { PARAM_ACTION_PROLOGUE; self->flags &= ~MF_NOGRAVITY; self->gravity = FRACUNIT/8; return 0; } //=========================================================================== // // FaceMovementDirection // //=========================================================================== void FaceMovementDirection (AActor *actor) { if (actor->movedir >= DI_EAST && actor->movedir <= DI_NORTHEAST) { actor->Angles.Yaw = 45. * actor->movedir; } }