#include "actor.h" #include "vm.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, 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, 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(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(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(FSerializer &arc); TObjPtr Corpse; DWORD Count; // Only the first corpse pointer's count is valid. private: DCorpsePointer () {} }; IMPLEMENT_CLASS(DCorpsePointer, false, true, false) IMPLEMENT_POINTERS_START(DCorpsePointer) IMPLEMENT_POINTER(Corpse) IMPLEMENT_POINTERS_END 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(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 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; } }