From 1c2a6e8457237bdbe355ad402c2fa11b6a470927 Mon Sep 17 00:00:00 2001 From: Braden Obrzut Date: Fri, 16 Jan 2015 13:12:18 -0500 Subject: [PATCH 01/19] - Fixed: fullscreenoffset coordinates were determined by translated (offsets) coordinates instead of input coordinates. --- src/g_shared/sbarinfo.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/g_shared/sbarinfo.cpp b/src/g_shared/sbarinfo.cpp index 7a11a28650..8ffedb5a4c 100644 --- a/src/g_shared/sbarinfo.cpp +++ b/src/g_shared/sbarinfo.cpp @@ -1280,8 +1280,8 @@ public: // We can't use DTA_HUDRules since it forces a width and height. // Translation: No high res. - bool xright = rx < 0; - bool ybot = ry < 0; + bool xright = *x < 0; + bool ybot = *y < 0; w = (forceWidth < 0 ? texture->GetScaledWidthDouble() : forceWidth); h = (forceHeight < 0 ? texture->GetScaledHeightDouble() : forceHeight); From 4bae3f99762ac7c1d602fdcc95142e10809d28ea Mon Sep 17 00:00:00 2001 From: Braden Obrzut Date: Fri, 16 Jan 2015 18:58:23 -0500 Subject: [PATCH 02/19] - Fixed: SDLVideo::CreateFrameBuffer cleared "retry" too soon resulting in infinite recursion if the code was used. --- src/posix/sdl/sdlvideo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/posix/sdl/sdlvideo.cpp b/src/posix/sdl/sdlvideo.cpp index 7a04ce901c..309002456a 100644 --- a/src/posix/sdl/sdlvideo.cpp +++ b/src/posix/sdl/sdlvideo.cpp @@ -286,7 +286,6 @@ DFrameBuffer *SDLVideo::CreateFrameBuffer (int width, int height, bool fullscree } SDLFB *fb = new SDLFB (width, height, fullscreen); - retry = 0; // If we could not create the framebuffer, try again with slightly // different parameters in this order: @@ -327,6 +326,7 @@ DFrameBuffer *SDLVideo::CreateFrameBuffer (int width, int height, bool fullscree ++retry; fb = static_cast(CreateFrameBuffer (width, height, fullscreen, NULL)); } + retry = 0; fb->SetFlash (flashColor, flashAmount); From 845bcdf14c0beda51d6bfd0c9db062c287e35d8b Mon Sep 17 00:00:00 2001 From: Braden Obrzut Date: Fri, 16 Jan 2015 19:13:51 -0500 Subject: [PATCH 03/19] - Attempt to disable all comparison operators on FString since unless we decide otherwise, it's a programming error to use them (caused implicit conversion to const char* and then the built in comparision was called). --- src/zstring.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/zstring.h b/src/zstring.h index 0104020fa7..82c38142c5 100644 --- a/src/zstring.h +++ b/src/zstring.h @@ -296,6 +296,16 @@ protected: static FNullStringData NullString; friend struct FStringData; + +private: + // Prevent these from being called as current practices are to use Compare. + // Without this FStrings will be accidentally compared against char* ptrs. + bool operator == (const FString &illegal) const; + bool operator != (const FString &illegal) const; + bool operator < (const FString &illegal) const; + bool operator > (const FString &illegal) const; + bool operator <= (const FString &illegal) const; + bool operator >= (const FString &illegal) const; }; namespace StringFormat From 105a62cc20f878e86ad3b348888d327aea330d5b Mon Sep 17 00:00:00 2001 From: MajorCooke Date: Mon, 19 Jan 2015 14:54:20 -0600 Subject: [PATCH 04/19] - Added pointer field to A_ChangeVelocity. - Defaults to AAPTR_DEFAULT, otherwise known as the calling actor. --- src/thingdef/thingdef_codeptr.cpp | 27 ++++++++++++++++++--------- wadsrc/static/actors/actor.txt | 2 +- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index 300ee75569..d522f284a3 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -4029,12 +4029,21 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeVelocity) ACTION_PARAM_FIXED(y, 1); ACTION_PARAM_FIXED(z, 2); ACTION_PARAM_INT(flags, 3); + ACTION_PARAM_INT(ptr, 4); - INTBOOL was_moving = self->velx | self->vely | self->velz; + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } + + INTBOOL was_moving = ref->velx | ref->vely | ref->velz; fixed_t vx = x, vy = y, vz = z; - fixed_t sina = finesine[self->angle >> ANGLETOFINESHIFT]; - fixed_t cosa = finecosine[self->angle >> ANGLETOFINESHIFT]; + fixed_t sina = finesine[ref->angle >> ANGLETOFINESHIFT]; + fixed_t cosa = finecosine[ref->angle >> ANGLETOFINESHIFT]; if (flags & 1) // relative axes - make x, y relative to actor's current angle { @@ -4043,15 +4052,15 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeVelocity) } if (flags & 2) // discard old velocity - replace old velocity with new velocity { - self->velx = vx; - self->vely = vy; - self->velz = vz; + ref->velx = vx; + ref->vely = vy; + ref->velz = vz; } else // add new velocity to old velocity { - self->velx += vx; - self->vely += vy; - self->velz += vz; + ref->velx += vx; + ref->vely += vy; + ref->velz += vz; } if (was_moving) diff --git a/wadsrc/static/actors/actor.txt b/wadsrc/static/actors/actor.txt index b2fa80c302..44ba7f9b65 100644 --- a/wadsrc/static/actors/actor.txt +++ b/wadsrc/static/actors/actor.txt @@ -293,7 +293,7 @@ ACTOR Actor native //: Thinker action native A_SetPitch(float pitch, int flags = 0); action native A_SetRoll(float roll, int flags = 0); action native A_ScaleVelocity(float scale); - action native A_ChangeVelocity(float x = 0, float y = 0, float z = 0, int flags = 0); + action native A_ChangeVelocity(float x = 0, float y = 0, float z = 0, int flags = 0, int ptr = AAPTR_DEFAULT); action native A_SetArg(int pos, int value); action native A_SetUserVar(name varname, int value); action native A_SetUserArray(name varname, int index, int value); From 67312b907b22bee6abc794fc19e954820171a649 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 20 Jan 2015 10:25:58 +0100 Subject: [PATCH 05/19] - fixed: AInventory::AbsorbDamage may only be called for positive damage values, otherwise it ends up adding armor points. --- src/p_interaction.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp index 36a68281e8..7bf19a03b6 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -1278,7 +1278,10 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, if (!(flags & DMG_NO_ARMOR) && player->mo->Inventory != NULL) { int newdam = damage; - player->mo->Inventory->AbsorbDamage(damage, mod, newdam); + if (damage > 0) + { + player->mo->Inventory->AbsorbDamage(damage, mod, newdam); + } if (damage < TELEFRAG_DAMAGE) { // if we are telefragging don't let the damage value go below that magic value. Some further checks would fail otherwise. From fa411af1daab62980d2b77cc73c3fd0723962e6d Mon Sep 17 00:00:00 2001 From: MajorCooke Date: Sun, 18 Jan 2015 09:21:59 -0600 Subject: [PATCH 06/19] - Fixed: Reflected projectiles were always changing their angles to 0. --- src/p_mobj.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index fea80f868c..bb33a88c46 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -1970,6 +1970,7 @@ fixed_t P_XYMovement (AActor *mo, fixed_t scrollx, fixed_t scrolly) { //int dir; //angle_t delta; + angle = R_PointToAngle2(BlockingMobj->x, BlockingMobj->y, mo->x, mo->y); bool dontReflect = (mo->AdjustReflectionAngle(BlockingMobj, angle)); // Change angle for deflection/reflection @@ -1989,7 +1990,7 @@ fixed_t P_XYMovement (AActor *mo, fixed_t scrollx, fixed_t scrolly) //dest->x - source->x FVector3 velocity(origin->x - mo->x, origin->y - mo->y, (origin->z + (origin->height/2)) - mo->z); velocity.Resize(speed); - angle = mo->angle >> ANGLETOFINESHIFT; + angle = mo->angle >>= ANGLETOFINESHIFT; mo->velx = (fixed_t)(velocity.X); mo->vely = (fixed_t)(velocity.Y); mo->velz = (fixed_t)(velocity.Z); @@ -2001,6 +2002,7 @@ fixed_t P_XYMovement (AActor *mo, fixed_t scrollx, fixed_t scrolly) } else { + mo->angle = angle; angle >>= ANGLETOFINESHIFT; mo->velx = FixedMul(mo->Speed >> 1, finecosine[angle]); From 4fd87a1ce911065f7d82405843318de99457784c Mon Sep 17 00:00:00 2001 From: MajorCooke Date: Tue, 20 Jan 2015 22:54:30 -0600 Subject: [PATCH 07/19] - Redid A_SetHealth. - This function will not take the actor's health below 1, and any attempts to do so will simply set it to 1 in its place. --- src/thingdef/thingdef_codeptr.cpp | 39 ++++++++++++++++++++++++++++++- wadsrc/static/actors/actor.txt | 1 + 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index 300ee75569..53b5f7f055 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -5656,7 +5656,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_SwapTeleFog) // // A_SetFloatBobPhase // -// Changes the FloatBobPhase of the +// Changes the FloatBobPhase of the actor. //=========================================================================== DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetFloatBobPhase) @@ -5669,6 +5669,43 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetFloatBobPhase) self->FloatBobPhase = bob; } +//=========================================================================== +// A_SetHealth +// +// Changes the health of the actor. +// Takes a pointer as well. +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetHealth) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(health, 0); + ACTION_PARAM_INT(ptr, 1); + + AActor *mobj = COPY_AAPTR(self, ptr); + + if (!mobj) + { + return; + } + + player_t *player = mobj->player; + if (player) + { + if (health <= 0) + player->mo->health = mobj->health = player->health = 1; //Copied from the buddha cheat. + else + player->mo->health = mobj->health = player->health = health; + } + else if (mobj) + { + if (health <= 0) + mobj->health = 1; + else + mobj->health = health; + } +} + //=========================================================================== // // A_SetRipperLevel(int level) diff --git a/wadsrc/static/actors/actor.txt b/wadsrc/static/actors/actor.txt index b2fa80c302..5fda5c590d 100644 --- a/wadsrc/static/actors/actor.txt +++ b/wadsrc/static/actors/actor.txt @@ -327,6 +327,7 @@ ACTOR Actor native //: Thinker action native A_SetTeleFog(name oldpos, name newpos); action native A_SwapTeleFog(); action native A_SetFloatBobPhase(int bob); + action native A_SetHealth(int health, int ptr = AAPTR_DEFAULT); action native A_SetRipperLevel(int level); action native A_SetRipMin(int min); action native A_SetRipMax(int max); From 73bdd06cebd094c8b4329830132ec5a036a2869f Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Thu, 22 Jan 2015 14:10:55 +0100 Subject: [PATCH 08/19] - removed bogus angle modification from reflection code. --- src/p_mobj.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index bb33a88c46..ef23ca0baa 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -1990,15 +1990,9 @@ fixed_t P_XYMovement (AActor *mo, fixed_t scrollx, fixed_t scrolly) //dest->x - source->x FVector3 velocity(origin->x - mo->x, origin->y - mo->y, (origin->z + (origin->height/2)) - mo->z); velocity.Resize(speed); - angle = mo->angle >>= ANGLETOFINESHIFT; mo->velx = (fixed_t)(velocity.X); mo->vely = (fixed_t)(velocity.Y); mo->velz = (fixed_t)(velocity.Z); - /* - mo->velx = FixedMul(mo->Speed, finecosine[angle]); - mo->vely = FixedMul(mo->Speed, finesine[angle]); - mo->velz = -mo->velz; - */ } else { From 7157db89b73db5f6edd2e42ad68a52964e48243c Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Thu, 22 Jan 2015 14:13:25 +0100 Subject: [PATCH 09/19] - fixed: A_VileAttack didn't check for MF7_DONTTHRUST. This is a fixed version of Major Cookes pull request. --- src/g_doom/a_archvile.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/g_doom/a_archvile.cpp b/src/g_doom/a_archvile.cpp index 8fce33324f..f24ff146e6 100644 --- a/src/g_doom/a_archvile.cpp +++ b/src/g_doom/a_archvile.cpp @@ -151,5 +151,6 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_VileAttack) P_RadiusAttack (fire, self, blastdmg, blastrad, dmgtype, 0); } - target->velz = Scale(thrust, 1000, target->Mass); + if (!(target->flags7 & MF7_DONTTHRUST)) + target->velz = Scale(thrust, 1000, target->Mass); } From 19b43d47528536c97542f6ae277617b43bf234d8 Mon Sep 17 00:00:00 2001 From: MajorCooke Date: Fri, 23 Jan 2015 09:26:34 -0600 Subject: [PATCH 10/19] - Added A_ResetHealth(ptr). Defaults to AAPTR_DEFAULT (the action caller). - Added pointers to the following functions, all of them set to AAPTR_DEFAULT: - A_SetAngle - A_SetPitch - A_SetScale - A_SetSpeed - A_ScaleVelocity --- src/thingdef/thingdef_codeptr.cpp | 113 +++++++++++++++++++++++++----- wadsrc/static/actors/actor.txt | 11 +-- 2 files changed, 100 insertions(+), 24 deletions(-) diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index 926799b7ab..32bc9d315b 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -2504,12 +2504,21 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeTo) //=========================================================================== DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetScale) { - ACTION_PARAM_START(2); + ACTION_PARAM_START(3); ACTION_PARAM_FIXED(scalex, 0); ACTION_PARAM_FIXED(scaley, 1); + ACTION_PARAM_INT(ptr, 2); - self->scaleX = scalex; - self->scaleY = scaley ? scaley : scalex; + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } + + ref->scaleX = scalex; + ref->scaleY = scaley ? scaley : scalex; } //=========================================================================== @@ -3934,10 +3943,19 @@ enum DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetAngle) { - ACTION_PARAM_START(2); + ACTION_PARAM_START(3); ACTION_PARAM_ANGLE(angle, 0); - ACTION_PARAM_INT(flags, 1) - self->SetAngle(angle, !!(flags & SPF_INTERPOLATE)); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_INT(ptr, 2); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } + ref->SetAngle(angle, !!(flags & SPF_INTERPOLATE)); } //=========================================================================== @@ -3950,18 +3968,27 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetAngle) DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetPitch) { - ACTION_PARAM_START(2); + ACTION_PARAM_START(3); ACTION_PARAM_ANGLE(pitch, 0); ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_INT(ptr, 2); - if (self->player != NULL || (flags & SPF_FORCECLAMP)) + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } + + if (ref->player != NULL || (flags & SPF_FORCECLAMP)) { // clamp the pitch we set int min, max; - if (self->player != NULL) + if (ref->player != NULL) { - min = self->player->MinPitch; - max = self->player->MaxPitch; + min = ref->player->MinPitch; + max = ref->player->MaxPitch; } else { @@ -3970,7 +3997,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetPitch) } pitch = clamp(pitch, min, max); } - self->SetPitch(pitch, !!(flags & SPF_INTERPOLATE)); + ref->SetPitch(pitch, !!(flags & SPF_INTERPOLATE)); } //=========================================================================== @@ -3999,20 +4026,29 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRoll) DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ScaleVelocity) { - ACTION_PARAM_START(1); + ACTION_PARAM_START(2); ACTION_PARAM_FIXED(scale, 0); + ACTION_PARAM_INT(ptr, 1); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } INTBOOL was_moving = self->velx | self->vely | self->velz; - self->velx = FixedMul(self->velx, scale); - self->vely = FixedMul(self->vely, scale); - self->velz = FixedMul(self->velz, scale); + ref->velx = FixedMul(ref->velx, scale); + ref->vely = FixedMul(ref->vely, scale); + ref->velz = FixedMul(ref->velz, scale); // If the actor was previously moving but now is not, and is a player, // update its player variables. (See A_Stop.) if (was_moving) { - CheckStopped(self); + CheckStopped(ref); } } @@ -5073,10 +5109,19 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropItem) DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpeed) { - ACTION_PARAM_START(1); + ACTION_PARAM_START(2); ACTION_PARAM_FIXED(speed, 0); + ACTION_PARAM_INT(ptr, 1); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } - self->Speed = speed; + ref->Speed = speed; } static bool DoCheckSpecies(AActor *mo, FName species, bool exclude) @@ -5715,6 +5760,36 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetHealth) } } +//=========================================================================== +// A_ResetHealth +// +// Resets the health of the actor to default, except if their dead. +// Takes a pointer. +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ResetHealth) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(ptr, 0); + + AActor *mobj = COPY_AAPTR(self, ptr); + + if (!mobj) + { + return; + } + + player_t *player = mobj->player; + if (player && (player->mo->health > 0)) + { + player->health = player->mo->health = player->mo->GetDefault()->health; //Copied from the resurrect cheat. + } + else if (mobj && (mobj->health > 0)) + { + mobj->health = mobj->GetDefault()->health; + } +} + //=========================================================================== // // A_SetRipperLevel(int level) diff --git a/wadsrc/static/actors/actor.txt b/wadsrc/static/actors/actor.txt index ded3a0b93c..f3c567c718 100644 --- a/wadsrc/static/actors/actor.txt +++ b/wadsrc/static/actors/actor.txt @@ -231,7 +231,7 @@ ACTOR Actor native //: Thinker action native A_FadeIn(float reduce = 0.1, int flags = 0); action native A_FadeOut(float reduce = 0.1, int flags = 1); //bool remove == true action native A_FadeTo(float target, float amount = 0.1, int flags = 0); - action native A_SetScale(float scalex, float scaley = 0); + action native A_SetScale(float scalex, float scaley = 0, int ptr = AAPTR_DEFAULT); action native A_SetMass(int mass); action native A_SpawnDebris(class spawntype, bool transfer_translation = false, float mult_h = 1, float mult_v = 1); action native A_CheckSight(state label); @@ -289,10 +289,10 @@ ACTOR Actor native //: Thinker action native A_DropWeaponPieces(class p1, class p2, class p3); action native A_PigPain (); action native A_MonsterRefire(int chance, state label); - action native A_SetAngle(float angle = 0, int flags = 0); - action native A_SetPitch(float pitch, int flags = 0); + action native A_SetAngle(float angle = 0, int flags = 0, int ptr = AAPTR_DEFAULT); + action native A_SetPitch(float pitch, int flags = 0, int ptr = AAPTR_DEFAULT); action native A_SetRoll(float roll, int flags = 0); - action native A_ScaleVelocity(float scale); + action native A_ScaleVelocity(float scale, int ptr = AAPTR_DEFAULT); action native A_ChangeVelocity(float x = 0, float y = 0, float z = 0, int flags = 0, int ptr = AAPTR_DEFAULT); action native A_SetArg(int pos, int value); action native A_SetUserVar(name varname, int value); @@ -302,7 +302,7 @@ ACTOR Actor native //: Thinker action native A_SetTics(int tics); action native A_SetDamageType(name damagetype); action native A_DropItem(class item, int dropamount = -1, int chance = 256); - action native A_SetSpeed(float speed); + action native A_SetSpeed(float speed, int ptr = AAPTR_DEFAULT); action native A_DamageSelf(int amount, name damagetype = "none", int flags = 0, class filter = "None", name species = "None"); action native A_DamageTarget(int amount, name damagetype = "none", int flags = 0, class filter = "None", name species = "None"); action native A_DamageMaster(int amount, name damagetype = "none", int flags = 0, class filter = "None", name species = "None"); @@ -328,6 +328,7 @@ ACTOR Actor native //: Thinker action native A_SwapTeleFog(); action native A_SetFloatBobPhase(int bob); action native A_SetHealth(int health, int ptr = AAPTR_DEFAULT); + action native A_ResetHealth(int ptr = AAPTR_DEFAULT); action native A_SetRipperLevel(int level); action native A_SetRipMin(int min); action native A_SetRipMax(int max); From 301c061ec3520ba592fe9b3c75b5f6c10f0a5145 Mon Sep 17 00:00:00 2001 From: MajorCooke Date: Fri, 23 Jan 2015 10:24:23 -0600 Subject: [PATCH 11/19] - Added pointer for A_SetRoll. --- src/thingdef/thingdef_codeptr.cpp | 13 +++++++++++-- wadsrc/static/actors/actor.txt | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index 32bc9d315b..324500f7c6 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -4010,10 +4010,19 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetPitch) DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRoll) { - ACTION_PARAM_START(2); + ACTION_PARAM_START(3); ACTION_PARAM_ANGLE(roll, 0); ACTION_PARAM_INT(flags, 1); - self->SetRoll(roll, !!(flags & SPF_INTERPOLATE)); + ACTION_PARAM_INT(ptr, 2); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } + ref->SetRoll(roll, !!(flags & SPF_INTERPOLATE)); } //=========================================================================== diff --git a/wadsrc/static/actors/actor.txt b/wadsrc/static/actors/actor.txt index f3c567c718..f0e68d9a59 100644 --- a/wadsrc/static/actors/actor.txt +++ b/wadsrc/static/actors/actor.txt @@ -291,7 +291,7 @@ ACTOR Actor native //: Thinker action native A_MonsterRefire(int chance, state label); action native A_SetAngle(float angle = 0, int flags = 0, int ptr = AAPTR_DEFAULT); action native A_SetPitch(float pitch, int flags = 0, int ptr = AAPTR_DEFAULT); - action native A_SetRoll(float roll, int flags = 0); + action native A_SetRoll(float roll, int flags = 0, int ptr = AAPTR_DEFAULT); action native A_ScaleVelocity(float scale, int ptr = AAPTR_DEFAULT); action native A_ChangeVelocity(float x = 0, float y = 0, float z = 0, int flags = 0, int ptr = AAPTR_DEFAULT); action native A_SetArg(int pos, int value); From d1cca79c3178afb4dc4ef087cb60e4c1a8ec79a8 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 23 Jan 2015 20:29:38 +0100 Subject: [PATCH 12/19] - Use SpawnHealth() when calling A_ResetHealth. --- src/thingdef/thingdef_codeptr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index 324500f7c6..be3921877c 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -5795,7 +5795,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ResetHealth) } else if (mobj && (mobj->health > 0)) { - mobj->health = mobj->GetDefault()->health; + mobj->health = mobj->SpawnHealth(); } } From 92fbe47ade6129585768d75ceb7b32c94b603e52 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 24 Jan 2015 14:04:06 +0100 Subject: [PATCH 13/19] - fixed and cleaned up ALoreShot::DoSpecialDamage. --- src/g_strife/a_loremaster.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/g_strife/a_loremaster.cpp b/src/g_strife/a_loremaster.cpp index d720b7b9b5..38cadab340 100644 --- a/src/g_strife/a_loremaster.cpp +++ b/src/g_strife/a_loremaster.cpp @@ -14,27 +14,27 @@ class ALoreShot : public AActor { DECLARE_CLASS (ALoreShot, AActor) public: - int DoSpecialDamage (AActor *target, int damage, FName damagetype); + int DoSpecialDamage (AActor *victim, int damage, FName damagetype); }; IMPLEMENT_CLASS (ALoreShot) -int ALoreShot::DoSpecialDamage (AActor *target, int damage, FName damagetype) +int ALoreShot::DoSpecialDamage (AActor *victim, int damage, FName damagetype) { FVector3 thrust; - if (this->target != NULL && !(this->target->flags7 & MF7_DONTTHRUST)) + if (victim != NULL && target != NULL && !(victim->flags7 & MF7_DONTTHRUST)) { - thrust.X = float(this->target->x - target->x); - thrust.Y = float(this->target->y - target->y); - thrust.Z = float(this->target->z - target->z); + thrust.X = float(target->x - victim->x); + thrust.Y = float(target->y - victim->y); + thrust.Z = float(target->z - victim->z); thrust.MakeUnit(); - thrust *= float((255*50*FRACUNIT) / (target->Mass ? target->Mass : 1)); + thrust *= float((255*50*FRACUNIT) / (victim->Mass ? target->Mass : 1)); - target->velx += fixed_t(thrust.X); - target->vely += fixed_t(thrust.Y); - target->velz += fixed_t(thrust.Z); + victim->velx += fixed_t(thrust.X); + victim->vely += fixed_t(thrust.Y); + victim->velz += fixed_t(thrust.Z); } return damage; } From 04c6c5ca55989ed07181ebdfbdb70bcde4546267 Mon Sep 17 00:00:00 2001 From: Edoardo Prezioso Date: Sat, 24 Jan 2015 14:17:47 +0100 Subject: [PATCH 14/19] Fix missing 'victim' replace in ALoreShot code. --- src/g_strife/a_loremaster.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/g_strife/a_loremaster.cpp b/src/g_strife/a_loremaster.cpp index 38cadab340..d984bbdd35 100644 --- a/src/g_strife/a_loremaster.cpp +++ b/src/g_strife/a_loremaster.cpp @@ -30,7 +30,7 @@ int ALoreShot::DoSpecialDamage (AActor *victim, int damage, FName damagetype) thrust.Z = float(target->z - victim->z); thrust.MakeUnit(); - thrust *= float((255*50*FRACUNIT) / (victim->Mass ? target->Mass : 1)); + thrust *= float((255*50*FRACUNIT) / (victim->Mass ? victim->Mass : 1)); victim->velx += fixed_t(thrust.X); victim->vely += fixed_t(thrust.Y); From a4c07a9ee3a445f095d9618f928b1cb83dc54b0f Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 25 Jan 2015 20:15:58 +0100 Subject: [PATCH 15/19] - fixed: Since A_Give/TaketoChildren/siblings use the same code as A_Give/TakeInventory but do not use the third parameter this must be ignored by the code. This is merely a band-aid fix, because this problem doesn't exist in the same form in the scripting branch. --- src/thingdef/thingdef_codeptr.cpp | 11665 ++++++++++++++-------------- 1 file changed, 5840 insertions(+), 5825 deletions(-) diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index be3921877c..63c5d74083 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -1,4005 +1,4020 @@ -/* -** thingdef.cpp -** -** Code pointers for Actor definitions -** -**--------------------------------------------------------------------------- -** Copyright 2002-2006 Christoph Oelckers -** Copyright 2004-2006 Randy Heit -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** 4. When not used as part of ZDoom or a ZDoom derivative, this code will be -** covered by the terms of the GNU General Public License as published by -** the Free Software Foundation; either version 2 of the License, or (at -** your option) any later version. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- -** -*/ - -#include "gi.h" -#include "g_level.h" -#include "actor.h" -#include "info.h" -#include "sc_man.h" -#include "tarray.h" -#include "w_wad.h" -#include "templates.h" -#include "r_defs.h" -#include "a_pickups.h" -#include "s_sound.h" -#include "cmdlib.h" -#include "p_lnspec.h" -#include "p_enemy.h" -#include "a_action.h" -#include "decallib.h" -#include "m_random.h" -#include "i_system.h" -#include "p_local.h" -#include "c_console.h" -#include "doomerrors.h" -#include "a_sharedglobal.h" -#include "thingdef/thingdef.h" -#include "v_video.h" -#include "v_font.h" -#include "doomstat.h" -#include "v_palette.h" -#include "g_shared/a_specialspot.h" -#include "actorptrselect.h" -#include "m_bbox.h" -#include "r_data/r_translate.h" -#include "p_trace.h" -#include "gstrings.h" - - -static FRandom pr_camissile ("CustomActorfire"); -static FRandom pr_camelee ("CustomMelee"); -static FRandom pr_cabullet ("CustomBullet"); -static FRandom pr_cajump ("CustomJump"); -static FRandom pr_cwbullet ("CustomWpBullet"); -static FRandom pr_cwjump ("CustomWpJump"); -static FRandom pr_cwpunch ("CustomWpPunch"); -static FRandom pr_grenade ("ThrowGrenade"); -static FRandom pr_crailgun ("CustomRailgun"); -static FRandom pr_spawndebris ("SpawnDebris"); -static FRandom pr_spawnitemex ("SpawnItemEx"); -static FRandom pr_burst ("Burst"); -static FRandom pr_monsterrefire ("MonsterRefire"); -static FRandom pr_teleport("A_Teleport"); - -//========================================================================== -// -// ACustomInventory :: CallStateChain -// -// Executes the code pointers in a chain of states -// until there is no next state -// -//========================================================================== - -bool ACustomInventory::CallStateChain (AActor *actor, FState * State) -{ - StateCallData StateCall; - bool result = false; - int counter = 0; - - while (State != NULL) - { - // Assume success. The code pointer will set this to false if necessary - StateCall.State = State; - StateCall.Result = true; - if (State->CallAction(actor, this, &StateCall)) - { - // collect all the results. Even one successful call signifies overall success. - result |= StateCall.Result; - } - - - // Since there are no delays it is a good idea to check for infinite loops here! - counter++; - if (counter >= 10000) break; - - if (StateCall.State == State) - { - // Abort immediately if the state jumps to itself! - if (State == State->GetNextState()) - { - return false; - } - - // If both variables are still the same there was no jump - // so we must advance to the next state. - State = State->GetNextState(); - } - else - { - State = StateCall.State; - } - } - return result; -} - -//========================================================================== -// -// A_RearrangePointers -// -// Allow an actor to change its relationship to other actors by -// copying pointers freely between TARGET MASTER and TRACER. -// Can also assign null value, but does not duplicate A_ClearTarget. -// -//========================================================================== - - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RearrangePointers) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_INT(ptr_target, 0); - ACTION_PARAM_INT(ptr_master, 1); - ACTION_PARAM_INT(ptr_tracer, 2); - ACTION_PARAM_INT(flags, 3); - - // Rearrange pointers internally - - // Fetch all values before modification, so that all fields can get original values - AActor - *gettarget = self->target, - *getmaster = self->master, - *gettracer = self->tracer; - - switch (ptr_target) // pick the new target - { - case AAPTR_MASTER: - self->target = getmaster; - if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self); - break; - case AAPTR_TRACER: - self->target = gettracer; - if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self); - break; - case AAPTR_NULL: - self->target = NULL; - // THIS IS NOT "A_ClearTarget", so no other targeting info is removed - break; - } - - // presently permitting non-monsters to set master - switch (ptr_master) // pick the new master - { - case AAPTR_TARGET: - self->master = gettarget; - if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self); - break; - case AAPTR_TRACER: - self->master = gettracer; - if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self); - break; - case AAPTR_NULL: - self->master = NULL; - break; - } - - switch (ptr_tracer) // pick the new tracer - { - case AAPTR_TARGET: - self->tracer = gettarget; - break; // no verification deemed necessary; the engine never follows a tracer chain(?) - case AAPTR_MASTER: - self->tracer = getmaster; - break; // no verification deemed necessary; the engine never follows a tracer chain(?) - case AAPTR_NULL: - self->tracer = NULL; - break; - } -} - -//========================================================================== -// -// A_TransferPointer -// -// Copy one pointer (MASTER, TARGET or TRACER) from this actor (SELF), -// or from this actor's MASTER, TARGET or TRACER. -// -// You can copy any one of that actor's pointers -// -// Assign the copied pointer to any one pointer in SELF, -// MASTER, TARGET or TRACER. -// -// Any attempt to make an actor point to itself will replace the pointer -// with a null value. -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TransferPointer) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(ptr_source, 0); - ACTION_PARAM_INT(ptr_recepient, 1); - ACTION_PARAM_INT(ptr_sourcefield, 2); - ACTION_PARAM_INT(ptr_recepientfield, 3); - ACTION_PARAM_INT(flags, 4); - - AActor *source, *recepient; - - // Exchange pointers with actors to whom you have pointers (or with yourself, if you must) - - source = COPY_AAPTR(self, ptr_source); - COPY_AAPTR_NOT_NULL(self, recepient, ptr_recepient); // pick an actor to store the provided pointer value - - // convert source from dataprovider to data - - source = COPY_AAPTR(source, ptr_sourcefield); - - if (source == recepient) source = NULL; // The recepient should not acquire a pointer to itself; will write NULL - - if (ptr_recepientfield == AAPTR_DEFAULT) ptr_recepientfield = ptr_sourcefield; // If default: Write to same field as data was read from - - ASSIGN_AAPTR(recepient, ptr_recepientfield, source, flags); -} - -//========================================================================== -// -// A_CopyFriendliness -// -// Join forces with one of the actors you are pointing to (MASTER by default) -// -// Normal CopyFriendliness reassigns health. This function will not. -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CopyFriendliness) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(ptr_source, 0); - - if (self->player) return; - - AActor *source; - COPY_AAPTR_NOT_NULL(self, source, ptr_source); - self->CopyFriendliness(source, false, false); // No change in current target or health -} - -//========================================================================== -// -// Simple flag changers -// -//========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_SetSolid) -{ - self->flags |= MF_SOLID; -} - -DEFINE_ACTION_FUNCTION(AActor, A_UnsetSolid) -{ - self->flags &= ~MF_SOLID; -} - -DEFINE_ACTION_FUNCTION(AActor, A_SetFloat) -{ - self->flags |= MF_FLOAT; -} - -DEFINE_ACTION_FUNCTION(AActor, A_UnsetFloat) -{ - self->flags &= ~(MF_FLOAT|MF_INFLOAT); -} - -//========================================================================== -// -// Customizable attack functions which use actor parameters. -// -//========================================================================== -static void DoAttack (AActor *self, bool domelee, bool domissile, - int MeleeDamage, FSoundID MeleeSound, const PClass *MissileType,fixed_t MissileHeight) -{ - if (self->target == NULL) return; - - A_FaceTarget (self); - if (domelee && MeleeDamage>0 && self->CheckMeleeRange ()) - { - int damage = pr_camelee.HitDice(MeleeDamage); - if (MeleeSound) S_Sound (self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM); - int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); - } - else if (domissile && MissileType != NULL) - { - // This seemingly senseless code is needed for proper aiming. - self->z += MissileHeight + self->GetBobOffset() - 32*FRACUNIT; - AActor *missile = P_SpawnMissileXYZ (self->x, self->y, self->z + 32*FRACUNIT, self, self->target, MissileType, false); - self->z -= MissileHeight + self->GetBobOffset() - 32*FRACUNIT; - - if (missile) - { - // automatic handling of seeker missiles - if (missile->flags2&MF2_SEEKERMISSILE) - { - missile->tracer=self->target; - } - P_CheckMissileSpawn(missile, self->radius); - } - } -} - -DEFINE_ACTION_FUNCTION(AActor, A_MeleeAttack) -{ - int MeleeDamage = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeDamage, 0); - FSoundID MeleeSound = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeSound, 0); - DoAttack(self, true, false, MeleeDamage, MeleeSound, NULL, 0); -} - -DEFINE_ACTION_FUNCTION(AActor, A_MissileAttack) -{ - const PClass *MissileType=PClass::FindClass((ENamedName) self->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None)); - fixed_t MissileHeight= self->GetClass()->Meta.GetMetaFixed (ACMETA_MissileHeight, 32*FRACUNIT); - DoAttack(self, false, true, 0, 0, MissileType, MissileHeight); -} - -DEFINE_ACTION_FUNCTION(AActor, A_ComboAttack) -{ - int MeleeDamage = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeDamage, 0); - FSoundID MeleeSound = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeSound, 0); - const PClass *MissileType=PClass::FindClass((ENamedName) self->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None)); - fixed_t MissileHeight= self->GetClass()->Meta.GetMetaFixed (ACMETA_MissileHeight, 32*FRACUNIT); - DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BasicAttack) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_INT(MeleeDamage, 0); - ACTION_PARAM_SOUND(MeleeSound, 1); - ACTION_PARAM_CLASS(MissileType, 2); - ACTION_PARAM_FIXED(MissileHeight, 3); - - if (MissileType == NULL) return; - DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight); -} - -//========================================================================== -// -// Custom sound functions. -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySound) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_SOUND(soundid, 0); - ACTION_PARAM_INT(channel, 1); - ACTION_PARAM_FLOAT(volume, 2); - ACTION_PARAM_BOOL(looping, 3); - ACTION_PARAM_FLOAT(attenuation, 4); - - if (!looping) - { - S_Sound (self, channel, soundid, volume, attenuation); - } - else - { - if (!S_IsActorPlayingSomething (self, channel&7, soundid)) - { - S_Sound (self, channel | CHAN_LOOP, soundid, volume, attenuation); - } - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSound) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(slot, 0); - - S_StopSound(self, slot); -} - -//========================================================================== -// -// These come from a time when DECORATE constants did not exist yet and -// the sound interface was less flexible. As a result the parameters are -// not optimal and these functions have been deprecated in favor of extending -// A_PlaySound and A_StopSound. -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayWeaponSound) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_SOUND(soundid, 0); - - S_Sound (self, CHAN_WEAPON, soundid, 1, ATTN_NORM); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySoundEx) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_SOUND(soundid, 0); - ACTION_PARAM_NAME(channel, 1); - ACTION_PARAM_BOOL(looping, 2); - ACTION_PARAM_INT(attenuation_raw, 3); - - float attenuation; - switch (attenuation_raw) - { - case -1: attenuation = ATTN_STATIC; break; // drop off rapidly - default: - case 0: attenuation = ATTN_NORM; break; // normal - case 1: - case 2: attenuation = ATTN_NONE; break; // full volume - } - - if (channel < NAME_Auto || channel > NAME_SoundSlot7) - { - channel = NAME_Auto; - } - - if (!looping) - { - S_Sound (self, int(channel) - NAME_Auto, soundid, 1, attenuation); - } - else - { - if (!S_IsActorPlayingSomething (self, int(channel) - NAME_Auto, soundid)) - { - S_Sound (self, (int(channel) - NAME_Auto) | CHAN_LOOP, soundid, 1, attenuation); - } - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSoundEx) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_NAME(channel, 0); - - if (channel > NAME_Auto && channel <= NAME_SoundSlot7) - { - S_StopSound (self, int(channel) - NAME_Auto); - } -} - -//========================================================================== -// -// Generic seeker missile function -// -//========================================================================== -static FRandom pr_seekermissile ("SeekerMissile"); -enum -{ - SMF_LOOK = 1, - SMF_PRECISE = 2, - SMF_CURSPEED = 4, -}; -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SeekerMissile) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(ang1, 0); - ACTION_PARAM_INT(ang2, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_INT(chance, 3); - ACTION_PARAM_INT(distance, 4); - - if ((flags & SMF_LOOK) && (self->tracer == 0) && (pr_seekermissile()tracer = P_RoughMonsterSearch (self, distance, true); - } - if (!P_SeekerMissile(self, clamp(ang1, 0, 90) * ANGLE_1, clamp(ang2, 0, 90) * ANGLE_1, !!(flags & SMF_PRECISE), !!(flags & SMF_CURSPEED))) - { - if (flags & SMF_LOOK) - { // This monster is no longer seekable, so let us look for another one next time. - self->tracer = NULL; - } - } -} - -//========================================================================== -// -// Hitscan attack with a customizable amount of bullets (specified in damage) -// -//========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_BulletAttack) -{ - int i; - int bangle; - int slope; - - if (!self->target) return; - - A_FaceTarget (self); - bangle = self->angle; - - slope = P_AimLineAttack (self, bangle, MISSILERANGE); - - S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); - for (i = self->GetMissileDamage (0, 1); i > 0; --i) - { - int angle = bangle + (pr_cabullet.Random2() << 20); - int damage = ((pr_cabullet()%5)+1)*3; - P_LineAttack(self, angle, MISSILERANGE, slope, damage, - NAME_Hitscan, NAME_BulletPuff); - } -} - - -//========================================================================== -// -// Do the state jump -// -//========================================================================== -static void DoJump(AActor * self, FState * CallingState, FState *jumpto, StateCallData *statecall) -{ - if (jumpto == NULL) return; - - if (statecall != NULL) - { - statecall->State = jumpto; - } - else if (self->player != NULL && CallingState == self->player->psprites[ps_weapon].state) - { - P_SetPsprite(self->player, ps_weapon, jumpto); - } - else if (self->player != NULL && CallingState == self->player->psprites[ps_flash].state) - { - P_SetPsprite(self->player, ps_flash, jumpto); - } - else if (CallingState == self->state) - { - self->SetState (jumpto); - } - else - { - // something went very wrong. This should never happen. - assert(false); - } -} - -// This is just to avoid having to directly reference the internally defined -// CallingState and statecall parameters in the code below. -#define ACTION_JUMP(offset) DoJump(self, CallingState, offset, statecall) - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Jump) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_INT(count, 0); - ACTION_PARAM_INT(maxchance, 1); - - if (count >= 2 && (maxchance >= 256 || pr_cajump() < maxchance)) - { - int jumps = 2 + (count == 2? 0 : (pr_cajump() % (count - 1))); - ACTION_PARAM_STATE(jumpto, jumps); - ACTION_JUMP(jumpto); - } - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfHealthLower) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_INT(health, 0); - ACTION_PARAM_STATE(jump, 1); - ACTION_PARAM_INT(ptr_selector, 2); - - AActor *measured; - - measured = COPY_AAPTR(self, ptr_selector); - - if (measured && measured->health < health) - { - ACTION_JUMP(jump); - } - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetOutsideMeleeRange) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - if (!self->CheckMeleeRange()) - { - ACTION_JUMP(jump); - } - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInsideMeleeRange) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - if (self->CheckMeleeRange()) - { - ACTION_JUMP(jump); - } - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! -} -//========================================================================== -// -// State jump function -// -//========================================================================== -void DoJumpIfCloser(AActor *target, DECLARE_PARAMINFO) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_FIXED(dist, 0); - ACTION_PARAM_STATE(jump, 1); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - // No target - no jump - if (target != NULL && P_AproxDistance(self->x-target->x, self->y-target->y) < dist && - ( (self->z > target->z && self->z - (target->z + target->height) < dist) || - (self->z <=target->z && target->z - (self->z + self->height) < dist) - ) - ) - { - ACTION_JUMP(jump); - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfCloser) -{ - AActor *target; - - if (!self->player) - { - target = self->target; - } - else - { - // Does the player aim at something that can be shot? - P_BulletSlope(self, &target); - } - DoJumpIfCloser(target, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTracerCloser) -{ - DoJumpIfCloser(self->tracer, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfMasterCloser) -{ - DoJumpIfCloser(self->master, PUSH_PARAMINFO); -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -void DoJumpIfInventory(AActor * owner, DECLARE_PARAMINFO) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_CLASS(Type, 0); - ACTION_PARAM_INT(ItemAmount, 1); - ACTION_PARAM_STATE(JumpOffset, 2); - ACTION_PARAM_INT(setowner, 3); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - if (!Type) return; - COPY_AAPTR_NOT_NULL(owner, owner, setowner); // returns if owner ends up being NULL - - AInventory *Item = owner->FindInventory(Type); - - if (Item) - { - if (ItemAmount > 0) - { - if (Item->Amount >= ItemAmount) - ACTION_JUMP(JumpOffset); - } - else if (Item->Amount >= Item->MaxAmount) - { - ACTION_JUMP(JumpOffset); - } - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInventory) -{ - DoJumpIfInventory(self, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetInventory) -{ - DoJumpIfInventory(self->target, PUSH_PARAMINFO); -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfArmorType) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_NAME(Type, 0); - ACTION_PARAM_STATE(JumpOffset, 1); - ACTION_PARAM_INT(amount, 2); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - ABasicArmor * armor = (ABasicArmor *) self->FindInventory(NAME_BasicArmor); - - if (armor && armor->ArmorType == Type && armor->Amount >= amount) - ACTION_JUMP(JumpOffset); -} - -//========================================================================== -// -// Parameterized version of A_Explode -// -//========================================================================== - -enum -{ - XF_HURTSOURCE = 1, - XF_NOTMISSILE = 4, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Explode) -{ - ACTION_PARAM_START(8); - ACTION_PARAM_INT(damage, 0); - ACTION_PARAM_INT(distance, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_BOOL(alert, 3); - ACTION_PARAM_INT(fulldmgdistance, 4); - ACTION_PARAM_INT(nails, 5); - ACTION_PARAM_INT(naildamage, 6); - ACTION_PARAM_CLASS(pufftype, 7); - - if (damage < 0) // get parameters from metadata - { - damage = self->GetClass()->Meta.GetMetaInt (ACMETA_ExplosionDamage, 128); - distance = self->GetClass()->Meta.GetMetaInt (ACMETA_ExplosionRadius, damage); - flags = !self->GetClass()->Meta.GetMetaInt (ACMETA_DontHurtShooter); - alert = false; - } - else - { - if (distance <= 0) distance = damage; - } - // NailBomb effect, from SMMU but not from its source code: instead it was implemented and - // generalized from the documentation at http://www.doomworld.com/eternity/engine/codeptrs.html - - if (nails) - { - angle_t ang; - for (int i = 0; i < nails; i++) - { - ang = i*(ANGLE_MAX/nails); - // Comparing the results of a test wad with Eternity, it seems A_NailBomb does not aim - P_LineAttack (self, ang, MISSILERANGE, 0, - //P_AimLineAttack (self, ang, MISSILERANGE), - naildamage, NAME_Hitscan, pufftype); - } - } - - P_RadiusAttack (self, self->target, damage, distance, self->DamageType, flags, fulldmgdistance); - P_CheckSplash(self, distance<target != NULL && self->target->player != NULL) - { - validcount++; - P_RecursiveSound (self->Sector, self->target, false, 0); - } -} - -//========================================================================== -// -// A_RadiusThrust -// -//========================================================================== - -enum -{ - RTF_AFFECTSOURCE = 1, - RTF_NOIMPACTDAMAGE = 2, - RTF_NOTMISSILE = 4, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusThrust) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_INT(force, 0); - ACTION_PARAM_INT(distance, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_INT(fullthrustdistance, 3); - - bool sourcenothrust = false; - - if (force == 0) force = 128; - if (distance <= 0) distance = abs(force); - - // Temporarily negate MF2_NODMGTHRUST on the shooter, since it renders this function useless. - if (!(flags & RTF_NOTMISSILE) && self->target != NULL && self->target->flags2 & MF2_NODMGTHRUST) - { - sourcenothrust = true; - self->target->flags2 &= ~MF2_NODMGTHRUST; - } - - P_RadiusAttack (self, self->target, force, distance, self->DamageType, flags | RADF_NODAMAGE, fullthrustdistance); - P_CheckSplash(self, distance << FRACBITS); - - if (sourcenothrust) - { - self->target->flags2 |= MF2_NODMGTHRUST; - } -} - -//========================================================================== -// -// Execute a line special / script -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CallSpecial) -{ - ACTION_PARAM_START(6); - ACTION_PARAM_INT(special, 0); - ACTION_PARAM_INT(arg1, 1); - ACTION_PARAM_INT(arg2, 2); - ACTION_PARAM_INT(arg3, 3); - ACTION_PARAM_INT(arg4, 4); - ACTION_PARAM_INT(arg5, 5); - - bool res = !!P_ExecuteSpecial(special, NULL, self, false, arg1, arg2, arg3, arg4, arg5); - - ACTION_SET_RESULT(res); -} - -//========================================================================== -// -// The ultimate code pointer: Fully customizable missiles! -// -//========================================================================== -enum CM_Flags -{ - CMF_AIMMODE = 3, - CMF_TRACKOWNER = 4, - CMF_CHECKTARGETDEAD = 8, - - CMF_ABSOLUTEPITCH = 16, - CMF_OFFSETPITCH = 32, - CMF_SAVEPITCH = 64, - - CMF_ABSOLUTEANGLE = 128 -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomMissile) -{ - ACTION_PARAM_START(7); - ACTION_PARAM_CLASS(ti, 0); - ACTION_PARAM_FIXED(SpawnHeight, 1); - ACTION_PARAM_INT(Spawnofs_XY, 2); - ACTION_PARAM_ANGLE(Angle, 3); - ACTION_PARAM_INT(flags, 4); - ACTION_PARAM_ANGLE(pitch, 5); - ACTION_PARAM_INT(ptr, 6); - - AActor *ref = COPY_AAPTR(self, ptr); - - int aimmode = flags & CMF_AIMMODE; - - AActor * targ; - AActor * missile; - - if (ref != NULL || aimmode==2) - { - if (ti) - { - angle_t ang = (self->angle - ANGLE_90) >> ANGLETOFINESHIFT; - fixed_t x = Spawnofs_XY * finecosine[ang]; - fixed_t y = Spawnofs_XY * finesine[ang]; - fixed_t z = SpawnHeight + self->GetBobOffset() - 32*FRACUNIT + (self->player? self->player->crouchoffset : 0); - - switch (aimmode) - { - case 0: - default: - // same adjustment as above (in all 3 directions this time) - for better aiming! - self->x += x; - self->y += y; - self->z += z; - missile = P_SpawnMissileXYZ(self->x, self->y, self->z + 32*FRACUNIT, self, ref, ti, false); - self->x -= x; - self->y -= y; - self->z -= z; - break; - - case 1: - missile = P_SpawnMissileXYZ(self->x+x, self->y+y, self->z + self->GetBobOffset() + SpawnHeight, self, ref, ti, false); - break; - - case 2: - self->x += x; - self->y += y; - missile = P_SpawnMissileAngleZSpeed(self, self->z + self->GetBobOffset() + SpawnHeight, ti, self->angle, 0, GetDefaultByType(ti)->Speed, self, false); - self->x -= x; - self->y -= y; - - flags |= CMF_ABSOLUTEPITCH; - - break; - } - - if (missile) - { - // Use the actual velocity instead of the missile's Speed property - // so that this can handle missiles with a high vertical velocity - // component properly. - - fixed_t missilespeed; - - if ( (CMF_ABSOLUTEPITCH|CMF_OFFSETPITCH) & flags) - { - if (CMF_OFFSETPITCH & flags) - { - FVector2 velocity (missile->velx, missile->vely); - pitch += R_PointToAngle2(0,0, (fixed_t)velocity.Length(), missile->velz); - } - ang = pitch >> ANGLETOFINESHIFT; - missilespeed = abs(FixedMul(finecosine[ang], missile->Speed)); - missile->velz = FixedMul(finesine[ang], missile->Speed); - } - else - { - FVector2 velocity (missile->velx, missile->vely); - missilespeed = (fixed_t)velocity.Length(); - } - - if (CMF_SAVEPITCH & flags) - { - missile->pitch = pitch; - // In aimmode 0 and 1 without absolutepitch or offsetpitch, the pitch parameter - // contains the unapplied parameter. In that case, it is set as pitch without - // otherwise affecting the spawned actor. - } - - missile->angle = (CMF_ABSOLUTEANGLE & flags) ? Angle : missile->angle + Angle ; - - ang = missile->angle >> ANGLETOFINESHIFT; - missile->velx = FixedMul (missilespeed, finecosine[ang]); - missile->vely = FixedMul (missilespeed, finesine[ang]); - - // handle projectile shooting projectiles - track the - // links back to a real owner - if (self->isMissile(!!(flags & CMF_TRACKOWNER))) - { - AActor * owner=self ;//->target; - while (owner->isMissile(!!(flags & CMF_TRACKOWNER)) && owner->target) owner=owner->target; - targ=owner; - missile->target=owner; - // automatic handling of seeker missiles - if (self->flags & missile->flags2 & MF2_SEEKERMISSILE) - { - missile->tracer=self->tracer; - } - } - else if (missile->flags2&MF2_SEEKERMISSILE) - { - // automatic handling of seeker missiles - missile->tracer=self->target; - } - // we must redo the spectral check here because the owner is set after spawning so the FriendPlayer value may be wrong - if (missile->flags4 & MF4_SPECTRAL) - { - if (missile->target != NULL) - { - missile->SetFriendPlayer(missile->target->player); - } - else - { - missile->FriendPlayer = 0; - } - } - P_CheckMissileSpawn(missile, self->radius); - } - } - } - else if (flags & CMF_CHECKTARGETDEAD) - { - // Target is dead and the attack shall be aborted. - if (self->SeeState != NULL && (self->health > 0 || !(self->flags3 & MF3_ISMONSTER))) self->SetState(self->SeeState); - } -} - -//========================================================================== -// -// An even more customizable hitscan attack -// -//========================================================================== -enum CBA_Flags -{ - CBAF_AIMFACING = 1, - CBAF_NORANDOM = 2, - CBAF_EXPLICITANGLE = 4, - CBAF_NOPITCH = 8, - CBAF_NORANDOMPUFFZ = 16, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomBulletAttack) -{ - ACTION_PARAM_START(8); - ACTION_PARAM_ANGLE(Spread_XY, 0); - ACTION_PARAM_ANGLE(Spread_Z, 1); - ACTION_PARAM_INT(NumBullets, 2); - ACTION_PARAM_INT(DamagePerBullet, 3); - ACTION_PARAM_CLASS(pufftype, 4); - ACTION_PARAM_FIXED(Range, 5); - ACTION_PARAM_INT(Flags, 6); - ACTION_PARAM_INT(ptr, 7); - - AActor *ref = COPY_AAPTR(self, ptr); - - if(Range==0) Range=MISSILERANGE; - - int i; - int bangle; - int bslope = 0; - int laflags = (Flags & CBAF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; - - if (ref || (Flags & CBAF_AIMFACING)) - { - if (!(Flags & CBAF_AIMFACING)) - { - A_Face(self, ref); - } - bangle = self->angle; - - if (!pufftype) pufftype = PClass::FindClass(NAME_BulletPuff); - - if (!(Flags & CBAF_NOPITCH)) bslope = P_AimLineAttack (self, bangle, MISSILERANGE); - - S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); - for (i=0 ; itarget) - return; - - A_FaceTarget (self); - if (self->CheckMeleeRange ()) - { - if (MeleeSound) S_Sound (self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM); - int newdam = P_DamageMobj (self->target, self, self, damage, DamageType); - if (bleed) P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); - } - else - { - if (MissSound) S_Sound (self, CHAN_WEAPON, MissSound, 1, ATTN_NORM); - } -} - -//========================================================================== -// -// A fully customizable combo attack -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomComboAttack) -{ - ACTION_PARAM_START(6); - ACTION_PARAM_CLASS(ti, 0); - ACTION_PARAM_FIXED(SpawnHeight, 1); - ACTION_PARAM_INT(damage, 2); - ACTION_PARAM_SOUND(MeleeSound, 3); - ACTION_PARAM_NAME(DamageType, 4); - ACTION_PARAM_BOOL(bleed, 5); - - if (!self->target) - return; - - A_FaceTarget (self); - if (self->CheckMeleeRange ()) - { - if (DamageType==NAME_None) DamageType = NAME_Melee; // Melee is the default type - if (MeleeSound) S_Sound (self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM); - int newdam = P_DamageMobj (self->target, self, self, damage, DamageType); - if (bleed) P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); - } - else if (ti) - { - // This seemingly senseless code is needed for proper aiming. - self->z += SpawnHeight + self->GetBobOffset() - 32*FRACUNIT; - AActor *missile = P_SpawnMissileXYZ (self->x, self->y, self->z + 32*FRACUNIT, self, self->target, ti, false); - self->z -= SpawnHeight + self->GetBobOffset() - 32*FRACUNIT; - - if (missile) - { - // automatic handling of seeker missiles - if (missile->flags2&MF2_SEEKERMISSILE) - { - missile->tracer=self->target; - } - P_CheckMissileSpawn(missile, self->radius); - } - } -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfNoAmmo) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (!ACTION_CALL_FROM_WEAPON()) return; - - if (!self->player->ReadyWeapon->CheckAmmo(self->player->ReadyWeapon->bAltFire, false, true)) - { - ACTION_JUMP(jump); - } - -} - - -//========================================================================== -// -// An even more customizable hitscan attack -// -//========================================================================== -enum FB_Flags -{ - FBF_USEAMMO = 1, - FBF_NORANDOM = 2, - FBF_EXPLICITANGLE = 4, - FBF_NOPITCH = 8, - FBF_NOFLASH = 16, - FBF_NORANDOMPUFFZ = 32, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) -{ - ACTION_PARAM_START(7); - ACTION_PARAM_ANGLE(Spread_XY, 0); - ACTION_PARAM_ANGLE(Spread_Z, 1); - ACTION_PARAM_INT(NumberOfBullets, 2); - ACTION_PARAM_INT(DamagePerBullet, 3); - ACTION_PARAM_CLASS(PuffType, 4); - ACTION_PARAM_INT(Flags, 5); - ACTION_PARAM_FIXED(Range, 6); - - if (!self->player) return; - - player_t * player=self->player; - AWeapon * weapon=player->ReadyWeapon; - - int i; - int bangle; - int bslope = 0; - int laflags = (Flags & FBF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; - - if ((Flags & FBF_USEAMMO) && weapon) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // out of ammo - } - - if (Range == 0) Range = PLAYERMISSILERANGE; - - if (!(Flags & FBF_NOFLASH)) static_cast(self)->PlayAttacking2 (); - - if (!(Flags & FBF_NOPITCH)) bslope = P_BulletSlope(self); - bangle = self->angle; - - if (!PuffType) PuffType = PClass::FindClass(NAME_BulletPuff); - - if (weapon != NULL) - { - S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); - } - - if ((NumberOfBullets==1 && !player->refire) || NumberOfBullets==0) - { - int damage = DamagePerBullet; - - if (!(Flags & FBF_NORANDOM)) - damage *= ((pr_cwbullet()%3)+1); - - P_LineAttack(self, bangle, Range, bslope, damage, NAME_Hitscan, PuffType, laflags); - } - else - { - if (NumberOfBullets == -1) NumberOfBullets = 1; - for (i=0 ; iplayer) return; - - - player_t *player=self->player; - AWeapon * weapon=player->ReadyWeapon; - AActor *linetarget; - - if (UseAmmo && weapon) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // out of ammo - } - - if (ti) - { - angle_t ang = (self->angle - ANGLE_90) >> ANGLETOFINESHIFT; - fixed_t x = SpawnOfs_XY * finecosine[ang]; - fixed_t y = SpawnOfs_XY * finesine[ang]; - fixed_t z = SpawnHeight; - fixed_t shootangle = self->angle; - - if (Flags & FPF_AIMATANGLE) shootangle += Angle; - - // Temporarily adjusts the pitch - fixed_t SavedPlayerPitch = self->pitch; - self->pitch -= pitch; - AActor * misl=P_SpawnPlayerMissile (self, x, y, z, ti, shootangle, &linetarget); - self->pitch = SavedPlayerPitch; - - // automatic handling of seeker missiles - if (misl) - { - if (Flags & FPF_TRANSFERTRANSLATION) misl->Translation = self->Translation; - if (linetarget && misl->flags2&MF2_SEEKERMISSILE) misl->tracer=linetarget; - if (!(Flags & FPF_AIMATANGLE)) - { - // This original implementation is to aim straight ahead and then offset - // the angle from the resulting direction. - FVector3 velocity(misl->velx, misl->vely, 0); - fixed_t missilespeed = (fixed_t)velocity.Length(); - misl->angle += Angle; - angle_t an = misl->angle >> ANGLETOFINESHIFT; - misl->velx = FixedMul (missilespeed, finecosine[an]); - misl->vely = FixedMul (missilespeed, finesine[an]); - } - } - } -} - - -//========================================================================== -// -// A_CustomPunch -// -// Berserk is not handled here. That can be done with A_CheckIfInventory -// -//========================================================================== - -enum -{ - CPF_USEAMMO = 1, - CPF_DAGGER = 2, - CPF_PULLIN = 4, - CPF_NORANDOMPUFFZ = 8, - CPF_NOTURN = 16, - CPF_STEALARMOR = 32, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomPunch) -{ - ACTION_PARAM_START(8); - ACTION_PARAM_INT(Damage, 0); - ACTION_PARAM_BOOL(norandom, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_CLASS(PuffType, 3); - ACTION_PARAM_FIXED(Range, 4); - ACTION_PARAM_FIXED(LifeSteal, 5); - ACTION_PARAM_INT(lifestealmax, 6); - ACTION_PARAM_CLASS(armorbonustype, 7); - - if (!self->player) return; - - player_t *player=self->player; - AWeapon * weapon=player->ReadyWeapon; - - - angle_t angle; - int pitch; - AActor * linetarget; - int actualdamage; - - if (!norandom) Damage *= (pr_cwpunch()%8+1); - - angle = self->angle + (pr_cwpunch.Random2() << 18); - if (Range == 0) Range = MELEERANGE; - pitch = P_AimLineAttack (self, angle, Range, &linetarget); - - // only use ammo when actually hitting something! - if ((flags & CPF_USEAMMO) && linetarget && weapon) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // out of ammo - } - - if (!PuffType) PuffType = PClass::FindClass(NAME_BulletPuff); - int puffFlags = LAF_ISMELEEATTACK | ((flags & CPF_NORANDOMPUFFZ) ? LAF_NORANDOMPUFFZ : 0); - - P_LineAttack (self, angle, Range, pitch, Damage, NAME_Melee, PuffType, puffFlags, &linetarget, &actualdamage); - - if (linetarget) - { - if (LifeSteal && !(linetarget->flags5 & MF5_DONTDRAIN)) - { - if (flags & CPF_STEALARMOR) - { - if (!armorbonustype) armorbonustype = PClass::FindClass("ArmorBonus"); - - if (armorbonustype->IsDescendantOf (RUNTIME_CLASS(ABasicArmorBonus))) - { - ABasicArmorBonus *armorbonus = static_cast(Spawn (armorbonustype, 0,0,0, NO_REPLACE)); - armorbonus->SaveAmount *= (actualdamage * LifeSteal) >> FRACBITS; - armorbonus->MaxSaveAmount = lifestealmax <= 0 ? armorbonus->MaxSaveAmount : lifestealmax; - armorbonus->flags |= MF_DROPPED; - armorbonus->ClearCounters(); - - if (!armorbonus->CallTryPickup (self)) - { - armorbonus->Destroy (); - } - } - } - - else - { - P_GiveBody (self, (actualdamage * LifeSteal) >> FRACBITS, lifestealmax); - } - } - - if (weapon != NULL) - { - S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); - } - - if (!(flags & CPF_NOTURN)) - { - // turn to face target - self->angle = R_PointToAngle2 (self->x, - self->y, - linetarget->x, - linetarget->y); - } - - if (flags & CPF_PULLIN) self->flags |= MF_JUSTATTACKED; - if (flags & CPF_DAGGER) P_DaggerAlert (self, linetarget); - } -} - - -//========================================================================== -// -// customizable railgun attack function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack) -{ - ACTION_PARAM_START(16); - ACTION_PARAM_INT(Damage, 0); - ACTION_PARAM_INT(Spawnofs_XY, 1); - ACTION_PARAM_BOOL(UseAmmo, 2); - ACTION_PARAM_COLOR(Color1, 3); - ACTION_PARAM_COLOR(Color2, 4); - ACTION_PARAM_INT(Flags, 5); - ACTION_PARAM_FLOAT(MaxDiff, 6); - ACTION_PARAM_CLASS(PuffType, 7); - ACTION_PARAM_ANGLE(Spread_XY, 8); - ACTION_PARAM_ANGLE(Spread_Z, 9); - ACTION_PARAM_FIXED(Range, 10); - ACTION_PARAM_INT(Duration, 11); - ACTION_PARAM_FLOAT(Sparsity, 12); - ACTION_PARAM_FLOAT(DriftSpeed, 13); - ACTION_PARAM_CLASS(SpawnClass, 14); - ACTION_PARAM_FIXED(Spawnofs_Z, 15); - - if(Range==0) Range=8192*FRACUNIT; - if(Sparsity==0) Sparsity=1.0; - - if (!self->player) return; - - AWeapon * weapon=self->player->ReadyWeapon; - - // only use ammo when actually hitting something! - if (UseAmmo) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // out of ammo - } - - angle_t angle; - angle_t slope; - - if (Flags & RAF_EXPLICITANGLE) - { - angle = Spread_XY; - slope = Spread_Z; - } - else - { - angle = pr_crailgun.Random2() * (Spread_XY / 255); - slope = pr_crailgun.Random2() * (Spread_Z / 255); - } - - P_RailAttack (self, Damage, Spawnofs_XY, Spawnofs_Z, Color1, Color2, MaxDiff, Flags, PuffType, angle, slope, Range, Duration, Sparsity, DriftSpeed, SpawnClass); -} - -//========================================================================== -// -// also for monsters -// -//========================================================================== -enum -{ - CRF_DONTAIM = 0, - CRF_AIMPARALLEL = 1, - CRF_AIMDIRECT = 2, - CRF_EXPLICITANGLE = 4, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) -{ - ACTION_PARAM_START(16); - ACTION_PARAM_INT(Damage, 0); - ACTION_PARAM_INT(Spawnofs_XY, 1); - ACTION_PARAM_COLOR(Color1, 2); - ACTION_PARAM_COLOR(Color2, 3); - ACTION_PARAM_INT(Flags, 4); - ACTION_PARAM_INT(aim, 5); - ACTION_PARAM_FLOAT(MaxDiff, 6); - ACTION_PARAM_CLASS(PuffType, 7); - ACTION_PARAM_ANGLE(Spread_XY, 8); - ACTION_PARAM_ANGLE(Spread_Z, 9); - ACTION_PARAM_FIXED(Range, 10); - ACTION_PARAM_INT(Duration, 11); - ACTION_PARAM_FLOAT(Sparsity, 12); - ACTION_PARAM_FLOAT(DriftSpeed, 13); - ACTION_PARAM_CLASS(SpawnClass, 14); - ACTION_PARAM_FIXED(Spawnofs_Z, 15); - - if(Range==0) Range=8192*FRACUNIT; - if(Sparsity==0) Sparsity=1.0; - - AActor *linetarget; - - fixed_t saved_x = self->x; - fixed_t saved_y = self->y; - angle_t saved_angle = self->angle; - fixed_t saved_pitch = self->pitch; - - if (aim && self->target == NULL) - { - return; - } - // [RH] Andy Baker's stealth monsters - if (self->flags & MF_STEALTH) - { - self->visdir = 1; - } - - self->flags &= ~MF_AMBUSH; - - - if (aim) - { - self->angle = R_PointToAngle2 (self->x, - self->y, - self->target->x, - self->target->y); - } - self->pitch = P_AimLineAttack (self, self->angle, MISSILERANGE, &linetarget, ANGLE_1*60, 0, aim ? self->target : NULL); - if (linetarget == NULL && aim) - { - // We probably won't hit the target, but aim at it anyway so we don't look stupid. - FVector2 xydiff(self->target->x - self->x, self->target->y - self->y); - double zdiff = (self->target->z + (self->target->height>>1)) - - (self->z + (self->height>>1) - self->floorclip); - self->pitch = int(atan2(zdiff, xydiff.Length()) * ANGLE_180 / -M_PI); - } - // Let the aim trail behind the player - if (aim) - { - saved_angle = self->angle = R_PointToAngle2 (self->x, self->y, - self->target->x - self->target->velx * 3, - self->target->y - self->target->vely * 3); - - if (aim == CRF_AIMDIRECT) - { - // Tricky: We must offset to the angle of the current position - // but then change the angle again to ensure proper aim. - self->x += Spawnofs_XY * finecosine[self->angle]; - self->y += Spawnofs_XY * finesine[self->angle]; - Spawnofs_XY = 0; - self->angle = R_PointToAngle2 (self->x, self->y, - self->target->x - self->target->velx * 3, - self->target->y - self->target->vely * 3); - } - - if (self->target->flags & MF_SHADOW) - { - angle_t rnd = pr_crailgun.Random2() << 21; - self->angle += rnd; - saved_angle = rnd; - } - } - - angle_t angle = (self->angle - ANG90) >> ANGLETOFINESHIFT; - - angle_t angleoffset; - angle_t slopeoffset; - - if (Flags & CRF_EXPLICITANGLE) - { - angleoffset = Spread_XY; - slopeoffset = Spread_Z; - } - else - { - angleoffset = pr_crailgun.Random2() * (Spread_XY / 255); - slopeoffset = pr_crailgun.Random2() * (Spread_Z / 255); - } - - P_RailAttack (self, Damage, Spawnofs_XY, Spawnofs_Z, Color1, Color2, MaxDiff, Flags, PuffType, angleoffset, slopeoffset, Range, Duration, Sparsity, DriftSpeed, SpawnClass); - - self->x = saved_x; - self->y = saved_y; - self->angle = saved_angle; - self->pitch = saved_pitch; -} - -//=========================================================================== -// -// DoGiveInventory -// -//=========================================================================== - -static void DoGiveInventory(AActor * receiver, DECLARE_PARAMINFO) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_CLASS(mi, 0); - ACTION_PARAM_INT(amount, 1); - ACTION_PARAM_INT(setreceiver, 2); - - COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver); - - bool res=true; - - if (amount==0) amount=1; - if (mi) - { - AInventory *item = static_cast(Spawn (mi, 0, 0, 0, NO_REPLACE)); - if (item->IsKindOf(RUNTIME_CLASS(AHealth))) - { - item->Amount *= amount; - } - else - { - item->Amount = amount; - } - item->flags |= MF_DROPPED; - item->ClearCounters(); - if (!item->CallTryPickup (receiver)) - { - item->Destroy (); - res = false; - } - else res = true; - } - else res = false; - ACTION_SET_RESULT(res); - -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveInventory) -{ - DoGiveInventory(self, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToTarget) -{ - DoGiveInventory(self->target, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToChildren) -{ - TThinkerIterator it; - AActor * mo; - - while ((mo = it.Next())) - { - if (mo->master == self) DoGiveInventory(mo, PUSH_PARAMINFO); - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToSiblings) -{ - TThinkerIterator it; - AActor * mo; - - if (self->master != NULL) - { - while ((mo = it.Next())) - { - if (mo->master == self->master && mo != self) DoGiveInventory(mo, PUSH_PARAMINFO); - } - } -} - -//=========================================================================== -// -// A_TakeInventory -// -//=========================================================================== - -enum -{ - TIF_NOTAKEINFINITE = 1, -}; - -void DoTakeInventory(AActor * receiver, DECLARE_PARAMINFO) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_CLASS(item, 0); - ACTION_PARAM_INT(amount, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_INT(setreceiver, 3); - - if (!item) return; - COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver); - - bool res = false; - - AInventory * inv = receiver->FindInventory(item); - - if (inv && !inv->IsKindOf(RUNTIME_CLASS(AHexenArmor))) - { - if (inv->Amount > 0) - { - res = true; - } - // Do not take ammo if the "no take infinite/take as ammo depletion" flag is set - // and infinite ammo is on - if (flags & TIF_NOTAKEINFINITE && - ((dmflags & DF_INFINITE_AMMO) || (receiver->player->cheats & CF_INFINITEAMMO)) && - inv->IsKindOf(RUNTIME_CLASS(AAmmo))) - { - // Nothing to do here, except maybe res = false;? Would it make sense? - } - else if (!amount || amount>=inv->Amount) - { - if (inv->ItemFlags&IF_KEEPDEPLETED) inv->Amount=0; - else inv->Destroy(); - } - else inv->Amount-=amount; - } - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeInventory) -{ - DoTakeInventory(self, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromTarget) -{ - DoTakeInventory(self->target, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromChildren) -{ - TThinkerIterator it; - AActor * mo; - - while ((mo = it.Next())) - { - if (mo->master == self) DoTakeInventory(mo, PUSH_PARAMINFO); - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromSiblings) -{ - TThinkerIterator it; - AActor * mo; - - if (self->master != NULL) - { - while ((mo = it.Next())) - { - if (mo->master == self->master && mo != self) DoTakeInventory(mo, PUSH_PARAMINFO); - } - } -} - -//=========================================================================== -// -// Common code for A_SpawnItem and A_SpawnItemEx -// -//=========================================================================== - -enum SIX_Flags -{ - SIXF_TRANSFERTRANSLATION = 1 << 0, - SIXF_ABSOLUTEPOSITION = 1 << 1, - SIXF_ABSOLUTEANGLE = 1 << 2, - SIXF_ABSOLUTEVELOCITY = 1 << 3, - SIXF_SETMASTER = 1 << 4, - SIXF_NOCHECKPOSITION = 1 << 5, - SIXF_TELEFRAG = 1 << 6, - SIXF_CLIENTSIDE = 1 << 7, // only used by Skulldronum - SIXF_TRANSFERAMBUSHFLAG = 1 << 8, - SIXF_TRANSFERPITCH = 1 << 9, - SIXF_TRANSFERPOINTERS = 1 << 10, - SIXF_USEBLOODCOLOR = 1 << 11, - SIXF_CLEARCALLERTID = 1 << 12, - SIXF_MULTIPLYSPEED = 1 << 13, - SIXF_TRANSFERSCALE = 1 << 14, - SIXF_TRANSFERSPECIAL = 1 << 15, - SIXF_CLEARCALLERSPECIAL = 1 << 16, - SIXF_TRANSFERSTENCILCOL = 1 << 17, - SIXF_TRANSFERALPHA = 1 << 18, - SIXF_TRANSFERRENDERSTYLE = 1 << 19, - SIXF_SETTARGET = 1 << 20, - SIXF_SETTRACER = 1 << 21, - SIXF_NOPOINTERS = 1 << 22, - SIXF_ORIGINATOR = 1 << 23, -}; - -static bool InitSpawnedItem(AActor *self, AActor *mo, int flags) -{ - if (mo == NULL) - { - return false; - } - AActor *originator = self; - - if (!(mo->flags2 & MF2_DONTTRANSLATE)) - { - if (flags & SIXF_TRANSFERTRANSLATION) - { - mo->Translation = self->Translation; - } - else if (flags & SIXF_USEBLOODCOLOR) - { - // [XA] Use the spawning actor's BloodColor to translate the newly-spawned object. - PalEntry bloodcolor = self->GetBloodColor(); - mo->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a); - } - } - if (flags & SIXF_TRANSFERPOINTERS) - { - mo->target = self->target; - mo->master = self->master; // This will be overridden later if SIXF_SETMASTER is set - mo->tracer = self->tracer; - } - - mo->angle = self->angle; - if (flags & SIXF_TRANSFERPITCH) - { - mo->pitch = self->pitch; - } - if (!(flags & SIXF_ORIGINATOR)) - { - while (originator && originator->isMissile()) - { - originator = originator->target; - } - } - if (flags & SIXF_TELEFRAG) - { - P_TeleportMove(mo, mo->x, mo->y, mo->z, true); - // This is needed to ensure consistent behavior. - // Otherwise it will only spawn if nothing gets telefragged - flags |= SIXF_NOCHECKPOSITION; - } - if (mo->flags3 & MF3_ISMONSTER) - { - if (!(flags & SIXF_NOCHECKPOSITION) && !P_TestMobjLocation(mo)) - { - // The monster is blocked so don't spawn it at all! - mo->ClearCounters(); - mo->Destroy(); - return false; - } - else if (originator && !(flags & SIXF_NOPOINTERS)) - { - if (originator->flags3 & MF3_ISMONSTER) - { - // If this is a monster transfer all friendliness information - mo->CopyFriendliness(originator, true); - } - else if (originator->player) - { - // A player always spawns a monster friendly to him - mo->flags |= MF_FRIENDLY; - mo->SetFriendPlayer(originator->player); - - AActor * attacker=originator->player->attacker; - if (attacker) - { - if (!(attacker->flags&MF_FRIENDLY) || - (deathmatch && attacker->FriendPlayer!=0 && attacker->FriendPlayer!=mo->FriendPlayer)) - { - // Target the monster which last attacked the player - mo->LastHeard = mo->target = attacker; - } - } - } - } - } - else if (!(flags & SIXF_TRANSFERPOINTERS)) - { - // If this is a missile or something else set the target to the originator - mo->target = originator ? originator : self; - } - if (flags & SIXF_NOPOINTERS) - { - //[MC]Intentionally eliminate pointers. Overrides TRANSFERPOINTERS, but is overridden by SETMASTER/TARGET/TRACER. - mo->LastHeard = NULL; //Sanity check. - mo->target = NULL; - mo->master = NULL; - mo->tracer = NULL; - } - if (flags & SIXF_SETMASTER) - { - mo->master = originator; - } - if (flags & SIXF_SETTARGET) - { - mo->target = originator; - } - if (flags & SIXF_SETTRACER) - { - mo->tracer = originator; - } - if (flags & SIXF_TRANSFERSCALE) - { - mo->scaleX = self->scaleX; - mo->scaleY = self->scaleY; - } - if (flags & SIXF_TRANSFERAMBUSHFLAG) - { - mo->flags = (mo->flags & ~MF_AMBUSH) | (self->flags & MF_AMBUSH); - } - if (flags & SIXF_CLEARCALLERTID) - { - self->RemoveFromHash(); - self->tid = 0; - } - if (flags & SIXF_TRANSFERSPECIAL) - { - mo->special = self->special; - memcpy(mo->args, self->args, sizeof(self->args)); - } - if (flags & SIXF_CLEARCALLERSPECIAL) - { - self->special = 0; - memset(self->args, 0, sizeof(self->args)); - } - if (flags & SIXF_TRANSFERSTENCILCOL) - { - mo->fillcolor = self->fillcolor; - } - if (flags & SIXF_TRANSFERALPHA) - { - mo->alpha = self->alpha; - } - if (flags & SIXF_TRANSFERRENDERSTYLE) - { - mo->RenderStyle = self->RenderStyle; - } - - return true; -} - -//=========================================================================== -// -// A_SpawnItem -// -// Spawns an item in front of the caller like Heretic's time bomb -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItem) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_CLASS(missile, 0); - ACTION_PARAM_FIXED(distance, 1); - ACTION_PARAM_FIXED(zheight, 2); - ACTION_PARAM_BOOL(useammo, 3); - ACTION_PARAM_BOOL(transfer_translation, 4); - - if (!missile) - { - ACTION_SET_RESULT(false); - return; - } - - // Don't spawn monsters if this actor has been massacred - if (self->DamageType == NAME_Massacre && GetDefaultByType(missile)->flags3&MF3_ISMONSTER) return; - - if (distance==0) - { - // use the minimum distance that does not result in an overlap - distance=(self->radius+GetDefaultByType(missile)->radius)>>FRACBITS; - } - - if (ACTION_CALL_FROM_WEAPON()) - { - // Used from a weapon so use some ammo - AWeapon * weapon=self->player->ReadyWeapon; - - if (!weapon) return; - if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) return; - } - - AActor * mo = Spawn( missile, - self->x + FixedMul(distance, finecosine[self->angle>>ANGLETOFINESHIFT]), - self->y + FixedMul(distance, finesine[self->angle>>ANGLETOFINESHIFT]), - self->z - self->floorclip + self->GetBobOffset() + zheight, ALLOW_REPLACE); - - int flags = (transfer_translation ? SIXF_TRANSFERTRANSLATION : 0) + (useammo ? SIXF_SETMASTER : 0); - bool res = InitSpawnedItem(self, mo, flags); - ACTION_SET_RESULT(res); // for an inventory item's use state -} - -//=========================================================================== -// -// A_SpawnItemEx -// -// Enhanced spawning function -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItemEx) -{ - ACTION_PARAM_START(11); - ACTION_PARAM_CLASS(missile, 0); - ACTION_PARAM_FIXED(xofs, 1); - ACTION_PARAM_FIXED(yofs, 2); - ACTION_PARAM_FIXED(zofs, 3); - ACTION_PARAM_FIXED(xvel, 4); - ACTION_PARAM_FIXED(yvel, 5); - ACTION_PARAM_FIXED(zvel, 6); - ACTION_PARAM_ANGLE(Angle, 7); - ACTION_PARAM_INT(flags, 8); - ACTION_PARAM_INT(chance, 9); - ACTION_PARAM_INT(tid, 10); - - if (!missile) - { - ACTION_SET_RESULT(false); - return; - } - - if (chance > 0 && pr_spawnitemex()DamageType == NAME_Massacre && GetDefaultByType(missile)->flags3&MF3_ISMONSTER) return; - - fixed_t x,y; - - if (!(flags & SIXF_ABSOLUTEANGLE)) - { - Angle += self->angle; - } - - angle_t ang = Angle >> ANGLETOFINESHIFT; - - if (flags & SIXF_ABSOLUTEPOSITION) - { - x = self->x + xofs; - y = self->y + yofs; - } - else - { - // in relative mode negative y values mean 'left' and positive ones mean 'right' - // This is the inverse orientation of the absolute mode! - x = self->x + FixedMul(xofs, finecosine[ang]) + FixedMul(yofs, finesine[ang]); - y = self->y + FixedMul(xofs, finesine[ang]) - FixedMul(yofs, finecosine[ang]); - } - - if (!(flags & SIXF_ABSOLUTEVELOCITY)) - { - // Same orientation issue here! - fixed_t newxvel = FixedMul(xvel, finecosine[ang]) + FixedMul(yvel, finesine[ang]); - yvel = FixedMul(xvel, finesine[ang]) - FixedMul(yvel, finecosine[ang]); - xvel = newxvel; - } - - AActor *mo = Spawn(missile, x, y, self->z - self->floorclip + self->GetBobOffset() + zofs, ALLOW_REPLACE); - bool res = InitSpawnedItem(self, mo, flags); - ACTION_SET_RESULT(res); // for an inventory item's use state - if (res) - { - if (tid != 0) - { - assert(mo->tid == 0); - mo->tid = tid; - mo->AddToHash(); - } - if (flags & SIXF_MULTIPLYSPEED) - { - mo->velx = FixedMul(xvel, mo->Speed); - mo->vely = FixedMul(yvel, mo->Speed); - mo->velz = FixedMul(zvel, mo->Speed); - } - else - { - mo->velx = xvel; - mo->vely = yvel; - mo->velz = zvel; - } - mo->angle = Angle; - } -} - -//=========================================================================== -// -// A_ThrowGrenade -// -// Throws a grenade (like Hexen's fighter flechette) -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ThrowGrenade) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_CLASS(missile, 0); - ACTION_PARAM_FIXED(zheight, 1); - ACTION_PARAM_FIXED(xyvel, 2); - ACTION_PARAM_FIXED(zvel, 3); - ACTION_PARAM_BOOL(useammo, 4); - - if (missile == NULL) return; - - if (ACTION_CALL_FROM_WEAPON()) - { - // Used from a weapon, so use some ammo - AWeapon *weapon = self->player->ReadyWeapon; - - if (!weapon) return; - if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) return; - } - - - AActor * bo; - - bo = Spawn(missile, self->x, self->y, - self->z - self->floorclip + self->GetBobOffset() + zheight + 35*FRACUNIT + (self->player? self->player->crouchoffset : 0), - ALLOW_REPLACE); - if (bo) - { - P_PlaySpawnSound(bo, self); - if (xyvel != 0) - bo->Speed = xyvel; - bo->angle = self->angle + (((pr_grenade()&7) - 4) << 24); - - angle_t pitch = angle_t(-self->pitch) >> ANGLETOFINESHIFT; - angle_t angle = bo->angle >> ANGLETOFINESHIFT; - - // There are two vectors we are concerned about here: xy and z. We rotate - // them separately according to the shooter's pitch and then sum them to - // get the final velocity vector to shoot with. - - fixed_t xy_xyscale = FixedMul(bo->Speed, finecosine[pitch]); - fixed_t xy_velz = FixedMul(bo->Speed, finesine[pitch]); - fixed_t xy_velx = FixedMul(xy_xyscale, finecosine[angle]); - fixed_t xy_vely = FixedMul(xy_xyscale, finesine[angle]); - - pitch = angle_t(self->pitch) >> ANGLETOFINESHIFT; - fixed_t z_xyscale = FixedMul(zvel, finesine[pitch]); - fixed_t z_velz = FixedMul(zvel, finecosine[pitch]); - fixed_t z_velx = FixedMul(z_xyscale, finecosine[angle]); - fixed_t z_vely = FixedMul(z_xyscale, finesine[angle]); - - bo->velx = xy_velx + z_velx + (self->velx >> 1); - bo->vely = xy_vely + z_vely + (self->vely >> 1); - bo->velz = xy_velz + z_velz; - - bo->target = self; - P_CheckMissileSpawn (bo, self->radius); - } - else ACTION_SET_RESULT(false); -} - - -//=========================================================================== -// -// A_Recoil -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Recoil) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_FIXED(xyvel, 0); - - angle_t angle = self->angle + ANG180; - angle >>= ANGLETOFINESHIFT; - self->velx += FixedMul (xyvel, finecosine[angle]); - self->vely += FixedMul (xyvel, finesine[angle]); -} - - -//=========================================================================== -// -// A_SelectWeapon -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SelectWeapon) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_CLASS(cls, 0); - - if (cls == NULL || self->player == NULL) - { - ACTION_SET_RESULT(false); - return; - } - - AWeapon * weaponitem = static_cast(self->FindInventory(cls)); - - if (weaponitem != NULL && weaponitem->IsKindOf(RUNTIME_CLASS(AWeapon))) - { - if (self->player->ReadyWeapon != weaponitem) - { - self->player->PendingWeapon = weaponitem; - } - } - else ACTION_SET_RESULT(false); - -} - - -//=========================================================================== -// -// A_Print -// -//=========================================================================== -EXTERN_CVAR(Float, con_midtime) - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Print) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_STRING(text, 0); - ACTION_PARAM_FLOAT(time, 1); - ACTION_PARAM_NAME(fontname, 2); - - if (text[0] == '$') text = GStrings(text+1); - if (self->CheckLocalView (consoleplayer) || - (self->target!=NULL && self->target->CheckLocalView (consoleplayer))) - { - float saved = con_midtime; - FFont *font = NULL; - - if (fontname != NAME_None) - { - font = V_GetFont(fontname); - } - if (time > 0) - { - con_midtime = time; - } - - FString formatted = strbin1(text); - C_MidPrint(font != NULL ? font : SmallFont, formatted.GetChars()); - con_midtime = saved; - } - ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! -} - -//=========================================================================== -// -// A_PrintBold -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PrintBold) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_STRING(text, 0); - ACTION_PARAM_FLOAT(time, 1); - ACTION_PARAM_NAME(fontname, 2); - - float saved = con_midtime; - FFont *font = NULL; - - if (text[0] == '$') text = GStrings(text+1); - if (fontname != NAME_None) - { - font = V_GetFont(fontname); - } - if (time > 0) - { - con_midtime = time; - } - - FString formatted = strbin1(text); - C_MidPrintBold(font != NULL ? font : SmallFont, formatted.GetChars()); - con_midtime = saved; - ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! -} - -//=========================================================================== -// -// A_Log -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Log) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STRING(text, 0); - - if (text[0] == '$') text = GStrings(text+1); - FString formatted = strbin1(text); - Printf("%s\n", formatted.GetChars()); - ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! -} - -//========================================================================= -// -// A_LogInt -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LogInt) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(num, 0); - Printf("%d\n", num); - ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! -} - -//=========================================================================== -// -// A_SetTranslucent -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTranslucent) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_FIXED(alpha, 0); - ACTION_PARAM_INT(mode, 1); - - mode = mode == 0 ? STYLE_Translucent : mode == 2 ? STYLE_Fuzzy : STYLE_Add; - - self->RenderStyle.Flags &= ~STYLEF_Alpha1; - self->alpha = clamp(alpha, 0, FRACUNIT); - self->RenderStyle = ERenderStyle(mode); -} - -//=========================================================================== -// -// A_FadeIn -// -// Fades the actor in -// -//=========================================================================== - -enum FadeFlags -{ - FTF_REMOVE = 1 << 0, - FTF_CLAMP = 1 << 1, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeIn) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_FIXED(reduce, 0); - ACTION_PARAM_INT(flags, 1); - - if (reduce == 0) - { - reduce = FRACUNIT / 10; - } - self->RenderStyle.Flags &= ~STYLEF_Alpha1; - self->alpha += reduce; - - if (self->alpha >= (FRACUNIT * 1)) - { - if (flags & FTF_CLAMP) - self->alpha = (FRACUNIT * 1); - if (flags & FTF_REMOVE) - self->Destroy(); - } -} - -//=========================================================================== -// -// A_FadeOut -// -// fades the actor out and destroys it when done -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeOut) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_FIXED(reduce, 0); - ACTION_PARAM_INT(flags, 1); - - if (reduce == 0) - { - reduce = FRACUNIT/10; - } - self->RenderStyle.Flags &= ~STYLEF_Alpha1; - self->alpha -= reduce; - if (self->alpha <= 0) - { - if (flags & FTF_CLAMP) - self->alpha = 0; - if (flags & FTF_REMOVE) - self->Destroy(); - } -} - -//=========================================================================== -// -// A_FadeTo -// -// fades the actor to a specified transparency by a specified amount and -// destroys it if so desired -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeTo) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_FIXED(target, 0); - ACTION_PARAM_FIXED(amount, 1); - ACTION_PARAM_INT(flags, 2); - - self->RenderStyle.Flags &= ~STYLEF_Alpha1; - - if (self->alpha > target) - { - self->alpha -= amount; - - if (self->alpha < target) - { - self->alpha = target; - } - } - else if (self->alpha < target) - { - self->alpha += amount; - - if (self->alpha > target) - { - self->alpha = target; - } - } - if (flags & FTF_CLAMP) - { - if (self->alpha > (FRACUNIT * 1)) - self->alpha = (FRACUNIT * 1); - else if (self->alpha < 0) - self->alpha = 0; - } - if (self->alpha == target && (flags & FTF_REMOVE)) - { - self->Destroy(); - } -} - -//=========================================================================== -// -// A_Scale(float scalex, optional float scaley) -// -// Scales the actor's graphics. If scaley is 0, use scalex. -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetScale) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_FIXED(scalex, 0); - ACTION_PARAM_FIXED(scaley, 1); - ACTION_PARAM_INT(ptr, 2); - - AActor *ref = COPY_AAPTR(self, ptr); - - if (!ref) - { - ACTION_SET_RESULT(false); - return; - } - - ref->scaleX = scalex; - ref->scaleY = scaley ? scaley : scalex; -} - -//=========================================================================== -// -// A_SetMass(int mass) -// -// Sets the actor's mass. -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetMass) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(mass, 0); - - self->Mass = mass; -} - -//=========================================================================== -// -// A_SpawnDebris -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnDebris) -{ - int i; - AActor * mo; - - ACTION_PARAM_START(4); - ACTION_PARAM_CLASS(debris, 0); - ACTION_PARAM_BOOL(transfer_translation, 1); - ACTION_PARAM_FIXED(mult_h, 2); - ACTION_PARAM_FIXED(mult_v, 3); - - if (debris == NULL) return; - - // only positive values make sense here - if (mult_v<=0) mult_v=FRACUNIT; - if (mult_h<=0) mult_h=FRACUNIT; - - for (i = 0; i < GetDefaultByType(debris)->health; i++) - { - mo = Spawn(debris, self->x+((pr_spawndebris()-128)<<12), - self->y + ((pr_spawndebris()-128)<<12), - self->z + (pr_spawndebris()*self->height/256+self->GetBobOffset()), ALLOW_REPLACE); - if (mo) - { - if (transfer_translation) - { - mo->Translation = self->Translation; - } - if (i < mo->GetClass()->ActorInfo->NumOwnedStates) - { - mo->SetState(mo->GetClass()->ActorInfo->OwnedStates + i); - } - mo->velz = FixedMul(mult_v, ((pr_spawndebris()&7)+5)*FRACUNIT); - mo->velx = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6)); - mo->vely = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6)); - } - } -} - - -//=========================================================================== -// -// A_CheckSight -// jumps if no player can see this actor -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSight) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - for (int i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i]) - { - // Always check sight from each player. - if (P_CheckSight(players[i].mo, self, SF_IGNOREVISIBILITY)) - { - return; - } - // If a player is viewing from a non-player, then check that too. - if (players[i].camera != NULL && players[i].camera->player == NULL && - P_CheckSight(players[i].camera, self, SF_IGNOREVISIBILITY)) - { - return; - } - } - } - - ACTION_JUMP(jump); -} - -//=========================================================================== -// -// A_CheckSightOrRange -// Jumps if this actor is out of range of all players *and* out of sight. -// Useful for maps with many multi-actor special effects. -// -//=========================================================================== -static bool DoCheckSightOrRange(AActor *self, AActor *camera, double range, bool twodi) -{ - if (camera == NULL) - { - return false; - } - // Check distance first, since it's cheaper than checking sight. - double dx = self->x - camera->x; - double dy = self->y - camera->y; - double dz; - fixed_t eyez = (camera->z + camera->height - (camera->height>>2)); // same eye height as P_CheckSight - if (eyez > self->z + self->height) - { - dz = self->z + self->height - eyez; - } - else if (eyez < self->z) - { - dz = self->z - eyez; - } - else - { - dz = 0; - } - double distance = (dx * dx) + (dy * dy) + (twodi == 0? (dz * dz) : 0); - if (distance <= range){ - // Within range - return true; - } - - // Now check LOS. - if (P_CheckSight(camera, self, SF_IGNOREVISIBILITY)) - { // Visible - return true; - } - return false; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSightOrRange) -{ - ACTION_PARAM_START(3); - double range = EvalExpressionF(ParameterIndex+0, self); - ACTION_PARAM_STATE(jump, 1); - ACTION_PARAM_BOOL(twodi, 2); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots - for (int i = 0; i < MAXPLAYERS; ++i) - { - if (playeringame[i]) - { - // Always check from each player. - if (DoCheckSightOrRange(self, players[i].mo, range, twodi)) - { - return; - } - // If a player is viewing from a non-player, check that too. - if (players[i].camera != NULL && players[i].camera->player == NULL && - DoCheckSightOrRange(self, players[i].camera, range, twodi)) - { - return; - } - } - } - ACTION_JUMP(jump); -} - -//=========================================================================== -// -// A_CheckRange -// Jumps if this actor is out of range of all players. -// -//=========================================================================== -static bool DoCheckRange(AActor *self, AActor *camera, double range, bool twodi) -{ - if (camera == NULL) - { - return false; - } - // Check distance first, since it's cheaper than checking sight. - double dx = self->x - camera->x; - double dy = self->y - camera->y; - double dz; - fixed_t eyez = (camera->z + camera->height - (camera->height>>2)); // same eye height as P_CheckSight - if (eyez > self->z + self->height){ - dz = self->z + self->height - eyez; - } - else if (eyez < self->z){ - dz = self->z - eyez; - } - else{ - dz = 0; - } - double distance = (dx * dx) + (dy * dy) + (twodi == 0? (dz * dz) : 0); - if (distance <= range){ - // Within range - return true; - } - return false; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckRange) -{ - ACTION_PARAM_START(3); - double range = EvalExpressionF(ParameterIndex+0, self); - ACTION_PARAM_STATE(jump, 1); - ACTION_PARAM_BOOL(twodi, 2); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots - for (int i = 0; i < MAXPLAYERS; ++i) - { - if (playeringame[i]) - { - // Always check from each player. - if (DoCheckRange(self, players[i].mo, range, twodi)) - { - return; - } - // If a player is viewing from a non-player, check that too. - if (players[i].camera != NULL && players[i].camera->player == NULL && - DoCheckRange(self, players[i].camera, range, twodi)) - { - return; - } - } - } - ACTION_JUMP(jump); -} - - -//=========================================================================== -// -// Inventory drop -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropInventory) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_CLASS(drop, 0); - - if (drop) - { - AInventory * inv = self->FindInventory(drop); - if (inv) - { - self->DropInventory(inv); - } - } -} - - -//=========================================================================== -// -// A_SetBlend -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetBlend) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_COLOR(color, 0); - ACTION_PARAM_FLOAT(alpha, 1); - ACTION_PARAM_INT(tics, 2); - ACTION_PARAM_COLOR(color2, 3); - - if (color == MAKEARGB(255,255,255,255)) color=0; - if (color2 == MAKEARGB(255,255,255,255)) color2=0; - if (!color2.a) - color2 = color; - - new DFlashFader(color.r/255.0f, color.g/255.0f, color.b/255.0f, alpha, - color2.r/255.0f, color2.g/255.0f, color2.b/255.0f, 0, - (float)tics/TICRATE, self); -} - - -//=========================================================================== -// -// A_JumpIf -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIf) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_BOOL(expression, 0); - ACTION_PARAM_STATE(jump, 1); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (expression) ACTION_JUMP(jump); - -} - -//=========================================================================== -// -// A_CountdownArg -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CountdownArg) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(cnt, 0); - ACTION_PARAM_STATE(state, 1); - - if (cnt<0 || cnt>=5) return; - if (!self->args[cnt]--) - { - if (self->flags&MF_MISSILE) - { - P_ExplodeMissile(self, NULL, NULL); - } - else if (self->flags&MF_SHOOTABLE) - { - P_DamageMobj (self, NULL, NULL, self->health, NAME_None, DMG_FORCED); - } - else - { - // can't use "Death" as default parameter with current DECORATE parser. - if (state == NULL) state = self->FindState(NAME_Death); - self->SetState(state); - } - } - -} - -//============================================================================ -// -// A_Burst -// -//============================================================================ - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Burst) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_CLASS(chunk, 0); - - int i, numChunks; - AActor * mo; - - if (chunk == NULL) return; - - self->velx = self->vely = self->velz = 0; - self->height = self->GetDefault()->height; - - // [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 (4, (self->radius>>FRACBITS)*(self->height>>FRACBITS)/32); - i = (pr_burst.Random2()) % (numChunks/4); - for (i = MAX (24, numChunks + i); i >= 0; i--) - { - mo = Spawn(chunk, - self->x + (((pr_burst()-128)*self->radius)>>7), - self->y + (((pr_burst()-128)*self->radius)>>7), - self->z + (pr_burst()*self->height/255 + self->GetBobOffset()), ALLOW_REPLACE); - - if (mo) - { - mo->velz = FixedDiv(mo->z - self->z, self->height)<<2; - mo->velx = pr_burst.Random2 () << (FRACBITS-7); - mo->vely = pr_burst.Random2 () << (FRACBITS-7); - mo->RenderStyle = self->RenderStyle; - mo->alpha = self->alpha; - mo->CopyFriendliness(self, true); - } - } - - // [RH] Do some stuff to make this more useful outside Hexen - if (self->flags4 & MF4_BOSSDEATH) - { - CALL_ACTION(A_BossDeath, self); - } - A_Unblock(self, true); - - self->Destroy (); -} - -//=========================================================================== -// -// A_CheckFloor -// [GRB] Jumps if actor is standing on floor -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFloor) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (self->z <= self->floorz) - { - ACTION_JUMP(jump); - } - -} - -//=========================================================================== -// -// A_CheckCeiling -// [GZ] Totally copied on A_CheckFloor, jumps if actor touches ceiling -// - -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckCeiling) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - ACTION_SET_RESULT(false); - if (self->z+self->height >= self->ceilingz) // Height needs to be counted - { - ACTION_JUMP(jump); - } - -} - -//=========================================================================== -// -// A_Stop -// resets all velocity of the actor to 0 -// -//=========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_Stop) -{ - self->velx = self->vely = self->velz = 0; - if (self->player && self->player->mo == self && !(self->player->cheats & CF_PREDICTING)) - { - self->player->mo->PlayIdle(); - self->player->velx = self->player->vely = 0; - } -} - -static void CheckStopped(AActor *self) -{ - if (self->player != NULL && - self->player->mo == self && - !(self->player->cheats & CF_PREDICTING) && - !(self->velx | self->vely | self->velz)) - { - self->player->mo->PlayIdle(); - self->player->velx = self->player->vely = 0; - } -} - -//=========================================================================== -// -// A_Respawn -// -//=========================================================================== - -extern void AF_A_RestoreSpecialPosition(DECLARE_PARAMINFO); - -enum RS_Flags -{ - RSF_FOG=1, - RSF_KEEPTARGET=2, - RSF_TELEFRAG=4, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Respawn) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(flags, 0); - bool oktorespawn = false; - fixed_t oldx = self->x; - fixed_t oldy = self->y; - fixed_t oldz = self->z; - self->flags |= MF_SOLID; - self->height = self->GetDefault()->height; - CALL_ACTION(A_RestoreSpecialPosition, self); - - if (flags & RSF_TELEFRAG) - { - // [KS] DIE DIE DIE DIE erm *ahem* =) - oktorespawn = P_TeleportMove(self, self->x, self->y, self->z, true); - if (oktorespawn) - { // Need to do this over again, since P_TeleportMove() will redo - // it with the proper point-on-side calculation. - self->UnlinkFromWorld(); - self->LinkToWorld(true); - sector_t *sec = self->Sector; - self->dropoffz = - self->floorz = sec->floorplane.ZatPoint(self->x, self->y); - self->ceilingz = sec->ceilingplane.ZatPoint(self->x, self->y); - P_FindFloorCeiling(self, FFCF_ONLYSPAWNPOS); - } - } - else - { - oktorespawn = P_CheckPosition(self, self->x, self->y, true); - } - - if (oktorespawn) - { - AActor *defs = self->GetDefault(); - self->health = defs->health; - - // [KS] Don't keep target, because it could be self if the monster committed suicide - // ...Actually it's better off an option, so you have better control over monster behavior. - if (!(flags & RSF_KEEPTARGET)) - { - self->target = NULL; - self->LastHeard = NULL; - self->lastenemy = NULL; - } - else - { - // Don't attack yourself (Re: "Marine targets itself after suicide") - if (self->target == self) self->target = NULL; - if (self->lastenemy == self) self->lastenemy = NULL; - } - - self->flags = (defs->flags & ~MF_FRIENDLY) | (self->flags & MF_FRIENDLY); - self->flags2 = defs->flags2; - self->flags3 = (defs->flags3 & ~(MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)) | (self->flags3 & (MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)); - self->flags4 = (defs->flags4 & ~MF4_NOHATEPLAYERS) | (self->flags4 & MF4_NOHATEPLAYERS); - self->flags5 = defs->flags5; - self->flags6 = defs->flags6; - self->flags7 = defs->flags7; - self->SetState (self->SpawnState); - self->renderflags &= ~RF_INVISIBLE; - - if (flags & RSF_FOG) - { - P_SpawnTeleportFog(self, oldx, oldy, oldz, true); - P_SpawnTeleportFog(self, self->x, self->y, self->z, false); - } - if (self->CountsAsKill()) - { - level.total_monsters++; - } - } - else - { - self->flags &= ~MF_SOLID; - } -} - - -//========================================================================== -// -// A_PlayerSkinCheck -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayerSkinCheck) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (self->player != NULL && - skins[self->player->userinfo.GetSkin()].othergame) - { - ACTION_JUMP(jump); - } -} - -//=========================================================================== -// -// A_SetGravity -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetGravity) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_FIXED(val, 0); - - self->gravity = clamp (val, 0, FRACUNIT*10); -} - - -// [KS] *** Start of my modifications *** - -//=========================================================================== -// -// A_ClearTarget -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION(AActor, A_ClearTarget) -{ - self->target = NULL; - self->LastHeard = NULL; - self->lastenemy = NULL; -} - -//========================================================================== -// -// A_CheckLOF (state jump, int flags = CRF_AIM_VERT|CRF_AIM_HOR, -// fixed range = 0, angle angle = 0, angle pitch = 0, -// fixed offsetheight = 32, fixed offsetwidth = 0, -// int ptr_target = AAPTR_DEFAULT (target) ) -// -//========================================================================== - -enum CLOF_flags -{ - CLOFF_NOAIM_VERT = 0x00000001, - CLOFF_NOAIM_HORZ = 0x00000002, - - CLOFF_JUMPENEMY = 0x00000004, - CLOFF_JUMPFRIEND = 0x00000008, - CLOFF_JUMPOBJECT = 0x00000010, - CLOFF_JUMPNONHOSTILE = 0x00000020, - - CLOFF_SKIPENEMY = 0x00000040, - CLOFF_SKIPFRIEND = 0x00000080, - CLOFF_SKIPOBJECT = 0x00000100, - CLOFF_SKIPNONHOSTILE = 0x00000200, - - CLOFF_MUSTBESHOOTABLE = 0x00000400, - - CLOFF_SKIPTARGET = 0x00000800, - CLOFF_ALLOWNULL = 0x00001000, - CLOFF_CHECKPARTIAL = 0x00002000, - - CLOFF_MUSTBEGHOST = 0x00004000, - CLOFF_IGNOREGHOST = 0x00008000, - - CLOFF_MUSTBESOLID = 0x00010000, - CLOFF_BEYONDTARGET = 0x00020000, - - CLOFF_FROMBASE = 0x00040000, - CLOFF_MUL_HEIGHT = 0x00080000, - CLOFF_MUL_WIDTH = 0x00100000, - - CLOFF_JUMP_ON_MISS = 0x00200000, - CLOFF_AIM_VERT_NOOFFSET = 0x00400000, - - CLOFF_SETTARGET = 0x00800000, - CLOFF_SETMASTER = 0x01000000, - CLOFF_SETTRACER = 0x02000000, -}; - -struct LOFData -{ - AActor *Self; - AActor *Target; - int Flags; - bool BadActor; -}; - -ETraceStatus CheckLOFTraceFunc(FTraceResults &trace, void *userdata) -{ - LOFData *data = (LOFData *)userdata; - int flags = data->Flags; - - if (trace.HitType != TRACE_HitActor) - { - return TRACE_Stop; - } - if (trace.Actor == data->Target) - { - if (flags & CLOFF_SKIPTARGET) - { - if (flags & CLOFF_BEYONDTARGET) - { - return TRACE_Skip; - } - return TRACE_Abort; - } - return TRACE_Stop; - } - if (flags & CLOFF_MUSTBESHOOTABLE) - { // all shootability checks go here - if (!(trace.Actor->flags & MF_SHOOTABLE)) - { - return TRACE_Skip; - } - if (trace.Actor->flags2 & MF2_NONSHOOTABLE) - { - return TRACE_Skip; - } - } - if ((flags & CLOFF_MUSTBESOLID) && !(trace.Actor->flags & MF_SOLID)) - { - return TRACE_Skip; - } - if (flags & CLOFF_MUSTBEGHOST) - { - if (!(trace.Actor->flags3 & MF3_GHOST)) - { - return TRACE_Skip; - } - } - else if (flags & CLOFF_IGNOREGHOST) - { - if (trace.Actor->flags3 & MF3_GHOST) - { - return TRACE_Skip; - } - } - if ( - ((flags & CLOFF_JUMPENEMY) && data->Self->IsHostile(trace.Actor)) || - ((flags & CLOFF_JUMPFRIEND) && data->Self->IsFriend(trace.Actor)) || - ((flags & CLOFF_JUMPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) || - ((flags & CLOFF_JUMPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor)) - ) - { - return TRACE_Stop; - } - if ( - ((flags & CLOFF_SKIPENEMY) && data->Self->IsHostile(trace.Actor)) || - ((flags & CLOFF_SKIPFRIEND) && data->Self->IsFriend(trace.Actor)) || - ((flags & CLOFF_SKIPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) || - ((flags & CLOFF_SKIPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor)) - ) - { - return TRACE_Skip; - } - data->BadActor = true; - return TRACE_Abort; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckLOF) -{ - // Check line of fire - - /* - Not accounted for / I don't know how it works: FLOORCLIP - */ - - AActor *target; - fixed_t - x1, y1, z1, - vx, vy, vz; - - ACTION_PARAM_START(9); - - ACTION_PARAM_STATE(jump, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_FIXED(range, 2); - ACTION_PARAM_FIXED(minrange, 3); - { - ACTION_PARAM_ANGLE(angle, 4); - ACTION_PARAM_ANGLE(pitch, 5); - ACTION_PARAM_FIXED(offsetheight, 6); - ACTION_PARAM_FIXED(offsetwidth, 7); - ACTION_PARAM_INT(ptr_target, 8); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - target = COPY_AAPTR(self, ptr_target == AAPTR_DEFAULT ? AAPTR_TARGET|AAPTR_PLAYER_GETTARGET|AAPTR_NULL : ptr_target); // no player-support by default - - if (flags & CLOFF_MUL_HEIGHT) - { - if (self->player != NULL) - { - // Synced with hitscan: self->player->mo->height is strangely conscientious about getting the right actor for player - offsetheight = FixedMul(offsetheight, FixedMul (self->player->mo->height, self->player->crouchfactor)); - } - else - { - offsetheight = FixedMul(offsetheight, self->height); - } - } - if (flags & CLOFF_MUL_WIDTH) - { - offsetwidth = FixedMul(self->radius, offsetwidth); - } - - x1 = self->x; - y1 = self->y; - z1 = self->z + offsetheight - self->floorclip; - - if (!(flags & CLOFF_FROMBASE)) - { // default to hitscan origin - - // Synced with hitscan: self->height is strangely NON-conscientious about getting the right actor for player - z1 += (self->height >> 1); - if (self->player != NULL) - { - z1 += FixedMul (self->player->mo->AttackZOffset, self->player->crouchfactor); - } - else - { - z1 += 8*FRACUNIT; - } - } - - if (target) - { - FVector2 xyvec(target->x - x1, target->y - y1); - fixed_t distance = P_AproxDistance((fixed_t)xyvec.Length(), target->z - z1); - - if (range && !(flags & CLOFF_CHECKPARTIAL)) - { - if (distance > range) return; - } - - { - angle_t ang; - - if (flags & CLOFF_NOAIM_HORZ) - { - ang = self->angle; - } - else ang = R_PointToAngle2 (x1, y1, target->x, target->y); - - angle += ang; - - ang >>= ANGLETOFINESHIFT; - x1 += FixedMul(offsetwidth, finesine[ang]); - y1 -= FixedMul(offsetwidth, finecosine[ang]); - } - - if (flags & CLOFF_NOAIM_VERT) - { - pitch += self->pitch; - } - else if (flags & CLOFF_AIM_VERT_NOOFFSET) - { - pitch += R_PointToAngle2 (0,0, (fixed_t)xyvec.Length(), target->z - z1 + offsetheight + target->height / 2); - } - else - { - pitch += R_PointToAngle2 (0,0, (fixed_t)xyvec.Length(), target->z - z1 + target->height / 2); - } - } - else if (flags & CLOFF_ALLOWNULL) - { - angle += self->angle; - pitch += self->pitch; - - angle_t ang = self->angle >> ANGLETOFINESHIFT; - x1 += FixedMul(offsetwidth, finesine[ang]); - y1 -= FixedMul(offsetwidth, finecosine[ang]); - } - else return; - - angle >>= ANGLETOFINESHIFT; - pitch = (0-pitch)>>ANGLETOFINESHIFT; - - vx = FixedMul (finecosine[pitch], finecosine[angle]); - vy = FixedMul (finecosine[pitch], finesine[angle]); - vz = -finesine[pitch]; - } - - /* Variable set: - - jump, flags, target - x1,y1,z1 (trace point of origin) - vx,vy,vz (trace unit vector) - range - */ - - sector_t *sec = P_PointInSector(x1, y1); - - if (range == 0) - { - range = (self->player != NULL) ? PLAYERMISSILERANGE : MISSILERANGE; - } - - FTraceResults trace; - LOFData lof_data; - - lof_data.Self = self; - lof_data.Target = target; - lof_data.Flags = flags; - lof_data.BadActor = false; - - Trace(x1, y1, z1, sec, vx, vy, vz, range, 0xFFFFFFFF, ML_BLOCKEVERYTHING, self, trace, 0, - CheckLOFTraceFunc, &lof_data); - - if (trace.HitType == TRACE_HitActor || - ((flags & CLOFF_JUMP_ON_MISS) && !lof_data.BadActor && trace.HitType != TRACE_HitNone)) - { - if (minrange > 0 && trace.Distance < minrange) - { - return; - } - if ((trace.HitType == TRACE_HitActor) && (trace.Actor != NULL) && !(lof_data.BadActor)) - { - if (flags & (CLOFF_SETTARGET)) self->target = trace.Actor; - if (flags & (CLOFF_SETMASTER)) self->master = trace.Actor; - if (flags & (CLOFF_SETTRACER)) self->tracer = trace.Actor; - } - - ACTION_JUMP(jump); - } -} - -//========================================================================== -// -// A_JumpIfTargetInLOS (state label, optional fixed fov, optional int flags, -// optional fixed dist_max, optional fixed dist_close) -// -// Jumps if the actor can see its target, or if the player has a linetarget. -// ProjectileTarget affects how projectiles are treated. If set, it will use -// the target of the projectile for seekers, and ignore the target for -// normal projectiles. If not set, it will use the missile's owner instead -// (the default). ProjectileTarget is now flag JLOSF_PROJECTILE. dist_max -// sets the maximum distance that actor can see, 0 means forever. dist_close -// uses special behavior if certain flags are set, 0 means no checks. -// -//========================================================================== - -enum JLOS_flags -{ - JLOSF_PROJECTILE = 1, - JLOSF_NOSIGHT = 1 << 1, - JLOSF_CLOSENOFOV = 1 << 2, - JLOSF_CLOSENOSIGHT = 1 << 3, - JLOSF_CLOSENOJUMP = 1 << 4, - JLOSF_DEADNOJUMP = 1 << 5, - JLOSF_CHECKMASTER = 1 << 6, - JLOSF_TARGETLOS = 1 << 7, - JLOSF_FLIPFOV = 1 << 8, - JLOSF_ALLYNOJUMP = 1 << 9, - JLOSF_COMBATANTONLY = 1 << 10, - JLOSF_NOAUTOAIM = 1 << 11, - JLOSF_CHECKTRACER = 1 << 12, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_STATE(jump, 0); - ACTION_PARAM_ANGLE(fov, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_FIXED(dist_max, 3); - ACTION_PARAM_FIXED(dist_close, 4); - - angle_t an; - AActor *target, *viewport; - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - bool doCheckSight; - - if (!self->player) - { - if (flags & JLOSF_CHECKMASTER) - { - target = self->master; - } - else if ((self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) || (flags & JLOSF_CHECKTRACER)) - { - if ((self->flags2 & MF2_SEEKERMISSILE) || (flags & JLOSF_CHECKTRACER)) - target = self->tracer; - else - target = NULL; - } - else - { - target = self->target; - } - - if (!target) return; // [KS] Let's not call P_CheckSight unnecessarily in this case. - - if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) return; - - doCheckSight = !(flags & JLOSF_NOSIGHT); - } - else - { - // Does the player aim at something that can be shot? - P_AimLineAttack(self, self->angle, MISSILERANGE, &target, (flags & JLOSF_NOAUTOAIM) ? ANGLE_1/2 : 0); - - if (!target) return; - - switch (flags & (JLOSF_TARGETLOS|JLOSF_FLIPFOV)) - { - case JLOSF_TARGETLOS|JLOSF_FLIPFOV: - // target makes sight check, player makes fov check; player has verified fov - fov = 0; - // fall-through - case JLOSF_TARGETLOS: - doCheckSight = !(flags & JLOSF_NOSIGHT); // The target is responsible for sight check and fov - break; - default: - // player has verified sight and fov - fov = 0; - // fall-through - case JLOSF_FLIPFOV: // Player has verified sight, but target must verify fov - doCheckSight = false; - break; - } - } - - // [FDARI] If target is not a combatant, don't jump - if ( (flags & JLOSF_COMBATANTONLY) && (!target->player) && !(target->flags3 & MF3_ISMONSTER)) return; - - // [FDARI] If actors share team, don't jump - if ((flags & JLOSF_ALLYNOJUMP) && self->IsFriend(target)) return; - - fixed_t distance = P_AproxDistance(target->x - self->x, target->y - self->y); - distance = P_AproxDistance(distance, target->z - self->z); - - if (dist_max && (distance > dist_max)) return; - - if (dist_close && (distance < dist_close)) - { - if (flags & JLOSF_CLOSENOJUMP) - return; - - if (flags & JLOSF_CLOSENOFOV) - fov = 0; - - if (flags & JLOSF_CLOSENOSIGHT) - doCheckSight = false; - } - - if (flags & JLOSF_TARGETLOS) { viewport = target; target = self; } - else { viewport = self; } - - if (doCheckSight && !P_CheckSight (viewport, target, SF_IGNOREVISIBILITY)) - return; - - if (flags & JLOSF_FLIPFOV) - { - if (viewport == self) { viewport = target; target = self; } - else { target = viewport; viewport = self; } - } - - if (fov && (fov < ANGLE_MAX)) - { - an = R_PointToAngle2 (viewport->x, - viewport->y, - target->x, - target->y) - - viewport->angle; - - if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2))) - { - return; // [KS] Outside of FOV - return - } - - } - - ACTION_JUMP(jump); -} - - -//========================================================================== -// -// A_JumpIfInTargetLOS (state label, optional fixed fov, optional int flags -// optional fixed dist_max, optional fixed dist_close) -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetLOS) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_STATE(jump, 0); - ACTION_PARAM_ANGLE(fov, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_FIXED(dist_max, 3); - ACTION_PARAM_FIXED(dist_close, 4); - - angle_t an; - AActor *target; - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - if (flags & JLOSF_CHECKMASTER) - { - target = self->master; - } - else if (self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) - { - if (self->flags2 & MF2_SEEKERMISSILE) - target = self->tracer; - else - target = NULL; - } - else - { - target = self->target; - } - - if (!target) return; // [KS] Let's not call P_CheckSight unnecessarily in this case. - - if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) return; - - fixed_t distance = P_AproxDistance(target->x - self->x, target->y - self->y); - distance = P_AproxDistance(distance, target->z - self->z); - - if (dist_max && (distance > dist_max)) return; - - bool doCheckSight = !(flags & JLOSF_NOSIGHT); - - if (dist_close && (distance < dist_close)) - { - if (flags & JLOSF_CLOSENOJUMP) - return; - - if (flags & JLOSF_CLOSENOFOV) - fov = 0; - - if (flags & JLOSF_CLOSENOSIGHT) - doCheckSight = false; - } - - if (fov && (fov < ANGLE_MAX)) - { - an = R_PointToAngle2 (target->x, - target->y, - self->x, - self->y) - - target->angle; - - if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2))) - { - return; // [KS] Outside of FOV - return - } - } - - if (doCheckSight && !P_CheckSight (target, self, SF_IGNOREVISIBILITY)) - return; - - ACTION_JUMP(jump); -} - -//=========================================================================== -// -// Modified code pointer from Skulltag -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckForReload) -{ - if ( self->player == NULL || self->player->ReadyWeapon == NULL ) - return; - - ACTION_PARAM_START(2); - ACTION_PARAM_INT(count, 0); - ACTION_PARAM_STATE(jump, 1); - ACTION_PARAM_BOOL(dontincrement, 2) - - if (count <= 0) return; - - AWeapon *weapon = self->player->ReadyWeapon; - - int ReloadCounter = weapon->ReloadCounter; - if(!dontincrement || ReloadCounter != 0) - ReloadCounter = (weapon->ReloadCounter+1) % count; - else // 0 % 1 = 1? So how do we check if the weapon was never fired? We should only do this when we're not incrementing the counter though. - ReloadCounter = 1; - - // If we have not made our last shot... - if (ReloadCounter != 0) - { - // Go back to the refire frames, instead of continuing on to the reload frames. - ACTION_JUMP(jump); - } - else - { - // We need to reload. However, don't reload if we're out of ammo. - weapon->CheckAmmo( false, false ); - } - - if(!dontincrement) - weapon->ReloadCounter = ReloadCounter; -} - -//=========================================================================== -// -// Resets the counter for the above function -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION(AActor, A_ResetReloadCounter) -{ - if ( self->player == NULL || self->player->ReadyWeapon == NULL ) - return; - - AWeapon *weapon = self->player->ReadyWeapon; - weapon->ReloadCounter = 0; -} - -//=========================================================================== -// -// A_ChangeFlag -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeFlag) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_STRING(flagname, 0); - ACTION_PARAM_BOOL(expression, 1); - - const char *dot = strchr (flagname, '.'); - FFlagDef *fd; - const PClass *cls = self->GetClass(); - - if (dot != NULL) - { - FString part1(flagname, dot-flagname); - fd = FindFlag (cls, part1, dot+1); - } - else - { - fd = FindFlag (cls, flagname, NULL); - } - - if (fd != NULL) - { - bool kill_before, kill_after; - INTBOOL item_before, item_after; - INTBOOL secret_before, secret_after; - - kill_before = self->CountsAsKill(); - item_before = self->flags & MF_COUNTITEM; - secret_before = self->flags5 & MF5_COUNTSECRET; - - if (fd->structoffset == -1) - { - HandleDeprecatedFlags(self, cls->ActorInfo, expression, fd->flagbit); - } - else - { - DWORD *flagp = (DWORD*) (((char*)self) + fd->structoffset); - - // If these 2 flags get changed we need to update the blockmap and sector links. - bool linkchange = flagp == &self->flags && (fd->flagbit == MF_NOBLOCKMAP || fd->flagbit == MF_NOSECTOR); - - if (linkchange) self->UnlinkFromWorld(); - ModActorFlag(self, fd, expression); - if (linkchange) self->LinkToWorld(); - } - kill_after = self->CountsAsKill(); - item_after = self->flags & MF_COUNTITEM; - secret_after = self->flags5 & MF5_COUNTSECRET; - // Was this monster previously worth a kill but no longer is? - // Or vice versa? - if (kill_before != kill_after) - { - if (kill_after) - { // It counts as a kill now. - level.total_monsters++; - } - else - { // It no longer counts as a kill. - level.total_monsters--; - } - } - // same for items - if (item_before != item_after) - { - if (item_after) - { // It counts as an item now. - level.total_items++; - } - else - { // It no longer counts as an item - level.total_items--; - } - } - // and secretd - if (secret_before != secret_after) - { - if (secret_after) - { // It counts as an secret now. - level.total_secrets++; - } - else - { // It no longer counts as an secret - level.total_secrets--; - } - } - } - else - { - Printf("Unknown flag '%s' in '%s'\n", flagname, cls->TypeName.GetChars()); - } -} - -//=========================================================================== -// -// A_CheckFlag -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFlag) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_STRING(flagname, 0); - ACTION_PARAM_STATE(jumpto, 1); - ACTION_PARAM_INT(checkpointer, 2); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - AActor *owner; - - COPY_AAPTR_NOT_NULL(self, owner, checkpointer); - - if (CheckActorFlag(owner, flagname)) - { - ACTION_JUMP(jumpto); - } -} - -//=========================================================================== -// -// A_RaiseMaster -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseMaster) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_BOOL(copy, 0); - - if (self->master != NULL) - { - if (copy) - P_Thing_Raise(self->master, self); - else - P_Thing_Raise(self->master, NULL); - } -} - -//=========================================================================== -// -// A_RaiseChildren -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseChildren) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_BOOL(copy, 0); - TThinkerIterator it; - AActor *mo; - - while ((mo = it.Next()) != NULL) - { - if (mo->master == self) - { - if (copy) - P_Thing_Raise(mo, self); - else - P_Thing_Raise(mo, NULL); - } - } -} - -//=========================================================================== -// -// A_RaiseSiblings -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseSiblings) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_BOOL(copy, 0); - TThinkerIterator it; - AActor *mo; - - if (self->master != NULL) - { - while ((mo = it.Next()) != NULL) - { - if (mo->master == self->master && mo != self) - { - if (copy) - P_Thing_Raise(mo, self); - else - P_Thing_Raise(mo, NULL); - } - } - } -} - -//=========================================================================== -// -// [TP] A_FaceConsolePlayer -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS (AActor, A_FaceConsolePlayer) { - ACTION_PARAM_START (1); - ACTION_PARAM_ANGLE (MaxTurnAngle, 0); - // NOTE: It does nothing for zdoom. -} - -//=========================================================================== -// -// A_MonsterRefire -// -// Keep firing unless target got out of sight -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_MonsterRefire) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(prob, 0); - ACTION_PARAM_STATE(jump, 1); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - A_FaceTarget (self); - - if (pr_monsterrefire() < prob) - return; - - if (!self->target - || P_HitFriend (self) - || self->target->health <= 0 - || !P_CheckSight (self, self->target, SF_SEEPASTBLOCKEVERYTHING|SF_SEEPASTSHOOTABLELINES) ) - { - ACTION_JUMP(jump); - } -} - -//=========================================================================== -// -// A_SetAngle -// -// Set actor's angle (in degrees). -// -//=========================================================================== -enum -{ - SPF_FORCECLAMP = 1, // players always clamp - SPF_INTERPOLATE = 2, -}; - - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetAngle) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_ANGLE(angle, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_INT(ptr, 2); - - AActor *ref = COPY_AAPTR(self, ptr); - - if (!ref) - { - ACTION_SET_RESULT(false); - return; - } - ref->SetAngle(angle, !!(flags & SPF_INTERPOLATE)); -} - -//=========================================================================== -// -// A_SetPitch -// -// Set actor's pitch (in degrees). -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetPitch) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_ANGLE(pitch, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_INT(ptr, 2); - - AActor *ref = COPY_AAPTR(self, ptr); - - if (!ref) - { - ACTION_SET_RESULT(false); - return; - } - - if (ref->player != NULL || (flags & SPF_FORCECLAMP)) - { // clamp the pitch we set - int min, max; - - if (ref->player != NULL) - { - min = ref->player->MinPitch; - max = ref->player->MaxPitch; - } - else - { - min = -ANGLE_90 + (1 << ANGLETOFINESHIFT); - max = ANGLE_90 - (1 << ANGLETOFINESHIFT); - } - pitch = clamp(pitch, min, max); - } - ref->SetPitch(pitch, !!(flags & SPF_INTERPOLATE)); -} - +/* +** thingdef.cpp +** +** Code pointers for Actor definitions +** +**--------------------------------------------------------------------------- +** Copyright 2002-2006 Christoph Oelckers +** Copyright 2004-2006 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** 4. When not used as part of ZDoom or a ZDoom derivative, this code will be +** covered by the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or (at +** your option) any later version. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "gi.h" +#include "g_level.h" +#include "actor.h" +#include "info.h" +#include "sc_man.h" +#include "tarray.h" +#include "w_wad.h" +#include "templates.h" +#include "r_defs.h" +#include "a_pickups.h" +#include "s_sound.h" +#include "cmdlib.h" +#include "p_lnspec.h" +#include "p_enemy.h" +#include "a_action.h" +#include "decallib.h" +#include "m_random.h" +#include "i_system.h" +#include "p_local.h" +#include "c_console.h" +#include "doomerrors.h" +#include "a_sharedglobal.h" +#include "thingdef/thingdef.h" +#include "v_video.h" +#include "v_font.h" +#include "doomstat.h" +#include "v_palette.h" +#include "g_shared/a_specialspot.h" +#include "actorptrselect.h" +#include "m_bbox.h" +#include "r_data/r_translate.h" +#include "p_trace.h" +#include "gstrings.h" + + +static FRandom pr_camissile ("CustomActorfire"); +static FRandom pr_camelee ("CustomMelee"); +static FRandom pr_cabullet ("CustomBullet"); +static FRandom pr_cajump ("CustomJump"); +static FRandom pr_cwbullet ("CustomWpBullet"); +static FRandom pr_cwjump ("CustomWpJump"); +static FRandom pr_cwpunch ("CustomWpPunch"); +static FRandom pr_grenade ("ThrowGrenade"); +static FRandom pr_crailgun ("CustomRailgun"); +static FRandom pr_spawndebris ("SpawnDebris"); +static FRandom pr_spawnitemex ("SpawnItemEx"); +static FRandom pr_burst ("Burst"); +static FRandom pr_monsterrefire ("MonsterRefire"); +static FRandom pr_teleport("A_Teleport"); + +//========================================================================== +// +// ACustomInventory :: CallStateChain +// +// Executes the code pointers in a chain of states +// until there is no next state +// +//========================================================================== + +bool ACustomInventory::CallStateChain (AActor *actor, FState * State) +{ + StateCallData StateCall; + bool result = false; + int counter = 0; + + while (State != NULL) + { + // Assume success. The code pointer will set this to false if necessary + StateCall.State = State; + StateCall.Result = true; + if (State->CallAction(actor, this, &StateCall)) + { + // collect all the results. Even one successful call signifies overall success. + result |= StateCall.Result; + } + + + // Since there are no delays it is a good idea to check for infinite loops here! + counter++; + if (counter >= 10000) break; + + if (StateCall.State == State) + { + // Abort immediately if the state jumps to itself! + if (State == State->GetNextState()) + { + return false; + } + + // If both variables are still the same there was no jump + // so we must advance to the next state. + State = State->GetNextState(); + } + else + { + State = StateCall.State; + } + } + return result; +} + +//========================================================================== +// +// A_RearrangePointers +// +// Allow an actor to change its relationship to other actors by +// copying pointers freely between TARGET MASTER and TRACER. +// Can also assign null value, but does not duplicate A_ClearTarget. +// +//========================================================================== + + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RearrangePointers) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_INT(ptr_target, 0); + ACTION_PARAM_INT(ptr_master, 1); + ACTION_PARAM_INT(ptr_tracer, 2); + ACTION_PARAM_INT(flags, 3); + + // Rearrange pointers internally + + // Fetch all values before modification, so that all fields can get original values + AActor + *gettarget = self->target, + *getmaster = self->master, + *gettracer = self->tracer; + + switch (ptr_target) // pick the new target + { + case AAPTR_MASTER: + self->target = getmaster; + if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self); + break; + case AAPTR_TRACER: + self->target = gettracer; + if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self); + break; + case AAPTR_NULL: + self->target = NULL; + // THIS IS NOT "A_ClearTarget", so no other targeting info is removed + break; + } + + // presently permitting non-monsters to set master + switch (ptr_master) // pick the new master + { + case AAPTR_TARGET: + self->master = gettarget; + if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self); + break; + case AAPTR_TRACER: + self->master = gettracer; + if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self); + break; + case AAPTR_NULL: + self->master = NULL; + break; + } + + switch (ptr_tracer) // pick the new tracer + { + case AAPTR_TARGET: + self->tracer = gettarget; + break; // no verification deemed necessary; the engine never follows a tracer chain(?) + case AAPTR_MASTER: + self->tracer = getmaster; + break; // no verification deemed necessary; the engine never follows a tracer chain(?) + case AAPTR_NULL: + self->tracer = NULL; + break; + } +} + +//========================================================================== +// +// A_TransferPointer +// +// Copy one pointer (MASTER, TARGET or TRACER) from this actor (SELF), +// or from this actor's MASTER, TARGET or TRACER. +// +// You can copy any one of that actor's pointers +// +// Assign the copied pointer to any one pointer in SELF, +// MASTER, TARGET or TRACER. +// +// Any attempt to make an actor point to itself will replace the pointer +// with a null value. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TransferPointer) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(ptr_source, 0); + ACTION_PARAM_INT(ptr_recepient, 1); + ACTION_PARAM_INT(ptr_sourcefield, 2); + ACTION_PARAM_INT(ptr_recepientfield, 3); + ACTION_PARAM_INT(flags, 4); + + AActor *source, *recepient; + + // Exchange pointers with actors to whom you have pointers (or with yourself, if you must) + + source = COPY_AAPTR(self, ptr_source); + COPY_AAPTR_NOT_NULL(self, recepient, ptr_recepient); // pick an actor to store the provided pointer value + + // convert source from dataprovider to data + + source = COPY_AAPTR(source, ptr_sourcefield); + + if (source == recepient) source = NULL; // The recepient should not acquire a pointer to itself; will write NULL + + if (ptr_recepientfield == AAPTR_DEFAULT) ptr_recepientfield = ptr_sourcefield; // If default: Write to same field as data was read from + + ASSIGN_AAPTR(recepient, ptr_recepientfield, source, flags); +} + +//========================================================================== +// +// A_CopyFriendliness +// +// Join forces with one of the actors you are pointing to (MASTER by default) +// +// Normal CopyFriendliness reassigns health. This function will not. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CopyFriendliness) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(ptr_source, 0); + + if (self->player) return; + + AActor *source; + COPY_AAPTR_NOT_NULL(self, source, ptr_source); + self->CopyFriendliness(source, false, false); // No change in current target or health +} + +//========================================================================== +// +// Simple flag changers +// +//========================================================================== +DEFINE_ACTION_FUNCTION(AActor, A_SetSolid) +{ + self->flags |= MF_SOLID; +} + +DEFINE_ACTION_FUNCTION(AActor, A_UnsetSolid) +{ + self->flags &= ~MF_SOLID; +} + +DEFINE_ACTION_FUNCTION(AActor, A_SetFloat) +{ + self->flags |= MF_FLOAT; +} + +DEFINE_ACTION_FUNCTION(AActor, A_UnsetFloat) +{ + self->flags &= ~(MF_FLOAT|MF_INFLOAT); +} + +//========================================================================== +// +// Customizable attack functions which use actor parameters. +// +//========================================================================== +static void DoAttack (AActor *self, bool domelee, bool domissile, + int MeleeDamage, FSoundID MeleeSound, const PClass *MissileType,fixed_t MissileHeight) +{ + if (self->target == NULL) return; + + A_FaceTarget (self); + if (domelee && MeleeDamage>0 && self->CheckMeleeRange ()) + { + int damage = pr_camelee.HitDice(MeleeDamage); + if (MeleeSound) S_Sound (self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); + } + else if (domissile && MissileType != NULL) + { + // This seemingly senseless code is needed for proper aiming. + self->z += MissileHeight + self->GetBobOffset() - 32*FRACUNIT; + AActor *missile = P_SpawnMissileXYZ (self->x, self->y, self->z + 32*FRACUNIT, self, self->target, MissileType, false); + self->z -= MissileHeight + self->GetBobOffset() - 32*FRACUNIT; + + if (missile) + { + // automatic handling of seeker missiles + if (missile->flags2&MF2_SEEKERMISSILE) + { + missile->tracer=self->target; + } + P_CheckMissileSpawn(missile, self->radius); + } + } +} + +DEFINE_ACTION_FUNCTION(AActor, A_MeleeAttack) +{ + int MeleeDamage = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeDamage, 0); + FSoundID MeleeSound = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeSound, 0); + DoAttack(self, true, false, MeleeDamage, MeleeSound, NULL, 0); +} + +DEFINE_ACTION_FUNCTION(AActor, A_MissileAttack) +{ + const PClass *MissileType=PClass::FindClass((ENamedName) self->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None)); + fixed_t MissileHeight= self->GetClass()->Meta.GetMetaFixed (ACMETA_MissileHeight, 32*FRACUNIT); + DoAttack(self, false, true, 0, 0, MissileType, MissileHeight); +} + +DEFINE_ACTION_FUNCTION(AActor, A_ComboAttack) +{ + int MeleeDamage = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeDamage, 0); + FSoundID MeleeSound = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeSound, 0); + const PClass *MissileType=PClass::FindClass((ENamedName) self->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None)); + fixed_t MissileHeight= self->GetClass()->Meta.GetMetaFixed (ACMETA_MissileHeight, 32*FRACUNIT); + DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BasicAttack) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_INT(MeleeDamage, 0); + ACTION_PARAM_SOUND(MeleeSound, 1); + ACTION_PARAM_CLASS(MissileType, 2); + ACTION_PARAM_FIXED(MissileHeight, 3); + + if (MissileType == NULL) return; + DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight); +} + +//========================================================================== +// +// Custom sound functions. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySound) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_SOUND(soundid, 0); + ACTION_PARAM_INT(channel, 1); + ACTION_PARAM_FLOAT(volume, 2); + ACTION_PARAM_BOOL(looping, 3); + ACTION_PARAM_FLOAT(attenuation, 4); + + if (!looping) + { + S_Sound (self, channel, soundid, volume, attenuation); + } + else + { + if (!S_IsActorPlayingSomething (self, channel&7, soundid)) + { + S_Sound (self, channel | CHAN_LOOP, soundid, volume, attenuation); + } + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSound) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(slot, 0); + + S_StopSound(self, slot); +} + +//========================================================================== +// +// These come from a time when DECORATE constants did not exist yet and +// the sound interface was less flexible. As a result the parameters are +// not optimal and these functions have been deprecated in favor of extending +// A_PlaySound and A_StopSound. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayWeaponSound) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_SOUND(soundid, 0); + + S_Sound (self, CHAN_WEAPON, soundid, 1, ATTN_NORM); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySoundEx) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_SOUND(soundid, 0); + ACTION_PARAM_NAME(channel, 1); + ACTION_PARAM_BOOL(looping, 2); + ACTION_PARAM_INT(attenuation_raw, 3); + + float attenuation; + switch (attenuation_raw) + { + case -1: attenuation = ATTN_STATIC; break; // drop off rapidly + default: + case 0: attenuation = ATTN_NORM; break; // normal + case 1: + case 2: attenuation = ATTN_NONE; break; // full volume + } + + if (channel < NAME_Auto || channel > NAME_SoundSlot7) + { + channel = NAME_Auto; + } + + if (!looping) + { + S_Sound (self, int(channel) - NAME_Auto, soundid, 1, attenuation); + } + else + { + if (!S_IsActorPlayingSomething (self, int(channel) - NAME_Auto, soundid)) + { + S_Sound (self, (int(channel) - NAME_Auto) | CHAN_LOOP, soundid, 1, attenuation); + } + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSoundEx) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_NAME(channel, 0); + + if (channel > NAME_Auto && channel <= NAME_SoundSlot7) + { + S_StopSound (self, int(channel) - NAME_Auto); + } +} + +//========================================================================== +// +// Generic seeker missile function +// +//========================================================================== +static FRandom pr_seekermissile ("SeekerMissile"); +enum +{ + SMF_LOOK = 1, + SMF_PRECISE = 2, + SMF_CURSPEED = 4, +}; +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SeekerMissile) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(ang1, 0); + ACTION_PARAM_INT(ang2, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_INT(chance, 3); + ACTION_PARAM_INT(distance, 4); + + if ((flags & SMF_LOOK) && (self->tracer == 0) && (pr_seekermissile()tracer = P_RoughMonsterSearch (self, distance, true); + } + if (!P_SeekerMissile(self, clamp(ang1, 0, 90) * ANGLE_1, clamp(ang2, 0, 90) * ANGLE_1, !!(flags & SMF_PRECISE), !!(flags & SMF_CURSPEED))) + { + if (flags & SMF_LOOK) + { // This monster is no longer seekable, so let us look for another one next time. + self->tracer = NULL; + } + } +} + +//========================================================================== +// +// Hitscan attack with a customizable amount of bullets (specified in damage) +// +//========================================================================== +DEFINE_ACTION_FUNCTION(AActor, A_BulletAttack) +{ + int i; + int bangle; + int slope; + + if (!self->target) return; + + A_FaceTarget (self); + bangle = self->angle; + + slope = P_AimLineAttack (self, bangle, MISSILERANGE); + + S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); + for (i = self->GetMissileDamage (0, 1); i > 0; --i) + { + int angle = bangle + (pr_cabullet.Random2() << 20); + int damage = ((pr_cabullet()%5)+1)*3; + P_LineAttack(self, angle, MISSILERANGE, slope, damage, + NAME_Hitscan, NAME_BulletPuff); + } +} + + +//========================================================================== +// +// Do the state jump +// +//========================================================================== +static void DoJump(AActor * self, FState * CallingState, FState *jumpto, StateCallData *statecall) +{ + if (jumpto == NULL) return; + + if (statecall != NULL) + { + statecall->State = jumpto; + } + else if (self->player != NULL && CallingState == self->player->psprites[ps_weapon].state) + { + P_SetPsprite(self->player, ps_weapon, jumpto); + } + else if (self->player != NULL && CallingState == self->player->psprites[ps_flash].state) + { + P_SetPsprite(self->player, ps_flash, jumpto); + } + else if (CallingState == self->state) + { + self->SetState (jumpto); + } + else + { + // something went very wrong. This should never happen. + assert(false); + } +} + +// This is just to avoid having to directly reference the internally defined +// CallingState and statecall parameters in the code below. +#define ACTION_JUMP(offset) DoJump(self, CallingState, offset, statecall) + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Jump) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_INT(count, 0); + ACTION_PARAM_INT(maxchance, 1); + + if (count >= 2 && (maxchance >= 256 || pr_cajump() < maxchance)) + { + int jumps = 2 + (count == 2? 0 : (pr_cajump() % (count - 1))); + ACTION_PARAM_STATE(jumpto, jumps); + ACTION_JUMP(jumpto); + } + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfHealthLower) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_INT(health, 0); + ACTION_PARAM_STATE(jump, 1); + ACTION_PARAM_INT(ptr_selector, 2); + + AActor *measured; + + measured = COPY_AAPTR(self, ptr_selector); + + if (measured && measured->health < health) + { + ACTION_JUMP(jump); + } + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetOutsideMeleeRange) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + if (!self->CheckMeleeRange()) + { + ACTION_JUMP(jump); + } + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInsideMeleeRange) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + if (self->CheckMeleeRange()) + { + ACTION_JUMP(jump); + } + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! +} +//========================================================================== +// +// State jump function +// +//========================================================================== +void DoJumpIfCloser(AActor *target, DECLARE_PARAMINFO) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_FIXED(dist, 0); + ACTION_PARAM_STATE(jump, 1); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + // No target - no jump + if (target != NULL && P_AproxDistance(self->x-target->x, self->y-target->y) < dist && + ( (self->z > target->z && self->z - (target->z + target->height) < dist) || + (self->z <=target->z && target->z - (self->z + self->height) < dist) + ) + ) + { + ACTION_JUMP(jump); + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfCloser) +{ + AActor *target; + + if (!self->player) + { + target = self->target; + } + else + { + // Does the player aim at something that can be shot? + P_BulletSlope(self, &target); + } + DoJumpIfCloser(target, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTracerCloser) +{ + DoJumpIfCloser(self->tracer, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfMasterCloser) +{ + DoJumpIfCloser(self->master, PUSH_PARAMINFO); +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +void DoJumpIfInventory(AActor * owner, DECLARE_PARAMINFO) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_CLASS(Type, 0); + ACTION_PARAM_INT(ItemAmount, 1); + ACTION_PARAM_STATE(JumpOffset, 2); + ACTION_PARAM_INT(setowner, 3); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + if (!Type) return; + COPY_AAPTR_NOT_NULL(owner, owner, setowner); // returns if owner ends up being NULL + + AInventory *Item = owner->FindInventory(Type); + + if (Item) + { + if (ItemAmount > 0) + { + if (Item->Amount >= ItemAmount) + ACTION_JUMP(JumpOffset); + } + else if (Item->Amount >= Item->MaxAmount) + { + ACTION_JUMP(JumpOffset); + } + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInventory) +{ + DoJumpIfInventory(self, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetInventory) +{ + DoJumpIfInventory(self->target, PUSH_PARAMINFO); +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfArmorType) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_NAME(Type, 0); + ACTION_PARAM_STATE(JumpOffset, 1); + ACTION_PARAM_INT(amount, 2); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + ABasicArmor * armor = (ABasicArmor *) self->FindInventory(NAME_BasicArmor); + + if (armor && armor->ArmorType == Type && armor->Amount >= amount) + ACTION_JUMP(JumpOffset); +} + +//========================================================================== +// +// Parameterized version of A_Explode +// +//========================================================================== + +enum +{ + XF_HURTSOURCE = 1, + XF_NOTMISSILE = 4, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Explode) +{ + ACTION_PARAM_START(8); + ACTION_PARAM_INT(damage, 0); + ACTION_PARAM_INT(distance, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_BOOL(alert, 3); + ACTION_PARAM_INT(fulldmgdistance, 4); + ACTION_PARAM_INT(nails, 5); + ACTION_PARAM_INT(naildamage, 6); + ACTION_PARAM_CLASS(pufftype, 7); + + if (damage < 0) // get parameters from metadata + { + damage = self->GetClass()->Meta.GetMetaInt (ACMETA_ExplosionDamage, 128); + distance = self->GetClass()->Meta.GetMetaInt (ACMETA_ExplosionRadius, damage); + flags = !self->GetClass()->Meta.GetMetaInt (ACMETA_DontHurtShooter); + alert = false; + } + else + { + if (distance <= 0) distance = damage; + } + // NailBomb effect, from SMMU but not from its source code: instead it was implemented and + // generalized from the documentation at http://www.doomworld.com/eternity/engine/codeptrs.html + + if (nails) + { + angle_t ang; + for (int i = 0; i < nails; i++) + { + ang = i*(ANGLE_MAX/nails); + // Comparing the results of a test wad with Eternity, it seems A_NailBomb does not aim + P_LineAttack (self, ang, MISSILERANGE, 0, + //P_AimLineAttack (self, ang, MISSILERANGE), + naildamage, NAME_Hitscan, pufftype); + } + } + + P_RadiusAttack (self, self->target, damage, distance, self->DamageType, flags, fulldmgdistance); + P_CheckSplash(self, distance<target != NULL && self->target->player != NULL) + { + validcount++; + P_RecursiveSound (self->Sector, self->target, false, 0); + } +} + +//========================================================================== +// +// A_RadiusThrust +// +//========================================================================== + +enum +{ + RTF_AFFECTSOURCE = 1, + RTF_NOIMPACTDAMAGE = 2, + RTF_NOTMISSILE = 4, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusThrust) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_INT(force, 0); + ACTION_PARAM_INT(distance, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_INT(fullthrustdistance, 3); + + bool sourcenothrust = false; + + if (force == 0) force = 128; + if (distance <= 0) distance = abs(force); + + // Temporarily negate MF2_NODMGTHRUST on the shooter, since it renders this function useless. + if (!(flags & RTF_NOTMISSILE) && self->target != NULL && self->target->flags2 & MF2_NODMGTHRUST) + { + sourcenothrust = true; + self->target->flags2 &= ~MF2_NODMGTHRUST; + } + + P_RadiusAttack (self, self->target, force, distance, self->DamageType, flags | RADF_NODAMAGE, fullthrustdistance); + P_CheckSplash(self, distance << FRACBITS); + + if (sourcenothrust) + { + self->target->flags2 |= MF2_NODMGTHRUST; + } +} + +//========================================================================== +// +// Execute a line special / script +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CallSpecial) +{ + ACTION_PARAM_START(6); + ACTION_PARAM_INT(special, 0); + ACTION_PARAM_INT(arg1, 1); + ACTION_PARAM_INT(arg2, 2); + ACTION_PARAM_INT(arg3, 3); + ACTION_PARAM_INT(arg4, 4); + ACTION_PARAM_INT(arg5, 5); + + bool res = !!P_ExecuteSpecial(special, NULL, self, false, arg1, arg2, arg3, arg4, arg5); + + ACTION_SET_RESULT(res); +} + +//========================================================================== +// +// The ultimate code pointer: Fully customizable missiles! +// +//========================================================================== +enum CM_Flags +{ + CMF_AIMMODE = 3, + CMF_TRACKOWNER = 4, + CMF_CHECKTARGETDEAD = 8, + + CMF_ABSOLUTEPITCH = 16, + CMF_OFFSETPITCH = 32, + CMF_SAVEPITCH = 64, + + CMF_ABSOLUTEANGLE = 128 +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomMissile) +{ + ACTION_PARAM_START(7); + ACTION_PARAM_CLASS(ti, 0); + ACTION_PARAM_FIXED(SpawnHeight, 1); + ACTION_PARAM_INT(Spawnofs_XY, 2); + ACTION_PARAM_ANGLE(Angle, 3); + ACTION_PARAM_INT(flags, 4); + ACTION_PARAM_ANGLE(pitch, 5); + ACTION_PARAM_INT(ptr, 6); + + AActor *ref = COPY_AAPTR(self, ptr); + + int aimmode = flags & CMF_AIMMODE; + + AActor * targ; + AActor * missile; + + if (ref != NULL || aimmode==2) + { + if (ti) + { + angle_t ang = (self->angle - ANGLE_90) >> ANGLETOFINESHIFT; + fixed_t x = Spawnofs_XY * finecosine[ang]; + fixed_t y = Spawnofs_XY * finesine[ang]; + fixed_t z = SpawnHeight + self->GetBobOffset() - 32*FRACUNIT + (self->player? self->player->crouchoffset : 0); + + switch (aimmode) + { + case 0: + default: + // same adjustment as above (in all 3 directions this time) - for better aiming! + self->x += x; + self->y += y; + self->z += z; + missile = P_SpawnMissileXYZ(self->x, self->y, self->z + 32*FRACUNIT, self, ref, ti, false); + self->x -= x; + self->y -= y; + self->z -= z; + break; + + case 1: + missile = P_SpawnMissileXYZ(self->x+x, self->y+y, self->z + self->GetBobOffset() + SpawnHeight, self, ref, ti, false); + break; + + case 2: + self->x += x; + self->y += y; + missile = P_SpawnMissileAngleZSpeed(self, self->z + self->GetBobOffset() + SpawnHeight, ti, self->angle, 0, GetDefaultByType(ti)->Speed, self, false); + self->x -= x; + self->y -= y; + + flags |= CMF_ABSOLUTEPITCH; + + break; + } + + if (missile) + { + // Use the actual velocity instead of the missile's Speed property + // so that this can handle missiles with a high vertical velocity + // component properly. + + fixed_t missilespeed; + + if ( (CMF_ABSOLUTEPITCH|CMF_OFFSETPITCH) & flags) + { + if (CMF_OFFSETPITCH & flags) + { + FVector2 velocity (missile->velx, missile->vely); + pitch += R_PointToAngle2(0,0, (fixed_t)velocity.Length(), missile->velz); + } + ang = pitch >> ANGLETOFINESHIFT; + missilespeed = abs(FixedMul(finecosine[ang], missile->Speed)); + missile->velz = FixedMul(finesine[ang], missile->Speed); + } + else + { + FVector2 velocity (missile->velx, missile->vely); + missilespeed = (fixed_t)velocity.Length(); + } + + if (CMF_SAVEPITCH & flags) + { + missile->pitch = pitch; + // In aimmode 0 and 1 without absolutepitch or offsetpitch, the pitch parameter + // contains the unapplied parameter. In that case, it is set as pitch without + // otherwise affecting the spawned actor. + } + + missile->angle = (CMF_ABSOLUTEANGLE & flags) ? Angle : missile->angle + Angle ; + + ang = missile->angle >> ANGLETOFINESHIFT; + missile->velx = FixedMul (missilespeed, finecosine[ang]); + missile->vely = FixedMul (missilespeed, finesine[ang]); + + // handle projectile shooting projectiles - track the + // links back to a real owner + if (self->isMissile(!!(flags & CMF_TRACKOWNER))) + { + AActor * owner=self ;//->target; + while (owner->isMissile(!!(flags & CMF_TRACKOWNER)) && owner->target) owner=owner->target; + targ=owner; + missile->target=owner; + // automatic handling of seeker missiles + if (self->flags & missile->flags2 & MF2_SEEKERMISSILE) + { + missile->tracer=self->tracer; + } + } + else if (missile->flags2&MF2_SEEKERMISSILE) + { + // automatic handling of seeker missiles + missile->tracer=self->target; + } + // we must redo the spectral check here because the owner is set after spawning so the FriendPlayer value may be wrong + if (missile->flags4 & MF4_SPECTRAL) + { + if (missile->target != NULL) + { + missile->SetFriendPlayer(missile->target->player); + } + else + { + missile->FriendPlayer = 0; + } + } + P_CheckMissileSpawn(missile, self->radius); + } + } + } + else if (flags & CMF_CHECKTARGETDEAD) + { + // Target is dead and the attack shall be aborted. + if (self->SeeState != NULL && (self->health > 0 || !(self->flags3 & MF3_ISMONSTER))) self->SetState(self->SeeState); + } +} + +//========================================================================== +// +// An even more customizable hitscan attack +// +//========================================================================== +enum CBA_Flags +{ + CBAF_AIMFACING = 1, + CBAF_NORANDOM = 2, + CBAF_EXPLICITANGLE = 4, + CBAF_NOPITCH = 8, + CBAF_NORANDOMPUFFZ = 16, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomBulletAttack) +{ + ACTION_PARAM_START(8); + ACTION_PARAM_ANGLE(Spread_XY, 0); + ACTION_PARAM_ANGLE(Spread_Z, 1); + ACTION_PARAM_INT(NumBullets, 2); + ACTION_PARAM_INT(DamagePerBullet, 3); + ACTION_PARAM_CLASS(pufftype, 4); + ACTION_PARAM_FIXED(Range, 5); + ACTION_PARAM_INT(Flags, 6); + ACTION_PARAM_INT(ptr, 7); + + AActor *ref = COPY_AAPTR(self, ptr); + + if(Range==0) Range=MISSILERANGE; + + int i; + int bangle; + int bslope = 0; + int laflags = (Flags & CBAF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; + + if (ref || (Flags & CBAF_AIMFACING)) + { + if (!(Flags & CBAF_AIMFACING)) + { + A_Face(self, ref); + } + bangle = self->angle; + + if (!pufftype) pufftype = PClass::FindClass(NAME_BulletPuff); + + if (!(Flags & CBAF_NOPITCH)) bslope = P_AimLineAttack (self, bangle, MISSILERANGE); + + S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); + for (i=0 ; itarget) + return; + + A_FaceTarget (self); + if (self->CheckMeleeRange ()) + { + if (MeleeSound) S_Sound (self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM); + int newdam = P_DamageMobj (self->target, self, self, damage, DamageType); + if (bleed) P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); + } + else + { + if (MissSound) S_Sound (self, CHAN_WEAPON, MissSound, 1, ATTN_NORM); + } +} + +//========================================================================== +// +// A fully customizable combo attack +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomComboAttack) +{ + ACTION_PARAM_START(6); + ACTION_PARAM_CLASS(ti, 0); + ACTION_PARAM_FIXED(SpawnHeight, 1); + ACTION_PARAM_INT(damage, 2); + ACTION_PARAM_SOUND(MeleeSound, 3); + ACTION_PARAM_NAME(DamageType, 4); + ACTION_PARAM_BOOL(bleed, 5); + + if (!self->target) + return; + + A_FaceTarget (self); + if (self->CheckMeleeRange ()) + { + if (DamageType==NAME_None) DamageType = NAME_Melee; // Melee is the default type + if (MeleeSound) S_Sound (self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM); + int newdam = P_DamageMobj (self->target, self, self, damage, DamageType); + if (bleed) P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); + } + else if (ti) + { + // This seemingly senseless code is needed for proper aiming. + self->z += SpawnHeight + self->GetBobOffset() - 32*FRACUNIT; + AActor *missile = P_SpawnMissileXYZ (self->x, self->y, self->z + 32*FRACUNIT, self, self->target, ti, false); + self->z -= SpawnHeight + self->GetBobOffset() - 32*FRACUNIT; + + if (missile) + { + // automatic handling of seeker missiles + if (missile->flags2&MF2_SEEKERMISSILE) + { + missile->tracer=self->target; + } + P_CheckMissileSpawn(missile, self->radius); + } + } +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfNoAmmo) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (!ACTION_CALL_FROM_WEAPON()) return; + + if (!self->player->ReadyWeapon->CheckAmmo(self->player->ReadyWeapon->bAltFire, false, true)) + { + ACTION_JUMP(jump); + } + +} + + +//========================================================================== +// +// An even more customizable hitscan attack +// +//========================================================================== +enum FB_Flags +{ + FBF_USEAMMO = 1, + FBF_NORANDOM = 2, + FBF_EXPLICITANGLE = 4, + FBF_NOPITCH = 8, + FBF_NOFLASH = 16, + FBF_NORANDOMPUFFZ = 32, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) +{ + ACTION_PARAM_START(7); + ACTION_PARAM_ANGLE(Spread_XY, 0); + ACTION_PARAM_ANGLE(Spread_Z, 1); + ACTION_PARAM_INT(NumberOfBullets, 2); + ACTION_PARAM_INT(DamagePerBullet, 3); + ACTION_PARAM_CLASS(PuffType, 4); + ACTION_PARAM_INT(Flags, 5); + ACTION_PARAM_FIXED(Range, 6); + + if (!self->player) return; + + player_t * player=self->player; + AWeapon * weapon=player->ReadyWeapon; + + int i; + int bangle; + int bslope = 0; + int laflags = (Flags & FBF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; + + if ((Flags & FBF_USEAMMO) && weapon) + { + if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // out of ammo + } + + if (Range == 0) Range = PLAYERMISSILERANGE; + + if (!(Flags & FBF_NOFLASH)) static_cast(self)->PlayAttacking2 (); + + if (!(Flags & FBF_NOPITCH)) bslope = P_BulletSlope(self); + bangle = self->angle; + + if (!PuffType) PuffType = PClass::FindClass(NAME_BulletPuff); + + if (weapon != NULL) + { + S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); + } + + if ((NumberOfBullets==1 && !player->refire) || NumberOfBullets==0) + { + int damage = DamagePerBullet; + + if (!(Flags & FBF_NORANDOM)) + damage *= ((pr_cwbullet()%3)+1); + + P_LineAttack(self, bangle, Range, bslope, damage, NAME_Hitscan, PuffType, laflags); + } + else + { + if (NumberOfBullets == -1) NumberOfBullets = 1; + for (i=0 ; iplayer) return; + + + player_t *player=self->player; + AWeapon * weapon=player->ReadyWeapon; + AActor *linetarget; + + if (UseAmmo && weapon) + { + if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // out of ammo + } + + if (ti) + { + angle_t ang = (self->angle - ANGLE_90) >> ANGLETOFINESHIFT; + fixed_t x = SpawnOfs_XY * finecosine[ang]; + fixed_t y = SpawnOfs_XY * finesine[ang]; + fixed_t z = SpawnHeight; + fixed_t shootangle = self->angle; + + if (Flags & FPF_AIMATANGLE) shootangle += Angle; + + // Temporarily adjusts the pitch + fixed_t SavedPlayerPitch = self->pitch; + self->pitch -= pitch; + AActor * misl=P_SpawnPlayerMissile (self, x, y, z, ti, shootangle, &linetarget); + self->pitch = SavedPlayerPitch; + + // automatic handling of seeker missiles + if (misl) + { + if (Flags & FPF_TRANSFERTRANSLATION) misl->Translation = self->Translation; + if (linetarget && misl->flags2&MF2_SEEKERMISSILE) misl->tracer=linetarget; + if (!(Flags & FPF_AIMATANGLE)) + { + // This original implementation is to aim straight ahead and then offset + // the angle from the resulting direction. + FVector3 velocity(misl->velx, misl->vely, 0); + fixed_t missilespeed = (fixed_t)velocity.Length(); + misl->angle += Angle; + angle_t an = misl->angle >> ANGLETOFINESHIFT; + misl->velx = FixedMul (missilespeed, finecosine[an]); + misl->vely = FixedMul (missilespeed, finesine[an]); + } + } + } +} + + +//========================================================================== +// +// A_CustomPunch +// +// Berserk is not handled here. That can be done with A_CheckIfInventory +// +//========================================================================== + +enum +{ + CPF_USEAMMO = 1, + CPF_DAGGER = 2, + CPF_PULLIN = 4, + CPF_NORANDOMPUFFZ = 8, + CPF_NOTURN = 16, + CPF_STEALARMOR = 32, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomPunch) +{ + ACTION_PARAM_START(8); + ACTION_PARAM_INT(Damage, 0); + ACTION_PARAM_BOOL(norandom, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(PuffType, 3); + ACTION_PARAM_FIXED(Range, 4); + ACTION_PARAM_FIXED(LifeSteal, 5); + ACTION_PARAM_INT(lifestealmax, 6); + ACTION_PARAM_CLASS(armorbonustype, 7); + + if (!self->player) return; + + player_t *player=self->player; + AWeapon * weapon=player->ReadyWeapon; + + + angle_t angle; + int pitch; + AActor * linetarget; + int actualdamage; + + if (!norandom) Damage *= (pr_cwpunch()%8+1); + + angle = self->angle + (pr_cwpunch.Random2() << 18); + if (Range == 0) Range = MELEERANGE; + pitch = P_AimLineAttack (self, angle, Range, &linetarget); + + // only use ammo when actually hitting something! + if ((flags & CPF_USEAMMO) && linetarget && weapon) + { + if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // out of ammo + } + + if (!PuffType) PuffType = PClass::FindClass(NAME_BulletPuff); + int puffFlags = LAF_ISMELEEATTACK | ((flags & CPF_NORANDOMPUFFZ) ? LAF_NORANDOMPUFFZ : 0); + + P_LineAttack (self, angle, Range, pitch, Damage, NAME_Melee, PuffType, puffFlags, &linetarget, &actualdamage); + + if (linetarget) + { + if (LifeSteal && !(linetarget->flags5 & MF5_DONTDRAIN)) + { + if (flags & CPF_STEALARMOR) + { + if (!armorbonustype) armorbonustype = PClass::FindClass("ArmorBonus"); + + if (armorbonustype->IsDescendantOf (RUNTIME_CLASS(ABasicArmorBonus))) + { + ABasicArmorBonus *armorbonus = static_cast(Spawn (armorbonustype, 0,0,0, NO_REPLACE)); + armorbonus->SaveAmount *= (actualdamage * LifeSteal) >> FRACBITS; + armorbonus->MaxSaveAmount = lifestealmax <= 0 ? armorbonus->MaxSaveAmount : lifestealmax; + armorbonus->flags |= MF_DROPPED; + armorbonus->ClearCounters(); + + if (!armorbonus->CallTryPickup (self)) + { + armorbonus->Destroy (); + } + } + } + + else + { + P_GiveBody (self, (actualdamage * LifeSteal) >> FRACBITS, lifestealmax); + } + } + + if (weapon != NULL) + { + S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); + } + + if (!(flags & CPF_NOTURN)) + { + // turn to face target + self->angle = R_PointToAngle2 (self->x, + self->y, + linetarget->x, + linetarget->y); + } + + if (flags & CPF_PULLIN) self->flags |= MF_JUSTATTACKED; + if (flags & CPF_DAGGER) P_DaggerAlert (self, linetarget); + } +} + + +//========================================================================== +// +// customizable railgun attack function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack) +{ + ACTION_PARAM_START(16); + ACTION_PARAM_INT(Damage, 0); + ACTION_PARAM_INT(Spawnofs_XY, 1); + ACTION_PARAM_BOOL(UseAmmo, 2); + ACTION_PARAM_COLOR(Color1, 3); + ACTION_PARAM_COLOR(Color2, 4); + ACTION_PARAM_INT(Flags, 5); + ACTION_PARAM_FLOAT(MaxDiff, 6); + ACTION_PARAM_CLASS(PuffType, 7); + ACTION_PARAM_ANGLE(Spread_XY, 8); + ACTION_PARAM_ANGLE(Spread_Z, 9); + ACTION_PARAM_FIXED(Range, 10); + ACTION_PARAM_INT(Duration, 11); + ACTION_PARAM_FLOAT(Sparsity, 12); + ACTION_PARAM_FLOAT(DriftSpeed, 13); + ACTION_PARAM_CLASS(SpawnClass, 14); + ACTION_PARAM_FIXED(Spawnofs_Z, 15); + + if(Range==0) Range=8192*FRACUNIT; + if(Sparsity==0) Sparsity=1.0; + + if (!self->player) return; + + AWeapon * weapon=self->player->ReadyWeapon; + + // only use ammo when actually hitting something! + if (UseAmmo) + { + if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // out of ammo + } + + angle_t angle; + angle_t slope; + + if (Flags & RAF_EXPLICITANGLE) + { + angle = Spread_XY; + slope = Spread_Z; + } + else + { + angle = pr_crailgun.Random2() * (Spread_XY / 255); + slope = pr_crailgun.Random2() * (Spread_Z / 255); + } + + P_RailAttack (self, Damage, Spawnofs_XY, Spawnofs_Z, Color1, Color2, MaxDiff, Flags, PuffType, angle, slope, Range, Duration, Sparsity, DriftSpeed, SpawnClass); +} + +//========================================================================== +// +// also for monsters +// +//========================================================================== +enum +{ + CRF_DONTAIM = 0, + CRF_AIMPARALLEL = 1, + CRF_AIMDIRECT = 2, + CRF_EXPLICITANGLE = 4, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) +{ + ACTION_PARAM_START(16); + ACTION_PARAM_INT(Damage, 0); + ACTION_PARAM_INT(Spawnofs_XY, 1); + ACTION_PARAM_COLOR(Color1, 2); + ACTION_PARAM_COLOR(Color2, 3); + ACTION_PARAM_INT(Flags, 4); + ACTION_PARAM_INT(aim, 5); + ACTION_PARAM_FLOAT(MaxDiff, 6); + ACTION_PARAM_CLASS(PuffType, 7); + ACTION_PARAM_ANGLE(Spread_XY, 8); + ACTION_PARAM_ANGLE(Spread_Z, 9); + ACTION_PARAM_FIXED(Range, 10); + ACTION_PARAM_INT(Duration, 11); + ACTION_PARAM_FLOAT(Sparsity, 12); + ACTION_PARAM_FLOAT(DriftSpeed, 13); + ACTION_PARAM_CLASS(SpawnClass, 14); + ACTION_PARAM_FIXED(Spawnofs_Z, 15); + + if(Range==0) Range=8192*FRACUNIT; + if(Sparsity==0) Sparsity=1.0; + + AActor *linetarget; + + fixed_t saved_x = self->x; + fixed_t saved_y = self->y; + angle_t saved_angle = self->angle; + fixed_t saved_pitch = self->pitch; + + if (aim && self->target == NULL) + { + return; + } + // [RH] Andy Baker's stealth monsters + if (self->flags & MF_STEALTH) + { + self->visdir = 1; + } + + self->flags &= ~MF_AMBUSH; + + + if (aim) + { + self->angle = R_PointToAngle2 (self->x, + self->y, + self->target->x, + self->target->y); + } + self->pitch = P_AimLineAttack (self, self->angle, MISSILERANGE, &linetarget, ANGLE_1*60, 0, aim ? self->target : NULL); + if (linetarget == NULL && aim) + { + // We probably won't hit the target, but aim at it anyway so we don't look stupid. + FVector2 xydiff(self->target->x - self->x, self->target->y - self->y); + double zdiff = (self->target->z + (self->target->height>>1)) - + (self->z + (self->height>>1) - self->floorclip); + self->pitch = int(atan2(zdiff, xydiff.Length()) * ANGLE_180 / -M_PI); + } + // Let the aim trail behind the player + if (aim) + { + saved_angle = self->angle = R_PointToAngle2 (self->x, self->y, + self->target->x - self->target->velx * 3, + self->target->y - self->target->vely * 3); + + if (aim == CRF_AIMDIRECT) + { + // Tricky: We must offset to the angle of the current position + // but then change the angle again to ensure proper aim. + self->x += Spawnofs_XY * finecosine[self->angle]; + self->y += Spawnofs_XY * finesine[self->angle]; + Spawnofs_XY = 0; + self->angle = R_PointToAngle2 (self->x, self->y, + self->target->x - self->target->velx * 3, + self->target->y - self->target->vely * 3); + } + + if (self->target->flags & MF_SHADOW) + { + angle_t rnd = pr_crailgun.Random2() << 21; + self->angle += rnd; + saved_angle = rnd; + } + } + + angle_t angle = (self->angle - ANG90) >> ANGLETOFINESHIFT; + + angle_t angleoffset; + angle_t slopeoffset; + + if (Flags & CRF_EXPLICITANGLE) + { + angleoffset = Spread_XY; + slopeoffset = Spread_Z; + } + else + { + angleoffset = pr_crailgun.Random2() * (Spread_XY / 255); + slopeoffset = pr_crailgun.Random2() * (Spread_Z / 255); + } + + P_RailAttack (self, Damage, Spawnofs_XY, Spawnofs_Z, Color1, Color2, MaxDiff, Flags, PuffType, angleoffset, slopeoffset, Range, Duration, Sparsity, DriftSpeed, SpawnClass); + + self->x = saved_x; + self->y = saved_y; + self->angle = saved_angle; + self->pitch = saved_pitch; +} + +//=========================================================================== +// +// DoGiveInventory +// +//=========================================================================== + +static void DoGiveInventory(AActor * receiver, bool use_aaptr, DECLARE_PARAMINFO) +{ + ACTION_PARAM_START(2+use_aaptr); + ACTION_PARAM_CLASS(mi, 0); + ACTION_PARAM_INT(amount, 1); + + if (use_aaptr) + { + ACTION_PARAM_INT(setreceiver, 2); + COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver); + } + + bool res=true; + + if (amount==0) amount=1; + if (mi) + { + AInventory *item = static_cast(Spawn (mi, 0, 0, 0, NO_REPLACE)); + if (!item) + { + ACTION_SET_RESULT(false); + return; + } + if (item->IsKindOf(RUNTIME_CLASS(AHealth))) + { + item->Amount *= amount; + } + else + { + item->Amount = amount; + } + item->flags |= MF_DROPPED; + item->ClearCounters(); + if (!item->CallTryPickup (receiver)) + { + item->Destroy (); + res = false; + } + else res = true; + } + else res = false; + ACTION_SET_RESULT(res); + +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveInventory) +{ + DoGiveInventory(self, true, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToTarget) +{ + DoGiveInventory(self->target, true, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToChildren) +{ + TThinkerIterator it; + AActor * mo; + + while ((mo = it.Next())) + { + if (mo->master == self) DoGiveInventory(mo, false, PUSH_PARAMINFO); + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToSiblings) +{ + TThinkerIterator it; + AActor * mo; + + if (self->master != NULL) + { + while ((mo = it.Next())) + { + if (mo->master == self->master && mo != self) DoGiveInventory(mo, false, PUSH_PARAMINFO); + } + } +} + +//=========================================================================== +// +// A_TakeInventory +// +//=========================================================================== + +enum +{ + TIF_NOTAKEINFINITE = 1, +}; + +void DoTakeInventory(AActor * receiver, bool use_aaptr, DECLARE_PARAMINFO) +{ + ACTION_PARAM_START(3+use_aaptr); + ACTION_PARAM_CLASS(item, 0); + ACTION_PARAM_INT(amount, 1); + ACTION_PARAM_INT(flags, 2); + + if (!item) + { + ACTION_SET_RESULT(false); + return; + } + if (use_aaptr) + { + ACTION_PARAM_INT(setreceiver, 3); + COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver); + } + + bool res = false; + + AInventory * inv = receiver->FindInventory(item); + + if (inv && !inv->IsKindOf(RUNTIME_CLASS(AHexenArmor))) + { + if (inv->Amount > 0) + { + res = true; + } + // Do not take ammo if the "no take infinite/take as ammo depletion" flag is set + // and infinite ammo is on + if (flags & TIF_NOTAKEINFINITE && + ((dmflags & DF_INFINITE_AMMO) || (receiver->player->cheats & CF_INFINITEAMMO)) && + inv->IsKindOf(RUNTIME_CLASS(AAmmo))) + { + // Nothing to do here, except maybe res = false;? Would it make sense? + } + else if (!amount || amount>=inv->Amount) + { + if (inv->ItemFlags&IF_KEEPDEPLETED) inv->Amount=0; + else inv->Destroy(); + } + else inv->Amount-=amount; + } + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeInventory) +{ + DoTakeInventory(self, true, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromTarget) +{ + DoTakeInventory(self->target, true, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromChildren) +{ + TThinkerIterator it; + AActor * mo; + + while ((mo = it.Next())) + { + if (mo->master == self) DoTakeInventory(mo, false, PUSH_PARAMINFO); + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromSiblings) +{ + TThinkerIterator it; + AActor * mo; + + if (self->master != NULL) + { + while ((mo = it.Next())) + { + if (mo->master == self->master && mo != self) DoTakeInventory(mo, false, PUSH_PARAMINFO); + } + } +} + +//=========================================================================== +// +// Common code for A_SpawnItem and A_SpawnItemEx +// +//=========================================================================== + +enum SIX_Flags +{ + SIXF_TRANSFERTRANSLATION = 1 << 0, + SIXF_ABSOLUTEPOSITION = 1 << 1, + SIXF_ABSOLUTEANGLE = 1 << 2, + SIXF_ABSOLUTEVELOCITY = 1 << 3, + SIXF_SETMASTER = 1 << 4, + SIXF_NOCHECKPOSITION = 1 << 5, + SIXF_TELEFRAG = 1 << 6, + SIXF_CLIENTSIDE = 1 << 7, // only used by Skulldronum + SIXF_TRANSFERAMBUSHFLAG = 1 << 8, + SIXF_TRANSFERPITCH = 1 << 9, + SIXF_TRANSFERPOINTERS = 1 << 10, + SIXF_USEBLOODCOLOR = 1 << 11, + SIXF_CLEARCALLERTID = 1 << 12, + SIXF_MULTIPLYSPEED = 1 << 13, + SIXF_TRANSFERSCALE = 1 << 14, + SIXF_TRANSFERSPECIAL = 1 << 15, + SIXF_CLEARCALLERSPECIAL = 1 << 16, + SIXF_TRANSFERSTENCILCOL = 1 << 17, + SIXF_TRANSFERALPHA = 1 << 18, + SIXF_TRANSFERRENDERSTYLE = 1 << 19, + SIXF_SETTARGET = 1 << 20, + SIXF_SETTRACER = 1 << 21, + SIXF_NOPOINTERS = 1 << 22, + SIXF_ORIGINATOR = 1 << 23, +}; + +static bool InitSpawnedItem(AActor *self, AActor *mo, int flags) +{ + if (mo == NULL) + { + return false; + } + AActor *originator = self; + + if (!(mo->flags2 & MF2_DONTTRANSLATE)) + { + if (flags & SIXF_TRANSFERTRANSLATION) + { + mo->Translation = self->Translation; + } + else if (flags & SIXF_USEBLOODCOLOR) + { + // [XA] Use the spawning actor's BloodColor to translate the newly-spawned object. + PalEntry bloodcolor = self->GetBloodColor(); + mo->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a); + } + } + if (flags & SIXF_TRANSFERPOINTERS) + { + mo->target = self->target; + mo->master = self->master; // This will be overridden later if SIXF_SETMASTER is set + mo->tracer = self->tracer; + } + + mo->angle = self->angle; + if (flags & SIXF_TRANSFERPITCH) + { + mo->pitch = self->pitch; + } + if (!(flags & SIXF_ORIGINATOR)) + { + while (originator && originator->isMissile()) + { + originator = originator->target; + } + } + if (flags & SIXF_TELEFRAG) + { + P_TeleportMove(mo, mo->x, mo->y, mo->z, true); + // This is needed to ensure consistent behavior. + // Otherwise it will only spawn if nothing gets telefragged + flags |= SIXF_NOCHECKPOSITION; + } + if (mo->flags3 & MF3_ISMONSTER) + { + if (!(flags & SIXF_NOCHECKPOSITION) && !P_TestMobjLocation(mo)) + { + // The monster is blocked so don't spawn it at all! + mo->ClearCounters(); + mo->Destroy(); + return false; + } + else if (originator && !(flags & SIXF_NOPOINTERS)) + { + if (originator->flags3 & MF3_ISMONSTER) + { + // If this is a monster transfer all friendliness information + mo->CopyFriendliness(originator, true); + } + else if (originator->player) + { + // A player always spawns a monster friendly to him + mo->flags |= MF_FRIENDLY; + mo->SetFriendPlayer(originator->player); + + AActor * attacker=originator->player->attacker; + if (attacker) + { + if (!(attacker->flags&MF_FRIENDLY) || + (deathmatch && attacker->FriendPlayer!=0 && attacker->FriendPlayer!=mo->FriendPlayer)) + { + // Target the monster which last attacked the player + mo->LastHeard = mo->target = attacker; + } + } + } + } + } + else if (!(flags & SIXF_TRANSFERPOINTERS)) + { + // If this is a missile or something else set the target to the originator + mo->target = originator ? originator : self; + } + if (flags & SIXF_NOPOINTERS) + { + //[MC]Intentionally eliminate pointers. Overrides TRANSFERPOINTERS, but is overridden by SETMASTER/TARGET/TRACER. + mo->LastHeard = NULL; //Sanity check. + mo->target = NULL; + mo->master = NULL; + mo->tracer = NULL; + } + if (flags & SIXF_SETMASTER) + { + mo->master = originator; + } + if (flags & SIXF_SETTARGET) + { + mo->target = originator; + } + if (flags & SIXF_SETTRACER) + { + mo->tracer = originator; + } + if (flags & SIXF_TRANSFERSCALE) + { + mo->scaleX = self->scaleX; + mo->scaleY = self->scaleY; + } + if (flags & SIXF_TRANSFERAMBUSHFLAG) + { + mo->flags = (mo->flags & ~MF_AMBUSH) | (self->flags & MF_AMBUSH); + } + if (flags & SIXF_CLEARCALLERTID) + { + self->RemoveFromHash(); + self->tid = 0; + } + if (flags & SIXF_TRANSFERSPECIAL) + { + mo->special = self->special; + memcpy(mo->args, self->args, sizeof(self->args)); + } + if (flags & SIXF_CLEARCALLERSPECIAL) + { + self->special = 0; + memset(self->args, 0, sizeof(self->args)); + } + if (flags & SIXF_TRANSFERSTENCILCOL) + { + mo->fillcolor = self->fillcolor; + } + if (flags & SIXF_TRANSFERALPHA) + { + mo->alpha = self->alpha; + } + if (flags & SIXF_TRANSFERRENDERSTYLE) + { + mo->RenderStyle = self->RenderStyle; + } + + return true; +} + +//=========================================================================== +// +// A_SpawnItem +// +// Spawns an item in front of the caller like Heretic's time bomb +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItem) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_CLASS(missile, 0); + ACTION_PARAM_FIXED(distance, 1); + ACTION_PARAM_FIXED(zheight, 2); + ACTION_PARAM_BOOL(useammo, 3); + ACTION_PARAM_BOOL(transfer_translation, 4); + + if (!missile) + { + ACTION_SET_RESULT(false); + return; + } + + // Don't spawn monsters if this actor has been massacred + if (self->DamageType == NAME_Massacre && GetDefaultByType(missile)->flags3&MF3_ISMONSTER) return; + + if (distance==0) + { + // use the minimum distance that does not result in an overlap + distance=(self->radius+GetDefaultByType(missile)->radius)>>FRACBITS; + } + + if (ACTION_CALL_FROM_WEAPON()) + { + // Used from a weapon so use some ammo + AWeapon * weapon=self->player->ReadyWeapon; + + if (!weapon) return; + if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) return; + } + + AActor * mo = Spawn( missile, + self->x + FixedMul(distance, finecosine[self->angle>>ANGLETOFINESHIFT]), + self->y + FixedMul(distance, finesine[self->angle>>ANGLETOFINESHIFT]), + self->z - self->floorclip + self->GetBobOffset() + zheight, ALLOW_REPLACE); + + int flags = (transfer_translation ? SIXF_TRANSFERTRANSLATION : 0) + (useammo ? SIXF_SETMASTER : 0); + bool res = InitSpawnedItem(self, mo, flags); + ACTION_SET_RESULT(res); // for an inventory item's use state +} + +//=========================================================================== +// +// A_SpawnItemEx +// +// Enhanced spawning function +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItemEx) +{ + ACTION_PARAM_START(11); + ACTION_PARAM_CLASS(missile, 0); + ACTION_PARAM_FIXED(xofs, 1); + ACTION_PARAM_FIXED(yofs, 2); + ACTION_PARAM_FIXED(zofs, 3); + ACTION_PARAM_FIXED(xvel, 4); + ACTION_PARAM_FIXED(yvel, 5); + ACTION_PARAM_FIXED(zvel, 6); + ACTION_PARAM_ANGLE(Angle, 7); + ACTION_PARAM_INT(flags, 8); + ACTION_PARAM_INT(chance, 9); + ACTION_PARAM_INT(tid, 10); + + if (!missile) + { + ACTION_SET_RESULT(false); + return; + } + + if (chance > 0 && pr_spawnitemex()DamageType == NAME_Massacre && GetDefaultByType(missile)->flags3&MF3_ISMONSTER) return; + + fixed_t x,y; + + if (!(flags & SIXF_ABSOLUTEANGLE)) + { + Angle += self->angle; + } + + angle_t ang = Angle >> ANGLETOFINESHIFT; + + if (flags & SIXF_ABSOLUTEPOSITION) + { + x = self->x + xofs; + y = self->y + yofs; + } + else + { + // in relative mode negative y values mean 'left' and positive ones mean 'right' + // This is the inverse orientation of the absolute mode! + x = self->x + FixedMul(xofs, finecosine[ang]) + FixedMul(yofs, finesine[ang]); + y = self->y + FixedMul(xofs, finesine[ang]) - FixedMul(yofs, finecosine[ang]); + } + + if (!(flags & SIXF_ABSOLUTEVELOCITY)) + { + // Same orientation issue here! + fixed_t newxvel = FixedMul(xvel, finecosine[ang]) + FixedMul(yvel, finesine[ang]); + yvel = FixedMul(xvel, finesine[ang]) - FixedMul(yvel, finecosine[ang]); + xvel = newxvel; + } + + AActor *mo = Spawn(missile, x, y, self->z - self->floorclip + self->GetBobOffset() + zofs, ALLOW_REPLACE); + bool res = InitSpawnedItem(self, mo, flags); + ACTION_SET_RESULT(res); // for an inventory item's use state + if (res) + { + if (tid != 0) + { + assert(mo->tid == 0); + mo->tid = tid; + mo->AddToHash(); + } + if (flags & SIXF_MULTIPLYSPEED) + { + mo->velx = FixedMul(xvel, mo->Speed); + mo->vely = FixedMul(yvel, mo->Speed); + mo->velz = FixedMul(zvel, mo->Speed); + } + else + { + mo->velx = xvel; + mo->vely = yvel; + mo->velz = zvel; + } + mo->angle = Angle; + } +} + +//=========================================================================== +// +// A_ThrowGrenade +// +// Throws a grenade (like Hexen's fighter flechette) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ThrowGrenade) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_CLASS(missile, 0); + ACTION_PARAM_FIXED(zheight, 1); + ACTION_PARAM_FIXED(xyvel, 2); + ACTION_PARAM_FIXED(zvel, 3); + ACTION_PARAM_BOOL(useammo, 4); + + if (missile == NULL) return; + + if (ACTION_CALL_FROM_WEAPON()) + { + // Used from a weapon, so use some ammo + AWeapon *weapon = self->player->ReadyWeapon; + + if (!weapon) return; + if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) return; + } + + + AActor * bo; + + bo = Spawn(missile, self->x, self->y, + self->z - self->floorclip + self->GetBobOffset() + zheight + 35*FRACUNIT + (self->player? self->player->crouchoffset : 0), + ALLOW_REPLACE); + if (bo) + { + P_PlaySpawnSound(bo, self); + if (xyvel != 0) + bo->Speed = xyvel; + bo->angle = self->angle + (((pr_grenade()&7) - 4) << 24); + + angle_t pitch = angle_t(-self->pitch) >> ANGLETOFINESHIFT; + angle_t angle = bo->angle >> ANGLETOFINESHIFT; + + // There are two vectors we are concerned about here: xy and z. We rotate + // them separately according to the shooter's pitch and then sum them to + // get the final velocity vector to shoot with. + + fixed_t xy_xyscale = FixedMul(bo->Speed, finecosine[pitch]); + fixed_t xy_velz = FixedMul(bo->Speed, finesine[pitch]); + fixed_t xy_velx = FixedMul(xy_xyscale, finecosine[angle]); + fixed_t xy_vely = FixedMul(xy_xyscale, finesine[angle]); + + pitch = angle_t(self->pitch) >> ANGLETOFINESHIFT; + fixed_t z_xyscale = FixedMul(zvel, finesine[pitch]); + fixed_t z_velz = FixedMul(zvel, finecosine[pitch]); + fixed_t z_velx = FixedMul(z_xyscale, finecosine[angle]); + fixed_t z_vely = FixedMul(z_xyscale, finesine[angle]); + + bo->velx = xy_velx + z_velx + (self->velx >> 1); + bo->vely = xy_vely + z_vely + (self->vely >> 1); + bo->velz = xy_velz + z_velz; + + bo->target = self; + P_CheckMissileSpawn (bo, self->radius); + } + else ACTION_SET_RESULT(false); +} + + +//=========================================================================== +// +// A_Recoil +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Recoil) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_FIXED(xyvel, 0); + + angle_t angle = self->angle + ANG180; + angle >>= ANGLETOFINESHIFT; + self->velx += FixedMul (xyvel, finecosine[angle]); + self->vely += FixedMul (xyvel, finesine[angle]); +} + + +//=========================================================================== +// +// A_SelectWeapon +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SelectWeapon) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_CLASS(cls, 0); + + if (cls == NULL || self->player == NULL) + { + ACTION_SET_RESULT(false); + return; + } + + AWeapon * weaponitem = static_cast(self->FindInventory(cls)); + + if (weaponitem != NULL && weaponitem->IsKindOf(RUNTIME_CLASS(AWeapon))) + { + if (self->player->ReadyWeapon != weaponitem) + { + self->player->PendingWeapon = weaponitem; + } + } + else ACTION_SET_RESULT(false); + +} + + +//=========================================================================== +// +// A_Print +// +//=========================================================================== +EXTERN_CVAR(Float, con_midtime) + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Print) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_STRING(text, 0); + ACTION_PARAM_FLOAT(time, 1); + ACTION_PARAM_NAME(fontname, 2); + + if (text[0] == '$') text = GStrings(text+1); + if (self->CheckLocalView (consoleplayer) || + (self->target!=NULL && self->target->CheckLocalView (consoleplayer))) + { + float saved = con_midtime; + FFont *font = NULL; + + if (fontname != NAME_None) + { + font = V_GetFont(fontname); + } + if (time > 0) + { + con_midtime = time; + } + + FString formatted = strbin1(text); + C_MidPrint(font != NULL ? font : SmallFont, formatted.GetChars()); + con_midtime = saved; + } + ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! +} + +//=========================================================================== +// +// A_PrintBold +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PrintBold) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_STRING(text, 0); + ACTION_PARAM_FLOAT(time, 1); + ACTION_PARAM_NAME(fontname, 2); + + float saved = con_midtime; + FFont *font = NULL; + + if (text[0] == '$') text = GStrings(text+1); + if (fontname != NAME_None) + { + font = V_GetFont(fontname); + } + if (time > 0) + { + con_midtime = time; + } + + FString formatted = strbin1(text); + C_MidPrintBold(font != NULL ? font : SmallFont, formatted.GetChars()); + con_midtime = saved; + ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! +} + +//=========================================================================== +// +// A_Log +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Log) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STRING(text, 0); + + if (text[0] == '$') text = GStrings(text+1); + FString formatted = strbin1(text); + Printf("%s\n", formatted.GetChars()); + ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! +} + +//========================================================================= +// +// A_LogInt +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LogInt) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(num, 0); + Printf("%d\n", num); + ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! +} + +//=========================================================================== +// +// A_SetTranslucent +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTranslucent) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_FIXED(alpha, 0); + ACTION_PARAM_INT(mode, 1); + + mode = mode == 0 ? STYLE_Translucent : mode == 2 ? STYLE_Fuzzy : STYLE_Add; + + self->RenderStyle.Flags &= ~STYLEF_Alpha1; + self->alpha = clamp(alpha, 0, FRACUNIT); + self->RenderStyle = ERenderStyle(mode); +} + +//=========================================================================== +// +// A_FadeIn +// +// Fades the actor in +// +//=========================================================================== + +enum FadeFlags +{ + FTF_REMOVE = 1 << 0, + FTF_CLAMP = 1 << 1, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeIn) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_FIXED(reduce, 0); + ACTION_PARAM_INT(flags, 1); + + if (reduce == 0) + { + reduce = FRACUNIT / 10; + } + self->RenderStyle.Flags &= ~STYLEF_Alpha1; + self->alpha += reduce; + + if (self->alpha >= (FRACUNIT * 1)) + { + if (flags & FTF_CLAMP) + self->alpha = (FRACUNIT * 1); + if (flags & FTF_REMOVE) + self->Destroy(); + } +} + +//=========================================================================== +// +// A_FadeOut +// +// fades the actor out and destroys it when done +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeOut) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_FIXED(reduce, 0); + ACTION_PARAM_INT(flags, 1); + + if (reduce == 0) + { + reduce = FRACUNIT/10; + } + self->RenderStyle.Flags &= ~STYLEF_Alpha1; + self->alpha -= reduce; + if (self->alpha <= 0) + { + if (flags & FTF_CLAMP) + self->alpha = 0; + if (flags & FTF_REMOVE) + self->Destroy(); + } +} + +//=========================================================================== +// +// A_FadeTo +// +// fades the actor to a specified transparency by a specified amount and +// destroys it if so desired +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeTo) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_FIXED(target, 0); + ACTION_PARAM_FIXED(amount, 1); + ACTION_PARAM_INT(flags, 2); + + self->RenderStyle.Flags &= ~STYLEF_Alpha1; + + if (self->alpha > target) + { + self->alpha -= amount; + + if (self->alpha < target) + { + self->alpha = target; + } + } + else if (self->alpha < target) + { + self->alpha += amount; + + if (self->alpha > target) + { + self->alpha = target; + } + } + if (flags & FTF_CLAMP) + { + if (self->alpha > (FRACUNIT * 1)) + self->alpha = (FRACUNIT * 1); + else if (self->alpha < 0) + self->alpha = 0; + } + if (self->alpha == target && (flags & FTF_REMOVE)) + { + self->Destroy(); + } +} + +//=========================================================================== +// +// A_Scale(float scalex, optional float scaley) +// +// Scales the actor's graphics. If scaley is 0, use scalex. +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetScale) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_FIXED(scalex, 0); + ACTION_PARAM_FIXED(scaley, 1); + ACTION_PARAM_INT(ptr, 2); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } + + ref->scaleX = scalex; + ref->scaleY = scaley ? scaley : scalex; +} + +//=========================================================================== +// +// A_SetMass(int mass) +// +// Sets the actor's mass. +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetMass) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(mass, 0); + + self->Mass = mass; +} + +//=========================================================================== +// +// A_SpawnDebris +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnDebris) +{ + int i; + AActor * mo; + + ACTION_PARAM_START(4); + ACTION_PARAM_CLASS(debris, 0); + ACTION_PARAM_BOOL(transfer_translation, 1); + ACTION_PARAM_FIXED(mult_h, 2); + ACTION_PARAM_FIXED(mult_v, 3); + + if (debris == NULL) return; + + // only positive values make sense here + if (mult_v<=0) mult_v=FRACUNIT; + if (mult_h<=0) mult_h=FRACUNIT; + + for (i = 0; i < GetDefaultByType(debris)->health; i++) + { + mo = Spawn(debris, self->x+((pr_spawndebris()-128)<<12), + self->y + ((pr_spawndebris()-128)<<12), + self->z + (pr_spawndebris()*self->height/256+self->GetBobOffset()), ALLOW_REPLACE); + if (mo) + { + if (transfer_translation) + { + mo->Translation = self->Translation; + } + if (i < mo->GetClass()->ActorInfo->NumOwnedStates) + { + mo->SetState(mo->GetClass()->ActorInfo->OwnedStates + i); + } + mo->velz = FixedMul(mult_v, ((pr_spawndebris()&7)+5)*FRACUNIT); + mo->velx = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6)); + mo->vely = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6)); + } + } +} + + +//=========================================================================== +// +// A_CheckSight +// jumps if no player can see this actor +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSight) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + for (int i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { + // Always check sight from each player. + if (P_CheckSight(players[i].mo, self, SF_IGNOREVISIBILITY)) + { + return; + } + // If a player is viewing from a non-player, then check that too. + if (players[i].camera != NULL && players[i].camera->player == NULL && + P_CheckSight(players[i].camera, self, SF_IGNOREVISIBILITY)) + { + return; + } + } + } + + ACTION_JUMP(jump); +} + +//=========================================================================== +// +// A_CheckSightOrRange +// Jumps if this actor is out of range of all players *and* out of sight. +// Useful for maps with many multi-actor special effects. +// +//=========================================================================== +static bool DoCheckSightOrRange(AActor *self, AActor *camera, double range, bool twodi) +{ + if (camera == NULL) + { + return false; + } + // Check distance first, since it's cheaper than checking sight. + double dx = self->x - camera->x; + double dy = self->y - camera->y; + double dz; + fixed_t eyez = (camera->z + camera->height - (camera->height>>2)); // same eye height as P_CheckSight + if (eyez > self->z + self->height) + { + dz = self->z + self->height - eyez; + } + else if (eyez < self->z) + { + dz = self->z - eyez; + } + else + { + dz = 0; + } + double distance = (dx * dx) + (dy * dy) + (twodi == 0? (dz * dz) : 0); + if (distance <= range){ + // Within range + return true; + } + + // Now check LOS. + if (P_CheckSight(camera, self, SF_IGNOREVISIBILITY)) + { // Visible + return true; + } + return false; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSightOrRange) +{ + ACTION_PARAM_START(3); + double range = EvalExpressionF(ParameterIndex+0, self); + ACTION_PARAM_STATE(jump, 1); + ACTION_PARAM_BOOL(twodi, 2); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots + for (int i = 0; i < MAXPLAYERS; ++i) + { + if (playeringame[i]) + { + // Always check from each player. + if (DoCheckSightOrRange(self, players[i].mo, range, twodi)) + { + return; + } + // If a player is viewing from a non-player, check that too. + if (players[i].camera != NULL && players[i].camera->player == NULL && + DoCheckSightOrRange(self, players[i].camera, range, twodi)) + { + return; + } + } + } + ACTION_JUMP(jump); +} + +//=========================================================================== +// +// A_CheckRange +// Jumps if this actor is out of range of all players. +// +//=========================================================================== +static bool DoCheckRange(AActor *self, AActor *camera, double range, bool twodi) +{ + if (camera == NULL) + { + return false; + } + // Check distance first, since it's cheaper than checking sight. + double dx = self->x - camera->x; + double dy = self->y - camera->y; + double dz; + fixed_t eyez = (camera->z + camera->height - (camera->height>>2)); // same eye height as P_CheckSight + if (eyez > self->z + self->height){ + dz = self->z + self->height - eyez; + } + else if (eyez < self->z){ + dz = self->z - eyez; + } + else{ + dz = 0; + } + double distance = (dx * dx) + (dy * dy) + (twodi == 0? (dz * dz) : 0); + if (distance <= range){ + // Within range + return true; + } + return false; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckRange) +{ + ACTION_PARAM_START(3); + double range = EvalExpressionF(ParameterIndex+0, self); + ACTION_PARAM_STATE(jump, 1); + ACTION_PARAM_BOOL(twodi, 2); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots + for (int i = 0; i < MAXPLAYERS; ++i) + { + if (playeringame[i]) + { + // Always check from each player. + if (DoCheckRange(self, players[i].mo, range, twodi)) + { + return; + } + // If a player is viewing from a non-player, check that too. + if (players[i].camera != NULL && players[i].camera->player == NULL && + DoCheckRange(self, players[i].camera, range, twodi)) + { + return; + } + } + } + ACTION_JUMP(jump); +} + + +//=========================================================================== +// +// Inventory drop +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropInventory) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_CLASS(drop, 0); + + if (drop) + { + AInventory * inv = self->FindInventory(drop); + if (inv) + { + self->DropInventory(inv); + } + } +} + + +//=========================================================================== +// +// A_SetBlend +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetBlend) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_COLOR(color, 0); + ACTION_PARAM_FLOAT(alpha, 1); + ACTION_PARAM_INT(tics, 2); + ACTION_PARAM_COLOR(color2, 3); + + if (color == MAKEARGB(255,255,255,255)) color=0; + if (color2 == MAKEARGB(255,255,255,255)) color2=0; + if (!color2.a) + color2 = color; + + new DFlashFader(color.r/255.0f, color.g/255.0f, color.b/255.0f, alpha, + color2.r/255.0f, color2.g/255.0f, color2.b/255.0f, 0, + (float)tics/TICRATE, self); +} + + +//=========================================================================== +// +// A_JumpIf +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIf) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_BOOL(expression, 0); + ACTION_PARAM_STATE(jump, 1); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (expression) ACTION_JUMP(jump); + +} + +//=========================================================================== +// +// A_CountdownArg +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CountdownArg) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(cnt, 0); + ACTION_PARAM_STATE(state, 1); + + if (cnt<0 || cnt>=5) return; + if (!self->args[cnt]--) + { + if (self->flags&MF_MISSILE) + { + P_ExplodeMissile(self, NULL, NULL); + } + else if (self->flags&MF_SHOOTABLE) + { + P_DamageMobj (self, NULL, NULL, self->health, NAME_None, DMG_FORCED); + } + else + { + // can't use "Death" as default parameter with current DECORATE parser. + if (state == NULL) state = self->FindState(NAME_Death); + self->SetState(state); + } + } + +} + +//============================================================================ +// +// A_Burst +// +//============================================================================ + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Burst) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_CLASS(chunk, 0); + + int i, numChunks; + AActor * mo; + + if (chunk == NULL) return; + + self->velx = self->vely = self->velz = 0; + self->height = self->GetDefault()->height; + + // [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 (4, (self->radius>>FRACBITS)*(self->height>>FRACBITS)/32); + i = (pr_burst.Random2()) % (numChunks/4); + for (i = MAX (24, numChunks + i); i >= 0; i--) + { + mo = Spawn(chunk, + self->x + (((pr_burst()-128)*self->radius)>>7), + self->y + (((pr_burst()-128)*self->radius)>>7), + self->z + (pr_burst()*self->height/255 + self->GetBobOffset()), ALLOW_REPLACE); + + if (mo) + { + mo->velz = FixedDiv(mo->z - self->z, self->height)<<2; + mo->velx = pr_burst.Random2 () << (FRACBITS-7); + mo->vely = pr_burst.Random2 () << (FRACBITS-7); + mo->RenderStyle = self->RenderStyle; + mo->alpha = self->alpha; + mo->CopyFriendliness(self, true); + } + } + + // [RH] Do some stuff to make this more useful outside Hexen + if (self->flags4 & MF4_BOSSDEATH) + { + CALL_ACTION(A_BossDeath, self); + } + A_Unblock(self, true); + + self->Destroy (); +} + +//=========================================================================== +// +// A_CheckFloor +// [GRB] Jumps if actor is standing on floor +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFloor) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (self->z <= self->floorz) + { + ACTION_JUMP(jump); + } + +} + +//=========================================================================== +// +// A_CheckCeiling +// [GZ] Totally copied on A_CheckFloor, jumps if actor touches ceiling +// + +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckCeiling) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + ACTION_SET_RESULT(false); + if (self->z+self->height >= self->ceilingz) // Height needs to be counted + { + ACTION_JUMP(jump); + } + +} + +//=========================================================================== +// +// A_Stop +// resets all velocity of the actor to 0 +// +//=========================================================================== +DEFINE_ACTION_FUNCTION(AActor, A_Stop) +{ + self->velx = self->vely = self->velz = 0; + if (self->player && self->player->mo == self && !(self->player->cheats & CF_PREDICTING)) + { + self->player->mo->PlayIdle(); + self->player->velx = self->player->vely = 0; + } +} + +static void CheckStopped(AActor *self) +{ + if (self->player != NULL && + self->player->mo == self && + !(self->player->cheats & CF_PREDICTING) && + !(self->velx | self->vely | self->velz)) + { + self->player->mo->PlayIdle(); + self->player->velx = self->player->vely = 0; + } +} + +//=========================================================================== +// +// A_Respawn +// +//=========================================================================== + +extern void AF_A_RestoreSpecialPosition(DECLARE_PARAMINFO); + +enum RS_Flags +{ + RSF_FOG=1, + RSF_KEEPTARGET=2, + RSF_TELEFRAG=4, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Respawn) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(flags, 0); + bool oktorespawn = false; + fixed_t oldx = self->x; + fixed_t oldy = self->y; + fixed_t oldz = self->z; + self->flags |= MF_SOLID; + self->height = self->GetDefault()->height; + CALL_ACTION(A_RestoreSpecialPosition, self); + + if (flags & RSF_TELEFRAG) + { + // [KS] DIE DIE DIE DIE erm *ahem* =) + oktorespawn = P_TeleportMove(self, self->x, self->y, self->z, true); + if (oktorespawn) + { // Need to do this over again, since P_TeleportMove() will redo + // it with the proper point-on-side calculation. + self->UnlinkFromWorld(); + self->LinkToWorld(true); + sector_t *sec = self->Sector; + self->dropoffz = + self->floorz = sec->floorplane.ZatPoint(self->x, self->y); + self->ceilingz = sec->ceilingplane.ZatPoint(self->x, self->y); + P_FindFloorCeiling(self, FFCF_ONLYSPAWNPOS); + } + } + else + { + oktorespawn = P_CheckPosition(self, self->x, self->y, true); + } + + if (oktorespawn) + { + AActor *defs = self->GetDefault(); + self->health = defs->health; + + // [KS] Don't keep target, because it could be self if the monster committed suicide + // ...Actually it's better off an option, so you have better control over monster behavior. + if (!(flags & RSF_KEEPTARGET)) + { + self->target = NULL; + self->LastHeard = NULL; + self->lastenemy = NULL; + } + else + { + // Don't attack yourself (Re: "Marine targets itself after suicide") + if (self->target == self) self->target = NULL; + if (self->lastenemy == self) self->lastenemy = NULL; + } + + self->flags = (defs->flags & ~MF_FRIENDLY) | (self->flags & MF_FRIENDLY); + self->flags2 = defs->flags2; + self->flags3 = (defs->flags3 & ~(MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)) | (self->flags3 & (MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)); + self->flags4 = (defs->flags4 & ~MF4_NOHATEPLAYERS) | (self->flags4 & MF4_NOHATEPLAYERS); + self->flags5 = defs->flags5; + self->flags6 = defs->flags6; + self->flags7 = defs->flags7; + self->SetState (self->SpawnState); + self->renderflags &= ~RF_INVISIBLE; + + if (flags & RSF_FOG) + { + P_SpawnTeleportFog(self, oldx, oldy, oldz, true); + P_SpawnTeleportFog(self, self->x, self->y, self->z, false); + } + if (self->CountsAsKill()) + { + level.total_monsters++; + } + } + else + { + self->flags &= ~MF_SOLID; + } +} + + +//========================================================================== +// +// A_PlayerSkinCheck +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayerSkinCheck) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (self->player != NULL && + skins[self->player->userinfo.GetSkin()].othergame) + { + ACTION_JUMP(jump); + } +} + +//=========================================================================== +// +// A_SetGravity +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetGravity) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_FIXED(val, 0); + + self->gravity = clamp (val, 0, FRACUNIT*10); +} + + +// [KS] *** Start of my modifications *** + +//=========================================================================== +// +// A_ClearTarget +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION(AActor, A_ClearTarget) +{ + self->target = NULL; + self->LastHeard = NULL; + self->lastenemy = NULL; +} + +//========================================================================== +// +// A_CheckLOF (state jump, int flags = CRF_AIM_VERT|CRF_AIM_HOR, +// fixed range = 0, angle angle = 0, angle pitch = 0, +// fixed offsetheight = 32, fixed offsetwidth = 0, +// int ptr_target = AAPTR_DEFAULT (target) ) +// +//========================================================================== + +enum CLOF_flags +{ + CLOFF_NOAIM_VERT = 0x00000001, + CLOFF_NOAIM_HORZ = 0x00000002, + + CLOFF_JUMPENEMY = 0x00000004, + CLOFF_JUMPFRIEND = 0x00000008, + CLOFF_JUMPOBJECT = 0x00000010, + CLOFF_JUMPNONHOSTILE = 0x00000020, + + CLOFF_SKIPENEMY = 0x00000040, + CLOFF_SKIPFRIEND = 0x00000080, + CLOFF_SKIPOBJECT = 0x00000100, + CLOFF_SKIPNONHOSTILE = 0x00000200, + + CLOFF_MUSTBESHOOTABLE = 0x00000400, + + CLOFF_SKIPTARGET = 0x00000800, + CLOFF_ALLOWNULL = 0x00001000, + CLOFF_CHECKPARTIAL = 0x00002000, + + CLOFF_MUSTBEGHOST = 0x00004000, + CLOFF_IGNOREGHOST = 0x00008000, + + CLOFF_MUSTBESOLID = 0x00010000, + CLOFF_BEYONDTARGET = 0x00020000, + + CLOFF_FROMBASE = 0x00040000, + CLOFF_MUL_HEIGHT = 0x00080000, + CLOFF_MUL_WIDTH = 0x00100000, + + CLOFF_JUMP_ON_MISS = 0x00200000, + CLOFF_AIM_VERT_NOOFFSET = 0x00400000, + + CLOFF_SETTARGET = 0x00800000, + CLOFF_SETMASTER = 0x01000000, + CLOFF_SETTRACER = 0x02000000, +}; + +struct LOFData +{ + AActor *Self; + AActor *Target; + int Flags; + bool BadActor; +}; + +ETraceStatus CheckLOFTraceFunc(FTraceResults &trace, void *userdata) +{ + LOFData *data = (LOFData *)userdata; + int flags = data->Flags; + + if (trace.HitType != TRACE_HitActor) + { + return TRACE_Stop; + } + if (trace.Actor == data->Target) + { + if (flags & CLOFF_SKIPTARGET) + { + if (flags & CLOFF_BEYONDTARGET) + { + return TRACE_Skip; + } + return TRACE_Abort; + } + return TRACE_Stop; + } + if (flags & CLOFF_MUSTBESHOOTABLE) + { // all shootability checks go here + if (!(trace.Actor->flags & MF_SHOOTABLE)) + { + return TRACE_Skip; + } + if (trace.Actor->flags2 & MF2_NONSHOOTABLE) + { + return TRACE_Skip; + } + } + if ((flags & CLOFF_MUSTBESOLID) && !(trace.Actor->flags & MF_SOLID)) + { + return TRACE_Skip; + } + if (flags & CLOFF_MUSTBEGHOST) + { + if (!(trace.Actor->flags3 & MF3_GHOST)) + { + return TRACE_Skip; + } + } + else if (flags & CLOFF_IGNOREGHOST) + { + if (trace.Actor->flags3 & MF3_GHOST) + { + return TRACE_Skip; + } + } + if ( + ((flags & CLOFF_JUMPENEMY) && data->Self->IsHostile(trace.Actor)) || + ((flags & CLOFF_JUMPFRIEND) && data->Self->IsFriend(trace.Actor)) || + ((flags & CLOFF_JUMPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) || + ((flags & CLOFF_JUMPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor)) + ) + { + return TRACE_Stop; + } + if ( + ((flags & CLOFF_SKIPENEMY) && data->Self->IsHostile(trace.Actor)) || + ((flags & CLOFF_SKIPFRIEND) && data->Self->IsFriend(trace.Actor)) || + ((flags & CLOFF_SKIPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) || + ((flags & CLOFF_SKIPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor)) + ) + { + return TRACE_Skip; + } + data->BadActor = true; + return TRACE_Abort; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckLOF) +{ + // Check line of fire + + /* + Not accounted for / I don't know how it works: FLOORCLIP + */ + + AActor *target; + fixed_t + x1, y1, z1, + vx, vy, vz; + + ACTION_PARAM_START(9); + + ACTION_PARAM_STATE(jump, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_FIXED(range, 2); + ACTION_PARAM_FIXED(minrange, 3); + { + ACTION_PARAM_ANGLE(angle, 4); + ACTION_PARAM_ANGLE(pitch, 5); + ACTION_PARAM_FIXED(offsetheight, 6); + ACTION_PARAM_FIXED(offsetwidth, 7); + ACTION_PARAM_INT(ptr_target, 8); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + target = COPY_AAPTR(self, ptr_target == AAPTR_DEFAULT ? AAPTR_TARGET|AAPTR_PLAYER_GETTARGET|AAPTR_NULL : ptr_target); // no player-support by default + + if (flags & CLOFF_MUL_HEIGHT) + { + if (self->player != NULL) + { + // Synced with hitscan: self->player->mo->height is strangely conscientious about getting the right actor for player + offsetheight = FixedMul(offsetheight, FixedMul (self->player->mo->height, self->player->crouchfactor)); + } + else + { + offsetheight = FixedMul(offsetheight, self->height); + } + } + if (flags & CLOFF_MUL_WIDTH) + { + offsetwidth = FixedMul(self->radius, offsetwidth); + } + + x1 = self->x; + y1 = self->y; + z1 = self->z + offsetheight - self->floorclip; + + if (!(flags & CLOFF_FROMBASE)) + { // default to hitscan origin + + // Synced with hitscan: self->height is strangely NON-conscientious about getting the right actor for player + z1 += (self->height >> 1); + if (self->player != NULL) + { + z1 += FixedMul (self->player->mo->AttackZOffset, self->player->crouchfactor); + } + else + { + z1 += 8*FRACUNIT; + } + } + + if (target) + { + FVector2 xyvec(target->x - x1, target->y - y1); + fixed_t distance = P_AproxDistance((fixed_t)xyvec.Length(), target->z - z1); + + if (range && !(flags & CLOFF_CHECKPARTIAL)) + { + if (distance > range) return; + } + + { + angle_t ang; + + if (flags & CLOFF_NOAIM_HORZ) + { + ang = self->angle; + } + else ang = R_PointToAngle2 (x1, y1, target->x, target->y); + + angle += ang; + + ang >>= ANGLETOFINESHIFT; + x1 += FixedMul(offsetwidth, finesine[ang]); + y1 -= FixedMul(offsetwidth, finecosine[ang]); + } + + if (flags & CLOFF_NOAIM_VERT) + { + pitch += self->pitch; + } + else if (flags & CLOFF_AIM_VERT_NOOFFSET) + { + pitch += R_PointToAngle2 (0,0, (fixed_t)xyvec.Length(), target->z - z1 + offsetheight + target->height / 2); + } + else + { + pitch += R_PointToAngle2 (0,0, (fixed_t)xyvec.Length(), target->z - z1 + target->height / 2); + } + } + else if (flags & CLOFF_ALLOWNULL) + { + angle += self->angle; + pitch += self->pitch; + + angle_t ang = self->angle >> ANGLETOFINESHIFT; + x1 += FixedMul(offsetwidth, finesine[ang]); + y1 -= FixedMul(offsetwidth, finecosine[ang]); + } + else return; + + angle >>= ANGLETOFINESHIFT; + pitch = (0-pitch)>>ANGLETOFINESHIFT; + + vx = FixedMul (finecosine[pitch], finecosine[angle]); + vy = FixedMul (finecosine[pitch], finesine[angle]); + vz = -finesine[pitch]; + } + + /* Variable set: + + jump, flags, target + x1,y1,z1 (trace point of origin) + vx,vy,vz (trace unit vector) + range + */ + + sector_t *sec = P_PointInSector(x1, y1); + + if (range == 0) + { + range = (self->player != NULL) ? PLAYERMISSILERANGE : MISSILERANGE; + } + + FTraceResults trace; + LOFData lof_data; + + lof_data.Self = self; + lof_data.Target = target; + lof_data.Flags = flags; + lof_data.BadActor = false; + + Trace(x1, y1, z1, sec, vx, vy, vz, range, 0xFFFFFFFF, ML_BLOCKEVERYTHING, self, trace, 0, + CheckLOFTraceFunc, &lof_data); + + if (trace.HitType == TRACE_HitActor || + ((flags & CLOFF_JUMP_ON_MISS) && !lof_data.BadActor && trace.HitType != TRACE_HitNone)) + { + if (minrange > 0 && trace.Distance < minrange) + { + return; + } + if ((trace.HitType == TRACE_HitActor) && (trace.Actor != NULL) && !(lof_data.BadActor)) + { + if (flags & (CLOFF_SETTARGET)) self->target = trace.Actor; + if (flags & (CLOFF_SETMASTER)) self->master = trace.Actor; + if (flags & (CLOFF_SETTRACER)) self->tracer = trace.Actor; + } + + ACTION_JUMP(jump); + } +} + +//========================================================================== +// +// A_JumpIfTargetInLOS (state label, optional fixed fov, optional int flags, +// optional fixed dist_max, optional fixed dist_close) +// +// Jumps if the actor can see its target, or if the player has a linetarget. +// ProjectileTarget affects how projectiles are treated. If set, it will use +// the target of the projectile for seekers, and ignore the target for +// normal projectiles. If not set, it will use the missile's owner instead +// (the default). ProjectileTarget is now flag JLOSF_PROJECTILE. dist_max +// sets the maximum distance that actor can see, 0 means forever. dist_close +// uses special behavior if certain flags are set, 0 means no checks. +// +//========================================================================== + +enum JLOS_flags +{ + JLOSF_PROJECTILE = 1, + JLOSF_NOSIGHT = 1 << 1, + JLOSF_CLOSENOFOV = 1 << 2, + JLOSF_CLOSENOSIGHT = 1 << 3, + JLOSF_CLOSENOJUMP = 1 << 4, + JLOSF_DEADNOJUMP = 1 << 5, + JLOSF_CHECKMASTER = 1 << 6, + JLOSF_TARGETLOS = 1 << 7, + JLOSF_FLIPFOV = 1 << 8, + JLOSF_ALLYNOJUMP = 1 << 9, + JLOSF_COMBATANTONLY = 1 << 10, + JLOSF_NOAUTOAIM = 1 << 11, + JLOSF_CHECKTRACER = 1 << 12, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_STATE(jump, 0); + ACTION_PARAM_ANGLE(fov, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_FIXED(dist_max, 3); + ACTION_PARAM_FIXED(dist_close, 4); + + angle_t an; + AActor *target, *viewport; + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + bool doCheckSight; + + if (!self->player) + { + if (flags & JLOSF_CHECKMASTER) + { + target = self->master; + } + else if ((self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) || (flags & JLOSF_CHECKTRACER)) + { + if ((self->flags2 & MF2_SEEKERMISSILE) || (flags & JLOSF_CHECKTRACER)) + target = self->tracer; + else + target = NULL; + } + else + { + target = self->target; + } + + if (!target) return; // [KS] Let's not call P_CheckSight unnecessarily in this case. + + if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) return; + + doCheckSight = !(flags & JLOSF_NOSIGHT); + } + else + { + // Does the player aim at something that can be shot? + P_AimLineAttack(self, self->angle, MISSILERANGE, &target, (flags & JLOSF_NOAUTOAIM) ? ANGLE_1/2 : 0); + + if (!target) return; + + switch (flags & (JLOSF_TARGETLOS|JLOSF_FLIPFOV)) + { + case JLOSF_TARGETLOS|JLOSF_FLIPFOV: + // target makes sight check, player makes fov check; player has verified fov + fov = 0; + // fall-through + case JLOSF_TARGETLOS: + doCheckSight = !(flags & JLOSF_NOSIGHT); // The target is responsible for sight check and fov + break; + default: + // player has verified sight and fov + fov = 0; + // fall-through + case JLOSF_FLIPFOV: // Player has verified sight, but target must verify fov + doCheckSight = false; + break; + } + } + + // [FDARI] If target is not a combatant, don't jump + if ( (flags & JLOSF_COMBATANTONLY) && (!target->player) && !(target->flags3 & MF3_ISMONSTER)) return; + + // [FDARI] If actors share team, don't jump + if ((flags & JLOSF_ALLYNOJUMP) && self->IsFriend(target)) return; + + fixed_t distance = P_AproxDistance(target->x - self->x, target->y - self->y); + distance = P_AproxDistance(distance, target->z - self->z); + + if (dist_max && (distance > dist_max)) return; + + if (dist_close && (distance < dist_close)) + { + if (flags & JLOSF_CLOSENOJUMP) + return; + + if (flags & JLOSF_CLOSENOFOV) + fov = 0; + + if (flags & JLOSF_CLOSENOSIGHT) + doCheckSight = false; + } + + if (flags & JLOSF_TARGETLOS) { viewport = target; target = self; } + else { viewport = self; } + + if (doCheckSight && !P_CheckSight (viewport, target, SF_IGNOREVISIBILITY)) + return; + + if (flags & JLOSF_FLIPFOV) + { + if (viewport == self) { viewport = target; target = self; } + else { target = viewport; viewport = self; } + } + + if (fov && (fov < ANGLE_MAX)) + { + an = R_PointToAngle2 (viewport->x, + viewport->y, + target->x, + target->y) + - viewport->angle; + + if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2))) + { + return; // [KS] Outside of FOV - return + } + + } + + ACTION_JUMP(jump); +} + + +//========================================================================== +// +// A_JumpIfInTargetLOS (state label, optional fixed fov, optional int flags +// optional fixed dist_max, optional fixed dist_close) +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetLOS) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_STATE(jump, 0); + ACTION_PARAM_ANGLE(fov, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_FIXED(dist_max, 3); + ACTION_PARAM_FIXED(dist_close, 4); + + angle_t an; + AActor *target; + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + if (flags & JLOSF_CHECKMASTER) + { + target = self->master; + } + else if (self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) + { + if (self->flags2 & MF2_SEEKERMISSILE) + target = self->tracer; + else + target = NULL; + } + else + { + target = self->target; + } + + if (!target) return; // [KS] Let's not call P_CheckSight unnecessarily in this case. + + if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) return; + + fixed_t distance = P_AproxDistance(target->x - self->x, target->y - self->y); + distance = P_AproxDistance(distance, target->z - self->z); + + if (dist_max && (distance > dist_max)) return; + + bool doCheckSight = !(flags & JLOSF_NOSIGHT); + + if (dist_close && (distance < dist_close)) + { + if (flags & JLOSF_CLOSENOJUMP) + return; + + if (flags & JLOSF_CLOSENOFOV) + fov = 0; + + if (flags & JLOSF_CLOSENOSIGHT) + doCheckSight = false; + } + + if (fov && (fov < ANGLE_MAX)) + { + an = R_PointToAngle2 (target->x, + target->y, + self->x, + self->y) + - target->angle; + + if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2))) + { + return; // [KS] Outside of FOV - return + } + } + + if (doCheckSight && !P_CheckSight (target, self, SF_IGNOREVISIBILITY)) + return; + + ACTION_JUMP(jump); +} + +//=========================================================================== +// +// Modified code pointer from Skulltag +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckForReload) +{ + if ( self->player == NULL || self->player->ReadyWeapon == NULL ) + return; + + ACTION_PARAM_START(2); + ACTION_PARAM_INT(count, 0); + ACTION_PARAM_STATE(jump, 1); + ACTION_PARAM_BOOL(dontincrement, 2) + + if (count <= 0) return; + + AWeapon *weapon = self->player->ReadyWeapon; + + int ReloadCounter = weapon->ReloadCounter; + if(!dontincrement || ReloadCounter != 0) + ReloadCounter = (weapon->ReloadCounter+1) % count; + else // 0 % 1 = 1? So how do we check if the weapon was never fired? We should only do this when we're not incrementing the counter though. + ReloadCounter = 1; + + // If we have not made our last shot... + if (ReloadCounter != 0) + { + // Go back to the refire frames, instead of continuing on to the reload frames. + ACTION_JUMP(jump); + } + else + { + // We need to reload. However, don't reload if we're out of ammo. + weapon->CheckAmmo( false, false ); + } + + if(!dontincrement) + weapon->ReloadCounter = ReloadCounter; +} + +//=========================================================================== +// +// Resets the counter for the above function +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION(AActor, A_ResetReloadCounter) +{ + if ( self->player == NULL || self->player->ReadyWeapon == NULL ) + return; + + AWeapon *weapon = self->player->ReadyWeapon; + weapon->ReloadCounter = 0; +} + +//=========================================================================== +// +// A_ChangeFlag +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeFlag) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_STRING(flagname, 0); + ACTION_PARAM_BOOL(expression, 1); + + const char *dot = strchr (flagname, '.'); + FFlagDef *fd; + const PClass *cls = self->GetClass(); + + if (dot != NULL) + { + FString part1(flagname, dot-flagname); + fd = FindFlag (cls, part1, dot+1); + } + else + { + fd = FindFlag (cls, flagname, NULL); + } + + if (fd != NULL) + { + bool kill_before, kill_after; + INTBOOL item_before, item_after; + INTBOOL secret_before, secret_after; + + kill_before = self->CountsAsKill(); + item_before = self->flags & MF_COUNTITEM; + secret_before = self->flags5 & MF5_COUNTSECRET; + + if (fd->structoffset == -1) + { + HandleDeprecatedFlags(self, cls->ActorInfo, expression, fd->flagbit); + } + else + { + DWORD *flagp = (DWORD*) (((char*)self) + fd->structoffset); + + // If these 2 flags get changed we need to update the blockmap and sector links. + bool linkchange = flagp == &self->flags && (fd->flagbit == MF_NOBLOCKMAP || fd->flagbit == MF_NOSECTOR); + + if (linkchange) self->UnlinkFromWorld(); + ModActorFlag(self, fd, expression); + if (linkchange) self->LinkToWorld(); + } + kill_after = self->CountsAsKill(); + item_after = self->flags & MF_COUNTITEM; + secret_after = self->flags5 & MF5_COUNTSECRET; + // Was this monster previously worth a kill but no longer is? + // Or vice versa? + if (kill_before != kill_after) + { + if (kill_after) + { // It counts as a kill now. + level.total_monsters++; + } + else + { // It no longer counts as a kill. + level.total_monsters--; + } + } + // same for items + if (item_before != item_after) + { + if (item_after) + { // It counts as an item now. + level.total_items++; + } + else + { // It no longer counts as an item + level.total_items--; + } + } + // and secretd + if (secret_before != secret_after) + { + if (secret_after) + { // It counts as an secret now. + level.total_secrets++; + } + else + { // It no longer counts as an secret + level.total_secrets--; + } + } + } + else + { + Printf("Unknown flag '%s' in '%s'\n", flagname, cls->TypeName.GetChars()); + } +} + +//=========================================================================== +// +// A_CheckFlag +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFlag) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_STRING(flagname, 0); + ACTION_PARAM_STATE(jumpto, 1); + ACTION_PARAM_INT(checkpointer, 2); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + AActor *owner; + + COPY_AAPTR_NOT_NULL(self, owner, checkpointer); + + if (CheckActorFlag(owner, flagname)) + { + ACTION_JUMP(jumpto); + } +} + +//=========================================================================== +// +// A_RaiseMaster +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseMaster) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_BOOL(copy, 0); + + if (self->master != NULL) + { + if (copy) + P_Thing_Raise(self->master, self); + else + P_Thing_Raise(self->master, NULL); + } +} + +//=========================================================================== +// +// A_RaiseChildren +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseChildren) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_BOOL(copy, 0); + TThinkerIterator it; + AActor *mo; + + while ((mo = it.Next()) != NULL) + { + if (mo->master == self) + { + if (copy) + P_Thing_Raise(mo, self); + else + P_Thing_Raise(mo, NULL); + } + } +} + +//=========================================================================== +// +// A_RaiseSiblings +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseSiblings) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_BOOL(copy, 0); + TThinkerIterator it; + AActor *mo; + + if (self->master != NULL) + { + while ((mo = it.Next()) != NULL) + { + if (mo->master == self->master && mo != self) + { + if (copy) + P_Thing_Raise(mo, self); + else + P_Thing_Raise(mo, NULL); + } + } + } +} + +//=========================================================================== +// +// [TP] A_FaceConsolePlayer +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS (AActor, A_FaceConsolePlayer) { + ACTION_PARAM_START (1); + ACTION_PARAM_ANGLE (MaxTurnAngle, 0); + // NOTE: It does nothing for zdoom. +} + +//=========================================================================== +// +// A_MonsterRefire +// +// Keep firing unless target got out of sight +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_MonsterRefire) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(prob, 0); + ACTION_PARAM_STATE(jump, 1); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + A_FaceTarget (self); + + if (pr_monsterrefire() < prob) + return; + + if (!self->target + || P_HitFriend (self) + || self->target->health <= 0 + || !P_CheckSight (self, self->target, SF_SEEPASTBLOCKEVERYTHING|SF_SEEPASTSHOOTABLELINES) ) + { + ACTION_JUMP(jump); + } +} + +//=========================================================================== +// +// A_SetAngle +// +// Set actor's angle (in degrees). +// +//=========================================================================== +enum +{ + SPF_FORCECLAMP = 1, // players always clamp + SPF_INTERPOLATE = 2, +}; + + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetAngle) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_ANGLE(angle, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_INT(ptr, 2); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } + ref->SetAngle(angle, !!(flags & SPF_INTERPOLATE)); +} + +//=========================================================================== +// +// A_SetPitch +// +// Set actor's pitch (in degrees). +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetPitch) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_ANGLE(pitch, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_INT(ptr, 2); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } + + if (ref->player != NULL || (flags & SPF_FORCECLAMP)) + { // clamp the pitch we set + int min, max; + + if (ref->player != NULL) + { + min = ref->player->MinPitch; + max = ref->player->MaxPitch; + } + else + { + min = -ANGLE_90 + (1 << ANGLETOFINESHIFT); + max = ANGLE_90 - (1 << ANGLETOFINESHIFT); + } + pitch = clamp(pitch, min, max); + } + ref->SetPitch(pitch, !!(flags & SPF_INTERPOLATE)); +} + //=========================================================================== // // [Nash] A_SetRoll @@ -4013,1830 +4028,1830 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRoll) ACTION_PARAM_START(3); ACTION_PARAM_ANGLE(roll, 0); ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_INT(ptr, 2); - - AActor *ref = COPY_AAPTR(self, ptr); - - if (!ref) - { - ACTION_SET_RESULT(false); - return; + ACTION_PARAM_INT(ptr, 2); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; } ref->SetRoll(roll, !!(flags & SPF_INTERPOLATE)); } - -//=========================================================================== -// -// A_ScaleVelocity -// -// Scale actor's velocity. -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ScaleVelocity) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_FIXED(scale, 0); - ACTION_PARAM_INT(ptr, 1); - - AActor *ref = COPY_AAPTR(self, ptr); - - if (!ref) - { - ACTION_SET_RESULT(false); - return; - } - - INTBOOL was_moving = self->velx | self->vely | self->velz; - - ref->velx = FixedMul(ref->velx, scale); - ref->vely = FixedMul(ref->vely, scale); - ref->velz = FixedMul(ref->velz, scale); - - // If the actor was previously moving but now is not, and is a player, - // update its player variables. (See A_Stop.) - if (was_moving) - { - CheckStopped(ref); - } -} - -//=========================================================================== -// -// A_ChangeVelocity -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeVelocity) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_FIXED(x, 0); - ACTION_PARAM_FIXED(y, 1); - ACTION_PARAM_FIXED(z, 2); - ACTION_PARAM_INT(flags, 3); - ACTION_PARAM_INT(ptr, 4); - - AActor *ref = COPY_AAPTR(self, ptr); - - if (!ref) - { - ACTION_SET_RESULT(false); - return; - } - - INTBOOL was_moving = ref->velx | ref->vely | ref->velz; - - fixed_t vx = x, vy = y, vz = z; - fixed_t sina = finesine[ref->angle >> ANGLETOFINESHIFT]; - fixed_t cosa = finecosine[ref->angle >> ANGLETOFINESHIFT]; - - if (flags & 1) // relative axes - make x, y relative to actor's current angle - { - vx = DMulScale16(x, cosa, -y, sina); - vy = DMulScale16(x, sina, y, cosa); - } - if (flags & 2) // discard old velocity - replace old velocity with new velocity - { - ref->velx = vx; - ref->vely = vy; - ref->velz = vz; - } - else // add new velocity to old velocity - { - ref->velx += vx; - ref->vely += vy; - ref->velz += vz; - } - - if (was_moving) - { - CheckStopped(self); - } -} - -//=========================================================================== -// -// A_SetArg -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetArg) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(pos, 0); - ACTION_PARAM_INT(value, 1); - - // Set the value of the specified arg - if ((size_t)pos < countof(self->args)) - { - self->args[pos] = value; - } -} - -//=========================================================================== -// -// A_SetSpecial -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpecial) -{ - ACTION_PARAM_START(6); - ACTION_PARAM_INT(spec, 0); - ACTION_PARAM_INT(arg0, 1); - ACTION_PARAM_INT(arg1, 2); - ACTION_PARAM_INT(arg2, 3); - ACTION_PARAM_INT(arg3, 4); - ACTION_PARAM_INT(arg4, 5); - - self->special = spec; - self->args[0] = arg0; - self->args[1] = arg1; - self->args[2] = arg2; - self->args[3] = arg3; - self->args[4] = arg4; -} - -//=========================================================================== -// -// A_SetUserVar -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserVar) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_NAME(varname, 0); - ACTION_PARAM_INT(value, 1); - - PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); - PSymbolVariable *var; - - if (sym == NULL || sym->SymbolType != SYM_Variable || - !(var = static_cast(sym))->bUserVar || - var->ValueType.Type != VAL_Int) - { - Printf("%s is not a user variable in class %s\n", varname.GetChars(), - self->GetClass()->TypeName.GetChars()); - return; - } - // Set the value of the specified user variable. - *(int *)(reinterpret_cast(self) + var->offset) = value; -} - -//=========================================================================== -// -// A_SetUserArray -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserArray) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_NAME(varname, 0); - ACTION_PARAM_INT(pos, 1); - ACTION_PARAM_INT(value, 2); - - PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); - PSymbolVariable *var; - - if (sym == NULL || sym->SymbolType != SYM_Variable || - !(var = static_cast(sym))->bUserVar || - var->ValueType.Type != VAL_Array || var->ValueType.BaseType != VAL_Int) - { - Printf("%s is not a user array in class %s\n", varname.GetChars(), - self->GetClass()->TypeName.GetChars()); - return; - } - if (pos < 0 || pos >= var->ValueType.size) - { - Printf("%d is out of bounds in array %s in class %s\n", pos, varname.GetChars(), - self->GetClass()->TypeName.GetChars()); - return; - } - // Set the value of the specified user array at index pos. - ((int *)(reinterpret_cast(self) + var->offset))[pos] = value; -} - -//=========================================================================== -// -// A_Teleport(optional state teleportstate, optional class targettype, -// optional class fogtype, optional int flags, optional fixed mindist, -// optional fixed maxdist) -// -// Attempts to teleport to a targettype at least mindist away and at most -// maxdist away (0 means unlimited). If successful, spawn a fogtype at old -// location and place calling actor in teleportstate. -// -//=========================================================================== -enum T_Flags -{ - TF_TELEFRAG = 0x00000001, // Allow telefrag in order to teleport. - TF_RANDOMDECIDE = 0x00000002, // Randomly fail based on health. (A_Srcr2Decide) - TF_FORCED = 0x00000004, // Forget what's in the way. TF_Telefrag takes precedence though. - TF_KEEPVELOCITY = 0x00000008, // Preserve velocity. - TF_KEEPANGLE = 0x00000010, // Keep angle. - TF_USESPOTZ = 0x00000020, // Set the z to the spot's z, instead of the floor. - TF_NOSRCFOG = 0x00000040, // Don't leave any fog behind when teleporting. - TF_NODESTFOG = 0x00000080, // Don't spawn any fog at the arrival position. - TF_USEACTORFOG = 0x00000100, // Use the actor's TeleFogSourceType and TeleFogDestType fogs. - TF_NOJUMP = 0x00000200, // Don't jump after teleporting. -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport) -{ - ACTION_PARAM_START(6); - ACTION_PARAM_STATE(TeleportState, 0); - ACTION_PARAM_CLASS(TargetType, 1); - ACTION_PARAM_CLASS(FogType, 2); - ACTION_PARAM_INT(Flags, 3); - ACTION_PARAM_FIXED(MinDist, 4); - ACTION_PARAM_FIXED(MaxDist, 5); - - // Randomly choose not to teleport like A_Srcr2Decide. - if (Flags & TF_RANDOMDECIDE) - { - static const int chance[] = - { - 192, 120, 120, 120, 64, 64, 32, 16, 0 - }; - - unsigned int chanceindex = self->health / ((self->SpawnHealth()/8 == 0) ? 1 : self->SpawnHealth()/8); - - if (chanceindex >= countof(chance)) - { - chanceindex = countof(chance) - 1; - } - - if (pr_teleport() >= chance[chanceindex]) return; - } - - DSpotState *state = DSpotState::GetSpotState(); - if (state == NULL) return; - - if (!TargetType) TargetType = PClass::FindClass("BossSpot"); - - AActor * spot = state->GetSpotWithMinMaxDistance(TargetType, self->x, self->y, MinDist, MaxDist); - if (spot == NULL) return; - - fixed_t prevX = self->x; - fixed_t prevY = self->y; - fixed_t prevZ = self->z; - fixed_t aboveFloor = spot->z - spot->floorz; - fixed_t finalz = spot->floorz + aboveFloor; - - if (spot->z + self->height > spot->ceilingz) - finalz = spot->ceilingz - self->height; - else if (spot->z < spot->floorz) - finalz = spot->floorz; - - - //Take precedence and cooperate with telefragging first. - bool teleResult = P_TeleportMove(self, spot->x, spot->y, finalz, Flags & TF_TELEFRAG); - - if (Flags & TF_FORCED) - { - //If for some reason the original move didn't work, regardless of telefrag, force it to move. - self->SetOrigin(spot->x, spot->y, finalz); - teleResult = true; - } - - if (teleResult) - { - //If a fog type is defined in the parameter, or the user wants to use the actor's predefined fogs, - //and if there's no desire to be fogless, spawn a fog based upon settings. - if (FogType || (Flags & TF_USEACTORFOG)) - { - if (!(Flags & TF_NOSRCFOG)) - { - if (Flags & TF_USEACTORFOG) - P_SpawnTeleportFog(self, prevX, prevY, prevZ, true); - else - Spawn(FogType, prevX, prevY, prevZ, ALLOW_REPLACE); - } - if (!(Flags & TF_NODESTFOG)) - { - if (Flags & TF_USEACTORFOG) - P_SpawnTeleportFog(self, self->x, self->y, self->z, false); - else - Spawn(FogType, self->x, self->y, self->z, ALLOW_REPLACE); - } - } - - if (Flags & TF_USESPOTZ) - self->z = spot->z; - else - self->z = self->floorz; - - if (!(Flags & TF_KEEPANGLE)) - self->angle = spot->angle; - - if (!(Flags & TF_KEEPVELOCITY)) - self->velx = self->vely = self->velz = 0; - - if (!(Flags & TF_NOJUMP)) - { - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (TeleportState == NULL) - { - // Default to Teleport. - TeleportState = self->FindState("Teleport"); - // If still nothing, then return. - if (!TeleportState) return; - } - ACTION_JUMP(TeleportState); - return; - } - } - ACTION_SET_RESULT(teleResult); -} - -//=========================================================================== -// -// A_Turn -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Turn) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_ANGLE(angle, 0); - self->angle += angle; -} - -//=========================================================================== -// -// A_Quake -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Quake) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(intensity, 0); - ACTION_PARAM_INT(duration, 1); - ACTION_PARAM_INT(damrad, 2); - ACTION_PARAM_INT(tremrad, 3); - ACTION_PARAM_SOUND(sound, 4); - P_StartQuake(self, 0, intensity, duration, damrad, tremrad, sound); -} - -//=========================================================================== -// -// A_Weave -// -//=========================================================================== - -void A_Weave(AActor *self, int xyspeed, int zspeed, fixed_t xydist, fixed_t zdist) -{ - fixed_t newX, newY; - int weaveXY, weaveZ; - int angle; - fixed_t dist; - - weaveXY = self->WeaveIndexXY & 63; - weaveZ = self->WeaveIndexZ & 63; - angle = (self->angle + ANG90) >> ANGLETOFINESHIFT; - - if (xydist != 0 && xyspeed != 0) - { - dist = MulScale13(finesine[weaveXY << BOBTOFINESHIFT], xydist); - newX = self->x - FixedMul (finecosine[angle], dist); - newY = self->y - FixedMul (finesine[angle], dist); - weaveXY = (weaveXY + xyspeed) & 63; - dist = MulScale13(finesine[weaveXY << BOBTOFINESHIFT], xydist); - newX += FixedMul (finecosine[angle], dist); - newY += FixedMul (finesine[angle], dist); - if (!(self->flags5 & MF5_NOINTERACTION)) - { - P_TryMove (self, newX, newY, true); - } - else - { - self->UnlinkFromWorld (); - self->flags |= MF_NOBLOCKMAP; - self->x = newX; - self->y = newY; - self->LinkToWorld (); - } - self->WeaveIndexXY = weaveXY; - } - if (zdist != 0 && zspeed != 0) - { - self->z -= MulScale13(finesine[weaveZ << BOBTOFINESHIFT], zdist); - weaveZ = (weaveZ + zspeed) & 63; - self->z += MulScale13(finesine[weaveZ << BOBTOFINESHIFT], zdist); - self->WeaveIndexZ = weaveZ; - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Weave) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_INT(xspeed, 0); - ACTION_PARAM_INT(yspeed, 1); - ACTION_PARAM_FIXED(xdist, 2); - ACTION_PARAM_FIXED(ydist, 3); - A_Weave(self, xspeed, yspeed, xdist, ydist); -} - - - - -//=========================================================================== -// -// A_LineEffect -// -// This allows linedef effects to be activated inside deh frames. -// -//=========================================================================== - - -void P_TranslateLineDef (line_t *ld, maplinedef_t *mld); -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LineEffect) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(special, 0); - ACTION_PARAM_INT(tag, 1); - - line_t junk; maplinedef_t oldjunk; - bool res = false; - if (!(self->flags6 & MF6_LINEDONE)) // Unless already used up - { - if ((oldjunk.special = special)) // Linedef type - { - oldjunk.tag = tag; // Sector tag for linedef - P_TranslateLineDef(&junk, &oldjunk); // Turn into native type - res = !!P_ExecuteSpecial(junk.special, NULL, self, false, junk.args[0], - junk.args[1], junk.args[2], junk.args[3], junk.args[4]); - if (res && !(junk.flags & ML_REPEAT_SPECIAL)) // If only once, - self->flags6 |= MF6_LINEDONE; // no more for this thing - } - } - ACTION_SET_RESULT(res); -} - -//========================================================================== -// -// A Wolf3D-style attack codepointer -// -//========================================================================== -enum WolfAttackFlags -{ - WAF_NORANDOM = 1, - WAF_USEPUFF = 2, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_WolfAttack) -{ - ACTION_PARAM_START(9); - ACTION_PARAM_INT(flags, 0); - ACTION_PARAM_SOUND(sound, 1); - ACTION_PARAM_FIXED(snipe, 2); - ACTION_PARAM_INT(maxdamage, 3); - ACTION_PARAM_INT(blocksize, 4); - ACTION_PARAM_INT(pointblank, 5); - ACTION_PARAM_INT(longrange, 6); - ACTION_PARAM_FIXED(runspeed, 7); - ACTION_PARAM_CLASS(pufftype, 8); - - if (!self->target) - return; - - // Enemy can't see target - if (!P_CheckSight(self, self->target)) - return; - - A_FaceTarget (self); - - - // Target can dodge if it can see enemy - angle_t angle = R_PointToAngle2(self->target->x, self->target->y, self->x, self->y) - self->target->angle; - angle >>= 24; - bool dodge = (P_CheckSight(self->target, self) && (angle>226 || angle<30)); - - // Distance check is simplistic - fixed_t dx = abs (self->x - self->target->x); - fixed_t dy = abs (self->y - self->target->y); - fixed_t dz; - fixed_t dist = dx > dy ? dx : dy; - - // Some enemies are more precise - dist = FixedMul(dist, snipe); - - // Convert distance into integer number of blocks - dist >>= FRACBITS; - dist /= blocksize; - - // Now for the speed accuracy thingie - fixed_t speed = FixedMul(self->target->velx, self->target->velx) - + FixedMul(self->target->vely, self->target->vely) - + FixedMul(self->target->velz, self->target->velz); - int hitchance = speed < runspeed ? 256 : 160; - - // Distance accuracy (factoring dodge) - hitchance -= dist * (dodge ? 16 : 8); - - // While we're here, we may as well do something for this: - if (self->target->flags & MF_SHADOW) - { - hitchance >>= 2; - } - - // The attack itself - if (pr_cabullet() < hitchance) - { - // Compute position for spawning blood/puff - dx = self->target->x; - dy = self->target->y; - dz = self->target->z + (self->target->height>>1); - angle = R_PointToAngle2(dx, dy, self->x, self->y); - - dx += FixedMul(self->target->radius, finecosine[angle>>ANGLETOFINESHIFT]); - dy += FixedMul(self->target->radius, finesine[angle>>ANGLETOFINESHIFT]); - - int damage = flags & WAF_NORANDOM ? maxdamage : (1 + (pr_cabullet() % maxdamage)); - if (dist >= pointblank) - damage >>= 1; - if (dist >= longrange) - damage >>= 1; - FName mod = NAME_None; - bool spawnblood = !((self->target->flags & MF_NOBLOOD) - || (self->target->flags2 & (MF2_INVULNERABLE|MF2_DORMANT))); - if (flags & WAF_USEPUFF && pufftype) - { - AActor * dpuff = GetDefaultByType(pufftype->GetReplacement()); - mod = dpuff->DamageType; - - if (dpuff->flags2 & MF2_THRUGHOST && self->target->flags3 & MF3_GHOST) - damage = 0; - - if ((0 && dpuff->flags3 & MF3_PUFFONACTORS) || !spawnblood) - { - spawnblood = false; - P_SpawnPuff(self, pufftype, dx, dy, dz, angle, 0); - } - } - else if (self->target->flags3 & MF3_GHOST) - damage >>= 2; - if (damage) - { - int newdam = P_DamageMobj(self->target, self, self, damage, mod, DMG_THRUSTLESS); - if (spawnblood) - { - P_SpawnBlood(dx, dy, dz, angle, newdam > 0 ? newdam : damage, self->target); - P_TraceBleed(newdam > 0 ? newdam : damage, self->target, R_PointToAngle2(self->x, self->y, dx, dy), 0); - } - } - } - - // And finally, let's play the sound - S_Sound (self, CHAN_WEAPON, sound, 1, ATTN_NORM); -} - - -//========================================================================== -// -// A_Warp -// -//========================================================================== - -enum WARPF -{ - WARPF_ABSOLUTEOFFSET = 0x1, - WARPF_ABSOLUTEANGLE = 0x2, - WARPF_USECALLERANGLE = 0x4, - - WARPF_NOCHECKPOSITION = 0x8, - - WARPF_INTERPOLATE = 0x10, - WARPF_WARPINTERPOLATION = 0x20, - WARPF_COPYINTERPOLATION = 0x40, - - WARPF_STOP = 0x80, - WARPF_TOFLOOR = 0x100, - WARPF_TESTONLY = 0x200, - WARPF_ABSOLUTEPOSITION = 0x400, - WARPF_BOB = 0x800, - WARPF_MOVEPTR = 0x1000, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Warp) -{ - ACTION_PARAM_START(7); - - ACTION_PARAM_INT(destination_selector, 0); - ACTION_PARAM_FIXED(xofs, 1); - ACTION_PARAM_FIXED(yofs, 2); - ACTION_PARAM_FIXED(zofs, 3); - ACTION_PARAM_ANGLE(angle, 4); - ACTION_PARAM_INT(flags, 5); - ACTION_PARAM_STATE(success_state, 6); - - AActor *reference = COPY_AAPTR(self, destination_selector); - - //If there is no actor to warp to, fail. - if (!reference) - { - ACTION_SET_RESULT(false); - return; - } - - AActor *caller = self; - - if (flags & WARPF_MOVEPTR) - { - AActor *temp = reference; - reference = caller; - caller = temp; - } - - fixed_t oldx = caller->x; - fixed_t oldy = caller->y; - fixed_t oldz = caller->z; - - if (!(flags & WARPF_ABSOLUTEANGLE)) - { - angle += (flags & WARPF_USECALLERANGLE) ? caller->angle : reference->angle; - } - if (!(flags & WARPF_ABSOLUTEPOSITION)) - { - if (!(flags & WARPF_ABSOLUTEOFFSET)) - { - angle_t fineangle = angle >> ANGLETOFINESHIFT; - fixed_t xofs1 = xofs; - - // (borrowed from A_SpawnItemEx, assumed workable) - // in relative mode negative y values mean 'left' and positive ones mean 'right' - // This is the inverse orientation of the absolute mode! - - xofs = FixedMul(xofs1, finecosine[fineangle]) + FixedMul(yofs, finesine[fineangle]); - yofs = FixedMul(xofs1, finesine[fineangle]) - FixedMul(yofs, finecosine[fineangle]); - } - - if (flags & WARPF_TOFLOOR) - { - // set correct xy - - caller->SetOrigin( - reference->x + xofs, - reference->y + yofs, - reference->z); - - // now the caller's floorz should be appropriate for the assigned xy-position - // assigning position again with - - if (zofs) - { - // extra unlink, link and environment calculation - caller->SetOrigin( - caller->x, - caller->y, - caller->floorz + zofs); - } - else - { - // if there is no offset, there should be no ill effect from moving down to the - // already identified floor - - // A_Teleport does the same thing anyway - caller->z = caller->floorz; - } - } - else - { - caller->SetOrigin( - reference->x + xofs, - reference->y + yofs, - reference->z + zofs); - } - } - else //[MC] The idea behind "absolute" is meant to be "absolute". Override everything, just like A_SpawnItemEx's. - { - if (flags & WARPF_TOFLOOR) - { - caller->SetOrigin(xofs, yofs, caller->floorz + zofs); - } - else - { - caller->SetOrigin(xofs, yofs, zofs); - } - } - - if ((flags & WARPF_NOCHECKPOSITION) || P_TestMobjLocation(caller)) - { - if (flags & WARPF_TESTONLY) - { - caller->SetOrigin(oldx, oldy, oldz); - } - else - { - caller->angle = angle; - - if (flags & WARPF_STOP) - { - caller->velx = 0; - caller->vely = 0; - caller->velz = 0; - } - - if (flags & WARPF_WARPINTERPOLATION) - { - caller->PrevX += caller->x - oldx; - caller->PrevY += caller->y - oldy; - caller->PrevZ += caller->z - oldz; - } - else if (flags & WARPF_COPYINTERPOLATION) - { - caller->PrevX = caller->x + reference->PrevX - reference->x; - caller->PrevY = caller->y + reference->PrevY - reference->y; - caller->PrevZ = caller->z + reference->PrevZ - reference->z; - } - else if (!(flags & WARPF_INTERPOLATE)) - { - caller->PrevX = caller->x; - caller->PrevY = caller->y; - caller->PrevZ = caller->z; - } - - if ((flags & WARPF_BOB) && (reference->flags2 & MF2_FLOATBOB)) - { - caller->z += reference->GetBobOffset(); - } - } - - - if (success_state) - { - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - // in this case, you have the statejump to help you handle all the success anyway. - ACTION_JUMP(success_state); - return; - } - - ACTION_SET_RESULT(true); - } - else - { - caller->SetOrigin(oldx, oldy, oldz); - ACTION_SET_RESULT(false); - } - -} - -//========================================================================== -// -// ACS_Named* stuff - -// -// These are exactly like their un-named line special equivalents, except -// they take strings instead of integers to indicate which script to run. -// Some of these probably aren't very useful, but they are included for -// the sake of completeness. -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteWithResult) -{ - ACTION_PARAM_START(5); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(arg1, 1); - ACTION_PARAM_INT(arg2, 2); - ACTION_PARAM_INT(arg3, 3); - ACTION_PARAM_INT(arg4, 4); - - bool res = !!P_ExecuteSpecial(ACS_ExecuteWithResult, NULL, self, false, -scriptname, arg1, arg2, arg3, arg4); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecute) -{ - ACTION_PARAM_START(5); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - ACTION_PARAM_INT(arg1, 2); - ACTION_PARAM_INT(arg2, 3); - ACTION_PARAM_INT(arg3, 4); - - bool res = !!P_ExecuteSpecial(ACS_Execute, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteAlways) -{ - ACTION_PARAM_START(5); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - ACTION_PARAM_INT(arg1, 2); - ACTION_PARAM_INT(arg2, 3); - ACTION_PARAM_INT(arg3, 4); - - bool res = !!P_ExecuteSpecial(ACS_ExecuteAlways, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecute) -{ - ACTION_PARAM_START(5); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - ACTION_PARAM_INT(arg1, 2); - ACTION_PARAM_INT(arg2, 3); - ACTION_PARAM_INT(lock, 4); - - bool res = !!P_ExecuteSpecial(ACS_LockedExecute, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecuteDoor) -{ - ACTION_PARAM_START(5); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - ACTION_PARAM_INT(arg1, 2); - ACTION_PARAM_INT(arg2, 3); - ACTION_PARAM_INT(lock, 4); - - bool res = !!P_ExecuteSpecial(ACS_LockedExecuteDoor, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedSuspend) -{ - ACTION_PARAM_START(2); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - - bool res = !!P_ExecuteSpecial(ACS_Suspend, NULL, self, false, -scriptname, mapnum, 0, 0, 0); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedTerminate) -{ - ACTION_PARAM_START(2); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - - bool res = !!P_ExecuteSpecial(ACS_Terminate, NULL, self, false, -scriptname, mapnum, 0, 0, 0); - - ACTION_SET_RESULT(res); -} - - -//========================================================================== -// -// A_RadiusGive -// -// Uses code roughly similar to A_Explode (but without all the compatibility -// baggage and damage computation code to give an item to all eligible mobjs -// in range. -// -//========================================================================== -enum RadiusGiveFlags -{ - RGF_GIVESELF = 1 << 0, - RGF_PLAYERS = 1 << 1, - RGF_MONSTERS = 1 << 2, - RGF_OBJECTS = 1 << 3, - RGF_VOODOO = 1 << 4, - RGF_CORPSES = 1 << 5, - RGF_MASK = 2111, - RGF_NOTARGET = 1 << 6, - RGF_NOTRACER = 1 << 7, - RGF_NOMASTER = 1 << 8, - RGF_CUBE = 1 << 9, - RGF_NOSIGHT = 1 << 10, - RGF_MISSILES = 1 << 11, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusGive) -{ - ACTION_PARAM_START(7); - ACTION_PARAM_CLASS(item, 0); - ACTION_PARAM_FIXED(distance, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_INT(amount, 3); - - // We need a valid item, valid targets, and a valid range - if (item == NULL || (flags & RGF_MASK) == 0 || distance <= 0) - { - return; - } - if (amount == 0) - { - amount = 1; - } - FBlockThingsIterator it(FBoundingBox(self->x, self->y, distance)); - double distsquared = double(distance) * double(distance); - - AActor *thing; - while ((thing = it.Next())) - { - // Don't give to inventory items - if (thing->flags & MF_SPECIAL) - { - continue; - } - // Avoid giving to self unless requested - if (thing == self && !(flags & RGF_GIVESELF)) - { - continue; - } - // Avoiding special pointers if requested - if (((thing == self->target) && (flags & RGF_NOTARGET)) || - ((thing == self->tracer) && (flags & RGF_NOTRACER)) || - ((thing == self->master) && (flags & RGF_NOMASTER))) - { - continue; - } - // Don't give to dead thing unless requested - if (thing->flags & MF_CORPSE) - { - if (!(flags & RGF_CORPSES)) - { - continue; - } - } - else if (thing->health <= 0 || thing->flags6 & MF6_KILLED) - { - continue; - } - // Players, monsters, and other shootable objects - if (thing->player) - { - if ((thing->player->mo == thing) && !(flags & RGF_PLAYERS)) - { - continue; - } - if ((thing->player->mo != thing) && !(flags & RGF_VOODOO)) - { - continue; - } - } - else if (thing->flags3 & MF3_ISMONSTER) - { - if (!(flags & RGF_MONSTERS)) - { - continue; - } - } - else if (thing->flags & MF_SHOOTABLE || thing->flags6 & MF6_VULNERABLE) - { - if (!(flags & RGF_OBJECTS)) - { - continue; - } - } - else if (thing->flags & MF_MISSILE) - { - if (!(flags & RGF_MISSILES)) - { - continue; - } - } - else - { - continue; - } - - if (flags & RGF_CUBE) - { // check if inside a cube - if (abs(thing->x - self->x) > distance || - abs(thing->y - self->y) > distance || - abs((thing->z + thing->height/2) - (self->z + self->height/2)) > distance) - { - continue; - } - } - else - { // check if inside a sphere - TVector3 tpos(thing->x, thing->y, thing->z + thing->height/2); - TVector3 spos(self->x, self->y, self->z + self->height/2); - if ((tpos - spos).LengthSquared() > distsquared) - { - continue; - } - } - fixed_t dz = abs ((thing->z + thing->height/2) - (self->z + self->height/2)); - - if ((flags & RGF_NOSIGHT) || P_CheckSight (thing, self, SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY)) - { // OK to give; target is in direct path, or the monster doesn't care about it being in line of sight. - AInventory *gift = static_cast(Spawn (item, 0, 0, 0, NO_REPLACE)); - if (gift->IsKindOf(RUNTIME_CLASS(AHealth))) - { - gift->Amount *= amount; - } - else - { - gift->Amount = amount; - } - gift->flags |= MF_DROPPED; - gift->ClearCounters(); - if (!gift->CallTryPickup (thing)) - { - gift->Destroy (); - } - } - } -} - - -//========================================================================== -// -// A_SetTics -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTics) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(tics_to_set, 0); - - if (stateowner != self && self->player != NULL && stateowner->IsKindOf(RUNTIME_CLASS(AWeapon))) - { // Is this a weapon? Need to check psp states for a match, then. Blah. - for (int i = 0; i < NUMPSPRITES; ++i) - { - if (self->player->psprites[i].state == CallingState) - { - self->player->psprites[i].tics = tics_to_set; - return; - } - } - } - // Just set tics for self. - self->tics = tics_to_set; -} - -//========================================================================== -// -// A_SetDamageType -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetDamageType) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_NAME(damagetype, 0); - - self->DamageType = damagetype; -} - -//========================================================================== -// -// A_DropItem -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropItem) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_CLASS(spawntype, 0); - ACTION_PARAM_INT(amount, 1); - ACTION_PARAM_INT(chance, 2); - - P_DropItem(self, spawntype, amount, chance); -} - -//========================================================================== -// -// A_SetSpeed -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpeed) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_FIXED(speed, 0); - ACTION_PARAM_INT(ptr, 1); - - AActor *ref = COPY_AAPTR(self, ptr); - - if (!ref) - { - ACTION_SET_RESULT(false); - return; - } - - ref->Speed = speed; -} - -static bool DoCheckSpecies(AActor *mo, FName species, bool exclude) -{ - return (!(species) || mo->Species == NAME_None || (species && ((exclude) ? (mo->Species != species) : (mo->Species == species)))); -} - -static bool DoCheckFilter(AActor *mo, const PClass *filter, bool exclude) -{ - const PClass *c1 = mo->GetClass(); - return (!(filter) || (filter == NULL) || (filter && ((exclude) ? (c1 != filter) : (c1 == filter)))); -} - -//=========================================================================== -// -// Common A_Damage handler -// -// A_Damage* (int amount, str damagetype, int flags, str filter, str species) -// Damages the specified actor by the specified amount. Negative values heal. -// Flags: See below. -// Filter: Specified actor is the only type allowed to be affected. -// Species: Specified species is the only type allowed to be affected. -// -// Examples: -// A_Damage(20,"Normal",DMSS_FOILINVUL,0,"DemonicSpecies") <--Only actors -// with a species "DemonicSpecies" will be affected. Use 0 to not filter by actor. -// -//=========================================================================== - -enum DMSS -{ - DMSS_FOILINVUL = 1, //Foil invulnerability - DMSS_AFFECTARMOR = 2, //Make it affect armor - DMSS_KILL = 4, //Damages them for their current health - DMSS_NOFACTOR = 8, //Ignore DamageFactors - DMSS_FOILBUDDHA = 16, //Can kill actors with Buddha flag, except the player. - DMSS_NOPROTECT = 32, //Ignores PowerProtection entirely - DMSS_EXFILTER = 64, //Changes filter into a blacklisted class instead of whitelisted. - DMSS_EXSPECIES = 128, // ^ but with species instead. - DMSS_EITHER = 256, //Allow either type or species to be affected. -}; - -static void DoDamage(AActor *dmgtarget, AActor *self, int amount, FName DamageType, int flags, const PClass *filter, FName species) -{ - bool filterpass = DoCheckFilter(dmgtarget, filter, (flags & DMSS_EXFILTER) ? true : false), - speciespass = DoCheckSpecies(dmgtarget, species, (flags & DMSS_EXSPECIES) ? true : false); - if ((flags & DMSS_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) - { - int dmgFlags = 0; - if (flags & DMSS_FOILINVUL) - dmgFlags += DMG_FOILINVUL; - if (flags & DMSS_FOILBUDDHA) - dmgFlags += DMG_FOILBUDDHA; - if ((flags & DMSS_KILL) || (flags & DMSS_NOFACTOR)) //Kill implies NoFactor - dmgFlags += DMG_NO_FACTOR; - if (!(flags & DMSS_AFFECTARMOR) || (flags & DMSS_KILL)) //Kill overrides AffectArmor - dmgFlags += DMG_NO_ARMOR; - if (flags & DMSS_KILL) //Kill adds the value of the damage done to it. Allows for more controlled extreme death types. - amount += dmgtarget->health; - if (flags & DMSS_NOPROTECT) //Ignore PowerProtection. - dmgFlags += DMG_NO_PROTECT; - - if (amount > 0) - P_DamageMobj(dmgtarget, self, self, amount, DamageType, dmgFlags); //Should wind up passing them through just fine. - - else if (amount < 0) - { - amount = -amount; - P_GiveBody(dmgtarget, amount); - } - } -} - -//=========================================================================== -// -// -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSelf) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(amount, 0); - ACTION_PARAM_NAME(DamageType, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_CLASS(filter, 3); - ACTION_PARAM_NAME(species, 4); - - DoDamage(self, self, amount, DamageType, flags, filter, species); -} - -//=========================================================================== -// -// -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTarget) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(amount, 0); - ACTION_PARAM_NAME(DamageType, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_CLASS(filter, 3); - ACTION_PARAM_NAME(species, 4); - - if (self->target != NULL) - { - DoDamage(self->target, self, amount, DamageType, flags, filter, species); - } -} - -//=========================================================================== -// -// -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTracer) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(amount, 0); - ACTION_PARAM_NAME(DamageType, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_CLASS(filter, 3); - ACTION_PARAM_NAME(species, 4); - - if (self->tracer != NULL) - { - DoDamage(self->tracer, self, amount, DamageType, flags, filter, species); - } -} - -//=========================================================================== -// -// -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageMaster) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(amount, 0); - ACTION_PARAM_NAME(DamageType, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_CLASS(filter, 3); - ACTION_PARAM_NAME(species, 4); - - if (self->master != NULL) - { - DoDamage(self->master, self, amount, DamageType, flags, filter, species); - } -} - -//=========================================================================== -// -// -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageChildren) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(amount, 0); - ACTION_PARAM_NAME(DamageType, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_CLASS(filter, 3); - ACTION_PARAM_NAME(species, 4); - - TThinkerIterator it; - AActor * mo; - - while ( (mo = it.Next()) ) - { - if (mo->master == self) - { - DoDamage(mo, self, amount, DamageType, flags, filter, species); - } - } -} - -//=========================================================================== -// -// -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSiblings) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(amount, 0); - ACTION_PARAM_NAME(DamageType, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_CLASS(filter, 3); - ACTION_PARAM_NAME(species, 4); - - TThinkerIterator it; - AActor * mo; - - if (self->master != NULL) - { - while ((mo = it.Next())) - { - if (mo->master == self->master && mo != self) - { - DoDamage(mo, self, amount, DamageType, flags, filter, species); - } - } - } -} - - -//=========================================================================== -// -// A_Kill*(damagetype, int flags) -// -//=========================================================================== -enum KILS -{ - KILS_FOILINVUL = 1 << 0, - KILS_KILLMISSILES = 1 << 1, - KILS_NOMONSTERS = 1 << 2, - KILS_FOILBUDDHA = 1 << 3, - KILS_EXFILTER = 1 << 4, - KILS_EXSPECIES = 1 << 5, - KILS_EITHER = 1 << 6, -}; - -static void DoKill(AActor *killtarget, AActor *self, FName damagetype, int flags, const PClass *filter, FName species) -{ - bool filterpass = DoCheckFilter(killtarget, filter, (flags & KILS_EXFILTER) ? true : false), - speciespass = DoCheckSpecies(killtarget, species, (flags & KILS_EXSPECIES) ? true : false); - if ((flags & KILS_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) //Check this first. I think it'll save the engine a lot more time this way. - { - int dmgFlags = DMG_NO_ARMOR + DMG_NO_FACTOR; - - if (KILS_FOILINVUL) - dmgFlags += DMG_FOILINVUL; - if (KILS_FOILBUDDHA) - dmgFlags += DMG_FOILBUDDHA; - - - if ((killtarget->flags & MF_MISSILE) && (flags & KILS_KILLMISSILES)) - { - //[MC] Now that missiles can set masters, lets put in a check to properly destroy projectiles. BUT FIRST! New feature~! - //Check to see if it's invulnerable. Disregarded if foilinvul is on, but never works on a missile with NODAMAGE - //since that's the whole point of it. - if ((!(killtarget->flags2 & MF2_INVULNERABLE) || (flags & KILS_FOILINVUL)) && - (!(killtarget->flags2 & MF7_BUDDHA) || (flags & KILS_FOILBUDDHA)) && !(killtarget->flags5 & MF5_NODAMAGE)) - { - P_ExplodeMissile(killtarget, NULL, NULL); - } - } - if (!(flags & KILS_NOMONSTERS)) - { - P_DamageMobj(killtarget, self, self, killtarget->health, damagetype, dmgFlags); - } - } -} - - -//=========================================================================== -// -// A_KillTarget(damagetype, int flags) -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillTarget) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_NAME(damagetype, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_CLASS(filter, 2); - ACTION_PARAM_NAME(species, 3); - - if (self->target != NULL) - { - DoKill(self->target, self, damagetype, flags, filter, species); - } -} - -//=========================================================================== -// -// A_KillTracer(damagetype, int flags) -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillTracer) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_NAME(damagetype, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_CLASS(filter, 2); - ACTION_PARAM_NAME(species, 3); - - if (self->tracer != NULL) - { - DoKill(self->tracer, self, damagetype, flags, filter, species); - } -} - -//=========================================================================== -// -// A_KillMaster(damagetype, int flags) -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillMaster) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_NAME(damagetype, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_CLASS(filter, 2); - ACTION_PARAM_NAME(species, 3); - - if (self->master != NULL) - { - DoKill(self->master, self, damagetype, flags, filter, species); - } -} - -//=========================================================================== -// -// A_KillChildren(damagetype, int flags) -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillChildren) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_NAME(damagetype, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_CLASS(filter, 2); - ACTION_PARAM_NAME(species, 3); - - TThinkerIterator it; - AActor *mo; - - while ( (mo = it.Next()) ) - { - if (mo->master == self) - { - DoKill(mo, self, damagetype, flags, filter, species); - } - } -} - -//=========================================================================== -// -// A_KillSiblings(damagetype, int flags) -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillSiblings) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_NAME(damagetype, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_CLASS(filter, 2); - ACTION_PARAM_NAME(species, 3); - - TThinkerIterator it; - AActor *mo; - - if (self->master != NULL) - { - while ( (mo = it.Next()) ) - { - if (mo->master == self->master && mo != self) - { - DoKill(mo, self, damagetype, flags, filter, species); - } - } - } -} - -//=========================================================================== -// -// DoRemove -// -//=========================================================================== - -enum RMVF_flags -{ - RMVF_MISSILES = 1 << 0, - RMVF_NOMONSTERS = 1 << 1, - RMVF_MISC = 1 << 2, - RMVF_EVERYTHING = 1 << 3, - RMVF_EXFILTER = 1 << 4, - RMVF_EXSPECIES = 1 << 5, - RMVF_EITHER = 1 << 6, -}; - -static void DoRemove(AActor *removetarget, int flags, const PClass *filter, FName species) -{ - bool filterpass = DoCheckFilter(removetarget, filter, (flags & RMVF_EXFILTER) ? true : false), - speciespass = DoCheckSpecies(removetarget, species, (flags & RMVF_EXSPECIES) ? true : false); - if ((flags & RMVF_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) - { - if ((flags & RMVF_EVERYTHING)) - { - P_RemoveThing(removetarget); - } - if ((flags & RMVF_MISC) && !((removetarget->flags3 & MF3_ISMONSTER) && (removetarget->flags & MF_MISSILE))) - { - P_RemoveThing(removetarget); - } - if ((removetarget->flags3 & MF3_ISMONSTER) && !(flags & RMVF_NOMONSTERS)) - { - P_RemoveThing(removetarget); - } - if ((removetarget->flags & MF_MISSILE) && (flags & RMVF_MISSILES)) - { - P_RemoveThing(removetarget); - } - } -} - -//=========================================================================== -// -// A_RemoveTarget -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveTarget) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(flags, 0); - ACTION_PARAM_CLASS(filter, 1); - ACTION_PARAM_NAME(species, 2); - - if (self->target != NULL) - { - DoRemove(self->target, flags, filter, species); - } -} - -//=========================================================================== -// -// A_RemoveTracer -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveTracer) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(flags, 0); - ACTION_PARAM_CLASS(filter, 1); - ACTION_PARAM_NAME(species, 2); - - if (self->tracer != NULL) - { - DoRemove(self->tracer, flags, filter, species); - } -} - -//=========================================================================== -// -// A_RemoveMaster -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveMaster) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(flags, 0); - ACTION_PARAM_CLASS(filter, 1); - ACTION_PARAM_NAME(species, 2); - - if (self->master != NULL) - { - DoRemove(self->master, flags, filter, species); - } -} - -//=========================================================================== -// -// A_RemoveChildren -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveChildren) -{ - TThinkerIterator it; - AActor *mo; - ACTION_PARAM_START(4); - ACTION_PARAM_BOOL(removeall, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_CLASS(filter, 2); - ACTION_PARAM_NAME(species, 3); - - - while ((mo = it.Next()) != NULL) - { - if (mo->master == self && (mo->health <= 0 || removeall)) - { - DoRemove(mo, flags, filter, species); - } - } -} - -//=========================================================================== -// -// A_RemoveSiblings -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveSiblings) -{ - TThinkerIterator it; - AActor *mo; - ACTION_PARAM_START(4); - ACTION_PARAM_BOOL(removeall, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_CLASS(filter, 2); - ACTION_PARAM_NAME(species, 3); - - if (self->master != NULL) - { - while ((mo = it.Next()) != NULL) - { - if (mo->master == self->master && mo != self && (mo->health <= 0 || removeall)) - { - DoRemove(mo, flags, filter, species); - } - } - } -} - -//=========================================================================== -// -// A_Remove -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Remove) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_INT(removee, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_CLASS(filter, 2); - ACTION_PARAM_NAME(species, 3); - - AActor *reference = COPY_AAPTR(self, removee); - if (reference != NULL) - { - DoRemove(reference, flags, filter, species); - } -} - -//=========================================================================== -// -// A_SetTeleFog -// -// Sets the teleport fog(s) for the calling actor. -// Takes a name of the classes for te source and destination. -// Can set both at the same time. Use "" to retain the previous fog without -// changing it. -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTeleFog) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_NAME(oldpos, 0); - ACTION_PARAM_NAME(newpos, 1); - const PClass *check = PClass::FindClass(oldpos); - if (check == NULL || !stricmp(oldpos, "none") || !stricmp(oldpos, "null")) - self->TeleFogSourceType = NULL; - else if (!stricmp(oldpos, "")) - { //Don't change it if it's just "" - } - else - self->TeleFogSourceType = check; - - check = PClass::FindClass(newpos); - if (check == NULL || !stricmp(newpos, "none") || !stricmp(newpos, "null")) - self->TeleFogDestType = NULL; - else if (!stricmp(newpos, "")) - { //Don't change it if it's just "" - } - else - self->TeleFogDestType = check; -} - -//=========================================================================== -// -// A_SwapTeleFog -// -// Switches the source and dest telefogs around. -//=========================================================================== - -DEFINE_ACTION_FUNCTION(AActor, A_SwapTeleFog) -{ - if ((self->TeleFogSourceType != self->TeleFogDestType)) //Does nothing if they're the same. - { - const PClass *temp = self->TeleFogSourceType; - self->TeleFogSourceType = self->TeleFogDestType; - self->TeleFogDestType = temp; - } -} - -//=========================================================================== -// -// A_SetFloatBobPhase -// -// Changes the FloatBobPhase of the actor. -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetFloatBobPhase) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(bob, 0); - - //Respect float bob phase limits. - if (self && (bob >= 0 && bob <= 63)) - self->FloatBobPhase = bob; -} - -//=========================================================================== -// A_SetHealth -// -// Changes the health of the actor. -// Takes a pointer as well. -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetHealth) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(health, 0); - ACTION_PARAM_INT(ptr, 1); - - AActor *mobj = COPY_AAPTR(self, ptr); - - if (!mobj) - { - return; - } - - player_t *player = mobj->player; - if (player) - { - if (health <= 0) - player->mo->health = mobj->health = player->health = 1; //Copied from the buddha cheat. - else - player->mo->health = mobj->health = player->health = health; - } - else if (mobj) - { - if (health <= 0) - mobj->health = 1; - else - mobj->health = health; - } -} - -//=========================================================================== -// A_ResetHealth -// -// Resets the health of the actor to default, except if their dead. -// Takes a pointer. -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ResetHealth) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(ptr, 0); - - AActor *mobj = COPY_AAPTR(self, ptr); - - if (!mobj) - { - return; - } - - player_t *player = mobj->player; - if (player && (player->mo->health > 0)) - { - player->health = player->mo->health = player->mo->GetDefault()->health; //Copied from the resurrect cheat. - } - else if (mobj && (mobj->health > 0)) - { - mobj->health = mobj->SpawnHealth(); - } -} - -//=========================================================================== -// -// A_SetRipperLevel(int level) -// -// Sets the ripper level/requirement of the calling actor. -// Also sets the minimum and maximum levels to rip through. -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipperLevel) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(level, 0); - self->RipperLevel = level; -} - -//=========================================================================== -// -// A_SetRipMin(int min) -// -// Sets the ripper level/requirement of the calling actor. -// Also sets the minimum and maximum levels to rip through. -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipMin) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(min, 1); - self->RipLevelMin = min; -} - -//=========================================================================== -// -// A_SetRipMin(int min) -// -// Sets the ripper level/requirement of the calling actor. -// Also sets the minimum and maximum levels to rip through. -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipMax) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(max, 1); - self->RipLevelMax = max; + +//=========================================================================== +// +// A_ScaleVelocity +// +// Scale actor's velocity. +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ScaleVelocity) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_FIXED(scale, 0); + ACTION_PARAM_INT(ptr, 1); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } + + INTBOOL was_moving = self->velx | self->vely | self->velz; + + ref->velx = FixedMul(ref->velx, scale); + ref->vely = FixedMul(ref->vely, scale); + ref->velz = FixedMul(ref->velz, scale); + + // If the actor was previously moving but now is not, and is a player, + // update its player variables. (See A_Stop.) + if (was_moving) + { + CheckStopped(ref); + } +} + +//=========================================================================== +// +// A_ChangeVelocity +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeVelocity) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_FIXED(x, 0); + ACTION_PARAM_FIXED(y, 1); + ACTION_PARAM_FIXED(z, 2); + ACTION_PARAM_INT(flags, 3); + ACTION_PARAM_INT(ptr, 4); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } + + INTBOOL was_moving = ref->velx | ref->vely | ref->velz; + + fixed_t vx = x, vy = y, vz = z; + fixed_t sina = finesine[ref->angle >> ANGLETOFINESHIFT]; + fixed_t cosa = finecosine[ref->angle >> ANGLETOFINESHIFT]; + + if (flags & 1) // relative axes - make x, y relative to actor's current angle + { + vx = DMulScale16(x, cosa, -y, sina); + vy = DMulScale16(x, sina, y, cosa); + } + if (flags & 2) // discard old velocity - replace old velocity with new velocity + { + ref->velx = vx; + ref->vely = vy; + ref->velz = vz; + } + else // add new velocity to old velocity + { + ref->velx += vx; + ref->vely += vy; + ref->velz += vz; + } + + if (was_moving) + { + CheckStopped(self); + } +} + +//=========================================================================== +// +// A_SetArg +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetArg) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(pos, 0); + ACTION_PARAM_INT(value, 1); + + // Set the value of the specified arg + if ((size_t)pos < countof(self->args)) + { + self->args[pos] = value; + } +} + +//=========================================================================== +// +// A_SetSpecial +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpecial) +{ + ACTION_PARAM_START(6); + ACTION_PARAM_INT(spec, 0); + ACTION_PARAM_INT(arg0, 1); + ACTION_PARAM_INT(arg1, 2); + ACTION_PARAM_INT(arg2, 3); + ACTION_PARAM_INT(arg3, 4); + ACTION_PARAM_INT(arg4, 5); + + self->special = spec; + self->args[0] = arg0; + self->args[1] = arg1; + self->args[2] = arg2; + self->args[3] = arg3; + self->args[4] = arg4; +} + +//=========================================================================== +// +// A_SetUserVar +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserVar) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_NAME(varname, 0); + ACTION_PARAM_INT(value, 1); + + PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); + PSymbolVariable *var; + + if (sym == NULL || sym->SymbolType != SYM_Variable || + !(var = static_cast(sym))->bUserVar || + var->ValueType.Type != VAL_Int) + { + Printf("%s is not a user variable in class %s\n", varname.GetChars(), + self->GetClass()->TypeName.GetChars()); + return; + } + // Set the value of the specified user variable. + *(int *)(reinterpret_cast(self) + var->offset) = value; +} + +//=========================================================================== +// +// A_SetUserArray +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserArray) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_NAME(varname, 0); + ACTION_PARAM_INT(pos, 1); + ACTION_PARAM_INT(value, 2); + + PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); + PSymbolVariable *var; + + if (sym == NULL || sym->SymbolType != SYM_Variable || + !(var = static_cast(sym))->bUserVar || + var->ValueType.Type != VAL_Array || var->ValueType.BaseType != VAL_Int) + { + Printf("%s is not a user array in class %s\n", varname.GetChars(), + self->GetClass()->TypeName.GetChars()); + return; + } + if (pos < 0 || pos >= var->ValueType.size) + { + Printf("%d is out of bounds in array %s in class %s\n", pos, varname.GetChars(), + self->GetClass()->TypeName.GetChars()); + return; + } + // Set the value of the specified user array at index pos. + ((int *)(reinterpret_cast(self) + var->offset))[pos] = value; +} + +//=========================================================================== +// +// A_Teleport(optional state teleportstate, optional class targettype, +// optional class fogtype, optional int flags, optional fixed mindist, +// optional fixed maxdist) +// +// Attempts to teleport to a targettype at least mindist away and at most +// maxdist away (0 means unlimited). If successful, spawn a fogtype at old +// location and place calling actor in teleportstate. +// +//=========================================================================== +enum T_Flags +{ + TF_TELEFRAG = 0x00000001, // Allow telefrag in order to teleport. + TF_RANDOMDECIDE = 0x00000002, // Randomly fail based on health. (A_Srcr2Decide) + TF_FORCED = 0x00000004, // Forget what's in the way. TF_Telefrag takes precedence though. + TF_KEEPVELOCITY = 0x00000008, // Preserve velocity. + TF_KEEPANGLE = 0x00000010, // Keep angle. + TF_USESPOTZ = 0x00000020, // Set the z to the spot's z, instead of the floor. + TF_NOSRCFOG = 0x00000040, // Don't leave any fog behind when teleporting. + TF_NODESTFOG = 0x00000080, // Don't spawn any fog at the arrival position. + TF_USEACTORFOG = 0x00000100, // Use the actor's TeleFogSourceType and TeleFogDestType fogs. + TF_NOJUMP = 0x00000200, // Don't jump after teleporting. +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport) +{ + ACTION_PARAM_START(6); + ACTION_PARAM_STATE(TeleportState, 0); + ACTION_PARAM_CLASS(TargetType, 1); + ACTION_PARAM_CLASS(FogType, 2); + ACTION_PARAM_INT(Flags, 3); + ACTION_PARAM_FIXED(MinDist, 4); + ACTION_PARAM_FIXED(MaxDist, 5); + + // Randomly choose not to teleport like A_Srcr2Decide. + if (Flags & TF_RANDOMDECIDE) + { + static const int chance[] = + { + 192, 120, 120, 120, 64, 64, 32, 16, 0 + }; + + unsigned int chanceindex = self->health / ((self->SpawnHealth()/8 == 0) ? 1 : self->SpawnHealth()/8); + + if (chanceindex >= countof(chance)) + { + chanceindex = countof(chance) - 1; + } + + if (pr_teleport() >= chance[chanceindex]) return; + } + + DSpotState *state = DSpotState::GetSpotState(); + if (state == NULL) return; + + if (!TargetType) TargetType = PClass::FindClass("BossSpot"); + + AActor * spot = state->GetSpotWithMinMaxDistance(TargetType, self->x, self->y, MinDist, MaxDist); + if (spot == NULL) return; + + fixed_t prevX = self->x; + fixed_t prevY = self->y; + fixed_t prevZ = self->z; + fixed_t aboveFloor = spot->z - spot->floorz; + fixed_t finalz = spot->floorz + aboveFloor; + + if (spot->z + self->height > spot->ceilingz) + finalz = spot->ceilingz - self->height; + else if (spot->z < spot->floorz) + finalz = spot->floorz; + + + //Take precedence and cooperate with telefragging first. + bool teleResult = P_TeleportMove(self, spot->x, spot->y, finalz, Flags & TF_TELEFRAG); + + if (Flags & TF_FORCED) + { + //If for some reason the original move didn't work, regardless of telefrag, force it to move. + self->SetOrigin(spot->x, spot->y, finalz); + teleResult = true; + } + + if (teleResult) + { + //If a fog type is defined in the parameter, or the user wants to use the actor's predefined fogs, + //and if there's no desire to be fogless, spawn a fog based upon settings. + if (FogType || (Flags & TF_USEACTORFOG)) + { + if (!(Flags & TF_NOSRCFOG)) + { + if (Flags & TF_USEACTORFOG) + P_SpawnTeleportFog(self, prevX, prevY, prevZ, true); + else + Spawn(FogType, prevX, prevY, prevZ, ALLOW_REPLACE); + } + if (!(Flags & TF_NODESTFOG)) + { + if (Flags & TF_USEACTORFOG) + P_SpawnTeleportFog(self, self->x, self->y, self->z, false); + else + Spawn(FogType, self->x, self->y, self->z, ALLOW_REPLACE); + } + } + + if (Flags & TF_USESPOTZ) + self->z = spot->z; + else + self->z = self->floorz; + + if (!(Flags & TF_KEEPANGLE)) + self->angle = spot->angle; + + if (!(Flags & TF_KEEPVELOCITY)) + self->velx = self->vely = self->velz = 0; + + if (!(Flags & TF_NOJUMP)) + { + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (TeleportState == NULL) + { + // Default to Teleport. + TeleportState = self->FindState("Teleport"); + // If still nothing, then return. + if (!TeleportState) return; + } + ACTION_JUMP(TeleportState); + return; + } + } + ACTION_SET_RESULT(teleResult); +} + +//=========================================================================== +// +// A_Turn +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Turn) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_ANGLE(angle, 0); + self->angle += angle; +} + +//=========================================================================== +// +// A_Quake +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Quake) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(intensity, 0); + ACTION_PARAM_INT(duration, 1); + ACTION_PARAM_INT(damrad, 2); + ACTION_PARAM_INT(tremrad, 3); + ACTION_PARAM_SOUND(sound, 4); + P_StartQuake(self, 0, intensity, duration, damrad, tremrad, sound); +} + +//=========================================================================== +// +// A_Weave +// +//=========================================================================== + +void A_Weave(AActor *self, int xyspeed, int zspeed, fixed_t xydist, fixed_t zdist) +{ + fixed_t newX, newY; + int weaveXY, weaveZ; + int angle; + fixed_t dist; + + weaveXY = self->WeaveIndexXY & 63; + weaveZ = self->WeaveIndexZ & 63; + angle = (self->angle + ANG90) >> ANGLETOFINESHIFT; + + if (xydist != 0 && xyspeed != 0) + { + dist = MulScale13(finesine[weaveXY << BOBTOFINESHIFT], xydist); + newX = self->x - FixedMul (finecosine[angle], dist); + newY = self->y - FixedMul (finesine[angle], dist); + weaveXY = (weaveXY + xyspeed) & 63; + dist = MulScale13(finesine[weaveXY << BOBTOFINESHIFT], xydist); + newX += FixedMul (finecosine[angle], dist); + newY += FixedMul (finesine[angle], dist); + if (!(self->flags5 & MF5_NOINTERACTION)) + { + P_TryMove (self, newX, newY, true); + } + else + { + self->UnlinkFromWorld (); + self->flags |= MF_NOBLOCKMAP; + self->x = newX; + self->y = newY; + self->LinkToWorld (); + } + self->WeaveIndexXY = weaveXY; + } + if (zdist != 0 && zspeed != 0) + { + self->z -= MulScale13(finesine[weaveZ << BOBTOFINESHIFT], zdist); + weaveZ = (weaveZ + zspeed) & 63; + self->z += MulScale13(finesine[weaveZ << BOBTOFINESHIFT], zdist); + self->WeaveIndexZ = weaveZ; + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Weave) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_INT(xspeed, 0); + ACTION_PARAM_INT(yspeed, 1); + ACTION_PARAM_FIXED(xdist, 2); + ACTION_PARAM_FIXED(ydist, 3); + A_Weave(self, xspeed, yspeed, xdist, ydist); +} + + + + +//=========================================================================== +// +// A_LineEffect +// +// This allows linedef effects to be activated inside deh frames. +// +//=========================================================================== + + +void P_TranslateLineDef (line_t *ld, maplinedef_t *mld); +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LineEffect) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(special, 0); + ACTION_PARAM_INT(tag, 1); + + line_t junk; maplinedef_t oldjunk; + bool res = false; + if (!(self->flags6 & MF6_LINEDONE)) // Unless already used up + { + if ((oldjunk.special = special)) // Linedef type + { + oldjunk.tag = tag; // Sector tag for linedef + P_TranslateLineDef(&junk, &oldjunk); // Turn into native type + res = !!P_ExecuteSpecial(junk.special, NULL, self, false, junk.args[0], + junk.args[1], junk.args[2], junk.args[3], junk.args[4]); + if (res && !(junk.flags & ML_REPEAT_SPECIAL)) // If only once, + self->flags6 |= MF6_LINEDONE; // no more for this thing + } + } + ACTION_SET_RESULT(res); +} + +//========================================================================== +// +// A Wolf3D-style attack codepointer +// +//========================================================================== +enum WolfAttackFlags +{ + WAF_NORANDOM = 1, + WAF_USEPUFF = 2, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_WolfAttack) +{ + ACTION_PARAM_START(9); + ACTION_PARAM_INT(flags, 0); + ACTION_PARAM_SOUND(sound, 1); + ACTION_PARAM_FIXED(snipe, 2); + ACTION_PARAM_INT(maxdamage, 3); + ACTION_PARAM_INT(blocksize, 4); + ACTION_PARAM_INT(pointblank, 5); + ACTION_PARAM_INT(longrange, 6); + ACTION_PARAM_FIXED(runspeed, 7); + ACTION_PARAM_CLASS(pufftype, 8); + + if (!self->target) + return; + + // Enemy can't see target + if (!P_CheckSight(self, self->target)) + return; + + A_FaceTarget (self); + + + // Target can dodge if it can see enemy + angle_t angle = R_PointToAngle2(self->target->x, self->target->y, self->x, self->y) - self->target->angle; + angle >>= 24; + bool dodge = (P_CheckSight(self->target, self) && (angle>226 || angle<30)); + + // Distance check is simplistic + fixed_t dx = abs (self->x - self->target->x); + fixed_t dy = abs (self->y - self->target->y); + fixed_t dz; + fixed_t dist = dx > dy ? dx : dy; + + // Some enemies are more precise + dist = FixedMul(dist, snipe); + + // Convert distance into integer number of blocks + dist >>= FRACBITS; + dist /= blocksize; + + // Now for the speed accuracy thingie + fixed_t speed = FixedMul(self->target->velx, self->target->velx) + + FixedMul(self->target->vely, self->target->vely) + + FixedMul(self->target->velz, self->target->velz); + int hitchance = speed < runspeed ? 256 : 160; + + // Distance accuracy (factoring dodge) + hitchance -= dist * (dodge ? 16 : 8); + + // While we're here, we may as well do something for this: + if (self->target->flags & MF_SHADOW) + { + hitchance >>= 2; + } + + // The attack itself + if (pr_cabullet() < hitchance) + { + // Compute position for spawning blood/puff + dx = self->target->x; + dy = self->target->y; + dz = self->target->z + (self->target->height>>1); + angle = R_PointToAngle2(dx, dy, self->x, self->y); + + dx += FixedMul(self->target->radius, finecosine[angle>>ANGLETOFINESHIFT]); + dy += FixedMul(self->target->radius, finesine[angle>>ANGLETOFINESHIFT]); + + int damage = flags & WAF_NORANDOM ? maxdamage : (1 + (pr_cabullet() % maxdamage)); + if (dist >= pointblank) + damage >>= 1; + if (dist >= longrange) + damage >>= 1; + FName mod = NAME_None; + bool spawnblood = !((self->target->flags & MF_NOBLOOD) + || (self->target->flags2 & (MF2_INVULNERABLE|MF2_DORMANT))); + if (flags & WAF_USEPUFF && pufftype) + { + AActor * dpuff = GetDefaultByType(pufftype->GetReplacement()); + mod = dpuff->DamageType; + + if (dpuff->flags2 & MF2_THRUGHOST && self->target->flags3 & MF3_GHOST) + damage = 0; + + if ((0 && dpuff->flags3 & MF3_PUFFONACTORS) || !spawnblood) + { + spawnblood = false; + P_SpawnPuff(self, pufftype, dx, dy, dz, angle, 0); + } + } + else if (self->target->flags3 & MF3_GHOST) + damage >>= 2; + if (damage) + { + int newdam = P_DamageMobj(self->target, self, self, damage, mod, DMG_THRUSTLESS); + if (spawnblood) + { + P_SpawnBlood(dx, dy, dz, angle, newdam > 0 ? newdam : damage, self->target); + P_TraceBleed(newdam > 0 ? newdam : damage, self->target, R_PointToAngle2(self->x, self->y, dx, dy), 0); + } + } + } + + // And finally, let's play the sound + S_Sound (self, CHAN_WEAPON, sound, 1, ATTN_NORM); +} + + +//========================================================================== +// +// A_Warp +// +//========================================================================== + +enum WARPF +{ + WARPF_ABSOLUTEOFFSET = 0x1, + WARPF_ABSOLUTEANGLE = 0x2, + WARPF_USECALLERANGLE = 0x4, + + WARPF_NOCHECKPOSITION = 0x8, + + WARPF_INTERPOLATE = 0x10, + WARPF_WARPINTERPOLATION = 0x20, + WARPF_COPYINTERPOLATION = 0x40, + + WARPF_STOP = 0x80, + WARPF_TOFLOOR = 0x100, + WARPF_TESTONLY = 0x200, + WARPF_ABSOLUTEPOSITION = 0x400, + WARPF_BOB = 0x800, + WARPF_MOVEPTR = 0x1000, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Warp) +{ + ACTION_PARAM_START(7); + + ACTION_PARAM_INT(destination_selector, 0); + ACTION_PARAM_FIXED(xofs, 1); + ACTION_PARAM_FIXED(yofs, 2); + ACTION_PARAM_FIXED(zofs, 3); + ACTION_PARAM_ANGLE(angle, 4); + ACTION_PARAM_INT(flags, 5); + ACTION_PARAM_STATE(success_state, 6); + + AActor *reference = COPY_AAPTR(self, destination_selector); + + //If there is no actor to warp to, fail. + if (!reference) + { + ACTION_SET_RESULT(false); + return; + } + + AActor *caller = self; + + if (flags & WARPF_MOVEPTR) + { + AActor *temp = reference; + reference = caller; + caller = temp; + } + + fixed_t oldx = caller->x; + fixed_t oldy = caller->y; + fixed_t oldz = caller->z; + + if (!(flags & WARPF_ABSOLUTEANGLE)) + { + angle += (flags & WARPF_USECALLERANGLE) ? caller->angle : reference->angle; + } + if (!(flags & WARPF_ABSOLUTEPOSITION)) + { + if (!(flags & WARPF_ABSOLUTEOFFSET)) + { + angle_t fineangle = angle >> ANGLETOFINESHIFT; + fixed_t xofs1 = xofs; + + // (borrowed from A_SpawnItemEx, assumed workable) + // in relative mode negative y values mean 'left' and positive ones mean 'right' + // This is the inverse orientation of the absolute mode! + + xofs = FixedMul(xofs1, finecosine[fineangle]) + FixedMul(yofs, finesine[fineangle]); + yofs = FixedMul(xofs1, finesine[fineangle]) - FixedMul(yofs, finecosine[fineangle]); + } + + if (flags & WARPF_TOFLOOR) + { + // set correct xy + + caller->SetOrigin( + reference->x + xofs, + reference->y + yofs, + reference->z); + + // now the caller's floorz should be appropriate for the assigned xy-position + // assigning position again with + + if (zofs) + { + // extra unlink, link and environment calculation + caller->SetOrigin( + caller->x, + caller->y, + caller->floorz + zofs); + } + else + { + // if there is no offset, there should be no ill effect from moving down to the + // already identified floor + + // A_Teleport does the same thing anyway + caller->z = caller->floorz; + } + } + else + { + caller->SetOrigin( + reference->x + xofs, + reference->y + yofs, + reference->z + zofs); + } + } + else //[MC] The idea behind "absolute" is meant to be "absolute". Override everything, just like A_SpawnItemEx's. + { + if (flags & WARPF_TOFLOOR) + { + caller->SetOrigin(xofs, yofs, caller->floorz + zofs); + } + else + { + caller->SetOrigin(xofs, yofs, zofs); + } + } + + if ((flags & WARPF_NOCHECKPOSITION) || P_TestMobjLocation(caller)) + { + if (flags & WARPF_TESTONLY) + { + caller->SetOrigin(oldx, oldy, oldz); + } + else + { + caller->angle = angle; + + if (flags & WARPF_STOP) + { + caller->velx = 0; + caller->vely = 0; + caller->velz = 0; + } + + if (flags & WARPF_WARPINTERPOLATION) + { + caller->PrevX += caller->x - oldx; + caller->PrevY += caller->y - oldy; + caller->PrevZ += caller->z - oldz; + } + else if (flags & WARPF_COPYINTERPOLATION) + { + caller->PrevX = caller->x + reference->PrevX - reference->x; + caller->PrevY = caller->y + reference->PrevY - reference->y; + caller->PrevZ = caller->z + reference->PrevZ - reference->z; + } + else if (!(flags & WARPF_INTERPOLATE)) + { + caller->PrevX = caller->x; + caller->PrevY = caller->y; + caller->PrevZ = caller->z; + } + + if ((flags & WARPF_BOB) && (reference->flags2 & MF2_FLOATBOB)) + { + caller->z += reference->GetBobOffset(); + } + } + + + if (success_state) + { + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + // in this case, you have the statejump to help you handle all the success anyway. + ACTION_JUMP(success_state); + return; + } + + ACTION_SET_RESULT(true); + } + else + { + caller->SetOrigin(oldx, oldy, oldz); + ACTION_SET_RESULT(false); + } + +} + +//========================================================================== +// +// ACS_Named* stuff + +// +// These are exactly like their un-named line special equivalents, except +// they take strings instead of integers to indicate which script to run. +// Some of these probably aren't very useful, but they are included for +// the sake of completeness. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteWithResult) +{ + ACTION_PARAM_START(5); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(arg1, 1); + ACTION_PARAM_INT(arg2, 2); + ACTION_PARAM_INT(arg3, 3); + ACTION_PARAM_INT(arg4, 4); + + bool res = !!P_ExecuteSpecial(ACS_ExecuteWithResult, NULL, self, false, -scriptname, arg1, arg2, arg3, arg4); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecute) +{ + ACTION_PARAM_START(5); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + ACTION_PARAM_INT(arg1, 2); + ACTION_PARAM_INT(arg2, 3); + ACTION_PARAM_INT(arg3, 4); + + bool res = !!P_ExecuteSpecial(ACS_Execute, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteAlways) +{ + ACTION_PARAM_START(5); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + ACTION_PARAM_INT(arg1, 2); + ACTION_PARAM_INT(arg2, 3); + ACTION_PARAM_INT(arg3, 4); + + bool res = !!P_ExecuteSpecial(ACS_ExecuteAlways, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecute) +{ + ACTION_PARAM_START(5); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + ACTION_PARAM_INT(arg1, 2); + ACTION_PARAM_INT(arg2, 3); + ACTION_PARAM_INT(lock, 4); + + bool res = !!P_ExecuteSpecial(ACS_LockedExecute, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecuteDoor) +{ + ACTION_PARAM_START(5); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + ACTION_PARAM_INT(arg1, 2); + ACTION_PARAM_INT(arg2, 3); + ACTION_PARAM_INT(lock, 4); + + bool res = !!P_ExecuteSpecial(ACS_LockedExecuteDoor, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedSuspend) +{ + ACTION_PARAM_START(2); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + + bool res = !!P_ExecuteSpecial(ACS_Suspend, NULL, self, false, -scriptname, mapnum, 0, 0, 0); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedTerminate) +{ + ACTION_PARAM_START(2); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + + bool res = !!P_ExecuteSpecial(ACS_Terminate, NULL, self, false, -scriptname, mapnum, 0, 0, 0); + + ACTION_SET_RESULT(res); +} + + +//========================================================================== +// +// A_RadiusGive +// +// Uses code roughly similar to A_Explode (but without all the compatibility +// baggage and damage computation code to give an item to all eligible mobjs +// in range. +// +//========================================================================== +enum RadiusGiveFlags +{ + RGF_GIVESELF = 1 << 0, + RGF_PLAYERS = 1 << 1, + RGF_MONSTERS = 1 << 2, + RGF_OBJECTS = 1 << 3, + RGF_VOODOO = 1 << 4, + RGF_CORPSES = 1 << 5, + RGF_MASK = 2111, + RGF_NOTARGET = 1 << 6, + RGF_NOTRACER = 1 << 7, + RGF_NOMASTER = 1 << 8, + RGF_CUBE = 1 << 9, + RGF_NOSIGHT = 1 << 10, + RGF_MISSILES = 1 << 11, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusGive) +{ + ACTION_PARAM_START(7); + ACTION_PARAM_CLASS(item, 0); + ACTION_PARAM_FIXED(distance, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_INT(amount, 3); + + // We need a valid item, valid targets, and a valid range + if (item == NULL || (flags & RGF_MASK) == 0 || distance <= 0) + { + return; + } + if (amount == 0) + { + amount = 1; + } + FBlockThingsIterator it(FBoundingBox(self->x, self->y, distance)); + double distsquared = double(distance) * double(distance); + + AActor *thing; + while ((thing = it.Next())) + { + // Don't give to inventory items + if (thing->flags & MF_SPECIAL) + { + continue; + } + // Avoid giving to self unless requested + if (thing == self && !(flags & RGF_GIVESELF)) + { + continue; + } + // Avoiding special pointers if requested + if (((thing == self->target) && (flags & RGF_NOTARGET)) || + ((thing == self->tracer) && (flags & RGF_NOTRACER)) || + ((thing == self->master) && (flags & RGF_NOMASTER))) + { + continue; + } + // Don't give to dead thing unless requested + if (thing->flags & MF_CORPSE) + { + if (!(flags & RGF_CORPSES)) + { + continue; + } + } + else if (thing->health <= 0 || thing->flags6 & MF6_KILLED) + { + continue; + } + // Players, monsters, and other shootable objects + if (thing->player) + { + if ((thing->player->mo == thing) && !(flags & RGF_PLAYERS)) + { + continue; + } + if ((thing->player->mo != thing) && !(flags & RGF_VOODOO)) + { + continue; + } + } + else if (thing->flags3 & MF3_ISMONSTER) + { + if (!(flags & RGF_MONSTERS)) + { + continue; + } + } + else if (thing->flags & MF_SHOOTABLE || thing->flags6 & MF6_VULNERABLE) + { + if (!(flags & RGF_OBJECTS)) + { + continue; + } + } + else if (thing->flags & MF_MISSILE) + { + if (!(flags & RGF_MISSILES)) + { + continue; + } + } + else + { + continue; + } + + if (flags & RGF_CUBE) + { // check if inside a cube + if (abs(thing->x - self->x) > distance || + abs(thing->y - self->y) > distance || + abs((thing->z + thing->height/2) - (self->z + self->height/2)) > distance) + { + continue; + } + } + else + { // check if inside a sphere + TVector3 tpos(thing->x, thing->y, thing->z + thing->height/2); + TVector3 spos(self->x, self->y, self->z + self->height/2); + if ((tpos - spos).LengthSquared() > distsquared) + { + continue; + } + } + fixed_t dz = abs ((thing->z + thing->height/2) - (self->z + self->height/2)); + + if ((flags & RGF_NOSIGHT) || P_CheckSight (thing, self, SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY)) + { // OK to give; target is in direct path, or the monster doesn't care about it being in line of sight. + AInventory *gift = static_cast(Spawn (item, 0, 0, 0, NO_REPLACE)); + if (gift->IsKindOf(RUNTIME_CLASS(AHealth))) + { + gift->Amount *= amount; + } + else + { + gift->Amount = amount; + } + gift->flags |= MF_DROPPED; + gift->ClearCounters(); + if (!gift->CallTryPickup (thing)) + { + gift->Destroy (); + } + } + } +} + + +//========================================================================== +// +// A_SetTics +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTics) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(tics_to_set, 0); + + if (stateowner != self && self->player != NULL && stateowner->IsKindOf(RUNTIME_CLASS(AWeapon))) + { // Is this a weapon? Need to check psp states for a match, then. Blah. + for (int i = 0; i < NUMPSPRITES; ++i) + { + if (self->player->psprites[i].state == CallingState) + { + self->player->psprites[i].tics = tics_to_set; + return; + } + } + } + // Just set tics for self. + self->tics = tics_to_set; +} + +//========================================================================== +// +// A_SetDamageType +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetDamageType) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_NAME(damagetype, 0); + + self->DamageType = damagetype; +} + +//========================================================================== +// +// A_DropItem +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropItem) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_CLASS(spawntype, 0); + ACTION_PARAM_INT(amount, 1); + ACTION_PARAM_INT(chance, 2); + + P_DropItem(self, spawntype, amount, chance); +} + +//========================================================================== +// +// A_SetSpeed +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpeed) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_FIXED(speed, 0); + ACTION_PARAM_INT(ptr, 1); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } + + ref->Speed = speed; +} + +static bool DoCheckSpecies(AActor *mo, FName species, bool exclude) +{ + return (!(species) || mo->Species == NAME_None || (species && ((exclude) ? (mo->Species != species) : (mo->Species == species)))); +} + +static bool DoCheckFilter(AActor *mo, const PClass *filter, bool exclude) +{ + const PClass *c1 = mo->GetClass(); + return (!(filter) || (filter == NULL) || (filter && ((exclude) ? (c1 != filter) : (c1 == filter)))); +} + +//=========================================================================== +// +// Common A_Damage handler +// +// A_Damage* (int amount, str damagetype, int flags, str filter, str species) +// Damages the specified actor by the specified amount. Negative values heal. +// Flags: See below. +// Filter: Specified actor is the only type allowed to be affected. +// Species: Specified species is the only type allowed to be affected. +// +// Examples: +// A_Damage(20,"Normal",DMSS_FOILINVUL,0,"DemonicSpecies") <--Only actors +// with a species "DemonicSpecies" will be affected. Use 0 to not filter by actor. +// +//=========================================================================== + +enum DMSS +{ + DMSS_FOILINVUL = 1, //Foil invulnerability + DMSS_AFFECTARMOR = 2, //Make it affect armor + DMSS_KILL = 4, //Damages them for their current health + DMSS_NOFACTOR = 8, //Ignore DamageFactors + DMSS_FOILBUDDHA = 16, //Can kill actors with Buddha flag, except the player. + DMSS_NOPROTECT = 32, //Ignores PowerProtection entirely + DMSS_EXFILTER = 64, //Changes filter into a blacklisted class instead of whitelisted. + DMSS_EXSPECIES = 128, // ^ but with species instead. + DMSS_EITHER = 256, //Allow either type or species to be affected. +}; + +static void DoDamage(AActor *dmgtarget, AActor *self, int amount, FName DamageType, int flags, const PClass *filter, FName species) +{ + bool filterpass = DoCheckFilter(dmgtarget, filter, (flags & DMSS_EXFILTER) ? true : false), + speciespass = DoCheckSpecies(dmgtarget, species, (flags & DMSS_EXSPECIES) ? true : false); + if ((flags & DMSS_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) + { + int dmgFlags = 0; + if (flags & DMSS_FOILINVUL) + dmgFlags += DMG_FOILINVUL; + if (flags & DMSS_FOILBUDDHA) + dmgFlags += DMG_FOILBUDDHA; + if ((flags & DMSS_KILL) || (flags & DMSS_NOFACTOR)) //Kill implies NoFactor + dmgFlags += DMG_NO_FACTOR; + if (!(flags & DMSS_AFFECTARMOR) || (flags & DMSS_KILL)) //Kill overrides AffectArmor + dmgFlags += DMG_NO_ARMOR; + if (flags & DMSS_KILL) //Kill adds the value of the damage done to it. Allows for more controlled extreme death types. + amount += dmgtarget->health; + if (flags & DMSS_NOPROTECT) //Ignore PowerProtection. + dmgFlags += DMG_NO_PROTECT; + + if (amount > 0) + P_DamageMobj(dmgtarget, self, self, amount, DamageType, dmgFlags); //Should wind up passing them through just fine. + + else if (amount < 0) + { + amount = -amount; + P_GiveBody(dmgtarget, amount); + } + } +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSelf) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + DoDamage(self, self, amount, DamageType, flags, filter, species); +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTarget) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + if (self->target != NULL) + { + DoDamage(self->target, self, amount, DamageType, flags, filter, species); + } +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTracer) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + if (self->tracer != NULL) + { + DoDamage(self->tracer, self, amount, DamageType, flags, filter, species); + } +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageMaster) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + if (self->master != NULL) + { + DoDamage(self->master, self, amount, DamageType, flags, filter, species); + } +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageChildren) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + TThinkerIterator it; + AActor * mo; + + while ( (mo = it.Next()) ) + { + if (mo->master == self) + { + DoDamage(mo, self, amount, DamageType, flags, filter, species); + } + } +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSiblings) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + TThinkerIterator it; + AActor * mo; + + if (self->master != NULL) + { + while ((mo = it.Next())) + { + if (mo->master == self->master && mo != self) + { + DoDamage(mo, self, amount, DamageType, flags, filter, species); + } + } + } +} + + +//=========================================================================== +// +// A_Kill*(damagetype, int flags) +// +//=========================================================================== +enum KILS +{ + KILS_FOILINVUL = 1 << 0, + KILS_KILLMISSILES = 1 << 1, + KILS_NOMONSTERS = 1 << 2, + KILS_FOILBUDDHA = 1 << 3, + KILS_EXFILTER = 1 << 4, + KILS_EXSPECIES = 1 << 5, + KILS_EITHER = 1 << 6, +}; + +static void DoKill(AActor *killtarget, AActor *self, FName damagetype, int flags, const PClass *filter, FName species) +{ + bool filterpass = DoCheckFilter(killtarget, filter, (flags & KILS_EXFILTER) ? true : false), + speciespass = DoCheckSpecies(killtarget, species, (flags & KILS_EXSPECIES) ? true : false); + if ((flags & KILS_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) //Check this first. I think it'll save the engine a lot more time this way. + { + int dmgFlags = DMG_NO_ARMOR + DMG_NO_FACTOR; + + if (KILS_FOILINVUL) + dmgFlags += DMG_FOILINVUL; + if (KILS_FOILBUDDHA) + dmgFlags += DMG_FOILBUDDHA; + + + if ((killtarget->flags & MF_MISSILE) && (flags & KILS_KILLMISSILES)) + { + //[MC] Now that missiles can set masters, lets put in a check to properly destroy projectiles. BUT FIRST! New feature~! + //Check to see if it's invulnerable. Disregarded if foilinvul is on, but never works on a missile with NODAMAGE + //since that's the whole point of it. + if ((!(killtarget->flags2 & MF2_INVULNERABLE) || (flags & KILS_FOILINVUL)) && + (!(killtarget->flags2 & MF7_BUDDHA) || (flags & KILS_FOILBUDDHA)) && !(killtarget->flags5 & MF5_NODAMAGE)) + { + P_ExplodeMissile(killtarget, NULL, NULL); + } + } + if (!(flags & KILS_NOMONSTERS)) + { + P_DamageMobj(killtarget, self, self, killtarget->health, damagetype, dmgFlags); + } + } +} + + +//=========================================================================== +// +// A_KillTarget(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillTarget) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_NAME(damagetype, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + if (self->target != NULL) + { + DoKill(self->target, self, damagetype, flags, filter, species); + } +} + +//=========================================================================== +// +// A_KillTracer(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillTracer) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_NAME(damagetype, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + if (self->tracer != NULL) + { + DoKill(self->tracer, self, damagetype, flags, filter, species); + } +} + +//=========================================================================== +// +// A_KillMaster(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillMaster) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_NAME(damagetype, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + if (self->master != NULL) + { + DoKill(self->master, self, damagetype, flags, filter, species); + } +} + +//=========================================================================== +// +// A_KillChildren(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillChildren) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_NAME(damagetype, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + TThinkerIterator it; + AActor *mo; + + while ( (mo = it.Next()) ) + { + if (mo->master == self) + { + DoKill(mo, self, damagetype, flags, filter, species); + } + } +} + +//=========================================================================== +// +// A_KillSiblings(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillSiblings) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_NAME(damagetype, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + TThinkerIterator it; + AActor *mo; + + if (self->master != NULL) + { + while ( (mo = it.Next()) ) + { + if (mo->master == self->master && mo != self) + { + DoKill(mo, self, damagetype, flags, filter, species); + } + } + } +} + +//=========================================================================== +// +// DoRemove +// +//=========================================================================== + +enum RMVF_flags +{ + RMVF_MISSILES = 1 << 0, + RMVF_NOMONSTERS = 1 << 1, + RMVF_MISC = 1 << 2, + RMVF_EVERYTHING = 1 << 3, + RMVF_EXFILTER = 1 << 4, + RMVF_EXSPECIES = 1 << 5, + RMVF_EITHER = 1 << 6, +}; + +static void DoRemove(AActor *removetarget, int flags, const PClass *filter, FName species) +{ + bool filterpass = DoCheckFilter(removetarget, filter, (flags & RMVF_EXFILTER) ? true : false), + speciespass = DoCheckSpecies(removetarget, species, (flags & RMVF_EXSPECIES) ? true : false); + if ((flags & RMVF_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) + { + if ((flags & RMVF_EVERYTHING)) + { + P_RemoveThing(removetarget); + } + if ((flags & RMVF_MISC) && !((removetarget->flags3 & MF3_ISMONSTER) && (removetarget->flags & MF_MISSILE))) + { + P_RemoveThing(removetarget); + } + if ((removetarget->flags3 & MF3_ISMONSTER) && !(flags & RMVF_NOMONSTERS)) + { + P_RemoveThing(removetarget); + } + if ((removetarget->flags & MF_MISSILE) && (flags & RMVF_MISSILES)) + { + P_RemoveThing(removetarget); + } + } +} + +//=========================================================================== +// +// A_RemoveTarget +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveTarget) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(flags, 0); + ACTION_PARAM_CLASS(filter, 1); + ACTION_PARAM_NAME(species, 2); + + if (self->target != NULL) + { + DoRemove(self->target, flags, filter, species); + } +} + +//=========================================================================== +// +// A_RemoveTracer +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveTracer) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(flags, 0); + ACTION_PARAM_CLASS(filter, 1); + ACTION_PARAM_NAME(species, 2); + + if (self->tracer != NULL) + { + DoRemove(self->tracer, flags, filter, species); + } +} + +//=========================================================================== +// +// A_RemoveMaster +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveMaster) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(flags, 0); + ACTION_PARAM_CLASS(filter, 1); + ACTION_PARAM_NAME(species, 2); + + if (self->master != NULL) + { + DoRemove(self->master, flags, filter, species); + } +} + +//=========================================================================== +// +// A_RemoveChildren +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveChildren) +{ + TThinkerIterator it; + AActor *mo; + ACTION_PARAM_START(4); + ACTION_PARAM_BOOL(removeall, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + + while ((mo = it.Next()) != NULL) + { + if (mo->master == self && (mo->health <= 0 || removeall)) + { + DoRemove(mo, flags, filter, species); + } + } +} + +//=========================================================================== +// +// A_RemoveSiblings +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveSiblings) +{ + TThinkerIterator it; + AActor *mo; + ACTION_PARAM_START(4); + ACTION_PARAM_BOOL(removeall, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + if (self->master != NULL) + { + while ((mo = it.Next()) != NULL) + { + if (mo->master == self->master && mo != self && (mo->health <= 0 || removeall)) + { + DoRemove(mo, flags, filter, species); + } + } + } +} + +//=========================================================================== +// +// A_Remove +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Remove) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_INT(removee, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + AActor *reference = COPY_AAPTR(self, removee); + if (reference != NULL) + { + DoRemove(reference, flags, filter, species); + } +} + +//=========================================================================== +// +// A_SetTeleFog +// +// Sets the teleport fog(s) for the calling actor. +// Takes a name of the classes for te source and destination. +// Can set both at the same time. Use "" to retain the previous fog without +// changing it. +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTeleFog) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_NAME(oldpos, 0); + ACTION_PARAM_NAME(newpos, 1); + const PClass *check = PClass::FindClass(oldpos); + if (check == NULL || !stricmp(oldpos, "none") || !stricmp(oldpos, "null")) + self->TeleFogSourceType = NULL; + else if (!stricmp(oldpos, "")) + { //Don't change it if it's just "" + } + else + self->TeleFogSourceType = check; + + check = PClass::FindClass(newpos); + if (check == NULL || !stricmp(newpos, "none") || !stricmp(newpos, "null")) + self->TeleFogDestType = NULL; + else if (!stricmp(newpos, "")) + { //Don't change it if it's just "" + } + else + self->TeleFogDestType = check; +} + +//=========================================================================== +// +// A_SwapTeleFog +// +// Switches the source and dest telefogs around. +//=========================================================================== + +DEFINE_ACTION_FUNCTION(AActor, A_SwapTeleFog) +{ + if ((self->TeleFogSourceType != self->TeleFogDestType)) //Does nothing if they're the same. + { + const PClass *temp = self->TeleFogSourceType; + self->TeleFogSourceType = self->TeleFogDestType; + self->TeleFogDestType = temp; + } +} + +//=========================================================================== +// +// A_SetFloatBobPhase +// +// Changes the FloatBobPhase of the actor. +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetFloatBobPhase) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(bob, 0); + + //Respect float bob phase limits. + if (self && (bob >= 0 && bob <= 63)) + self->FloatBobPhase = bob; +} + +//=========================================================================== +// A_SetHealth +// +// Changes the health of the actor. +// Takes a pointer as well. +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetHealth) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(health, 0); + ACTION_PARAM_INT(ptr, 1); + + AActor *mobj = COPY_AAPTR(self, ptr); + + if (!mobj) + { + return; + } + + player_t *player = mobj->player; + if (player) + { + if (health <= 0) + player->mo->health = mobj->health = player->health = 1; //Copied from the buddha cheat. + else + player->mo->health = mobj->health = player->health = health; + } + else if (mobj) + { + if (health <= 0) + mobj->health = 1; + else + mobj->health = health; + } +} + +//=========================================================================== +// A_ResetHealth +// +// Resets the health of the actor to default, except if their dead. +// Takes a pointer. +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ResetHealth) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(ptr, 0); + + AActor *mobj = COPY_AAPTR(self, ptr); + + if (!mobj) + { + return; + } + + player_t *player = mobj->player; + if (player && (player->mo->health > 0)) + { + player->health = player->mo->health = player->mo->GetDefault()->health; //Copied from the resurrect cheat. + } + else if (mobj && (mobj->health > 0)) + { + mobj->health = mobj->SpawnHealth(); + } +} + +//=========================================================================== +// +// A_SetRipperLevel(int level) +// +// Sets the ripper level/requirement of the calling actor. +// Also sets the minimum and maximum levels to rip through. +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipperLevel) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(level, 0); + self->RipperLevel = level; +} + +//=========================================================================== +// +// A_SetRipMin(int min) +// +// Sets the ripper level/requirement of the calling actor. +// Also sets the minimum and maximum levels to rip through. +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipMin) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(min, 1); + self->RipLevelMin = min; +} + +//=========================================================================== +// +// A_SetRipMin(int min) +// +// Sets the ripper level/requirement of the calling actor. +// Also sets the minimum and maximum levels to rip through. +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipMax) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(max, 1); + self->RipLevelMax = max; } \ No newline at end of file From 72506fa6e90286250a302dd99cf9a9250bc5db11 Mon Sep 17 00:00:00 2001 From: Braden Obrzut Date: Mon, 26 Jan 2015 15:00:36 -0500 Subject: [PATCH 16/19] - Fixed: Regression with centered coordinates. --- src/g_shared/sbarinfo.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/g_shared/sbarinfo.cpp b/src/g_shared/sbarinfo.cpp index 8ffedb5a4c..791021ae8f 100644 --- a/src/g_shared/sbarinfo.cpp +++ b/src/g_shared/sbarinfo.cpp @@ -1280,8 +1280,8 @@ public: // We can't use DTA_HUDRules since it forces a width and height. // Translation: No high res. - bool xright = *x < 0; - bool ybot = *y < 0; + bool xright = *x < 0 && !x.RelCenter(); + bool ybot = *y < 0 && !y.RelCenter(); w = (forceWidth < 0 ? texture->GetScaledWidthDouble() : forceWidth); h = (forceHeight < 0 ? texture->GetScaledHeightDouble() : forceHeight); From d828173ce36f92de616718edfdaadcd91d081e5d Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Thu, 29 Jan 2015 19:30:26 -0600 Subject: [PATCH 17/19] Revert "- fixed: Since A_Give/TaketoChildren/siblings use the same code as A_Give/TakeInventory but do not use the third parameter this must be ignored by the code. This is merely a band-aid fix, because this problem doesn't exist in the same form in the scripting branch." This reverts commit a4c07a9ee3a445f095d9618f928b1cb83dc54b0f. - This commit touched every line of thingdef_codeptr.cpp, which would make merging it into the scripting branch a pain in the ass. --- src/thingdef/thingdef_codeptr.cpp | 11665 ++++++++++++++-------------- 1 file changed, 5825 insertions(+), 5840 deletions(-) diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index 63c5d74083..be3921877c 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -1,4020 +1,4005 @@ -/* -** thingdef.cpp -** -** Code pointers for Actor definitions -** -**--------------------------------------------------------------------------- -** Copyright 2002-2006 Christoph Oelckers -** Copyright 2004-2006 Randy Heit -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** 4. When not used as part of ZDoom or a ZDoom derivative, this code will be -** covered by the terms of the GNU General Public License as published by -** the Free Software Foundation; either version 2 of the License, or (at -** your option) any later version. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- -** -*/ - -#include "gi.h" -#include "g_level.h" -#include "actor.h" -#include "info.h" -#include "sc_man.h" -#include "tarray.h" -#include "w_wad.h" -#include "templates.h" -#include "r_defs.h" -#include "a_pickups.h" -#include "s_sound.h" -#include "cmdlib.h" -#include "p_lnspec.h" -#include "p_enemy.h" -#include "a_action.h" -#include "decallib.h" -#include "m_random.h" -#include "i_system.h" -#include "p_local.h" -#include "c_console.h" -#include "doomerrors.h" -#include "a_sharedglobal.h" -#include "thingdef/thingdef.h" -#include "v_video.h" -#include "v_font.h" -#include "doomstat.h" -#include "v_palette.h" -#include "g_shared/a_specialspot.h" -#include "actorptrselect.h" -#include "m_bbox.h" -#include "r_data/r_translate.h" -#include "p_trace.h" -#include "gstrings.h" - - -static FRandom pr_camissile ("CustomActorfire"); -static FRandom pr_camelee ("CustomMelee"); -static FRandom pr_cabullet ("CustomBullet"); -static FRandom pr_cajump ("CustomJump"); -static FRandom pr_cwbullet ("CustomWpBullet"); -static FRandom pr_cwjump ("CustomWpJump"); -static FRandom pr_cwpunch ("CustomWpPunch"); -static FRandom pr_grenade ("ThrowGrenade"); -static FRandom pr_crailgun ("CustomRailgun"); -static FRandom pr_spawndebris ("SpawnDebris"); -static FRandom pr_spawnitemex ("SpawnItemEx"); -static FRandom pr_burst ("Burst"); -static FRandom pr_monsterrefire ("MonsterRefire"); -static FRandom pr_teleport("A_Teleport"); - -//========================================================================== -// -// ACustomInventory :: CallStateChain -// -// Executes the code pointers in a chain of states -// until there is no next state -// -//========================================================================== - -bool ACustomInventory::CallStateChain (AActor *actor, FState * State) -{ - StateCallData StateCall; - bool result = false; - int counter = 0; - - while (State != NULL) - { - // Assume success. The code pointer will set this to false if necessary - StateCall.State = State; - StateCall.Result = true; - if (State->CallAction(actor, this, &StateCall)) - { - // collect all the results. Even one successful call signifies overall success. - result |= StateCall.Result; - } - - - // Since there are no delays it is a good idea to check for infinite loops here! - counter++; - if (counter >= 10000) break; - - if (StateCall.State == State) - { - // Abort immediately if the state jumps to itself! - if (State == State->GetNextState()) - { - return false; - } - - // If both variables are still the same there was no jump - // so we must advance to the next state. - State = State->GetNextState(); - } - else - { - State = StateCall.State; - } - } - return result; -} - -//========================================================================== -// -// A_RearrangePointers -// -// Allow an actor to change its relationship to other actors by -// copying pointers freely between TARGET MASTER and TRACER. -// Can also assign null value, but does not duplicate A_ClearTarget. -// -//========================================================================== - - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RearrangePointers) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_INT(ptr_target, 0); - ACTION_PARAM_INT(ptr_master, 1); - ACTION_PARAM_INT(ptr_tracer, 2); - ACTION_PARAM_INT(flags, 3); - - // Rearrange pointers internally - - // Fetch all values before modification, so that all fields can get original values - AActor - *gettarget = self->target, - *getmaster = self->master, - *gettracer = self->tracer; - - switch (ptr_target) // pick the new target - { - case AAPTR_MASTER: - self->target = getmaster; - if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self); - break; - case AAPTR_TRACER: - self->target = gettracer; - if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self); - break; - case AAPTR_NULL: - self->target = NULL; - // THIS IS NOT "A_ClearTarget", so no other targeting info is removed - break; - } - - // presently permitting non-monsters to set master - switch (ptr_master) // pick the new master - { - case AAPTR_TARGET: - self->master = gettarget; - if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self); - break; - case AAPTR_TRACER: - self->master = gettracer; - if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self); - break; - case AAPTR_NULL: - self->master = NULL; - break; - } - - switch (ptr_tracer) // pick the new tracer - { - case AAPTR_TARGET: - self->tracer = gettarget; - break; // no verification deemed necessary; the engine never follows a tracer chain(?) - case AAPTR_MASTER: - self->tracer = getmaster; - break; // no verification deemed necessary; the engine never follows a tracer chain(?) - case AAPTR_NULL: - self->tracer = NULL; - break; - } -} - -//========================================================================== -// -// A_TransferPointer -// -// Copy one pointer (MASTER, TARGET or TRACER) from this actor (SELF), -// or from this actor's MASTER, TARGET or TRACER. -// -// You can copy any one of that actor's pointers -// -// Assign the copied pointer to any one pointer in SELF, -// MASTER, TARGET or TRACER. -// -// Any attempt to make an actor point to itself will replace the pointer -// with a null value. -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TransferPointer) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(ptr_source, 0); - ACTION_PARAM_INT(ptr_recepient, 1); - ACTION_PARAM_INT(ptr_sourcefield, 2); - ACTION_PARAM_INT(ptr_recepientfield, 3); - ACTION_PARAM_INT(flags, 4); - - AActor *source, *recepient; - - // Exchange pointers with actors to whom you have pointers (or with yourself, if you must) - - source = COPY_AAPTR(self, ptr_source); - COPY_AAPTR_NOT_NULL(self, recepient, ptr_recepient); // pick an actor to store the provided pointer value - - // convert source from dataprovider to data - - source = COPY_AAPTR(source, ptr_sourcefield); - - if (source == recepient) source = NULL; // The recepient should not acquire a pointer to itself; will write NULL - - if (ptr_recepientfield == AAPTR_DEFAULT) ptr_recepientfield = ptr_sourcefield; // If default: Write to same field as data was read from - - ASSIGN_AAPTR(recepient, ptr_recepientfield, source, flags); -} - -//========================================================================== -// -// A_CopyFriendliness -// -// Join forces with one of the actors you are pointing to (MASTER by default) -// -// Normal CopyFriendliness reassigns health. This function will not. -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CopyFriendliness) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(ptr_source, 0); - - if (self->player) return; - - AActor *source; - COPY_AAPTR_NOT_NULL(self, source, ptr_source); - self->CopyFriendliness(source, false, false); // No change in current target or health -} - -//========================================================================== -// -// Simple flag changers -// -//========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_SetSolid) -{ - self->flags |= MF_SOLID; -} - -DEFINE_ACTION_FUNCTION(AActor, A_UnsetSolid) -{ - self->flags &= ~MF_SOLID; -} - -DEFINE_ACTION_FUNCTION(AActor, A_SetFloat) -{ - self->flags |= MF_FLOAT; -} - -DEFINE_ACTION_FUNCTION(AActor, A_UnsetFloat) -{ - self->flags &= ~(MF_FLOAT|MF_INFLOAT); -} - -//========================================================================== -// -// Customizable attack functions which use actor parameters. -// -//========================================================================== -static void DoAttack (AActor *self, bool domelee, bool domissile, - int MeleeDamage, FSoundID MeleeSound, const PClass *MissileType,fixed_t MissileHeight) -{ - if (self->target == NULL) return; - - A_FaceTarget (self); - if (domelee && MeleeDamage>0 && self->CheckMeleeRange ()) - { - int damage = pr_camelee.HitDice(MeleeDamage); - if (MeleeSound) S_Sound (self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM); - int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); - } - else if (domissile && MissileType != NULL) - { - // This seemingly senseless code is needed for proper aiming. - self->z += MissileHeight + self->GetBobOffset() - 32*FRACUNIT; - AActor *missile = P_SpawnMissileXYZ (self->x, self->y, self->z + 32*FRACUNIT, self, self->target, MissileType, false); - self->z -= MissileHeight + self->GetBobOffset() - 32*FRACUNIT; - - if (missile) - { - // automatic handling of seeker missiles - if (missile->flags2&MF2_SEEKERMISSILE) - { - missile->tracer=self->target; - } - P_CheckMissileSpawn(missile, self->radius); - } - } -} - -DEFINE_ACTION_FUNCTION(AActor, A_MeleeAttack) -{ - int MeleeDamage = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeDamage, 0); - FSoundID MeleeSound = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeSound, 0); - DoAttack(self, true, false, MeleeDamage, MeleeSound, NULL, 0); -} - -DEFINE_ACTION_FUNCTION(AActor, A_MissileAttack) -{ - const PClass *MissileType=PClass::FindClass((ENamedName) self->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None)); - fixed_t MissileHeight= self->GetClass()->Meta.GetMetaFixed (ACMETA_MissileHeight, 32*FRACUNIT); - DoAttack(self, false, true, 0, 0, MissileType, MissileHeight); -} - -DEFINE_ACTION_FUNCTION(AActor, A_ComboAttack) -{ - int MeleeDamage = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeDamage, 0); - FSoundID MeleeSound = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeSound, 0); - const PClass *MissileType=PClass::FindClass((ENamedName) self->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None)); - fixed_t MissileHeight= self->GetClass()->Meta.GetMetaFixed (ACMETA_MissileHeight, 32*FRACUNIT); - DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BasicAttack) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_INT(MeleeDamage, 0); - ACTION_PARAM_SOUND(MeleeSound, 1); - ACTION_PARAM_CLASS(MissileType, 2); - ACTION_PARAM_FIXED(MissileHeight, 3); - - if (MissileType == NULL) return; - DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight); -} - -//========================================================================== -// -// Custom sound functions. -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySound) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_SOUND(soundid, 0); - ACTION_PARAM_INT(channel, 1); - ACTION_PARAM_FLOAT(volume, 2); - ACTION_PARAM_BOOL(looping, 3); - ACTION_PARAM_FLOAT(attenuation, 4); - - if (!looping) - { - S_Sound (self, channel, soundid, volume, attenuation); - } - else - { - if (!S_IsActorPlayingSomething (self, channel&7, soundid)) - { - S_Sound (self, channel | CHAN_LOOP, soundid, volume, attenuation); - } - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSound) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(slot, 0); - - S_StopSound(self, slot); -} - -//========================================================================== -// -// These come from a time when DECORATE constants did not exist yet and -// the sound interface was less flexible. As a result the parameters are -// not optimal and these functions have been deprecated in favor of extending -// A_PlaySound and A_StopSound. -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayWeaponSound) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_SOUND(soundid, 0); - - S_Sound (self, CHAN_WEAPON, soundid, 1, ATTN_NORM); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySoundEx) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_SOUND(soundid, 0); - ACTION_PARAM_NAME(channel, 1); - ACTION_PARAM_BOOL(looping, 2); - ACTION_PARAM_INT(attenuation_raw, 3); - - float attenuation; - switch (attenuation_raw) - { - case -1: attenuation = ATTN_STATIC; break; // drop off rapidly - default: - case 0: attenuation = ATTN_NORM; break; // normal - case 1: - case 2: attenuation = ATTN_NONE; break; // full volume - } - - if (channel < NAME_Auto || channel > NAME_SoundSlot7) - { - channel = NAME_Auto; - } - - if (!looping) - { - S_Sound (self, int(channel) - NAME_Auto, soundid, 1, attenuation); - } - else - { - if (!S_IsActorPlayingSomething (self, int(channel) - NAME_Auto, soundid)) - { - S_Sound (self, (int(channel) - NAME_Auto) | CHAN_LOOP, soundid, 1, attenuation); - } - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSoundEx) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_NAME(channel, 0); - - if (channel > NAME_Auto && channel <= NAME_SoundSlot7) - { - S_StopSound (self, int(channel) - NAME_Auto); - } -} - -//========================================================================== -// -// Generic seeker missile function -// -//========================================================================== -static FRandom pr_seekermissile ("SeekerMissile"); -enum -{ - SMF_LOOK = 1, - SMF_PRECISE = 2, - SMF_CURSPEED = 4, -}; -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SeekerMissile) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(ang1, 0); - ACTION_PARAM_INT(ang2, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_INT(chance, 3); - ACTION_PARAM_INT(distance, 4); - - if ((flags & SMF_LOOK) && (self->tracer == 0) && (pr_seekermissile()tracer = P_RoughMonsterSearch (self, distance, true); - } - if (!P_SeekerMissile(self, clamp(ang1, 0, 90) * ANGLE_1, clamp(ang2, 0, 90) * ANGLE_1, !!(flags & SMF_PRECISE), !!(flags & SMF_CURSPEED))) - { - if (flags & SMF_LOOK) - { // This monster is no longer seekable, so let us look for another one next time. - self->tracer = NULL; - } - } -} - -//========================================================================== -// -// Hitscan attack with a customizable amount of bullets (specified in damage) -// -//========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_BulletAttack) -{ - int i; - int bangle; - int slope; - - if (!self->target) return; - - A_FaceTarget (self); - bangle = self->angle; - - slope = P_AimLineAttack (self, bangle, MISSILERANGE); - - S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); - for (i = self->GetMissileDamage (0, 1); i > 0; --i) - { - int angle = bangle + (pr_cabullet.Random2() << 20); - int damage = ((pr_cabullet()%5)+1)*3; - P_LineAttack(self, angle, MISSILERANGE, slope, damage, - NAME_Hitscan, NAME_BulletPuff); - } -} - - -//========================================================================== -// -// Do the state jump -// -//========================================================================== -static void DoJump(AActor * self, FState * CallingState, FState *jumpto, StateCallData *statecall) -{ - if (jumpto == NULL) return; - - if (statecall != NULL) - { - statecall->State = jumpto; - } - else if (self->player != NULL && CallingState == self->player->psprites[ps_weapon].state) - { - P_SetPsprite(self->player, ps_weapon, jumpto); - } - else if (self->player != NULL && CallingState == self->player->psprites[ps_flash].state) - { - P_SetPsprite(self->player, ps_flash, jumpto); - } - else if (CallingState == self->state) - { - self->SetState (jumpto); - } - else - { - // something went very wrong. This should never happen. - assert(false); - } -} - -// This is just to avoid having to directly reference the internally defined -// CallingState and statecall parameters in the code below. -#define ACTION_JUMP(offset) DoJump(self, CallingState, offset, statecall) - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Jump) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_INT(count, 0); - ACTION_PARAM_INT(maxchance, 1); - - if (count >= 2 && (maxchance >= 256 || pr_cajump() < maxchance)) - { - int jumps = 2 + (count == 2? 0 : (pr_cajump() % (count - 1))); - ACTION_PARAM_STATE(jumpto, jumps); - ACTION_JUMP(jumpto); - } - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfHealthLower) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_INT(health, 0); - ACTION_PARAM_STATE(jump, 1); - ACTION_PARAM_INT(ptr_selector, 2); - - AActor *measured; - - measured = COPY_AAPTR(self, ptr_selector); - - if (measured && measured->health < health) - { - ACTION_JUMP(jump); - } - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetOutsideMeleeRange) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - if (!self->CheckMeleeRange()) - { - ACTION_JUMP(jump); - } - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInsideMeleeRange) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - if (self->CheckMeleeRange()) - { - ACTION_JUMP(jump); - } - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! -} -//========================================================================== -// -// State jump function -// -//========================================================================== -void DoJumpIfCloser(AActor *target, DECLARE_PARAMINFO) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_FIXED(dist, 0); - ACTION_PARAM_STATE(jump, 1); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - // No target - no jump - if (target != NULL && P_AproxDistance(self->x-target->x, self->y-target->y) < dist && - ( (self->z > target->z && self->z - (target->z + target->height) < dist) || - (self->z <=target->z && target->z - (self->z + self->height) < dist) - ) - ) - { - ACTION_JUMP(jump); - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfCloser) -{ - AActor *target; - - if (!self->player) - { - target = self->target; - } - else - { - // Does the player aim at something that can be shot? - P_BulletSlope(self, &target); - } - DoJumpIfCloser(target, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTracerCloser) -{ - DoJumpIfCloser(self->tracer, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfMasterCloser) -{ - DoJumpIfCloser(self->master, PUSH_PARAMINFO); -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -void DoJumpIfInventory(AActor * owner, DECLARE_PARAMINFO) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_CLASS(Type, 0); - ACTION_PARAM_INT(ItemAmount, 1); - ACTION_PARAM_STATE(JumpOffset, 2); - ACTION_PARAM_INT(setowner, 3); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - if (!Type) return; - COPY_AAPTR_NOT_NULL(owner, owner, setowner); // returns if owner ends up being NULL - - AInventory *Item = owner->FindInventory(Type); - - if (Item) - { - if (ItemAmount > 0) - { - if (Item->Amount >= ItemAmount) - ACTION_JUMP(JumpOffset); - } - else if (Item->Amount >= Item->MaxAmount) - { - ACTION_JUMP(JumpOffset); - } - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInventory) -{ - DoJumpIfInventory(self, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetInventory) -{ - DoJumpIfInventory(self->target, PUSH_PARAMINFO); -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfArmorType) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_NAME(Type, 0); - ACTION_PARAM_STATE(JumpOffset, 1); - ACTION_PARAM_INT(amount, 2); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - ABasicArmor * armor = (ABasicArmor *) self->FindInventory(NAME_BasicArmor); - - if (armor && armor->ArmorType == Type && armor->Amount >= amount) - ACTION_JUMP(JumpOffset); -} - -//========================================================================== -// -// Parameterized version of A_Explode -// -//========================================================================== - -enum -{ - XF_HURTSOURCE = 1, - XF_NOTMISSILE = 4, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Explode) -{ - ACTION_PARAM_START(8); - ACTION_PARAM_INT(damage, 0); - ACTION_PARAM_INT(distance, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_BOOL(alert, 3); - ACTION_PARAM_INT(fulldmgdistance, 4); - ACTION_PARAM_INT(nails, 5); - ACTION_PARAM_INT(naildamage, 6); - ACTION_PARAM_CLASS(pufftype, 7); - - if (damage < 0) // get parameters from metadata - { - damage = self->GetClass()->Meta.GetMetaInt (ACMETA_ExplosionDamage, 128); - distance = self->GetClass()->Meta.GetMetaInt (ACMETA_ExplosionRadius, damage); - flags = !self->GetClass()->Meta.GetMetaInt (ACMETA_DontHurtShooter); - alert = false; - } - else - { - if (distance <= 0) distance = damage; - } - // NailBomb effect, from SMMU but not from its source code: instead it was implemented and - // generalized from the documentation at http://www.doomworld.com/eternity/engine/codeptrs.html - - if (nails) - { - angle_t ang; - for (int i = 0; i < nails; i++) - { - ang = i*(ANGLE_MAX/nails); - // Comparing the results of a test wad with Eternity, it seems A_NailBomb does not aim - P_LineAttack (self, ang, MISSILERANGE, 0, - //P_AimLineAttack (self, ang, MISSILERANGE), - naildamage, NAME_Hitscan, pufftype); - } - } - - P_RadiusAttack (self, self->target, damage, distance, self->DamageType, flags, fulldmgdistance); - P_CheckSplash(self, distance<target != NULL && self->target->player != NULL) - { - validcount++; - P_RecursiveSound (self->Sector, self->target, false, 0); - } -} - -//========================================================================== -// -// A_RadiusThrust -// -//========================================================================== - -enum -{ - RTF_AFFECTSOURCE = 1, - RTF_NOIMPACTDAMAGE = 2, - RTF_NOTMISSILE = 4, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusThrust) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_INT(force, 0); - ACTION_PARAM_INT(distance, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_INT(fullthrustdistance, 3); - - bool sourcenothrust = false; - - if (force == 0) force = 128; - if (distance <= 0) distance = abs(force); - - // Temporarily negate MF2_NODMGTHRUST on the shooter, since it renders this function useless. - if (!(flags & RTF_NOTMISSILE) && self->target != NULL && self->target->flags2 & MF2_NODMGTHRUST) - { - sourcenothrust = true; - self->target->flags2 &= ~MF2_NODMGTHRUST; - } - - P_RadiusAttack (self, self->target, force, distance, self->DamageType, flags | RADF_NODAMAGE, fullthrustdistance); - P_CheckSplash(self, distance << FRACBITS); - - if (sourcenothrust) - { - self->target->flags2 |= MF2_NODMGTHRUST; - } -} - -//========================================================================== -// -// Execute a line special / script -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CallSpecial) -{ - ACTION_PARAM_START(6); - ACTION_PARAM_INT(special, 0); - ACTION_PARAM_INT(arg1, 1); - ACTION_PARAM_INT(arg2, 2); - ACTION_PARAM_INT(arg3, 3); - ACTION_PARAM_INT(arg4, 4); - ACTION_PARAM_INT(arg5, 5); - - bool res = !!P_ExecuteSpecial(special, NULL, self, false, arg1, arg2, arg3, arg4, arg5); - - ACTION_SET_RESULT(res); -} - -//========================================================================== -// -// The ultimate code pointer: Fully customizable missiles! -// -//========================================================================== -enum CM_Flags -{ - CMF_AIMMODE = 3, - CMF_TRACKOWNER = 4, - CMF_CHECKTARGETDEAD = 8, - - CMF_ABSOLUTEPITCH = 16, - CMF_OFFSETPITCH = 32, - CMF_SAVEPITCH = 64, - - CMF_ABSOLUTEANGLE = 128 -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomMissile) -{ - ACTION_PARAM_START(7); - ACTION_PARAM_CLASS(ti, 0); - ACTION_PARAM_FIXED(SpawnHeight, 1); - ACTION_PARAM_INT(Spawnofs_XY, 2); - ACTION_PARAM_ANGLE(Angle, 3); - ACTION_PARAM_INT(flags, 4); - ACTION_PARAM_ANGLE(pitch, 5); - ACTION_PARAM_INT(ptr, 6); - - AActor *ref = COPY_AAPTR(self, ptr); - - int aimmode = flags & CMF_AIMMODE; - - AActor * targ; - AActor * missile; - - if (ref != NULL || aimmode==2) - { - if (ti) - { - angle_t ang = (self->angle - ANGLE_90) >> ANGLETOFINESHIFT; - fixed_t x = Spawnofs_XY * finecosine[ang]; - fixed_t y = Spawnofs_XY * finesine[ang]; - fixed_t z = SpawnHeight + self->GetBobOffset() - 32*FRACUNIT + (self->player? self->player->crouchoffset : 0); - - switch (aimmode) - { - case 0: - default: - // same adjustment as above (in all 3 directions this time) - for better aiming! - self->x += x; - self->y += y; - self->z += z; - missile = P_SpawnMissileXYZ(self->x, self->y, self->z + 32*FRACUNIT, self, ref, ti, false); - self->x -= x; - self->y -= y; - self->z -= z; - break; - - case 1: - missile = P_SpawnMissileXYZ(self->x+x, self->y+y, self->z + self->GetBobOffset() + SpawnHeight, self, ref, ti, false); - break; - - case 2: - self->x += x; - self->y += y; - missile = P_SpawnMissileAngleZSpeed(self, self->z + self->GetBobOffset() + SpawnHeight, ti, self->angle, 0, GetDefaultByType(ti)->Speed, self, false); - self->x -= x; - self->y -= y; - - flags |= CMF_ABSOLUTEPITCH; - - break; - } - - if (missile) - { - // Use the actual velocity instead of the missile's Speed property - // so that this can handle missiles with a high vertical velocity - // component properly. - - fixed_t missilespeed; - - if ( (CMF_ABSOLUTEPITCH|CMF_OFFSETPITCH) & flags) - { - if (CMF_OFFSETPITCH & flags) - { - FVector2 velocity (missile->velx, missile->vely); - pitch += R_PointToAngle2(0,0, (fixed_t)velocity.Length(), missile->velz); - } - ang = pitch >> ANGLETOFINESHIFT; - missilespeed = abs(FixedMul(finecosine[ang], missile->Speed)); - missile->velz = FixedMul(finesine[ang], missile->Speed); - } - else - { - FVector2 velocity (missile->velx, missile->vely); - missilespeed = (fixed_t)velocity.Length(); - } - - if (CMF_SAVEPITCH & flags) - { - missile->pitch = pitch; - // In aimmode 0 and 1 without absolutepitch or offsetpitch, the pitch parameter - // contains the unapplied parameter. In that case, it is set as pitch without - // otherwise affecting the spawned actor. - } - - missile->angle = (CMF_ABSOLUTEANGLE & flags) ? Angle : missile->angle + Angle ; - - ang = missile->angle >> ANGLETOFINESHIFT; - missile->velx = FixedMul (missilespeed, finecosine[ang]); - missile->vely = FixedMul (missilespeed, finesine[ang]); - - // handle projectile shooting projectiles - track the - // links back to a real owner - if (self->isMissile(!!(flags & CMF_TRACKOWNER))) - { - AActor * owner=self ;//->target; - while (owner->isMissile(!!(flags & CMF_TRACKOWNER)) && owner->target) owner=owner->target; - targ=owner; - missile->target=owner; - // automatic handling of seeker missiles - if (self->flags & missile->flags2 & MF2_SEEKERMISSILE) - { - missile->tracer=self->tracer; - } - } - else if (missile->flags2&MF2_SEEKERMISSILE) - { - // automatic handling of seeker missiles - missile->tracer=self->target; - } - // we must redo the spectral check here because the owner is set after spawning so the FriendPlayer value may be wrong - if (missile->flags4 & MF4_SPECTRAL) - { - if (missile->target != NULL) - { - missile->SetFriendPlayer(missile->target->player); - } - else - { - missile->FriendPlayer = 0; - } - } - P_CheckMissileSpawn(missile, self->radius); - } - } - } - else if (flags & CMF_CHECKTARGETDEAD) - { - // Target is dead and the attack shall be aborted. - if (self->SeeState != NULL && (self->health > 0 || !(self->flags3 & MF3_ISMONSTER))) self->SetState(self->SeeState); - } -} - -//========================================================================== -// -// An even more customizable hitscan attack -// -//========================================================================== -enum CBA_Flags -{ - CBAF_AIMFACING = 1, - CBAF_NORANDOM = 2, - CBAF_EXPLICITANGLE = 4, - CBAF_NOPITCH = 8, - CBAF_NORANDOMPUFFZ = 16, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomBulletAttack) -{ - ACTION_PARAM_START(8); - ACTION_PARAM_ANGLE(Spread_XY, 0); - ACTION_PARAM_ANGLE(Spread_Z, 1); - ACTION_PARAM_INT(NumBullets, 2); - ACTION_PARAM_INT(DamagePerBullet, 3); - ACTION_PARAM_CLASS(pufftype, 4); - ACTION_PARAM_FIXED(Range, 5); - ACTION_PARAM_INT(Flags, 6); - ACTION_PARAM_INT(ptr, 7); - - AActor *ref = COPY_AAPTR(self, ptr); - - if(Range==0) Range=MISSILERANGE; - - int i; - int bangle; - int bslope = 0; - int laflags = (Flags & CBAF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; - - if (ref || (Flags & CBAF_AIMFACING)) - { - if (!(Flags & CBAF_AIMFACING)) - { - A_Face(self, ref); - } - bangle = self->angle; - - if (!pufftype) pufftype = PClass::FindClass(NAME_BulletPuff); - - if (!(Flags & CBAF_NOPITCH)) bslope = P_AimLineAttack (self, bangle, MISSILERANGE); - - S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); - for (i=0 ; itarget) - return; - - A_FaceTarget (self); - if (self->CheckMeleeRange ()) - { - if (MeleeSound) S_Sound (self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM); - int newdam = P_DamageMobj (self->target, self, self, damage, DamageType); - if (bleed) P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); - } - else - { - if (MissSound) S_Sound (self, CHAN_WEAPON, MissSound, 1, ATTN_NORM); - } -} - -//========================================================================== -// -// A fully customizable combo attack -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomComboAttack) -{ - ACTION_PARAM_START(6); - ACTION_PARAM_CLASS(ti, 0); - ACTION_PARAM_FIXED(SpawnHeight, 1); - ACTION_PARAM_INT(damage, 2); - ACTION_PARAM_SOUND(MeleeSound, 3); - ACTION_PARAM_NAME(DamageType, 4); - ACTION_PARAM_BOOL(bleed, 5); - - if (!self->target) - return; - - A_FaceTarget (self); - if (self->CheckMeleeRange ()) - { - if (DamageType==NAME_None) DamageType = NAME_Melee; // Melee is the default type - if (MeleeSound) S_Sound (self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM); - int newdam = P_DamageMobj (self->target, self, self, damage, DamageType); - if (bleed) P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); - } - else if (ti) - { - // This seemingly senseless code is needed for proper aiming. - self->z += SpawnHeight + self->GetBobOffset() - 32*FRACUNIT; - AActor *missile = P_SpawnMissileXYZ (self->x, self->y, self->z + 32*FRACUNIT, self, self->target, ti, false); - self->z -= SpawnHeight + self->GetBobOffset() - 32*FRACUNIT; - - if (missile) - { - // automatic handling of seeker missiles - if (missile->flags2&MF2_SEEKERMISSILE) - { - missile->tracer=self->target; - } - P_CheckMissileSpawn(missile, self->radius); - } - } -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfNoAmmo) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (!ACTION_CALL_FROM_WEAPON()) return; - - if (!self->player->ReadyWeapon->CheckAmmo(self->player->ReadyWeapon->bAltFire, false, true)) - { - ACTION_JUMP(jump); - } - -} - - -//========================================================================== -// -// An even more customizable hitscan attack -// -//========================================================================== -enum FB_Flags -{ - FBF_USEAMMO = 1, - FBF_NORANDOM = 2, - FBF_EXPLICITANGLE = 4, - FBF_NOPITCH = 8, - FBF_NOFLASH = 16, - FBF_NORANDOMPUFFZ = 32, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) -{ - ACTION_PARAM_START(7); - ACTION_PARAM_ANGLE(Spread_XY, 0); - ACTION_PARAM_ANGLE(Spread_Z, 1); - ACTION_PARAM_INT(NumberOfBullets, 2); - ACTION_PARAM_INT(DamagePerBullet, 3); - ACTION_PARAM_CLASS(PuffType, 4); - ACTION_PARAM_INT(Flags, 5); - ACTION_PARAM_FIXED(Range, 6); - - if (!self->player) return; - - player_t * player=self->player; - AWeapon * weapon=player->ReadyWeapon; - - int i; - int bangle; - int bslope = 0; - int laflags = (Flags & FBF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; - - if ((Flags & FBF_USEAMMO) && weapon) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // out of ammo - } - - if (Range == 0) Range = PLAYERMISSILERANGE; - - if (!(Flags & FBF_NOFLASH)) static_cast(self)->PlayAttacking2 (); - - if (!(Flags & FBF_NOPITCH)) bslope = P_BulletSlope(self); - bangle = self->angle; - - if (!PuffType) PuffType = PClass::FindClass(NAME_BulletPuff); - - if (weapon != NULL) - { - S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); - } - - if ((NumberOfBullets==1 && !player->refire) || NumberOfBullets==0) - { - int damage = DamagePerBullet; - - if (!(Flags & FBF_NORANDOM)) - damage *= ((pr_cwbullet()%3)+1); - - P_LineAttack(self, bangle, Range, bslope, damage, NAME_Hitscan, PuffType, laflags); - } - else - { - if (NumberOfBullets == -1) NumberOfBullets = 1; - for (i=0 ; iplayer) return; - - - player_t *player=self->player; - AWeapon * weapon=player->ReadyWeapon; - AActor *linetarget; - - if (UseAmmo && weapon) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // out of ammo - } - - if (ti) - { - angle_t ang = (self->angle - ANGLE_90) >> ANGLETOFINESHIFT; - fixed_t x = SpawnOfs_XY * finecosine[ang]; - fixed_t y = SpawnOfs_XY * finesine[ang]; - fixed_t z = SpawnHeight; - fixed_t shootangle = self->angle; - - if (Flags & FPF_AIMATANGLE) shootangle += Angle; - - // Temporarily adjusts the pitch - fixed_t SavedPlayerPitch = self->pitch; - self->pitch -= pitch; - AActor * misl=P_SpawnPlayerMissile (self, x, y, z, ti, shootangle, &linetarget); - self->pitch = SavedPlayerPitch; - - // automatic handling of seeker missiles - if (misl) - { - if (Flags & FPF_TRANSFERTRANSLATION) misl->Translation = self->Translation; - if (linetarget && misl->flags2&MF2_SEEKERMISSILE) misl->tracer=linetarget; - if (!(Flags & FPF_AIMATANGLE)) - { - // This original implementation is to aim straight ahead and then offset - // the angle from the resulting direction. - FVector3 velocity(misl->velx, misl->vely, 0); - fixed_t missilespeed = (fixed_t)velocity.Length(); - misl->angle += Angle; - angle_t an = misl->angle >> ANGLETOFINESHIFT; - misl->velx = FixedMul (missilespeed, finecosine[an]); - misl->vely = FixedMul (missilespeed, finesine[an]); - } - } - } -} - - -//========================================================================== -// -// A_CustomPunch -// -// Berserk is not handled here. That can be done with A_CheckIfInventory -// -//========================================================================== - -enum -{ - CPF_USEAMMO = 1, - CPF_DAGGER = 2, - CPF_PULLIN = 4, - CPF_NORANDOMPUFFZ = 8, - CPF_NOTURN = 16, - CPF_STEALARMOR = 32, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomPunch) -{ - ACTION_PARAM_START(8); - ACTION_PARAM_INT(Damage, 0); - ACTION_PARAM_BOOL(norandom, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_CLASS(PuffType, 3); - ACTION_PARAM_FIXED(Range, 4); - ACTION_PARAM_FIXED(LifeSteal, 5); - ACTION_PARAM_INT(lifestealmax, 6); - ACTION_PARAM_CLASS(armorbonustype, 7); - - if (!self->player) return; - - player_t *player=self->player; - AWeapon * weapon=player->ReadyWeapon; - - - angle_t angle; - int pitch; - AActor * linetarget; - int actualdamage; - - if (!norandom) Damage *= (pr_cwpunch()%8+1); - - angle = self->angle + (pr_cwpunch.Random2() << 18); - if (Range == 0) Range = MELEERANGE; - pitch = P_AimLineAttack (self, angle, Range, &linetarget); - - // only use ammo when actually hitting something! - if ((flags & CPF_USEAMMO) && linetarget && weapon) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // out of ammo - } - - if (!PuffType) PuffType = PClass::FindClass(NAME_BulletPuff); - int puffFlags = LAF_ISMELEEATTACK | ((flags & CPF_NORANDOMPUFFZ) ? LAF_NORANDOMPUFFZ : 0); - - P_LineAttack (self, angle, Range, pitch, Damage, NAME_Melee, PuffType, puffFlags, &linetarget, &actualdamage); - - if (linetarget) - { - if (LifeSteal && !(linetarget->flags5 & MF5_DONTDRAIN)) - { - if (flags & CPF_STEALARMOR) - { - if (!armorbonustype) armorbonustype = PClass::FindClass("ArmorBonus"); - - if (armorbonustype->IsDescendantOf (RUNTIME_CLASS(ABasicArmorBonus))) - { - ABasicArmorBonus *armorbonus = static_cast(Spawn (armorbonustype, 0,0,0, NO_REPLACE)); - armorbonus->SaveAmount *= (actualdamage * LifeSteal) >> FRACBITS; - armorbonus->MaxSaveAmount = lifestealmax <= 0 ? armorbonus->MaxSaveAmount : lifestealmax; - armorbonus->flags |= MF_DROPPED; - armorbonus->ClearCounters(); - - if (!armorbonus->CallTryPickup (self)) - { - armorbonus->Destroy (); - } - } - } - - else - { - P_GiveBody (self, (actualdamage * LifeSteal) >> FRACBITS, lifestealmax); - } - } - - if (weapon != NULL) - { - S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); - } - - if (!(flags & CPF_NOTURN)) - { - // turn to face target - self->angle = R_PointToAngle2 (self->x, - self->y, - linetarget->x, - linetarget->y); - } - - if (flags & CPF_PULLIN) self->flags |= MF_JUSTATTACKED; - if (flags & CPF_DAGGER) P_DaggerAlert (self, linetarget); - } -} - - -//========================================================================== -// -// customizable railgun attack function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack) -{ - ACTION_PARAM_START(16); - ACTION_PARAM_INT(Damage, 0); - ACTION_PARAM_INT(Spawnofs_XY, 1); - ACTION_PARAM_BOOL(UseAmmo, 2); - ACTION_PARAM_COLOR(Color1, 3); - ACTION_PARAM_COLOR(Color2, 4); - ACTION_PARAM_INT(Flags, 5); - ACTION_PARAM_FLOAT(MaxDiff, 6); - ACTION_PARAM_CLASS(PuffType, 7); - ACTION_PARAM_ANGLE(Spread_XY, 8); - ACTION_PARAM_ANGLE(Spread_Z, 9); - ACTION_PARAM_FIXED(Range, 10); - ACTION_PARAM_INT(Duration, 11); - ACTION_PARAM_FLOAT(Sparsity, 12); - ACTION_PARAM_FLOAT(DriftSpeed, 13); - ACTION_PARAM_CLASS(SpawnClass, 14); - ACTION_PARAM_FIXED(Spawnofs_Z, 15); - - if(Range==0) Range=8192*FRACUNIT; - if(Sparsity==0) Sparsity=1.0; - - if (!self->player) return; - - AWeapon * weapon=self->player->ReadyWeapon; - - // only use ammo when actually hitting something! - if (UseAmmo) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // out of ammo - } - - angle_t angle; - angle_t slope; - - if (Flags & RAF_EXPLICITANGLE) - { - angle = Spread_XY; - slope = Spread_Z; - } - else - { - angle = pr_crailgun.Random2() * (Spread_XY / 255); - slope = pr_crailgun.Random2() * (Spread_Z / 255); - } - - P_RailAttack (self, Damage, Spawnofs_XY, Spawnofs_Z, Color1, Color2, MaxDiff, Flags, PuffType, angle, slope, Range, Duration, Sparsity, DriftSpeed, SpawnClass); -} - -//========================================================================== -// -// also for monsters -// -//========================================================================== -enum -{ - CRF_DONTAIM = 0, - CRF_AIMPARALLEL = 1, - CRF_AIMDIRECT = 2, - CRF_EXPLICITANGLE = 4, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) -{ - ACTION_PARAM_START(16); - ACTION_PARAM_INT(Damage, 0); - ACTION_PARAM_INT(Spawnofs_XY, 1); - ACTION_PARAM_COLOR(Color1, 2); - ACTION_PARAM_COLOR(Color2, 3); - ACTION_PARAM_INT(Flags, 4); - ACTION_PARAM_INT(aim, 5); - ACTION_PARAM_FLOAT(MaxDiff, 6); - ACTION_PARAM_CLASS(PuffType, 7); - ACTION_PARAM_ANGLE(Spread_XY, 8); - ACTION_PARAM_ANGLE(Spread_Z, 9); - ACTION_PARAM_FIXED(Range, 10); - ACTION_PARAM_INT(Duration, 11); - ACTION_PARAM_FLOAT(Sparsity, 12); - ACTION_PARAM_FLOAT(DriftSpeed, 13); - ACTION_PARAM_CLASS(SpawnClass, 14); - ACTION_PARAM_FIXED(Spawnofs_Z, 15); - - if(Range==0) Range=8192*FRACUNIT; - if(Sparsity==0) Sparsity=1.0; - - AActor *linetarget; - - fixed_t saved_x = self->x; - fixed_t saved_y = self->y; - angle_t saved_angle = self->angle; - fixed_t saved_pitch = self->pitch; - - if (aim && self->target == NULL) - { - return; - } - // [RH] Andy Baker's stealth monsters - if (self->flags & MF_STEALTH) - { - self->visdir = 1; - } - - self->flags &= ~MF_AMBUSH; - - - if (aim) - { - self->angle = R_PointToAngle2 (self->x, - self->y, - self->target->x, - self->target->y); - } - self->pitch = P_AimLineAttack (self, self->angle, MISSILERANGE, &linetarget, ANGLE_1*60, 0, aim ? self->target : NULL); - if (linetarget == NULL && aim) - { - // We probably won't hit the target, but aim at it anyway so we don't look stupid. - FVector2 xydiff(self->target->x - self->x, self->target->y - self->y); - double zdiff = (self->target->z + (self->target->height>>1)) - - (self->z + (self->height>>1) - self->floorclip); - self->pitch = int(atan2(zdiff, xydiff.Length()) * ANGLE_180 / -M_PI); - } - // Let the aim trail behind the player - if (aim) - { - saved_angle = self->angle = R_PointToAngle2 (self->x, self->y, - self->target->x - self->target->velx * 3, - self->target->y - self->target->vely * 3); - - if (aim == CRF_AIMDIRECT) - { - // Tricky: We must offset to the angle of the current position - // but then change the angle again to ensure proper aim. - self->x += Spawnofs_XY * finecosine[self->angle]; - self->y += Spawnofs_XY * finesine[self->angle]; - Spawnofs_XY = 0; - self->angle = R_PointToAngle2 (self->x, self->y, - self->target->x - self->target->velx * 3, - self->target->y - self->target->vely * 3); - } - - if (self->target->flags & MF_SHADOW) - { - angle_t rnd = pr_crailgun.Random2() << 21; - self->angle += rnd; - saved_angle = rnd; - } - } - - angle_t angle = (self->angle - ANG90) >> ANGLETOFINESHIFT; - - angle_t angleoffset; - angle_t slopeoffset; - - if (Flags & CRF_EXPLICITANGLE) - { - angleoffset = Spread_XY; - slopeoffset = Spread_Z; - } - else - { - angleoffset = pr_crailgun.Random2() * (Spread_XY / 255); - slopeoffset = pr_crailgun.Random2() * (Spread_Z / 255); - } - - P_RailAttack (self, Damage, Spawnofs_XY, Spawnofs_Z, Color1, Color2, MaxDiff, Flags, PuffType, angleoffset, slopeoffset, Range, Duration, Sparsity, DriftSpeed, SpawnClass); - - self->x = saved_x; - self->y = saved_y; - self->angle = saved_angle; - self->pitch = saved_pitch; -} - -//=========================================================================== -// -// DoGiveInventory -// -//=========================================================================== - -static void DoGiveInventory(AActor * receiver, bool use_aaptr, DECLARE_PARAMINFO) -{ - ACTION_PARAM_START(2+use_aaptr); - ACTION_PARAM_CLASS(mi, 0); - ACTION_PARAM_INT(amount, 1); - - if (use_aaptr) - { - ACTION_PARAM_INT(setreceiver, 2); - COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver); - } - - bool res=true; - - if (amount==0) amount=1; - if (mi) - { - AInventory *item = static_cast(Spawn (mi, 0, 0, 0, NO_REPLACE)); - if (!item) - { - ACTION_SET_RESULT(false); - return; - } - if (item->IsKindOf(RUNTIME_CLASS(AHealth))) - { - item->Amount *= amount; - } - else - { - item->Amount = amount; - } - item->flags |= MF_DROPPED; - item->ClearCounters(); - if (!item->CallTryPickup (receiver)) - { - item->Destroy (); - res = false; - } - else res = true; - } - else res = false; - ACTION_SET_RESULT(res); - -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveInventory) -{ - DoGiveInventory(self, true, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToTarget) -{ - DoGiveInventory(self->target, true, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToChildren) -{ - TThinkerIterator it; - AActor * mo; - - while ((mo = it.Next())) - { - if (mo->master == self) DoGiveInventory(mo, false, PUSH_PARAMINFO); - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToSiblings) -{ - TThinkerIterator it; - AActor * mo; - - if (self->master != NULL) - { - while ((mo = it.Next())) - { - if (mo->master == self->master && mo != self) DoGiveInventory(mo, false, PUSH_PARAMINFO); - } - } -} - -//=========================================================================== -// -// A_TakeInventory -// -//=========================================================================== - -enum -{ - TIF_NOTAKEINFINITE = 1, -}; - -void DoTakeInventory(AActor * receiver, bool use_aaptr, DECLARE_PARAMINFO) -{ - ACTION_PARAM_START(3+use_aaptr); - ACTION_PARAM_CLASS(item, 0); - ACTION_PARAM_INT(amount, 1); - ACTION_PARAM_INT(flags, 2); - - if (!item) - { - ACTION_SET_RESULT(false); - return; - } - if (use_aaptr) - { - ACTION_PARAM_INT(setreceiver, 3); - COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver); - } - - bool res = false; - - AInventory * inv = receiver->FindInventory(item); - - if (inv && !inv->IsKindOf(RUNTIME_CLASS(AHexenArmor))) - { - if (inv->Amount > 0) - { - res = true; - } - // Do not take ammo if the "no take infinite/take as ammo depletion" flag is set - // and infinite ammo is on - if (flags & TIF_NOTAKEINFINITE && - ((dmflags & DF_INFINITE_AMMO) || (receiver->player->cheats & CF_INFINITEAMMO)) && - inv->IsKindOf(RUNTIME_CLASS(AAmmo))) - { - // Nothing to do here, except maybe res = false;? Would it make sense? - } - else if (!amount || amount>=inv->Amount) - { - if (inv->ItemFlags&IF_KEEPDEPLETED) inv->Amount=0; - else inv->Destroy(); - } - else inv->Amount-=amount; - } - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeInventory) -{ - DoTakeInventory(self, true, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromTarget) -{ - DoTakeInventory(self->target, true, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromChildren) -{ - TThinkerIterator it; - AActor * mo; - - while ((mo = it.Next())) - { - if (mo->master == self) DoTakeInventory(mo, false, PUSH_PARAMINFO); - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromSiblings) -{ - TThinkerIterator it; - AActor * mo; - - if (self->master != NULL) - { - while ((mo = it.Next())) - { - if (mo->master == self->master && mo != self) DoTakeInventory(mo, false, PUSH_PARAMINFO); - } - } -} - -//=========================================================================== -// -// Common code for A_SpawnItem and A_SpawnItemEx -// -//=========================================================================== - -enum SIX_Flags -{ - SIXF_TRANSFERTRANSLATION = 1 << 0, - SIXF_ABSOLUTEPOSITION = 1 << 1, - SIXF_ABSOLUTEANGLE = 1 << 2, - SIXF_ABSOLUTEVELOCITY = 1 << 3, - SIXF_SETMASTER = 1 << 4, - SIXF_NOCHECKPOSITION = 1 << 5, - SIXF_TELEFRAG = 1 << 6, - SIXF_CLIENTSIDE = 1 << 7, // only used by Skulldronum - SIXF_TRANSFERAMBUSHFLAG = 1 << 8, - SIXF_TRANSFERPITCH = 1 << 9, - SIXF_TRANSFERPOINTERS = 1 << 10, - SIXF_USEBLOODCOLOR = 1 << 11, - SIXF_CLEARCALLERTID = 1 << 12, - SIXF_MULTIPLYSPEED = 1 << 13, - SIXF_TRANSFERSCALE = 1 << 14, - SIXF_TRANSFERSPECIAL = 1 << 15, - SIXF_CLEARCALLERSPECIAL = 1 << 16, - SIXF_TRANSFERSTENCILCOL = 1 << 17, - SIXF_TRANSFERALPHA = 1 << 18, - SIXF_TRANSFERRENDERSTYLE = 1 << 19, - SIXF_SETTARGET = 1 << 20, - SIXF_SETTRACER = 1 << 21, - SIXF_NOPOINTERS = 1 << 22, - SIXF_ORIGINATOR = 1 << 23, -}; - -static bool InitSpawnedItem(AActor *self, AActor *mo, int flags) -{ - if (mo == NULL) - { - return false; - } - AActor *originator = self; - - if (!(mo->flags2 & MF2_DONTTRANSLATE)) - { - if (flags & SIXF_TRANSFERTRANSLATION) - { - mo->Translation = self->Translation; - } - else if (flags & SIXF_USEBLOODCOLOR) - { - // [XA] Use the spawning actor's BloodColor to translate the newly-spawned object. - PalEntry bloodcolor = self->GetBloodColor(); - mo->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a); - } - } - if (flags & SIXF_TRANSFERPOINTERS) - { - mo->target = self->target; - mo->master = self->master; // This will be overridden later if SIXF_SETMASTER is set - mo->tracer = self->tracer; - } - - mo->angle = self->angle; - if (flags & SIXF_TRANSFERPITCH) - { - mo->pitch = self->pitch; - } - if (!(flags & SIXF_ORIGINATOR)) - { - while (originator && originator->isMissile()) - { - originator = originator->target; - } - } - if (flags & SIXF_TELEFRAG) - { - P_TeleportMove(mo, mo->x, mo->y, mo->z, true); - // This is needed to ensure consistent behavior. - // Otherwise it will only spawn if nothing gets telefragged - flags |= SIXF_NOCHECKPOSITION; - } - if (mo->flags3 & MF3_ISMONSTER) - { - if (!(flags & SIXF_NOCHECKPOSITION) && !P_TestMobjLocation(mo)) - { - // The monster is blocked so don't spawn it at all! - mo->ClearCounters(); - mo->Destroy(); - return false; - } - else if (originator && !(flags & SIXF_NOPOINTERS)) - { - if (originator->flags3 & MF3_ISMONSTER) - { - // If this is a monster transfer all friendliness information - mo->CopyFriendliness(originator, true); - } - else if (originator->player) - { - // A player always spawns a monster friendly to him - mo->flags |= MF_FRIENDLY; - mo->SetFriendPlayer(originator->player); - - AActor * attacker=originator->player->attacker; - if (attacker) - { - if (!(attacker->flags&MF_FRIENDLY) || - (deathmatch && attacker->FriendPlayer!=0 && attacker->FriendPlayer!=mo->FriendPlayer)) - { - // Target the monster which last attacked the player - mo->LastHeard = mo->target = attacker; - } - } - } - } - } - else if (!(flags & SIXF_TRANSFERPOINTERS)) - { - // If this is a missile or something else set the target to the originator - mo->target = originator ? originator : self; - } - if (flags & SIXF_NOPOINTERS) - { - //[MC]Intentionally eliminate pointers. Overrides TRANSFERPOINTERS, but is overridden by SETMASTER/TARGET/TRACER. - mo->LastHeard = NULL; //Sanity check. - mo->target = NULL; - mo->master = NULL; - mo->tracer = NULL; - } - if (flags & SIXF_SETMASTER) - { - mo->master = originator; - } - if (flags & SIXF_SETTARGET) - { - mo->target = originator; - } - if (flags & SIXF_SETTRACER) - { - mo->tracer = originator; - } - if (flags & SIXF_TRANSFERSCALE) - { - mo->scaleX = self->scaleX; - mo->scaleY = self->scaleY; - } - if (flags & SIXF_TRANSFERAMBUSHFLAG) - { - mo->flags = (mo->flags & ~MF_AMBUSH) | (self->flags & MF_AMBUSH); - } - if (flags & SIXF_CLEARCALLERTID) - { - self->RemoveFromHash(); - self->tid = 0; - } - if (flags & SIXF_TRANSFERSPECIAL) - { - mo->special = self->special; - memcpy(mo->args, self->args, sizeof(self->args)); - } - if (flags & SIXF_CLEARCALLERSPECIAL) - { - self->special = 0; - memset(self->args, 0, sizeof(self->args)); - } - if (flags & SIXF_TRANSFERSTENCILCOL) - { - mo->fillcolor = self->fillcolor; - } - if (flags & SIXF_TRANSFERALPHA) - { - mo->alpha = self->alpha; - } - if (flags & SIXF_TRANSFERRENDERSTYLE) - { - mo->RenderStyle = self->RenderStyle; - } - - return true; -} - -//=========================================================================== -// -// A_SpawnItem -// -// Spawns an item in front of the caller like Heretic's time bomb -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItem) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_CLASS(missile, 0); - ACTION_PARAM_FIXED(distance, 1); - ACTION_PARAM_FIXED(zheight, 2); - ACTION_PARAM_BOOL(useammo, 3); - ACTION_PARAM_BOOL(transfer_translation, 4); - - if (!missile) - { - ACTION_SET_RESULT(false); - return; - } - - // Don't spawn monsters if this actor has been massacred - if (self->DamageType == NAME_Massacre && GetDefaultByType(missile)->flags3&MF3_ISMONSTER) return; - - if (distance==0) - { - // use the minimum distance that does not result in an overlap - distance=(self->radius+GetDefaultByType(missile)->radius)>>FRACBITS; - } - - if (ACTION_CALL_FROM_WEAPON()) - { - // Used from a weapon so use some ammo - AWeapon * weapon=self->player->ReadyWeapon; - - if (!weapon) return; - if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) return; - } - - AActor * mo = Spawn( missile, - self->x + FixedMul(distance, finecosine[self->angle>>ANGLETOFINESHIFT]), - self->y + FixedMul(distance, finesine[self->angle>>ANGLETOFINESHIFT]), - self->z - self->floorclip + self->GetBobOffset() + zheight, ALLOW_REPLACE); - - int flags = (transfer_translation ? SIXF_TRANSFERTRANSLATION : 0) + (useammo ? SIXF_SETMASTER : 0); - bool res = InitSpawnedItem(self, mo, flags); - ACTION_SET_RESULT(res); // for an inventory item's use state -} - -//=========================================================================== -// -// A_SpawnItemEx -// -// Enhanced spawning function -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItemEx) -{ - ACTION_PARAM_START(11); - ACTION_PARAM_CLASS(missile, 0); - ACTION_PARAM_FIXED(xofs, 1); - ACTION_PARAM_FIXED(yofs, 2); - ACTION_PARAM_FIXED(zofs, 3); - ACTION_PARAM_FIXED(xvel, 4); - ACTION_PARAM_FIXED(yvel, 5); - ACTION_PARAM_FIXED(zvel, 6); - ACTION_PARAM_ANGLE(Angle, 7); - ACTION_PARAM_INT(flags, 8); - ACTION_PARAM_INT(chance, 9); - ACTION_PARAM_INT(tid, 10); - - if (!missile) - { - ACTION_SET_RESULT(false); - return; - } - - if (chance > 0 && pr_spawnitemex()DamageType == NAME_Massacre && GetDefaultByType(missile)->flags3&MF3_ISMONSTER) return; - - fixed_t x,y; - - if (!(flags & SIXF_ABSOLUTEANGLE)) - { - Angle += self->angle; - } - - angle_t ang = Angle >> ANGLETOFINESHIFT; - - if (flags & SIXF_ABSOLUTEPOSITION) - { - x = self->x + xofs; - y = self->y + yofs; - } - else - { - // in relative mode negative y values mean 'left' and positive ones mean 'right' - // This is the inverse orientation of the absolute mode! - x = self->x + FixedMul(xofs, finecosine[ang]) + FixedMul(yofs, finesine[ang]); - y = self->y + FixedMul(xofs, finesine[ang]) - FixedMul(yofs, finecosine[ang]); - } - - if (!(flags & SIXF_ABSOLUTEVELOCITY)) - { - // Same orientation issue here! - fixed_t newxvel = FixedMul(xvel, finecosine[ang]) + FixedMul(yvel, finesine[ang]); - yvel = FixedMul(xvel, finesine[ang]) - FixedMul(yvel, finecosine[ang]); - xvel = newxvel; - } - - AActor *mo = Spawn(missile, x, y, self->z - self->floorclip + self->GetBobOffset() + zofs, ALLOW_REPLACE); - bool res = InitSpawnedItem(self, mo, flags); - ACTION_SET_RESULT(res); // for an inventory item's use state - if (res) - { - if (tid != 0) - { - assert(mo->tid == 0); - mo->tid = tid; - mo->AddToHash(); - } - if (flags & SIXF_MULTIPLYSPEED) - { - mo->velx = FixedMul(xvel, mo->Speed); - mo->vely = FixedMul(yvel, mo->Speed); - mo->velz = FixedMul(zvel, mo->Speed); - } - else - { - mo->velx = xvel; - mo->vely = yvel; - mo->velz = zvel; - } - mo->angle = Angle; - } -} - -//=========================================================================== -// -// A_ThrowGrenade -// -// Throws a grenade (like Hexen's fighter flechette) -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ThrowGrenade) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_CLASS(missile, 0); - ACTION_PARAM_FIXED(zheight, 1); - ACTION_PARAM_FIXED(xyvel, 2); - ACTION_PARAM_FIXED(zvel, 3); - ACTION_PARAM_BOOL(useammo, 4); - - if (missile == NULL) return; - - if (ACTION_CALL_FROM_WEAPON()) - { - // Used from a weapon, so use some ammo - AWeapon *weapon = self->player->ReadyWeapon; - - if (!weapon) return; - if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) return; - } - - - AActor * bo; - - bo = Spawn(missile, self->x, self->y, - self->z - self->floorclip + self->GetBobOffset() + zheight + 35*FRACUNIT + (self->player? self->player->crouchoffset : 0), - ALLOW_REPLACE); - if (bo) - { - P_PlaySpawnSound(bo, self); - if (xyvel != 0) - bo->Speed = xyvel; - bo->angle = self->angle + (((pr_grenade()&7) - 4) << 24); - - angle_t pitch = angle_t(-self->pitch) >> ANGLETOFINESHIFT; - angle_t angle = bo->angle >> ANGLETOFINESHIFT; - - // There are two vectors we are concerned about here: xy and z. We rotate - // them separately according to the shooter's pitch and then sum them to - // get the final velocity vector to shoot with. - - fixed_t xy_xyscale = FixedMul(bo->Speed, finecosine[pitch]); - fixed_t xy_velz = FixedMul(bo->Speed, finesine[pitch]); - fixed_t xy_velx = FixedMul(xy_xyscale, finecosine[angle]); - fixed_t xy_vely = FixedMul(xy_xyscale, finesine[angle]); - - pitch = angle_t(self->pitch) >> ANGLETOFINESHIFT; - fixed_t z_xyscale = FixedMul(zvel, finesine[pitch]); - fixed_t z_velz = FixedMul(zvel, finecosine[pitch]); - fixed_t z_velx = FixedMul(z_xyscale, finecosine[angle]); - fixed_t z_vely = FixedMul(z_xyscale, finesine[angle]); - - bo->velx = xy_velx + z_velx + (self->velx >> 1); - bo->vely = xy_vely + z_vely + (self->vely >> 1); - bo->velz = xy_velz + z_velz; - - bo->target = self; - P_CheckMissileSpawn (bo, self->radius); - } - else ACTION_SET_RESULT(false); -} - - -//=========================================================================== -// -// A_Recoil -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Recoil) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_FIXED(xyvel, 0); - - angle_t angle = self->angle + ANG180; - angle >>= ANGLETOFINESHIFT; - self->velx += FixedMul (xyvel, finecosine[angle]); - self->vely += FixedMul (xyvel, finesine[angle]); -} - - -//=========================================================================== -// -// A_SelectWeapon -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SelectWeapon) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_CLASS(cls, 0); - - if (cls == NULL || self->player == NULL) - { - ACTION_SET_RESULT(false); - return; - } - - AWeapon * weaponitem = static_cast(self->FindInventory(cls)); - - if (weaponitem != NULL && weaponitem->IsKindOf(RUNTIME_CLASS(AWeapon))) - { - if (self->player->ReadyWeapon != weaponitem) - { - self->player->PendingWeapon = weaponitem; - } - } - else ACTION_SET_RESULT(false); - -} - - -//=========================================================================== -// -// A_Print -// -//=========================================================================== -EXTERN_CVAR(Float, con_midtime) - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Print) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_STRING(text, 0); - ACTION_PARAM_FLOAT(time, 1); - ACTION_PARAM_NAME(fontname, 2); - - if (text[0] == '$') text = GStrings(text+1); - if (self->CheckLocalView (consoleplayer) || - (self->target!=NULL && self->target->CheckLocalView (consoleplayer))) - { - float saved = con_midtime; - FFont *font = NULL; - - if (fontname != NAME_None) - { - font = V_GetFont(fontname); - } - if (time > 0) - { - con_midtime = time; - } - - FString formatted = strbin1(text); - C_MidPrint(font != NULL ? font : SmallFont, formatted.GetChars()); - con_midtime = saved; - } - ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! -} - -//=========================================================================== -// -// A_PrintBold -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PrintBold) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_STRING(text, 0); - ACTION_PARAM_FLOAT(time, 1); - ACTION_PARAM_NAME(fontname, 2); - - float saved = con_midtime; - FFont *font = NULL; - - if (text[0] == '$') text = GStrings(text+1); - if (fontname != NAME_None) - { - font = V_GetFont(fontname); - } - if (time > 0) - { - con_midtime = time; - } - - FString formatted = strbin1(text); - C_MidPrintBold(font != NULL ? font : SmallFont, formatted.GetChars()); - con_midtime = saved; - ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! -} - -//=========================================================================== -// -// A_Log -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Log) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STRING(text, 0); - - if (text[0] == '$') text = GStrings(text+1); - FString formatted = strbin1(text); - Printf("%s\n", formatted.GetChars()); - ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! -} - -//========================================================================= -// -// A_LogInt -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LogInt) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(num, 0); - Printf("%d\n", num); - ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! -} - -//=========================================================================== -// -// A_SetTranslucent -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTranslucent) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_FIXED(alpha, 0); - ACTION_PARAM_INT(mode, 1); - - mode = mode == 0 ? STYLE_Translucent : mode == 2 ? STYLE_Fuzzy : STYLE_Add; - - self->RenderStyle.Flags &= ~STYLEF_Alpha1; - self->alpha = clamp(alpha, 0, FRACUNIT); - self->RenderStyle = ERenderStyle(mode); -} - -//=========================================================================== -// -// A_FadeIn -// -// Fades the actor in -// -//=========================================================================== - -enum FadeFlags -{ - FTF_REMOVE = 1 << 0, - FTF_CLAMP = 1 << 1, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeIn) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_FIXED(reduce, 0); - ACTION_PARAM_INT(flags, 1); - - if (reduce == 0) - { - reduce = FRACUNIT / 10; - } - self->RenderStyle.Flags &= ~STYLEF_Alpha1; - self->alpha += reduce; - - if (self->alpha >= (FRACUNIT * 1)) - { - if (flags & FTF_CLAMP) - self->alpha = (FRACUNIT * 1); - if (flags & FTF_REMOVE) - self->Destroy(); - } -} - -//=========================================================================== -// -// A_FadeOut -// -// fades the actor out and destroys it when done -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeOut) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_FIXED(reduce, 0); - ACTION_PARAM_INT(flags, 1); - - if (reduce == 0) - { - reduce = FRACUNIT/10; - } - self->RenderStyle.Flags &= ~STYLEF_Alpha1; - self->alpha -= reduce; - if (self->alpha <= 0) - { - if (flags & FTF_CLAMP) - self->alpha = 0; - if (flags & FTF_REMOVE) - self->Destroy(); - } -} - -//=========================================================================== -// -// A_FadeTo -// -// fades the actor to a specified transparency by a specified amount and -// destroys it if so desired -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeTo) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_FIXED(target, 0); - ACTION_PARAM_FIXED(amount, 1); - ACTION_PARAM_INT(flags, 2); - - self->RenderStyle.Flags &= ~STYLEF_Alpha1; - - if (self->alpha > target) - { - self->alpha -= amount; - - if (self->alpha < target) - { - self->alpha = target; - } - } - else if (self->alpha < target) - { - self->alpha += amount; - - if (self->alpha > target) - { - self->alpha = target; - } - } - if (flags & FTF_CLAMP) - { - if (self->alpha > (FRACUNIT * 1)) - self->alpha = (FRACUNIT * 1); - else if (self->alpha < 0) - self->alpha = 0; - } - if (self->alpha == target && (flags & FTF_REMOVE)) - { - self->Destroy(); - } -} - -//=========================================================================== -// -// A_Scale(float scalex, optional float scaley) -// -// Scales the actor's graphics. If scaley is 0, use scalex. -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetScale) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_FIXED(scalex, 0); - ACTION_PARAM_FIXED(scaley, 1); - ACTION_PARAM_INT(ptr, 2); - - AActor *ref = COPY_AAPTR(self, ptr); - - if (!ref) - { - ACTION_SET_RESULT(false); - return; - } - - ref->scaleX = scalex; - ref->scaleY = scaley ? scaley : scalex; -} - -//=========================================================================== -// -// A_SetMass(int mass) -// -// Sets the actor's mass. -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetMass) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(mass, 0); - - self->Mass = mass; -} - -//=========================================================================== -// -// A_SpawnDebris -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnDebris) -{ - int i; - AActor * mo; - - ACTION_PARAM_START(4); - ACTION_PARAM_CLASS(debris, 0); - ACTION_PARAM_BOOL(transfer_translation, 1); - ACTION_PARAM_FIXED(mult_h, 2); - ACTION_PARAM_FIXED(mult_v, 3); - - if (debris == NULL) return; - - // only positive values make sense here - if (mult_v<=0) mult_v=FRACUNIT; - if (mult_h<=0) mult_h=FRACUNIT; - - for (i = 0; i < GetDefaultByType(debris)->health; i++) - { - mo = Spawn(debris, self->x+((pr_spawndebris()-128)<<12), - self->y + ((pr_spawndebris()-128)<<12), - self->z + (pr_spawndebris()*self->height/256+self->GetBobOffset()), ALLOW_REPLACE); - if (mo) - { - if (transfer_translation) - { - mo->Translation = self->Translation; - } - if (i < mo->GetClass()->ActorInfo->NumOwnedStates) - { - mo->SetState(mo->GetClass()->ActorInfo->OwnedStates + i); - } - mo->velz = FixedMul(mult_v, ((pr_spawndebris()&7)+5)*FRACUNIT); - mo->velx = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6)); - mo->vely = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6)); - } - } -} - - -//=========================================================================== -// -// A_CheckSight -// jumps if no player can see this actor -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSight) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - for (int i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i]) - { - // Always check sight from each player. - if (P_CheckSight(players[i].mo, self, SF_IGNOREVISIBILITY)) - { - return; - } - // If a player is viewing from a non-player, then check that too. - if (players[i].camera != NULL && players[i].camera->player == NULL && - P_CheckSight(players[i].camera, self, SF_IGNOREVISIBILITY)) - { - return; - } - } - } - - ACTION_JUMP(jump); -} - -//=========================================================================== -// -// A_CheckSightOrRange -// Jumps if this actor is out of range of all players *and* out of sight. -// Useful for maps with many multi-actor special effects. -// -//=========================================================================== -static bool DoCheckSightOrRange(AActor *self, AActor *camera, double range, bool twodi) -{ - if (camera == NULL) - { - return false; - } - // Check distance first, since it's cheaper than checking sight. - double dx = self->x - camera->x; - double dy = self->y - camera->y; - double dz; - fixed_t eyez = (camera->z + camera->height - (camera->height>>2)); // same eye height as P_CheckSight - if (eyez > self->z + self->height) - { - dz = self->z + self->height - eyez; - } - else if (eyez < self->z) - { - dz = self->z - eyez; - } - else - { - dz = 0; - } - double distance = (dx * dx) + (dy * dy) + (twodi == 0? (dz * dz) : 0); - if (distance <= range){ - // Within range - return true; - } - - // Now check LOS. - if (P_CheckSight(camera, self, SF_IGNOREVISIBILITY)) - { // Visible - return true; - } - return false; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSightOrRange) -{ - ACTION_PARAM_START(3); - double range = EvalExpressionF(ParameterIndex+0, self); - ACTION_PARAM_STATE(jump, 1); - ACTION_PARAM_BOOL(twodi, 2); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots - for (int i = 0; i < MAXPLAYERS; ++i) - { - if (playeringame[i]) - { - // Always check from each player. - if (DoCheckSightOrRange(self, players[i].mo, range, twodi)) - { - return; - } - // If a player is viewing from a non-player, check that too. - if (players[i].camera != NULL && players[i].camera->player == NULL && - DoCheckSightOrRange(self, players[i].camera, range, twodi)) - { - return; - } - } - } - ACTION_JUMP(jump); -} - -//=========================================================================== -// -// A_CheckRange -// Jumps if this actor is out of range of all players. -// -//=========================================================================== -static bool DoCheckRange(AActor *self, AActor *camera, double range, bool twodi) -{ - if (camera == NULL) - { - return false; - } - // Check distance first, since it's cheaper than checking sight. - double dx = self->x - camera->x; - double dy = self->y - camera->y; - double dz; - fixed_t eyez = (camera->z + camera->height - (camera->height>>2)); // same eye height as P_CheckSight - if (eyez > self->z + self->height){ - dz = self->z + self->height - eyez; - } - else if (eyez < self->z){ - dz = self->z - eyez; - } - else{ - dz = 0; - } - double distance = (dx * dx) + (dy * dy) + (twodi == 0? (dz * dz) : 0); - if (distance <= range){ - // Within range - return true; - } - return false; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckRange) -{ - ACTION_PARAM_START(3); - double range = EvalExpressionF(ParameterIndex+0, self); - ACTION_PARAM_STATE(jump, 1); - ACTION_PARAM_BOOL(twodi, 2); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots - for (int i = 0; i < MAXPLAYERS; ++i) - { - if (playeringame[i]) - { - // Always check from each player. - if (DoCheckRange(self, players[i].mo, range, twodi)) - { - return; - } - // If a player is viewing from a non-player, check that too. - if (players[i].camera != NULL && players[i].camera->player == NULL && - DoCheckRange(self, players[i].camera, range, twodi)) - { - return; - } - } - } - ACTION_JUMP(jump); -} - - -//=========================================================================== -// -// Inventory drop -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropInventory) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_CLASS(drop, 0); - - if (drop) - { - AInventory * inv = self->FindInventory(drop); - if (inv) - { - self->DropInventory(inv); - } - } -} - - -//=========================================================================== -// -// A_SetBlend -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetBlend) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_COLOR(color, 0); - ACTION_PARAM_FLOAT(alpha, 1); - ACTION_PARAM_INT(tics, 2); - ACTION_PARAM_COLOR(color2, 3); - - if (color == MAKEARGB(255,255,255,255)) color=0; - if (color2 == MAKEARGB(255,255,255,255)) color2=0; - if (!color2.a) - color2 = color; - - new DFlashFader(color.r/255.0f, color.g/255.0f, color.b/255.0f, alpha, - color2.r/255.0f, color2.g/255.0f, color2.b/255.0f, 0, - (float)tics/TICRATE, self); -} - - -//=========================================================================== -// -// A_JumpIf -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIf) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_BOOL(expression, 0); - ACTION_PARAM_STATE(jump, 1); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (expression) ACTION_JUMP(jump); - -} - -//=========================================================================== -// -// A_CountdownArg -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CountdownArg) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(cnt, 0); - ACTION_PARAM_STATE(state, 1); - - if (cnt<0 || cnt>=5) return; - if (!self->args[cnt]--) - { - if (self->flags&MF_MISSILE) - { - P_ExplodeMissile(self, NULL, NULL); - } - else if (self->flags&MF_SHOOTABLE) - { - P_DamageMobj (self, NULL, NULL, self->health, NAME_None, DMG_FORCED); - } - else - { - // can't use "Death" as default parameter with current DECORATE parser. - if (state == NULL) state = self->FindState(NAME_Death); - self->SetState(state); - } - } - -} - -//============================================================================ -// -// A_Burst -// -//============================================================================ - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Burst) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_CLASS(chunk, 0); - - int i, numChunks; - AActor * mo; - - if (chunk == NULL) return; - - self->velx = self->vely = self->velz = 0; - self->height = self->GetDefault()->height; - - // [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 (4, (self->radius>>FRACBITS)*(self->height>>FRACBITS)/32); - i = (pr_burst.Random2()) % (numChunks/4); - for (i = MAX (24, numChunks + i); i >= 0; i--) - { - mo = Spawn(chunk, - self->x + (((pr_burst()-128)*self->radius)>>7), - self->y + (((pr_burst()-128)*self->radius)>>7), - self->z + (pr_burst()*self->height/255 + self->GetBobOffset()), ALLOW_REPLACE); - - if (mo) - { - mo->velz = FixedDiv(mo->z - self->z, self->height)<<2; - mo->velx = pr_burst.Random2 () << (FRACBITS-7); - mo->vely = pr_burst.Random2 () << (FRACBITS-7); - mo->RenderStyle = self->RenderStyle; - mo->alpha = self->alpha; - mo->CopyFriendliness(self, true); - } - } - - // [RH] Do some stuff to make this more useful outside Hexen - if (self->flags4 & MF4_BOSSDEATH) - { - CALL_ACTION(A_BossDeath, self); - } - A_Unblock(self, true); - - self->Destroy (); -} - -//=========================================================================== -// -// A_CheckFloor -// [GRB] Jumps if actor is standing on floor -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFloor) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (self->z <= self->floorz) - { - ACTION_JUMP(jump); - } - -} - -//=========================================================================== -// -// A_CheckCeiling -// [GZ] Totally copied on A_CheckFloor, jumps if actor touches ceiling -// - -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckCeiling) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - ACTION_SET_RESULT(false); - if (self->z+self->height >= self->ceilingz) // Height needs to be counted - { - ACTION_JUMP(jump); - } - -} - -//=========================================================================== -// -// A_Stop -// resets all velocity of the actor to 0 -// -//=========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_Stop) -{ - self->velx = self->vely = self->velz = 0; - if (self->player && self->player->mo == self && !(self->player->cheats & CF_PREDICTING)) - { - self->player->mo->PlayIdle(); - self->player->velx = self->player->vely = 0; - } -} - -static void CheckStopped(AActor *self) -{ - if (self->player != NULL && - self->player->mo == self && - !(self->player->cheats & CF_PREDICTING) && - !(self->velx | self->vely | self->velz)) - { - self->player->mo->PlayIdle(); - self->player->velx = self->player->vely = 0; - } -} - -//=========================================================================== -// -// A_Respawn -// -//=========================================================================== - -extern void AF_A_RestoreSpecialPosition(DECLARE_PARAMINFO); - -enum RS_Flags -{ - RSF_FOG=1, - RSF_KEEPTARGET=2, - RSF_TELEFRAG=4, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Respawn) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(flags, 0); - bool oktorespawn = false; - fixed_t oldx = self->x; - fixed_t oldy = self->y; - fixed_t oldz = self->z; - self->flags |= MF_SOLID; - self->height = self->GetDefault()->height; - CALL_ACTION(A_RestoreSpecialPosition, self); - - if (flags & RSF_TELEFRAG) - { - // [KS] DIE DIE DIE DIE erm *ahem* =) - oktorespawn = P_TeleportMove(self, self->x, self->y, self->z, true); - if (oktorespawn) - { // Need to do this over again, since P_TeleportMove() will redo - // it with the proper point-on-side calculation. - self->UnlinkFromWorld(); - self->LinkToWorld(true); - sector_t *sec = self->Sector; - self->dropoffz = - self->floorz = sec->floorplane.ZatPoint(self->x, self->y); - self->ceilingz = sec->ceilingplane.ZatPoint(self->x, self->y); - P_FindFloorCeiling(self, FFCF_ONLYSPAWNPOS); - } - } - else - { - oktorespawn = P_CheckPosition(self, self->x, self->y, true); - } - - if (oktorespawn) - { - AActor *defs = self->GetDefault(); - self->health = defs->health; - - // [KS] Don't keep target, because it could be self if the monster committed suicide - // ...Actually it's better off an option, so you have better control over monster behavior. - if (!(flags & RSF_KEEPTARGET)) - { - self->target = NULL; - self->LastHeard = NULL; - self->lastenemy = NULL; - } - else - { - // Don't attack yourself (Re: "Marine targets itself after suicide") - if (self->target == self) self->target = NULL; - if (self->lastenemy == self) self->lastenemy = NULL; - } - - self->flags = (defs->flags & ~MF_FRIENDLY) | (self->flags & MF_FRIENDLY); - self->flags2 = defs->flags2; - self->flags3 = (defs->flags3 & ~(MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)) | (self->flags3 & (MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)); - self->flags4 = (defs->flags4 & ~MF4_NOHATEPLAYERS) | (self->flags4 & MF4_NOHATEPLAYERS); - self->flags5 = defs->flags5; - self->flags6 = defs->flags6; - self->flags7 = defs->flags7; - self->SetState (self->SpawnState); - self->renderflags &= ~RF_INVISIBLE; - - if (flags & RSF_FOG) - { - P_SpawnTeleportFog(self, oldx, oldy, oldz, true); - P_SpawnTeleportFog(self, self->x, self->y, self->z, false); - } - if (self->CountsAsKill()) - { - level.total_monsters++; - } - } - else - { - self->flags &= ~MF_SOLID; - } -} - - -//========================================================================== -// -// A_PlayerSkinCheck -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayerSkinCheck) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (self->player != NULL && - skins[self->player->userinfo.GetSkin()].othergame) - { - ACTION_JUMP(jump); - } -} - -//=========================================================================== -// -// A_SetGravity -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetGravity) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_FIXED(val, 0); - - self->gravity = clamp (val, 0, FRACUNIT*10); -} - - -// [KS] *** Start of my modifications *** - -//=========================================================================== -// -// A_ClearTarget -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION(AActor, A_ClearTarget) -{ - self->target = NULL; - self->LastHeard = NULL; - self->lastenemy = NULL; -} - -//========================================================================== -// -// A_CheckLOF (state jump, int flags = CRF_AIM_VERT|CRF_AIM_HOR, -// fixed range = 0, angle angle = 0, angle pitch = 0, -// fixed offsetheight = 32, fixed offsetwidth = 0, -// int ptr_target = AAPTR_DEFAULT (target) ) -// -//========================================================================== - -enum CLOF_flags -{ - CLOFF_NOAIM_VERT = 0x00000001, - CLOFF_NOAIM_HORZ = 0x00000002, - - CLOFF_JUMPENEMY = 0x00000004, - CLOFF_JUMPFRIEND = 0x00000008, - CLOFF_JUMPOBJECT = 0x00000010, - CLOFF_JUMPNONHOSTILE = 0x00000020, - - CLOFF_SKIPENEMY = 0x00000040, - CLOFF_SKIPFRIEND = 0x00000080, - CLOFF_SKIPOBJECT = 0x00000100, - CLOFF_SKIPNONHOSTILE = 0x00000200, - - CLOFF_MUSTBESHOOTABLE = 0x00000400, - - CLOFF_SKIPTARGET = 0x00000800, - CLOFF_ALLOWNULL = 0x00001000, - CLOFF_CHECKPARTIAL = 0x00002000, - - CLOFF_MUSTBEGHOST = 0x00004000, - CLOFF_IGNOREGHOST = 0x00008000, - - CLOFF_MUSTBESOLID = 0x00010000, - CLOFF_BEYONDTARGET = 0x00020000, - - CLOFF_FROMBASE = 0x00040000, - CLOFF_MUL_HEIGHT = 0x00080000, - CLOFF_MUL_WIDTH = 0x00100000, - - CLOFF_JUMP_ON_MISS = 0x00200000, - CLOFF_AIM_VERT_NOOFFSET = 0x00400000, - - CLOFF_SETTARGET = 0x00800000, - CLOFF_SETMASTER = 0x01000000, - CLOFF_SETTRACER = 0x02000000, -}; - -struct LOFData -{ - AActor *Self; - AActor *Target; - int Flags; - bool BadActor; -}; - -ETraceStatus CheckLOFTraceFunc(FTraceResults &trace, void *userdata) -{ - LOFData *data = (LOFData *)userdata; - int flags = data->Flags; - - if (trace.HitType != TRACE_HitActor) - { - return TRACE_Stop; - } - if (trace.Actor == data->Target) - { - if (flags & CLOFF_SKIPTARGET) - { - if (flags & CLOFF_BEYONDTARGET) - { - return TRACE_Skip; - } - return TRACE_Abort; - } - return TRACE_Stop; - } - if (flags & CLOFF_MUSTBESHOOTABLE) - { // all shootability checks go here - if (!(trace.Actor->flags & MF_SHOOTABLE)) - { - return TRACE_Skip; - } - if (trace.Actor->flags2 & MF2_NONSHOOTABLE) - { - return TRACE_Skip; - } - } - if ((flags & CLOFF_MUSTBESOLID) && !(trace.Actor->flags & MF_SOLID)) - { - return TRACE_Skip; - } - if (flags & CLOFF_MUSTBEGHOST) - { - if (!(trace.Actor->flags3 & MF3_GHOST)) - { - return TRACE_Skip; - } - } - else if (flags & CLOFF_IGNOREGHOST) - { - if (trace.Actor->flags3 & MF3_GHOST) - { - return TRACE_Skip; - } - } - if ( - ((flags & CLOFF_JUMPENEMY) && data->Self->IsHostile(trace.Actor)) || - ((flags & CLOFF_JUMPFRIEND) && data->Self->IsFriend(trace.Actor)) || - ((flags & CLOFF_JUMPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) || - ((flags & CLOFF_JUMPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor)) - ) - { - return TRACE_Stop; - } - if ( - ((flags & CLOFF_SKIPENEMY) && data->Self->IsHostile(trace.Actor)) || - ((flags & CLOFF_SKIPFRIEND) && data->Self->IsFriend(trace.Actor)) || - ((flags & CLOFF_SKIPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) || - ((flags & CLOFF_SKIPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor)) - ) - { - return TRACE_Skip; - } - data->BadActor = true; - return TRACE_Abort; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckLOF) -{ - // Check line of fire - - /* - Not accounted for / I don't know how it works: FLOORCLIP - */ - - AActor *target; - fixed_t - x1, y1, z1, - vx, vy, vz; - - ACTION_PARAM_START(9); - - ACTION_PARAM_STATE(jump, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_FIXED(range, 2); - ACTION_PARAM_FIXED(minrange, 3); - { - ACTION_PARAM_ANGLE(angle, 4); - ACTION_PARAM_ANGLE(pitch, 5); - ACTION_PARAM_FIXED(offsetheight, 6); - ACTION_PARAM_FIXED(offsetwidth, 7); - ACTION_PARAM_INT(ptr_target, 8); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - target = COPY_AAPTR(self, ptr_target == AAPTR_DEFAULT ? AAPTR_TARGET|AAPTR_PLAYER_GETTARGET|AAPTR_NULL : ptr_target); // no player-support by default - - if (flags & CLOFF_MUL_HEIGHT) - { - if (self->player != NULL) - { - // Synced with hitscan: self->player->mo->height is strangely conscientious about getting the right actor for player - offsetheight = FixedMul(offsetheight, FixedMul (self->player->mo->height, self->player->crouchfactor)); - } - else - { - offsetheight = FixedMul(offsetheight, self->height); - } - } - if (flags & CLOFF_MUL_WIDTH) - { - offsetwidth = FixedMul(self->radius, offsetwidth); - } - - x1 = self->x; - y1 = self->y; - z1 = self->z + offsetheight - self->floorclip; - - if (!(flags & CLOFF_FROMBASE)) - { // default to hitscan origin - - // Synced with hitscan: self->height is strangely NON-conscientious about getting the right actor for player - z1 += (self->height >> 1); - if (self->player != NULL) - { - z1 += FixedMul (self->player->mo->AttackZOffset, self->player->crouchfactor); - } - else - { - z1 += 8*FRACUNIT; - } - } - - if (target) - { - FVector2 xyvec(target->x - x1, target->y - y1); - fixed_t distance = P_AproxDistance((fixed_t)xyvec.Length(), target->z - z1); - - if (range && !(flags & CLOFF_CHECKPARTIAL)) - { - if (distance > range) return; - } - - { - angle_t ang; - - if (flags & CLOFF_NOAIM_HORZ) - { - ang = self->angle; - } - else ang = R_PointToAngle2 (x1, y1, target->x, target->y); - - angle += ang; - - ang >>= ANGLETOFINESHIFT; - x1 += FixedMul(offsetwidth, finesine[ang]); - y1 -= FixedMul(offsetwidth, finecosine[ang]); - } - - if (flags & CLOFF_NOAIM_VERT) - { - pitch += self->pitch; - } - else if (flags & CLOFF_AIM_VERT_NOOFFSET) - { - pitch += R_PointToAngle2 (0,0, (fixed_t)xyvec.Length(), target->z - z1 + offsetheight + target->height / 2); - } - else - { - pitch += R_PointToAngle2 (0,0, (fixed_t)xyvec.Length(), target->z - z1 + target->height / 2); - } - } - else if (flags & CLOFF_ALLOWNULL) - { - angle += self->angle; - pitch += self->pitch; - - angle_t ang = self->angle >> ANGLETOFINESHIFT; - x1 += FixedMul(offsetwidth, finesine[ang]); - y1 -= FixedMul(offsetwidth, finecosine[ang]); - } - else return; - - angle >>= ANGLETOFINESHIFT; - pitch = (0-pitch)>>ANGLETOFINESHIFT; - - vx = FixedMul (finecosine[pitch], finecosine[angle]); - vy = FixedMul (finecosine[pitch], finesine[angle]); - vz = -finesine[pitch]; - } - - /* Variable set: - - jump, flags, target - x1,y1,z1 (trace point of origin) - vx,vy,vz (trace unit vector) - range - */ - - sector_t *sec = P_PointInSector(x1, y1); - - if (range == 0) - { - range = (self->player != NULL) ? PLAYERMISSILERANGE : MISSILERANGE; - } - - FTraceResults trace; - LOFData lof_data; - - lof_data.Self = self; - lof_data.Target = target; - lof_data.Flags = flags; - lof_data.BadActor = false; - - Trace(x1, y1, z1, sec, vx, vy, vz, range, 0xFFFFFFFF, ML_BLOCKEVERYTHING, self, trace, 0, - CheckLOFTraceFunc, &lof_data); - - if (trace.HitType == TRACE_HitActor || - ((flags & CLOFF_JUMP_ON_MISS) && !lof_data.BadActor && trace.HitType != TRACE_HitNone)) - { - if (minrange > 0 && trace.Distance < minrange) - { - return; - } - if ((trace.HitType == TRACE_HitActor) && (trace.Actor != NULL) && !(lof_data.BadActor)) - { - if (flags & (CLOFF_SETTARGET)) self->target = trace.Actor; - if (flags & (CLOFF_SETMASTER)) self->master = trace.Actor; - if (flags & (CLOFF_SETTRACER)) self->tracer = trace.Actor; - } - - ACTION_JUMP(jump); - } -} - -//========================================================================== -// -// A_JumpIfTargetInLOS (state label, optional fixed fov, optional int flags, -// optional fixed dist_max, optional fixed dist_close) -// -// Jumps if the actor can see its target, or if the player has a linetarget. -// ProjectileTarget affects how projectiles are treated. If set, it will use -// the target of the projectile for seekers, and ignore the target for -// normal projectiles. If not set, it will use the missile's owner instead -// (the default). ProjectileTarget is now flag JLOSF_PROJECTILE. dist_max -// sets the maximum distance that actor can see, 0 means forever. dist_close -// uses special behavior if certain flags are set, 0 means no checks. -// -//========================================================================== - -enum JLOS_flags -{ - JLOSF_PROJECTILE = 1, - JLOSF_NOSIGHT = 1 << 1, - JLOSF_CLOSENOFOV = 1 << 2, - JLOSF_CLOSENOSIGHT = 1 << 3, - JLOSF_CLOSENOJUMP = 1 << 4, - JLOSF_DEADNOJUMP = 1 << 5, - JLOSF_CHECKMASTER = 1 << 6, - JLOSF_TARGETLOS = 1 << 7, - JLOSF_FLIPFOV = 1 << 8, - JLOSF_ALLYNOJUMP = 1 << 9, - JLOSF_COMBATANTONLY = 1 << 10, - JLOSF_NOAUTOAIM = 1 << 11, - JLOSF_CHECKTRACER = 1 << 12, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_STATE(jump, 0); - ACTION_PARAM_ANGLE(fov, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_FIXED(dist_max, 3); - ACTION_PARAM_FIXED(dist_close, 4); - - angle_t an; - AActor *target, *viewport; - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - bool doCheckSight; - - if (!self->player) - { - if (flags & JLOSF_CHECKMASTER) - { - target = self->master; - } - else if ((self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) || (flags & JLOSF_CHECKTRACER)) - { - if ((self->flags2 & MF2_SEEKERMISSILE) || (flags & JLOSF_CHECKTRACER)) - target = self->tracer; - else - target = NULL; - } - else - { - target = self->target; - } - - if (!target) return; // [KS] Let's not call P_CheckSight unnecessarily in this case. - - if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) return; - - doCheckSight = !(flags & JLOSF_NOSIGHT); - } - else - { - // Does the player aim at something that can be shot? - P_AimLineAttack(self, self->angle, MISSILERANGE, &target, (flags & JLOSF_NOAUTOAIM) ? ANGLE_1/2 : 0); - - if (!target) return; - - switch (flags & (JLOSF_TARGETLOS|JLOSF_FLIPFOV)) - { - case JLOSF_TARGETLOS|JLOSF_FLIPFOV: - // target makes sight check, player makes fov check; player has verified fov - fov = 0; - // fall-through - case JLOSF_TARGETLOS: - doCheckSight = !(flags & JLOSF_NOSIGHT); // The target is responsible for sight check and fov - break; - default: - // player has verified sight and fov - fov = 0; - // fall-through - case JLOSF_FLIPFOV: // Player has verified sight, but target must verify fov - doCheckSight = false; - break; - } - } - - // [FDARI] If target is not a combatant, don't jump - if ( (flags & JLOSF_COMBATANTONLY) && (!target->player) && !(target->flags3 & MF3_ISMONSTER)) return; - - // [FDARI] If actors share team, don't jump - if ((flags & JLOSF_ALLYNOJUMP) && self->IsFriend(target)) return; - - fixed_t distance = P_AproxDistance(target->x - self->x, target->y - self->y); - distance = P_AproxDistance(distance, target->z - self->z); - - if (dist_max && (distance > dist_max)) return; - - if (dist_close && (distance < dist_close)) - { - if (flags & JLOSF_CLOSENOJUMP) - return; - - if (flags & JLOSF_CLOSENOFOV) - fov = 0; - - if (flags & JLOSF_CLOSENOSIGHT) - doCheckSight = false; - } - - if (flags & JLOSF_TARGETLOS) { viewport = target; target = self; } - else { viewport = self; } - - if (doCheckSight && !P_CheckSight (viewport, target, SF_IGNOREVISIBILITY)) - return; - - if (flags & JLOSF_FLIPFOV) - { - if (viewport == self) { viewport = target; target = self; } - else { target = viewport; viewport = self; } - } - - if (fov && (fov < ANGLE_MAX)) - { - an = R_PointToAngle2 (viewport->x, - viewport->y, - target->x, - target->y) - - viewport->angle; - - if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2))) - { - return; // [KS] Outside of FOV - return - } - - } - - ACTION_JUMP(jump); -} - - -//========================================================================== -// -// A_JumpIfInTargetLOS (state label, optional fixed fov, optional int flags -// optional fixed dist_max, optional fixed dist_close) -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetLOS) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_STATE(jump, 0); - ACTION_PARAM_ANGLE(fov, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_FIXED(dist_max, 3); - ACTION_PARAM_FIXED(dist_close, 4); - - angle_t an; - AActor *target; - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - if (flags & JLOSF_CHECKMASTER) - { - target = self->master; - } - else if (self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) - { - if (self->flags2 & MF2_SEEKERMISSILE) - target = self->tracer; - else - target = NULL; - } - else - { - target = self->target; - } - - if (!target) return; // [KS] Let's not call P_CheckSight unnecessarily in this case. - - if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) return; - - fixed_t distance = P_AproxDistance(target->x - self->x, target->y - self->y); - distance = P_AproxDistance(distance, target->z - self->z); - - if (dist_max && (distance > dist_max)) return; - - bool doCheckSight = !(flags & JLOSF_NOSIGHT); - - if (dist_close && (distance < dist_close)) - { - if (flags & JLOSF_CLOSENOJUMP) - return; - - if (flags & JLOSF_CLOSENOFOV) - fov = 0; - - if (flags & JLOSF_CLOSENOSIGHT) - doCheckSight = false; - } - - if (fov && (fov < ANGLE_MAX)) - { - an = R_PointToAngle2 (target->x, - target->y, - self->x, - self->y) - - target->angle; - - if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2))) - { - return; // [KS] Outside of FOV - return - } - } - - if (doCheckSight && !P_CheckSight (target, self, SF_IGNOREVISIBILITY)) - return; - - ACTION_JUMP(jump); -} - -//=========================================================================== -// -// Modified code pointer from Skulltag -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckForReload) -{ - if ( self->player == NULL || self->player->ReadyWeapon == NULL ) - return; - - ACTION_PARAM_START(2); - ACTION_PARAM_INT(count, 0); - ACTION_PARAM_STATE(jump, 1); - ACTION_PARAM_BOOL(dontincrement, 2) - - if (count <= 0) return; - - AWeapon *weapon = self->player->ReadyWeapon; - - int ReloadCounter = weapon->ReloadCounter; - if(!dontincrement || ReloadCounter != 0) - ReloadCounter = (weapon->ReloadCounter+1) % count; - else // 0 % 1 = 1? So how do we check if the weapon was never fired? We should only do this when we're not incrementing the counter though. - ReloadCounter = 1; - - // If we have not made our last shot... - if (ReloadCounter != 0) - { - // Go back to the refire frames, instead of continuing on to the reload frames. - ACTION_JUMP(jump); - } - else - { - // We need to reload. However, don't reload if we're out of ammo. - weapon->CheckAmmo( false, false ); - } - - if(!dontincrement) - weapon->ReloadCounter = ReloadCounter; -} - -//=========================================================================== -// -// Resets the counter for the above function -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION(AActor, A_ResetReloadCounter) -{ - if ( self->player == NULL || self->player->ReadyWeapon == NULL ) - return; - - AWeapon *weapon = self->player->ReadyWeapon; - weapon->ReloadCounter = 0; -} - -//=========================================================================== -// -// A_ChangeFlag -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeFlag) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_STRING(flagname, 0); - ACTION_PARAM_BOOL(expression, 1); - - const char *dot = strchr (flagname, '.'); - FFlagDef *fd; - const PClass *cls = self->GetClass(); - - if (dot != NULL) - { - FString part1(flagname, dot-flagname); - fd = FindFlag (cls, part1, dot+1); - } - else - { - fd = FindFlag (cls, flagname, NULL); - } - - if (fd != NULL) - { - bool kill_before, kill_after; - INTBOOL item_before, item_after; - INTBOOL secret_before, secret_after; - - kill_before = self->CountsAsKill(); - item_before = self->flags & MF_COUNTITEM; - secret_before = self->flags5 & MF5_COUNTSECRET; - - if (fd->structoffset == -1) - { - HandleDeprecatedFlags(self, cls->ActorInfo, expression, fd->flagbit); - } - else - { - DWORD *flagp = (DWORD*) (((char*)self) + fd->structoffset); - - // If these 2 flags get changed we need to update the blockmap and sector links. - bool linkchange = flagp == &self->flags && (fd->flagbit == MF_NOBLOCKMAP || fd->flagbit == MF_NOSECTOR); - - if (linkchange) self->UnlinkFromWorld(); - ModActorFlag(self, fd, expression); - if (linkchange) self->LinkToWorld(); - } - kill_after = self->CountsAsKill(); - item_after = self->flags & MF_COUNTITEM; - secret_after = self->flags5 & MF5_COUNTSECRET; - // Was this monster previously worth a kill but no longer is? - // Or vice versa? - if (kill_before != kill_after) - { - if (kill_after) - { // It counts as a kill now. - level.total_monsters++; - } - else - { // It no longer counts as a kill. - level.total_monsters--; - } - } - // same for items - if (item_before != item_after) - { - if (item_after) - { // It counts as an item now. - level.total_items++; - } - else - { // It no longer counts as an item - level.total_items--; - } - } - // and secretd - if (secret_before != secret_after) - { - if (secret_after) - { // It counts as an secret now. - level.total_secrets++; - } - else - { // It no longer counts as an secret - level.total_secrets--; - } - } - } - else - { - Printf("Unknown flag '%s' in '%s'\n", flagname, cls->TypeName.GetChars()); - } -} - -//=========================================================================== -// -// A_CheckFlag -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFlag) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_STRING(flagname, 0); - ACTION_PARAM_STATE(jumpto, 1); - ACTION_PARAM_INT(checkpointer, 2); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - AActor *owner; - - COPY_AAPTR_NOT_NULL(self, owner, checkpointer); - - if (CheckActorFlag(owner, flagname)) - { - ACTION_JUMP(jumpto); - } -} - -//=========================================================================== -// -// A_RaiseMaster -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseMaster) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_BOOL(copy, 0); - - if (self->master != NULL) - { - if (copy) - P_Thing_Raise(self->master, self); - else - P_Thing_Raise(self->master, NULL); - } -} - -//=========================================================================== -// -// A_RaiseChildren -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseChildren) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_BOOL(copy, 0); - TThinkerIterator it; - AActor *mo; - - while ((mo = it.Next()) != NULL) - { - if (mo->master == self) - { - if (copy) - P_Thing_Raise(mo, self); - else - P_Thing_Raise(mo, NULL); - } - } -} - -//=========================================================================== -// -// A_RaiseSiblings -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseSiblings) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_BOOL(copy, 0); - TThinkerIterator it; - AActor *mo; - - if (self->master != NULL) - { - while ((mo = it.Next()) != NULL) - { - if (mo->master == self->master && mo != self) - { - if (copy) - P_Thing_Raise(mo, self); - else - P_Thing_Raise(mo, NULL); - } - } - } -} - -//=========================================================================== -// -// [TP] A_FaceConsolePlayer -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS (AActor, A_FaceConsolePlayer) { - ACTION_PARAM_START (1); - ACTION_PARAM_ANGLE (MaxTurnAngle, 0); - // NOTE: It does nothing for zdoom. -} - -//=========================================================================== -// -// A_MonsterRefire -// -// Keep firing unless target got out of sight -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_MonsterRefire) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(prob, 0); - ACTION_PARAM_STATE(jump, 1); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - A_FaceTarget (self); - - if (pr_monsterrefire() < prob) - return; - - if (!self->target - || P_HitFriend (self) - || self->target->health <= 0 - || !P_CheckSight (self, self->target, SF_SEEPASTBLOCKEVERYTHING|SF_SEEPASTSHOOTABLELINES) ) - { - ACTION_JUMP(jump); - } -} - -//=========================================================================== -// -// A_SetAngle -// -// Set actor's angle (in degrees). -// -//=========================================================================== -enum -{ - SPF_FORCECLAMP = 1, // players always clamp - SPF_INTERPOLATE = 2, -}; - - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetAngle) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_ANGLE(angle, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_INT(ptr, 2); - - AActor *ref = COPY_AAPTR(self, ptr); - - if (!ref) - { - ACTION_SET_RESULT(false); - return; - } - ref->SetAngle(angle, !!(flags & SPF_INTERPOLATE)); -} - -//=========================================================================== -// -// A_SetPitch -// -// Set actor's pitch (in degrees). -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetPitch) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_ANGLE(pitch, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_INT(ptr, 2); - - AActor *ref = COPY_AAPTR(self, ptr); - - if (!ref) - { - ACTION_SET_RESULT(false); - return; - } - - if (ref->player != NULL || (flags & SPF_FORCECLAMP)) - { // clamp the pitch we set - int min, max; - - if (ref->player != NULL) - { - min = ref->player->MinPitch; - max = ref->player->MaxPitch; - } - else - { - min = -ANGLE_90 + (1 << ANGLETOFINESHIFT); - max = ANGLE_90 - (1 << ANGLETOFINESHIFT); - } - pitch = clamp(pitch, min, max); - } - ref->SetPitch(pitch, !!(flags & SPF_INTERPOLATE)); -} - +/* +** thingdef.cpp +** +** Code pointers for Actor definitions +** +**--------------------------------------------------------------------------- +** Copyright 2002-2006 Christoph Oelckers +** Copyright 2004-2006 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** 4. When not used as part of ZDoom or a ZDoom derivative, this code will be +** covered by the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or (at +** your option) any later version. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "gi.h" +#include "g_level.h" +#include "actor.h" +#include "info.h" +#include "sc_man.h" +#include "tarray.h" +#include "w_wad.h" +#include "templates.h" +#include "r_defs.h" +#include "a_pickups.h" +#include "s_sound.h" +#include "cmdlib.h" +#include "p_lnspec.h" +#include "p_enemy.h" +#include "a_action.h" +#include "decallib.h" +#include "m_random.h" +#include "i_system.h" +#include "p_local.h" +#include "c_console.h" +#include "doomerrors.h" +#include "a_sharedglobal.h" +#include "thingdef/thingdef.h" +#include "v_video.h" +#include "v_font.h" +#include "doomstat.h" +#include "v_palette.h" +#include "g_shared/a_specialspot.h" +#include "actorptrselect.h" +#include "m_bbox.h" +#include "r_data/r_translate.h" +#include "p_trace.h" +#include "gstrings.h" + + +static FRandom pr_camissile ("CustomActorfire"); +static FRandom pr_camelee ("CustomMelee"); +static FRandom pr_cabullet ("CustomBullet"); +static FRandom pr_cajump ("CustomJump"); +static FRandom pr_cwbullet ("CustomWpBullet"); +static FRandom pr_cwjump ("CustomWpJump"); +static FRandom pr_cwpunch ("CustomWpPunch"); +static FRandom pr_grenade ("ThrowGrenade"); +static FRandom pr_crailgun ("CustomRailgun"); +static FRandom pr_spawndebris ("SpawnDebris"); +static FRandom pr_spawnitemex ("SpawnItemEx"); +static FRandom pr_burst ("Burst"); +static FRandom pr_monsterrefire ("MonsterRefire"); +static FRandom pr_teleport("A_Teleport"); + +//========================================================================== +// +// ACustomInventory :: CallStateChain +// +// Executes the code pointers in a chain of states +// until there is no next state +// +//========================================================================== + +bool ACustomInventory::CallStateChain (AActor *actor, FState * State) +{ + StateCallData StateCall; + bool result = false; + int counter = 0; + + while (State != NULL) + { + // Assume success. The code pointer will set this to false if necessary + StateCall.State = State; + StateCall.Result = true; + if (State->CallAction(actor, this, &StateCall)) + { + // collect all the results. Even one successful call signifies overall success. + result |= StateCall.Result; + } + + + // Since there are no delays it is a good idea to check for infinite loops here! + counter++; + if (counter >= 10000) break; + + if (StateCall.State == State) + { + // Abort immediately if the state jumps to itself! + if (State == State->GetNextState()) + { + return false; + } + + // If both variables are still the same there was no jump + // so we must advance to the next state. + State = State->GetNextState(); + } + else + { + State = StateCall.State; + } + } + return result; +} + +//========================================================================== +// +// A_RearrangePointers +// +// Allow an actor to change its relationship to other actors by +// copying pointers freely between TARGET MASTER and TRACER. +// Can also assign null value, but does not duplicate A_ClearTarget. +// +//========================================================================== + + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RearrangePointers) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_INT(ptr_target, 0); + ACTION_PARAM_INT(ptr_master, 1); + ACTION_PARAM_INT(ptr_tracer, 2); + ACTION_PARAM_INT(flags, 3); + + // Rearrange pointers internally + + // Fetch all values before modification, so that all fields can get original values + AActor + *gettarget = self->target, + *getmaster = self->master, + *gettracer = self->tracer; + + switch (ptr_target) // pick the new target + { + case AAPTR_MASTER: + self->target = getmaster; + if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self); + break; + case AAPTR_TRACER: + self->target = gettracer; + if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self); + break; + case AAPTR_NULL: + self->target = NULL; + // THIS IS NOT "A_ClearTarget", so no other targeting info is removed + break; + } + + // presently permitting non-monsters to set master + switch (ptr_master) // pick the new master + { + case AAPTR_TARGET: + self->master = gettarget; + if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self); + break; + case AAPTR_TRACER: + self->master = gettracer; + if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self); + break; + case AAPTR_NULL: + self->master = NULL; + break; + } + + switch (ptr_tracer) // pick the new tracer + { + case AAPTR_TARGET: + self->tracer = gettarget; + break; // no verification deemed necessary; the engine never follows a tracer chain(?) + case AAPTR_MASTER: + self->tracer = getmaster; + break; // no verification deemed necessary; the engine never follows a tracer chain(?) + case AAPTR_NULL: + self->tracer = NULL; + break; + } +} + +//========================================================================== +// +// A_TransferPointer +// +// Copy one pointer (MASTER, TARGET or TRACER) from this actor (SELF), +// or from this actor's MASTER, TARGET or TRACER. +// +// You can copy any one of that actor's pointers +// +// Assign the copied pointer to any one pointer in SELF, +// MASTER, TARGET or TRACER. +// +// Any attempt to make an actor point to itself will replace the pointer +// with a null value. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TransferPointer) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(ptr_source, 0); + ACTION_PARAM_INT(ptr_recepient, 1); + ACTION_PARAM_INT(ptr_sourcefield, 2); + ACTION_PARAM_INT(ptr_recepientfield, 3); + ACTION_PARAM_INT(flags, 4); + + AActor *source, *recepient; + + // Exchange pointers with actors to whom you have pointers (or with yourself, if you must) + + source = COPY_AAPTR(self, ptr_source); + COPY_AAPTR_NOT_NULL(self, recepient, ptr_recepient); // pick an actor to store the provided pointer value + + // convert source from dataprovider to data + + source = COPY_AAPTR(source, ptr_sourcefield); + + if (source == recepient) source = NULL; // The recepient should not acquire a pointer to itself; will write NULL + + if (ptr_recepientfield == AAPTR_DEFAULT) ptr_recepientfield = ptr_sourcefield; // If default: Write to same field as data was read from + + ASSIGN_AAPTR(recepient, ptr_recepientfield, source, flags); +} + +//========================================================================== +// +// A_CopyFriendliness +// +// Join forces with one of the actors you are pointing to (MASTER by default) +// +// Normal CopyFriendliness reassigns health. This function will not. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CopyFriendliness) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(ptr_source, 0); + + if (self->player) return; + + AActor *source; + COPY_AAPTR_NOT_NULL(self, source, ptr_source); + self->CopyFriendliness(source, false, false); // No change in current target or health +} + +//========================================================================== +// +// Simple flag changers +// +//========================================================================== +DEFINE_ACTION_FUNCTION(AActor, A_SetSolid) +{ + self->flags |= MF_SOLID; +} + +DEFINE_ACTION_FUNCTION(AActor, A_UnsetSolid) +{ + self->flags &= ~MF_SOLID; +} + +DEFINE_ACTION_FUNCTION(AActor, A_SetFloat) +{ + self->flags |= MF_FLOAT; +} + +DEFINE_ACTION_FUNCTION(AActor, A_UnsetFloat) +{ + self->flags &= ~(MF_FLOAT|MF_INFLOAT); +} + +//========================================================================== +// +// Customizable attack functions which use actor parameters. +// +//========================================================================== +static void DoAttack (AActor *self, bool domelee, bool domissile, + int MeleeDamage, FSoundID MeleeSound, const PClass *MissileType,fixed_t MissileHeight) +{ + if (self->target == NULL) return; + + A_FaceTarget (self); + if (domelee && MeleeDamage>0 && self->CheckMeleeRange ()) + { + int damage = pr_camelee.HitDice(MeleeDamage); + if (MeleeSound) S_Sound (self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); + } + else if (domissile && MissileType != NULL) + { + // This seemingly senseless code is needed for proper aiming. + self->z += MissileHeight + self->GetBobOffset() - 32*FRACUNIT; + AActor *missile = P_SpawnMissileXYZ (self->x, self->y, self->z + 32*FRACUNIT, self, self->target, MissileType, false); + self->z -= MissileHeight + self->GetBobOffset() - 32*FRACUNIT; + + if (missile) + { + // automatic handling of seeker missiles + if (missile->flags2&MF2_SEEKERMISSILE) + { + missile->tracer=self->target; + } + P_CheckMissileSpawn(missile, self->radius); + } + } +} + +DEFINE_ACTION_FUNCTION(AActor, A_MeleeAttack) +{ + int MeleeDamage = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeDamage, 0); + FSoundID MeleeSound = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeSound, 0); + DoAttack(self, true, false, MeleeDamage, MeleeSound, NULL, 0); +} + +DEFINE_ACTION_FUNCTION(AActor, A_MissileAttack) +{ + const PClass *MissileType=PClass::FindClass((ENamedName) self->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None)); + fixed_t MissileHeight= self->GetClass()->Meta.GetMetaFixed (ACMETA_MissileHeight, 32*FRACUNIT); + DoAttack(self, false, true, 0, 0, MissileType, MissileHeight); +} + +DEFINE_ACTION_FUNCTION(AActor, A_ComboAttack) +{ + int MeleeDamage = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeDamage, 0); + FSoundID MeleeSound = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeSound, 0); + const PClass *MissileType=PClass::FindClass((ENamedName) self->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None)); + fixed_t MissileHeight= self->GetClass()->Meta.GetMetaFixed (ACMETA_MissileHeight, 32*FRACUNIT); + DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BasicAttack) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_INT(MeleeDamage, 0); + ACTION_PARAM_SOUND(MeleeSound, 1); + ACTION_PARAM_CLASS(MissileType, 2); + ACTION_PARAM_FIXED(MissileHeight, 3); + + if (MissileType == NULL) return; + DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight); +} + +//========================================================================== +// +// Custom sound functions. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySound) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_SOUND(soundid, 0); + ACTION_PARAM_INT(channel, 1); + ACTION_PARAM_FLOAT(volume, 2); + ACTION_PARAM_BOOL(looping, 3); + ACTION_PARAM_FLOAT(attenuation, 4); + + if (!looping) + { + S_Sound (self, channel, soundid, volume, attenuation); + } + else + { + if (!S_IsActorPlayingSomething (self, channel&7, soundid)) + { + S_Sound (self, channel | CHAN_LOOP, soundid, volume, attenuation); + } + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSound) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(slot, 0); + + S_StopSound(self, slot); +} + +//========================================================================== +// +// These come from a time when DECORATE constants did not exist yet and +// the sound interface was less flexible. As a result the parameters are +// not optimal and these functions have been deprecated in favor of extending +// A_PlaySound and A_StopSound. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayWeaponSound) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_SOUND(soundid, 0); + + S_Sound (self, CHAN_WEAPON, soundid, 1, ATTN_NORM); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySoundEx) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_SOUND(soundid, 0); + ACTION_PARAM_NAME(channel, 1); + ACTION_PARAM_BOOL(looping, 2); + ACTION_PARAM_INT(attenuation_raw, 3); + + float attenuation; + switch (attenuation_raw) + { + case -1: attenuation = ATTN_STATIC; break; // drop off rapidly + default: + case 0: attenuation = ATTN_NORM; break; // normal + case 1: + case 2: attenuation = ATTN_NONE; break; // full volume + } + + if (channel < NAME_Auto || channel > NAME_SoundSlot7) + { + channel = NAME_Auto; + } + + if (!looping) + { + S_Sound (self, int(channel) - NAME_Auto, soundid, 1, attenuation); + } + else + { + if (!S_IsActorPlayingSomething (self, int(channel) - NAME_Auto, soundid)) + { + S_Sound (self, (int(channel) - NAME_Auto) | CHAN_LOOP, soundid, 1, attenuation); + } + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSoundEx) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_NAME(channel, 0); + + if (channel > NAME_Auto && channel <= NAME_SoundSlot7) + { + S_StopSound (self, int(channel) - NAME_Auto); + } +} + +//========================================================================== +// +// Generic seeker missile function +// +//========================================================================== +static FRandom pr_seekermissile ("SeekerMissile"); +enum +{ + SMF_LOOK = 1, + SMF_PRECISE = 2, + SMF_CURSPEED = 4, +}; +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SeekerMissile) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(ang1, 0); + ACTION_PARAM_INT(ang2, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_INT(chance, 3); + ACTION_PARAM_INT(distance, 4); + + if ((flags & SMF_LOOK) && (self->tracer == 0) && (pr_seekermissile()tracer = P_RoughMonsterSearch (self, distance, true); + } + if (!P_SeekerMissile(self, clamp(ang1, 0, 90) * ANGLE_1, clamp(ang2, 0, 90) * ANGLE_1, !!(flags & SMF_PRECISE), !!(flags & SMF_CURSPEED))) + { + if (flags & SMF_LOOK) + { // This monster is no longer seekable, so let us look for another one next time. + self->tracer = NULL; + } + } +} + +//========================================================================== +// +// Hitscan attack with a customizable amount of bullets (specified in damage) +// +//========================================================================== +DEFINE_ACTION_FUNCTION(AActor, A_BulletAttack) +{ + int i; + int bangle; + int slope; + + if (!self->target) return; + + A_FaceTarget (self); + bangle = self->angle; + + slope = P_AimLineAttack (self, bangle, MISSILERANGE); + + S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); + for (i = self->GetMissileDamage (0, 1); i > 0; --i) + { + int angle = bangle + (pr_cabullet.Random2() << 20); + int damage = ((pr_cabullet()%5)+1)*3; + P_LineAttack(self, angle, MISSILERANGE, slope, damage, + NAME_Hitscan, NAME_BulletPuff); + } +} + + +//========================================================================== +// +// Do the state jump +// +//========================================================================== +static void DoJump(AActor * self, FState * CallingState, FState *jumpto, StateCallData *statecall) +{ + if (jumpto == NULL) return; + + if (statecall != NULL) + { + statecall->State = jumpto; + } + else if (self->player != NULL && CallingState == self->player->psprites[ps_weapon].state) + { + P_SetPsprite(self->player, ps_weapon, jumpto); + } + else if (self->player != NULL && CallingState == self->player->psprites[ps_flash].state) + { + P_SetPsprite(self->player, ps_flash, jumpto); + } + else if (CallingState == self->state) + { + self->SetState (jumpto); + } + else + { + // something went very wrong. This should never happen. + assert(false); + } +} + +// This is just to avoid having to directly reference the internally defined +// CallingState and statecall parameters in the code below. +#define ACTION_JUMP(offset) DoJump(self, CallingState, offset, statecall) + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Jump) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_INT(count, 0); + ACTION_PARAM_INT(maxchance, 1); + + if (count >= 2 && (maxchance >= 256 || pr_cajump() < maxchance)) + { + int jumps = 2 + (count == 2? 0 : (pr_cajump() % (count - 1))); + ACTION_PARAM_STATE(jumpto, jumps); + ACTION_JUMP(jumpto); + } + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfHealthLower) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_INT(health, 0); + ACTION_PARAM_STATE(jump, 1); + ACTION_PARAM_INT(ptr_selector, 2); + + AActor *measured; + + measured = COPY_AAPTR(self, ptr_selector); + + if (measured && measured->health < health) + { + ACTION_JUMP(jump); + } + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetOutsideMeleeRange) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + if (!self->CheckMeleeRange()) + { + ACTION_JUMP(jump); + } + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInsideMeleeRange) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + if (self->CheckMeleeRange()) + { + ACTION_JUMP(jump); + } + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! +} +//========================================================================== +// +// State jump function +// +//========================================================================== +void DoJumpIfCloser(AActor *target, DECLARE_PARAMINFO) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_FIXED(dist, 0); + ACTION_PARAM_STATE(jump, 1); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + // No target - no jump + if (target != NULL && P_AproxDistance(self->x-target->x, self->y-target->y) < dist && + ( (self->z > target->z && self->z - (target->z + target->height) < dist) || + (self->z <=target->z && target->z - (self->z + self->height) < dist) + ) + ) + { + ACTION_JUMP(jump); + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfCloser) +{ + AActor *target; + + if (!self->player) + { + target = self->target; + } + else + { + // Does the player aim at something that can be shot? + P_BulletSlope(self, &target); + } + DoJumpIfCloser(target, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTracerCloser) +{ + DoJumpIfCloser(self->tracer, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfMasterCloser) +{ + DoJumpIfCloser(self->master, PUSH_PARAMINFO); +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +void DoJumpIfInventory(AActor * owner, DECLARE_PARAMINFO) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_CLASS(Type, 0); + ACTION_PARAM_INT(ItemAmount, 1); + ACTION_PARAM_STATE(JumpOffset, 2); + ACTION_PARAM_INT(setowner, 3); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + if (!Type) return; + COPY_AAPTR_NOT_NULL(owner, owner, setowner); // returns if owner ends up being NULL + + AInventory *Item = owner->FindInventory(Type); + + if (Item) + { + if (ItemAmount > 0) + { + if (Item->Amount >= ItemAmount) + ACTION_JUMP(JumpOffset); + } + else if (Item->Amount >= Item->MaxAmount) + { + ACTION_JUMP(JumpOffset); + } + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInventory) +{ + DoJumpIfInventory(self, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetInventory) +{ + DoJumpIfInventory(self->target, PUSH_PARAMINFO); +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfArmorType) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_NAME(Type, 0); + ACTION_PARAM_STATE(JumpOffset, 1); + ACTION_PARAM_INT(amount, 2); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + ABasicArmor * armor = (ABasicArmor *) self->FindInventory(NAME_BasicArmor); + + if (armor && armor->ArmorType == Type && armor->Amount >= amount) + ACTION_JUMP(JumpOffset); +} + +//========================================================================== +// +// Parameterized version of A_Explode +// +//========================================================================== + +enum +{ + XF_HURTSOURCE = 1, + XF_NOTMISSILE = 4, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Explode) +{ + ACTION_PARAM_START(8); + ACTION_PARAM_INT(damage, 0); + ACTION_PARAM_INT(distance, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_BOOL(alert, 3); + ACTION_PARAM_INT(fulldmgdistance, 4); + ACTION_PARAM_INT(nails, 5); + ACTION_PARAM_INT(naildamage, 6); + ACTION_PARAM_CLASS(pufftype, 7); + + if (damage < 0) // get parameters from metadata + { + damage = self->GetClass()->Meta.GetMetaInt (ACMETA_ExplosionDamage, 128); + distance = self->GetClass()->Meta.GetMetaInt (ACMETA_ExplosionRadius, damage); + flags = !self->GetClass()->Meta.GetMetaInt (ACMETA_DontHurtShooter); + alert = false; + } + else + { + if (distance <= 0) distance = damage; + } + // NailBomb effect, from SMMU but not from its source code: instead it was implemented and + // generalized from the documentation at http://www.doomworld.com/eternity/engine/codeptrs.html + + if (nails) + { + angle_t ang; + for (int i = 0; i < nails; i++) + { + ang = i*(ANGLE_MAX/nails); + // Comparing the results of a test wad with Eternity, it seems A_NailBomb does not aim + P_LineAttack (self, ang, MISSILERANGE, 0, + //P_AimLineAttack (self, ang, MISSILERANGE), + naildamage, NAME_Hitscan, pufftype); + } + } + + P_RadiusAttack (self, self->target, damage, distance, self->DamageType, flags, fulldmgdistance); + P_CheckSplash(self, distance<target != NULL && self->target->player != NULL) + { + validcount++; + P_RecursiveSound (self->Sector, self->target, false, 0); + } +} + +//========================================================================== +// +// A_RadiusThrust +// +//========================================================================== + +enum +{ + RTF_AFFECTSOURCE = 1, + RTF_NOIMPACTDAMAGE = 2, + RTF_NOTMISSILE = 4, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusThrust) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_INT(force, 0); + ACTION_PARAM_INT(distance, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_INT(fullthrustdistance, 3); + + bool sourcenothrust = false; + + if (force == 0) force = 128; + if (distance <= 0) distance = abs(force); + + // Temporarily negate MF2_NODMGTHRUST on the shooter, since it renders this function useless. + if (!(flags & RTF_NOTMISSILE) && self->target != NULL && self->target->flags2 & MF2_NODMGTHRUST) + { + sourcenothrust = true; + self->target->flags2 &= ~MF2_NODMGTHRUST; + } + + P_RadiusAttack (self, self->target, force, distance, self->DamageType, flags | RADF_NODAMAGE, fullthrustdistance); + P_CheckSplash(self, distance << FRACBITS); + + if (sourcenothrust) + { + self->target->flags2 |= MF2_NODMGTHRUST; + } +} + +//========================================================================== +// +// Execute a line special / script +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CallSpecial) +{ + ACTION_PARAM_START(6); + ACTION_PARAM_INT(special, 0); + ACTION_PARAM_INT(arg1, 1); + ACTION_PARAM_INT(arg2, 2); + ACTION_PARAM_INT(arg3, 3); + ACTION_PARAM_INT(arg4, 4); + ACTION_PARAM_INT(arg5, 5); + + bool res = !!P_ExecuteSpecial(special, NULL, self, false, arg1, arg2, arg3, arg4, arg5); + + ACTION_SET_RESULT(res); +} + +//========================================================================== +// +// The ultimate code pointer: Fully customizable missiles! +// +//========================================================================== +enum CM_Flags +{ + CMF_AIMMODE = 3, + CMF_TRACKOWNER = 4, + CMF_CHECKTARGETDEAD = 8, + + CMF_ABSOLUTEPITCH = 16, + CMF_OFFSETPITCH = 32, + CMF_SAVEPITCH = 64, + + CMF_ABSOLUTEANGLE = 128 +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomMissile) +{ + ACTION_PARAM_START(7); + ACTION_PARAM_CLASS(ti, 0); + ACTION_PARAM_FIXED(SpawnHeight, 1); + ACTION_PARAM_INT(Spawnofs_XY, 2); + ACTION_PARAM_ANGLE(Angle, 3); + ACTION_PARAM_INT(flags, 4); + ACTION_PARAM_ANGLE(pitch, 5); + ACTION_PARAM_INT(ptr, 6); + + AActor *ref = COPY_AAPTR(self, ptr); + + int aimmode = flags & CMF_AIMMODE; + + AActor * targ; + AActor * missile; + + if (ref != NULL || aimmode==2) + { + if (ti) + { + angle_t ang = (self->angle - ANGLE_90) >> ANGLETOFINESHIFT; + fixed_t x = Spawnofs_XY * finecosine[ang]; + fixed_t y = Spawnofs_XY * finesine[ang]; + fixed_t z = SpawnHeight + self->GetBobOffset() - 32*FRACUNIT + (self->player? self->player->crouchoffset : 0); + + switch (aimmode) + { + case 0: + default: + // same adjustment as above (in all 3 directions this time) - for better aiming! + self->x += x; + self->y += y; + self->z += z; + missile = P_SpawnMissileXYZ(self->x, self->y, self->z + 32*FRACUNIT, self, ref, ti, false); + self->x -= x; + self->y -= y; + self->z -= z; + break; + + case 1: + missile = P_SpawnMissileXYZ(self->x+x, self->y+y, self->z + self->GetBobOffset() + SpawnHeight, self, ref, ti, false); + break; + + case 2: + self->x += x; + self->y += y; + missile = P_SpawnMissileAngleZSpeed(self, self->z + self->GetBobOffset() + SpawnHeight, ti, self->angle, 0, GetDefaultByType(ti)->Speed, self, false); + self->x -= x; + self->y -= y; + + flags |= CMF_ABSOLUTEPITCH; + + break; + } + + if (missile) + { + // Use the actual velocity instead of the missile's Speed property + // so that this can handle missiles with a high vertical velocity + // component properly. + + fixed_t missilespeed; + + if ( (CMF_ABSOLUTEPITCH|CMF_OFFSETPITCH) & flags) + { + if (CMF_OFFSETPITCH & flags) + { + FVector2 velocity (missile->velx, missile->vely); + pitch += R_PointToAngle2(0,0, (fixed_t)velocity.Length(), missile->velz); + } + ang = pitch >> ANGLETOFINESHIFT; + missilespeed = abs(FixedMul(finecosine[ang], missile->Speed)); + missile->velz = FixedMul(finesine[ang], missile->Speed); + } + else + { + FVector2 velocity (missile->velx, missile->vely); + missilespeed = (fixed_t)velocity.Length(); + } + + if (CMF_SAVEPITCH & flags) + { + missile->pitch = pitch; + // In aimmode 0 and 1 without absolutepitch or offsetpitch, the pitch parameter + // contains the unapplied parameter. In that case, it is set as pitch without + // otherwise affecting the spawned actor. + } + + missile->angle = (CMF_ABSOLUTEANGLE & flags) ? Angle : missile->angle + Angle ; + + ang = missile->angle >> ANGLETOFINESHIFT; + missile->velx = FixedMul (missilespeed, finecosine[ang]); + missile->vely = FixedMul (missilespeed, finesine[ang]); + + // handle projectile shooting projectiles - track the + // links back to a real owner + if (self->isMissile(!!(flags & CMF_TRACKOWNER))) + { + AActor * owner=self ;//->target; + while (owner->isMissile(!!(flags & CMF_TRACKOWNER)) && owner->target) owner=owner->target; + targ=owner; + missile->target=owner; + // automatic handling of seeker missiles + if (self->flags & missile->flags2 & MF2_SEEKERMISSILE) + { + missile->tracer=self->tracer; + } + } + else if (missile->flags2&MF2_SEEKERMISSILE) + { + // automatic handling of seeker missiles + missile->tracer=self->target; + } + // we must redo the spectral check here because the owner is set after spawning so the FriendPlayer value may be wrong + if (missile->flags4 & MF4_SPECTRAL) + { + if (missile->target != NULL) + { + missile->SetFriendPlayer(missile->target->player); + } + else + { + missile->FriendPlayer = 0; + } + } + P_CheckMissileSpawn(missile, self->radius); + } + } + } + else if (flags & CMF_CHECKTARGETDEAD) + { + // Target is dead and the attack shall be aborted. + if (self->SeeState != NULL && (self->health > 0 || !(self->flags3 & MF3_ISMONSTER))) self->SetState(self->SeeState); + } +} + +//========================================================================== +// +// An even more customizable hitscan attack +// +//========================================================================== +enum CBA_Flags +{ + CBAF_AIMFACING = 1, + CBAF_NORANDOM = 2, + CBAF_EXPLICITANGLE = 4, + CBAF_NOPITCH = 8, + CBAF_NORANDOMPUFFZ = 16, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomBulletAttack) +{ + ACTION_PARAM_START(8); + ACTION_PARAM_ANGLE(Spread_XY, 0); + ACTION_PARAM_ANGLE(Spread_Z, 1); + ACTION_PARAM_INT(NumBullets, 2); + ACTION_PARAM_INT(DamagePerBullet, 3); + ACTION_PARAM_CLASS(pufftype, 4); + ACTION_PARAM_FIXED(Range, 5); + ACTION_PARAM_INT(Flags, 6); + ACTION_PARAM_INT(ptr, 7); + + AActor *ref = COPY_AAPTR(self, ptr); + + if(Range==0) Range=MISSILERANGE; + + int i; + int bangle; + int bslope = 0; + int laflags = (Flags & CBAF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; + + if (ref || (Flags & CBAF_AIMFACING)) + { + if (!(Flags & CBAF_AIMFACING)) + { + A_Face(self, ref); + } + bangle = self->angle; + + if (!pufftype) pufftype = PClass::FindClass(NAME_BulletPuff); + + if (!(Flags & CBAF_NOPITCH)) bslope = P_AimLineAttack (self, bangle, MISSILERANGE); + + S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); + for (i=0 ; itarget) + return; + + A_FaceTarget (self); + if (self->CheckMeleeRange ()) + { + if (MeleeSound) S_Sound (self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM); + int newdam = P_DamageMobj (self->target, self, self, damage, DamageType); + if (bleed) P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); + } + else + { + if (MissSound) S_Sound (self, CHAN_WEAPON, MissSound, 1, ATTN_NORM); + } +} + +//========================================================================== +// +// A fully customizable combo attack +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomComboAttack) +{ + ACTION_PARAM_START(6); + ACTION_PARAM_CLASS(ti, 0); + ACTION_PARAM_FIXED(SpawnHeight, 1); + ACTION_PARAM_INT(damage, 2); + ACTION_PARAM_SOUND(MeleeSound, 3); + ACTION_PARAM_NAME(DamageType, 4); + ACTION_PARAM_BOOL(bleed, 5); + + if (!self->target) + return; + + A_FaceTarget (self); + if (self->CheckMeleeRange ()) + { + if (DamageType==NAME_None) DamageType = NAME_Melee; // Melee is the default type + if (MeleeSound) S_Sound (self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM); + int newdam = P_DamageMobj (self->target, self, self, damage, DamageType); + if (bleed) P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); + } + else if (ti) + { + // This seemingly senseless code is needed for proper aiming. + self->z += SpawnHeight + self->GetBobOffset() - 32*FRACUNIT; + AActor *missile = P_SpawnMissileXYZ (self->x, self->y, self->z + 32*FRACUNIT, self, self->target, ti, false); + self->z -= SpawnHeight + self->GetBobOffset() - 32*FRACUNIT; + + if (missile) + { + // automatic handling of seeker missiles + if (missile->flags2&MF2_SEEKERMISSILE) + { + missile->tracer=self->target; + } + P_CheckMissileSpawn(missile, self->radius); + } + } +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfNoAmmo) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (!ACTION_CALL_FROM_WEAPON()) return; + + if (!self->player->ReadyWeapon->CheckAmmo(self->player->ReadyWeapon->bAltFire, false, true)) + { + ACTION_JUMP(jump); + } + +} + + +//========================================================================== +// +// An even more customizable hitscan attack +// +//========================================================================== +enum FB_Flags +{ + FBF_USEAMMO = 1, + FBF_NORANDOM = 2, + FBF_EXPLICITANGLE = 4, + FBF_NOPITCH = 8, + FBF_NOFLASH = 16, + FBF_NORANDOMPUFFZ = 32, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) +{ + ACTION_PARAM_START(7); + ACTION_PARAM_ANGLE(Spread_XY, 0); + ACTION_PARAM_ANGLE(Spread_Z, 1); + ACTION_PARAM_INT(NumberOfBullets, 2); + ACTION_PARAM_INT(DamagePerBullet, 3); + ACTION_PARAM_CLASS(PuffType, 4); + ACTION_PARAM_INT(Flags, 5); + ACTION_PARAM_FIXED(Range, 6); + + if (!self->player) return; + + player_t * player=self->player; + AWeapon * weapon=player->ReadyWeapon; + + int i; + int bangle; + int bslope = 0; + int laflags = (Flags & FBF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; + + if ((Flags & FBF_USEAMMO) && weapon) + { + if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // out of ammo + } + + if (Range == 0) Range = PLAYERMISSILERANGE; + + if (!(Flags & FBF_NOFLASH)) static_cast(self)->PlayAttacking2 (); + + if (!(Flags & FBF_NOPITCH)) bslope = P_BulletSlope(self); + bangle = self->angle; + + if (!PuffType) PuffType = PClass::FindClass(NAME_BulletPuff); + + if (weapon != NULL) + { + S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); + } + + if ((NumberOfBullets==1 && !player->refire) || NumberOfBullets==0) + { + int damage = DamagePerBullet; + + if (!(Flags & FBF_NORANDOM)) + damage *= ((pr_cwbullet()%3)+1); + + P_LineAttack(self, bangle, Range, bslope, damage, NAME_Hitscan, PuffType, laflags); + } + else + { + if (NumberOfBullets == -1) NumberOfBullets = 1; + for (i=0 ; iplayer) return; + + + player_t *player=self->player; + AWeapon * weapon=player->ReadyWeapon; + AActor *linetarget; + + if (UseAmmo && weapon) + { + if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // out of ammo + } + + if (ti) + { + angle_t ang = (self->angle - ANGLE_90) >> ANGLETOFINESHIFT; + fixed_t x = SpawnOfs_XY * finecosine[ang]; + fixed_t y = SpawnOfs_XY * finesine[ang]; + fixed_t z = SpawnHeight; + fixed_t shootangle = self->angle; + + if (Flags & FPF_AIMATANGLE) shootangle += Angle; + + // Temporarily adjusts the pitch + fixed_t SavedPlayerPitch = self->pitch; + self->pitch -= pitch; + AActor * misl=P_SpawnPlayerMissile (self, x, y, z, ti, shootangle, &linetarget); + self->pitch = SavedPlayerPitch; + + // automatic handling of seeker missiles + if (misl) + { + if (Flags & FPF_TRANSFERTRANSLATION) misl->Translation = self->Translation; + if (linetarget && misl->flags2&MF2_SEEKERMISSILE) misl->tracer=linetarget; + if (!(Flags & FPF_AIMATANGLE)) + { + // This original implementation is to aim straight ahead and then offset + // the angle from the resulting direction. + FVector3 velocity(misl->velx, misl->vely, 0); + fixed_t missilespeed = (fixed_t)velocity.Length(); + misl->angle += Angle; + angle_t an = misl->angle >> ANGLETOFINESHIFT; + misl->velx = FixedMul (missilespeed, finecosine[an]); + misl->vely = FixedMul (missilespeed, finesine[an]); + } + } + } +} + + +//========================================================================== +// +// A_CustomPunch +// +// Berserk is not handled here. That can be done with A_CheckIfInventory +// +//========================================================================== + +enum +{ + CPF_USEAMMO = 1, + CPF_DAGGER = 2, + CPF_PULLIN = 4, + CPF_NORANDOMPUFFZ = 8, + CPF_NOTURN = 16, + CPF_STEALARMOR = 32, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomPunch) +{ + ACTION_PARAM_START(8); + ACTION_PARAM_INT(Damage, 0); + ACTION_PARAM_BOOL(norandom, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(PuffType, 3); + ACTION_PARAM_FIXED(Range, 4); + ACTION_PARAM_FIXED(LifeSteal, 5); + ACTION_PARAM_INT(lifestealmax, 6); + ACTION_PARAM_CLASS(armorbonustype, 7); + + if (!self->player) return; + + player_t *player=self->player; + AWeapon * weapon=player->ReadyWeapon; + + + angle_t angle; + int pitch; + AActor * linetarget; + int actualdamage; + + if (!norandom) Damage *= (pr_cwpunch()%8+1); + + angle = self->angle + (pr_cwpunch.Random2() << 18); + if (Range == 0) Range = MELEERANGE; + pitch = P_AimLineAttack (self, angle, Range, &linetarget); + + // only use ammo when actually hitting something! + if ((flags & CPF_USEAMMO) && linetarget && weapon) + { + if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // out of ammo + } + + if (!PuffType) PuffType = PClass::FindClass(NAME_BulletPuff); + int puffFlags = LAF_ISMELEEATTACK | ((flags & CPF_NORANDOMPUFFZ) ? LAF_NORANDOMPUFFZ : 0); + + P_LineAttack (self, angle, Range, pitch, Damage, NAME_Melee, PuffType, puffFlags, &linetarget, &actualdamage); + + if (linetarget) + { + if (LifeSteal && !(linetarget->flags5 & MF5_DONTDRAIN)) + { + if (flags & CPF_STEALARMOR) + { + if (!armorbonustype) armorbonustype = PClass::FindClass("ArmorBonus"); + + if (armorbonustype->IsDescendantOf (RUNTIME_CLASS(ABasicArmorBonus))) + { + ABasicArmorBonus *armorbonus = static_cast(Spawn (armorbonustype, 0,0,0, NO_REPLACE)); + armorbonus->SaveAmount *= (actualdamage * LifeSteal) >> FRACBITS; + armorbonus->MaxSaveAmount = lifestealmax <= 0 ? armorbonus->MaxSaveAmount : lifestealmax; + armorbonus->flags |= MF_DROPPED; + armorbonus->ClearCounters(); + + if (!armorbonus->CallTryPickup (self)) + { + armorbonus->Destroy (); + } + } + } + + else + { + P_GiveBody (self, (actualdamage * LifeSteal) >> FRACBITS, lifestealmax); + } + } + + if (weapon != NULL) + { + S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); + } + + if (!(flags & CPF_NOTURN)) + { + // turn to face target + self->angle = R_PointToAngle2 (self->x, + self->y, + linetarget->x, + linetarget->y); + } + + if (flags & CPF_PULLIN) self->flags |= MF_JUSTATTACKED; + if (flags & CPF_DAGGER) P_DaggerAlert (self, linetarget); + } +} + + +//========================================================================== +// +// customizable railgun attack function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack) +{ + ACTION_PARAM_START(16); + ACTION_PARAM_INT(Damage, 0); + ACTION_PARAM_INT(Spawnofs_XY, 1); + ACTION_PARAM_BOOL(UseAmmo, 2); + ACTION_PARAM_COLOR(Color1, 3); + ACTION_PARAM_COLOR(Color2, 4); + ACTION_PARAM_INT(Flags, 5); + ACTION_PARAM_FLOAT(MaxDiff, 6); + ACTION_PARAM_CLASS(PuffType, 7); + ACTION_PARAM_ANGLE(Spread_XY, 8); + ACTION_PARAM_ANGLE(Spread_Z, 9); + ACTION_PARAM_FIXED(Range, 10); + ACTION_PARAM_INT(Duration, 11); + ACTION_PARAM_FLOAT(Sparsity, 12); + ACTION_PARAM_FLOAT(DriftSpeed, 13); + ACTION_PARAM_CLASS(SpawnClass, 14); + ACTION_PARAM_FIXED(Spawnofs_Z, 15); + + if(Range==0) Range=8192*FRACUNIT; + if(Sparsity==0) Sparsity=1.0; + + if (!self->player) return; + + AWeapon * weapon=self->player->ReadyWeapon; + + // only use ammo when actually hitting something! + if (UseAmmo) + { + if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // out of ammo + } + + angle_t angle; + angle_t slope; + + if (Flags & RAF_EXPLICITANGLE) + { + angle = Spread_XY; + slope = Spread_Z; + } + else + { + angle = pr_crailgun.Random2() * (Spread_XY / 255); + slope = pr_crailgun.Random2() * (Spread_Z / 255); + } + + P_RailAttack (self, Damage, Spawnofs_XY, Spawnofs_Z, Color1, Color2, MaxDiff, Flags, PuffType, angle, slope, Range, Duration, Sparsity, DriftSpeed, SpawnClass); +} + +//========================================================================== +// +// also for monsters +// +//========================================================================== +enum +{ + CRF_DONTAIM = 0, + CRF_AIMPARALLEL = 1, + CRF_AIMDIRECT = 2, + CRF_EXPLICITANGLE = 4, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) +{ + ACTION_PARAM_START(16); + ACTION_PARAM_INT(Damage, 0); + ACTION_PARAM_INT(Spawnofs_XY, 1); + ACTION_PARAM_COLOR(Color1, 2); + ACTION_PARAM_COLOR(Color2, 3); + ACTION_PARAM_INT(Flags, 4); + ACTION_PARAM_INT(aim, 5); + ACTION_PARAM_FLOAT(MaxDiff, 6); + ACTION_PARAM_CLASS(PuffType, 7); + ACTION_PARAM_ANGLE(Spread_XY, 8); + ACTION_PARAM_ANGLE(Spread_Z, 9); + ACTION_PARAM_FIXED(Range, 10); + ACTION_PARAM_INT(Duration, 11); + ACTION_PARAM_FLOAT(Sparsity, 12); + ACTION_PARAM_FLOAT(DriftSpeed, 13); + ACTION_PARAM_CLASS(SpawnClass, 14); + ACTION_PARAM_FIXED(Spawnofs_Z, 15); + + if(Range==0) Range=8192*FRACUNIT; + if(Sparsity==0) Sparsity=1.0; + + AActor *linetarget; + + fixed_t saved_x = self->x; + fixed_t saved_y = self->y; + angle_t saved_angle = self->angle; + fixed_t saved_pitch = self->pitch; + + if (aim && self->target == NULL) + { + return; + } + // [RH] Andy Baker's stealth monsters + if (self->flags & MF_STEALTH) + { + self->visdir = 1; + } + + self->flags &= ~MF_AMBUSH; + + + if (aim) + { + self->angle = R_PointToAngle2 (self->x, + self->y, + self->target->x, + self->target->y); + } + self->pitch = P_AimLineAttack (self, self->angle, MISSILERANGE, &linetarget, ANGLE_1*60, 0, aim ? self->target : NULL); + if (linetarget == NULL && aim) + { + // We probably won't hit the target, but aim at it anyway so we don't look stupid. + FVector2 xydiff(self->target->x - self->x, self->target->y - self->y); + double zdiff = (self->target->z + (self->target->height>>1)) - + (self->z + (self->height>>1) - self->floorclip); + self->pitch = int(atan2(zdiff, xydiff.Length()) * ANGLE_180 / -M_PI); + } + // Let the aim trail behind the player + if (aim) + { + saved_angle = self->angle = R_PointToAngle2 (self->x, self->y, + self->target->x - self->target->velx * 3, + self->target->y - self->target->vely * 3); + + if (aim == CRF_AIMDIRECT) + { + // Tricky: We must offset to the angle of the current position + // but then change the angle again to ensure proper aim. + self->x += Spawnofs_XY * finecosine[self->angle]; + self->y += Spawnofs_XY * finesine[self->angle]; + Spawnofs_XY = 0; + self->angle = R_PointToAngle2 (self->x, self->y, + self->target->x - self->target->velx * 3, + self->target->y - self->target->vely * 3); + } + + if (self->target->flags & MF_SHADOW) + { + angle_t rnd = pr_crailgun.Random2() << 21; + self->angle += rnd; + saved_angle = rnd; + } + } + + angle_t angle = (self->angle - ANG90) >> ANGLETOFINESHIFT; + + angle_t angleoffset; + angle_t slopeoffset; + + if (Flags & CRF_EXPLICITANGLE) + { + angleoffset = Spread_XY; + slopeoffset = Spread_Z; + } + else + { + angleoffset = pr_crailgun.Random2() * (Spread_XY / 255); + slopeoffset = pr_crailgun.Random2() * (Spread_Z / 255); + } + + P_RailAttack (self, Damage, Spawnofs_XY, Spawnofs_Z, Color1, Color2, MaxDiff, Flags, PuffType, angleoffset, slopeoffset, Range, Duration, Sparsity, DriftSpeed, SpawnClass); + + self->x = saved_x; + self->y = saved_y; + self->angle = saved_angle; + self->pitch = saved_pitch; +} + +//=========================================================================== +// +// DoGiveInventory +// +//=========================================================================== + +static void DoGiveInventory(AActor * receiver, DECLARE_PARAMINFO) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_CLASS(mi, 0); + ACTION_PARAM_INT(amount, 1); + ACTION_PARAM_INT(setreceiver, 2); + + COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver); + + bool res=true; + + if (amount==0) amount=1; + if (mi) + { + AInventory *item = static_cast(Spawn (mi, 0, 0, 0, NO_REPLACE)); + if (item->IsKindOf(RUNTIME_CLASS(AHealth))) + { + item->Amount *= amount; + } + else + { + item->Amount = amount; + } + item->flags |= MF_DROPPED; + item->ClearCounters(); + if (!item->CallTryPickup (receiver)) + { + item->Destroy (); + res = false; + } + else res = true; + } + else res = false; + ACTION_SET_RESULT(res); + +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveInventory) +{ + DoGiveInventory(self, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToTarget) +{ + DoGiveInventory(self->target, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToChildren) +{ + TThinkerIterator it; + AActor * mo; + + while ((mo = it.Next())) + { + if (mo->master == self) DoGiveInventory(mo, PUSH_PARAMINFO); + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToSiblings) +{ + TThinkerIterator it; + AActor * mo; + + if (self->master != NULL) + { + while ((mo = it.Next())) + { + if (mo->master == self->master && mo != self) DoGiveInventory(mo, PUSH_PARAMINFO); + } + } +} + +//=========================================================================== +// +// A_TakeInventory +// +//=========================================================================== + +enum +{ + TIF_NOTAKEINFINITE = 1, +}; + +void DoTakeInventory(AActor * receiver, DECLARE_PARAMINFO) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_CLASS(item, 0); + ACTION_PARAM_INT(amount, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_INT(setreceiver, 3); + + if (!item) return; + COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver); + + bool res = false; + + AInventory * inv = receiver->FindInventory(item); + + if (inv && !inv->IsKindOf(RUNTIME_CLASS(AHexenArmor))) + { + if (inv->Amount > 0) + { + res = true; + } + // Do not take ammo if the "no take infinite/take as ammo depletion" flag is set + // and infinite ammo is on + if (flags & TIF_NOTAKEINFINITE && + ((dmflags & DF_INFINITE_AMMO) || (receiver->player->cheats & CF_INFINITEAMMO)) && + inv->IsKindOf(RUNTIME_CLASS(AAmmo))) + { + // Nothing to do here, except maybe res = false;? Would it make sense? + } + else if (!amount || amount>=inv->Amount) + { + if (inv->ItemFlags&IF_KEEPDEPLETED) inv->Amount=0; + else inv->Destroy(); + } + else inv->Amount-=amount; + } + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeInventory) +{ + DoTakeInventory(self, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromTarget) +{ + DoTakeInventory(self->target, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromChildren) +{ + TThinkerIterator it; + AActor * mo; + + while ((mo = it.Next())) + { + if (mo->master == self) DoTakeInventory(mo, PUSH_PARAMINFO); + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromSiblings) +{ + TThinkerIterator it; + AActor * mo; + + if (self->master != NULL) + { + while ((mo = it.Next())) + { + if (mo->master == self->master && mo != self) DoTakeInventory(mo, PUSH_PARAMINFO); + } + } +} + +//=========================================================================== +// +// Common code for A_SpawnItem and A_SpawnItemEx +// +//=========================================================================== + +enum SIX_Flags +{ + SIXF_TRANSFERTRANSLATION = 1 << 0, + SIXF_ABSOLUTEPOSITION = 1 << 1, + SIXF_ABSOLUTEANGLE = 1 << 2, + SIXF_ABSOLUTEVELOCITY = 1 << 3, + SIXF_SETMASTER = 1 << 4, + SIXF_NOCHECKPOSITION = 1 << 5, + SIXF_TELEFRAG = 1 << 6, + SIXF_CLIENTSIDE = 1 << 7, // only used by Skulldronum + SIXF_TRANSFERAMBUSHFLAG = 1 << 8, + SIXF_TRANSFERPITCH = 1 << 9, + SIXF_TRANSFERPOINTERS = 1 << 10, + SIXF_USEBLOODCOLOR = 1 << 11, + SIXF_CLEARCALLERTID = 1 << 12, + SIXF_MULTIPLYSPEED = 1 << 13, + SIXF_TRANSFERSCALE = 1 << 14, + SIXF_TRANSFERSPECIAL = 1 << 15, + SIXF_CLEARCALLERSPECIAL = 1 << 16, + SIXF_TRANSFERSTENCILCOL = 1 << 17, + SIXF_TRANSFERALPHA = 1 << 18, + SIXF_TRANSFERRENDERSTYLE = 1 << 19, + SIXF_SETTARGET = 1 << 20, + SIXF_SETTRACER = 1 << 21, + SIXF_NOPOINTERS = 1 << 22, + SIXF_ORIGINATOR = 1 << 23, +}; + +static bool InitSpawnedItem(AActor *self, AActor *mo, int flags) +{ + if (mo == NULL) + { + return false; + } + AActor *originator = self; + + if (!(mo->flags2 & MF2_DONTTRANSLATE)) + { + if (flags & SIXF_TRANSFERTRANSLATION) + { + mo->Translation = self->Translation; + } + else if (flags & SIXF_USEBLOODCOLOR) + { + // [XA] Use the spawning actor's BloodColor to translate the newly-spawned object. + PalEntry bloodcolor = self->GetBloodColor(); + mo->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a); + } + } + if (flags & SIXF_TRANSFERPOINTERS) + { + mo->target = self->target; + mo->master = self->master; // This will be overridden later if SIXF_SETMASTER is set + mo->tracer = self->tracer; + } + + mo->angle = self->angle; + if (flags & SIXF_TRANSFERPITCH) + { + mo->pitch = self->pitch; + } + if (!(flags & SIXF_ORIGINATOR)) + { + while (originator && originator->isMissile()) + { + originator = originator->target; + } + } + if (flags & SIXF_TELEFRAG) + { + P_TeleportMove(mo, mo->x, mo->y, mo->z, true); + // This is needed to ensure consistent behavior. + // Otherwise it will only spawn if nothing gets telefragged + flags |= SIXF_NOCHECKPOSITION; + } + if (mo->flags3 & MF3_ISMONSTER) + { + if (!(flags & SIXF_NOCHECKPOSITION) && !P_TestMobjLocation(mo)) + { + // The monster is blocked so don't spawn it at all! + mo->ClearCounters(); + mo->Destroy(); + return false; + } + else if (originator && !(flags & SIXF_NOPOINTERS)) + { + if (originator->flags3 & MF3_ISMONSTER) + { + // If this is a monster transfer all friendliness information + mo->CopyFriendliness(originator, true); + } + else if (originator->player) + { + // A player always spawns a monster friendly to him + mo->flags |= MF_FRIENDLY; + mo->SetFriendPlayer(originator->player); + + AActor * attacker=originator->player->attacker; + if (attacker) + { + if (!(attacker->flags&MF_FRIENDLY) || + (deathmatch && attacker->FriendPlayer!=0 && attacker->FriendPlayer!=mo->FriendPlayer)) + { + // Target the monster which last attacked the player + mo->LastHeard = mo->target = attacker; + } + } + } + } + } + else if (!(flags & SIXF_TRANSFERPOINTERS)) + { + // If this is a missile or something else set the target to the originator + mo->target = originator ? originator : self; + } + if (flags & SIXF_NOPOINTERS) + { + //[MC]Intentionally eliminate pointers. Overrides TRANSFERPOINTERS, but is overridden by SETMASTER/TARGET/TRACER. + mo->LastHeard = NULL; //Sanity check. + mo->target = NULL; + mo->master = NULL; + mo->tracer = NULL; + } + if (flags & SIXF_SETMASTER) + { + mo->master = originator; + } + if (flags & SIXF_SETTARGET) + { + mo->target = originator; + } + if (flags & SIXF_SETTRACER) + { + mo->tracer = originator; + } + if (flags & SIXF_TRANSFERSCALE) + { + mo->scaleX = self->scaleX; + mo->scaleY = self->scaleY; + } + if (flags & SIXF_TRANSFERAMBUSHFLAG) + { + mo->flags = (mo->flags & ~MF_AMBUSH) | (self->flags & MF_AMBUSH); + } + if (flags & SIXF_CLEARCALLERTID) + { + self->RemoveFromHash(); + self->tid = 0; + } + if (flags & SIXF_TRANSFERSPECIAL) + { + mo->special = self->special; + memcpy(mo->args, self->args, sizeof(self->args)); + } + if (flags & SIXF_CLEARCALLERSPECIAL) + { + self->special = 0; + memset(self->args, 0, sizeof(self->args)); + } + if (flags & SIXF_TRANSFERSTENCILCOL) + { + mo->fillcolor = self->fillcolor; + } + if (flags & SIXF_TRANSFERALPHA) + { + mo->alpha = self->alpha; + } + if (flags & SIXF_TRANSFERRENDERSTYLE) + { + mo->RenderStyle = self->RenderStyle; + } + + return true; +} + +//=========================================================================== +// +// A_SpawnItem +// +// Spawns an item in front of the caller like Heretic's time bomb +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItem) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_CLASS(missile, 0); + ACTION_PARAM_FIXED(distance, 1); + ACTION_PARAM_FIXED(zheight, 2); + ACTION_PARAM_BOOL(useammo, 3); + ACTION_PARAM_BOOL(transfer_translation, 4); + + if (!missile) + { + ACTION_SET_RESULT(false); + return; + } + + // Don't spawn monsters if this actor has been massacred + if (self->DamageType == NAME_Massacre && GetDefaultByType(missile)->flags3&MF3_ISMONSTER) return; + + if (distance==0) + { + // use the minimum distance that does not result in an overlap + distance=(self->radius+GetDefaultByType(missile)->radius)>>FRACBITS; + } + + if (ACTION_CALL_FROM_WEAPON()) + { + // Used from a weapon so use some ammo + AWeapon * weapon=self->player->ReadyWeapon; + + if (!weapon) return; + if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) return; + } + + AActor * mo = Spawn( missile, + self->x + FixedMul(distance, finecosine[self->angle>>ANGLETOFINESHIFT]), + self->y + FixedMul(distance, finesine[self->angle>>ANGLETOFINESHIFT]), + self->z - self->floorclip + self->GetBobOffset() + zheight, ALLOW_REPLACE); + + int flags = (transfer_translation ? SIXF_TRANSFERTRANSLATION : 0) + (useammo ? SIXF_SETMASTER : 0); + bool res = InitSpawnedItem(self, mo, flags); + ACTION_SET_RESULT(res); // for an inventory item's use state +} + +//=========================================================================== +// +// A_SpawnItemEx +// +// Enhanced spawning function +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItemEx) +{ + ACTION_PARAM_START(11); + ACTION_PARAM_CLASS(missile, 0); + ACTION_PARAM_FIXED(xofs, 1); + ACTION_PARAM_FIXED(yofs, 2); + ACTION_PARAM_FIXED(zofs, 3); + ACTION_PARAM_FIXED(xvel, 4); + ACTION_PARAM_FIXED(yvel, 5); + ACTION_PARAM_FIXED(zvel, 6); + ACTION_PARAM_ANGLE(Angle, 7); + ACTION_PARAM_INT(flags, 8); + ACTION_PARAM_INT(chance, 9); + ACTION_PARAM_INT(tid, 10); + + if (!missile) + { + ACTION_SET_RESULT(false); + return; + } + + if (chance > 0 && pr_spawnitemex()DamageType == NAME_Massacre && GetDefaultByType(missile)->flags3&MF3_ISMONSTER) return; + + fixed_t x,y; + + if (!(flags & SIXF_ABSOLUTEANGLE)) + { + Angle += self->angle; + } + + angle_t ang = Angle >> ANGLETOFINESHIFT; + + if (flags & SIXF_ABSOLUTEPOSITION) + { + x = self->x + xofs; + y = self->y + yofs; + } + else + { + // in relative mode negative y values mean 'left' and positive ones mean 'right' + // This is the inverse orientation of the absolute mode! + x = self->x + FixedMul(xofs, finecosine[ang]) + FixedMul(yofs, finesine[ang]); + y = self->y + FixedMul(xofs, finesine[ang]) - FixedMul(yofs, finecosine[ang]); + } + + if (!(flags & SIXF_ABSOLUTEVELOCITY)) + { + // Same orientation issue here! + fixed_t newxvel = FixedMul(xvel, finecosine[ang]) + FixedMul(yvel, finesine[ang]); + yvel = FixedMul(xvel, finesine[ang]) - FixedMul(yvel, finecosine[ang]); + xvel = newxvel; + } + + AActor *mo = Spawn(missile, x, y, self->z - self->floorclip + self->GetBobOffset() + zofs, ALLOW_REPLACE); + bool res = InitSpawnedItem(self, mo, flags); + ACTION_SET_RESULT(res); // for an inventory item's use state + if (res) + { + if (tid != 0) + { + assert(mo->tid == 0); + mo->tid = tid; + mo->AddToHash(); + } + if (flags & SIXF_MULTIPLYSPEED) + { + mo->velx = FixedMul(xvel, mo->Speed); + mo->vely = FixedMul(yvel, mo->Speed); + mo->velz = FixedMul(zvel, mo->Speed); + } + else + { + mo->velx = xvel; + mo->vely = yvel; + mo->velz = zvel; + } + mo->angle = Angle; + } +} + +//=========================================================================== +// +// A_ThrowGrenade +// +// Throws a grenade (like Hexen's fighter flechette) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ThrowGrenade) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_CLASS(missile, 0); + ACTION_PARAM_FIXED(zheight, 1); + ACTION_PARAM_FIXED(xyvel, 2); + ACTION_PARAM_FIXED(zvel, 3); + ACTION_PARAM_BOOL(useammo, 4); + + if (missile == NULL) return; + + if (ACTION_CALL_FROM_WEAPON()) + { + // Used from a weapon, so use some ammo + AWeapon *weapon = self->player->ReadyWeapon; + + if (!weapon) return; + if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) return; + } + + + AActor * bo; + + bo = Spawn(missile, self->x, self->y, + self->z - self->floorclip + self->GetBobOffset() + zheight + 35*FRACUNIT + (self->player? self->player->crouchoffset : 0), + ALLOW_REPLACE); + if (bo) + { + P_PlaySpawnSound(bo, self); + if (xyvel != 0) + bo->Speed = xyvel; + bo->angle = self->angle + (((pr_grenade()&7) - 4) << 24); + + angle_t pitch = angle_t(-self->pitch) >> ANGLETOFINESHIFT; + angle_t angle = bo->angle >> ANGLETOFINESHIFT; + + // There are two vectors we are concerned about here: xy and z. We rotate + // them separately according to the shooter's pitch and then sum them to + // get the final velocity vector to shoot with. + + fixed_t xy_xyscale = FixedMul(bo->Speed, finecosine[pitch]); + fixed_t xy_velz = FixedMul(bo->Speed, finesine[pitch]); + fixed_t xy_velx = FixedMul(xy_xyscale, finecosine[angle]); + fixed_t xy_vely = FixedMul(xy_xyscale, finesine[angle]); + + pitch = angle_t(self->pitch) >> ANGLETOFINESHIFT; + fixed_t z_xyscale = FixedMul(zvel, finesine[pitch]); + fixed_t z_velz = FixedMul(zvel, finecosine[pitch]); + fixed_t z_velx = FixedMul(z_xyscale, finecosine[angle]); + fixed_t z_vely = FixedMul(z_xyscale, finesine[angle]); + + bo->velx = xy_velx + z_velx + (self->velx >> 1); + bo->vely = xy_vely + z_vely + (self->vely >> 1); + bo->velz = xy_velz + z_velz; + + bo->target = self; + P_CheckMissileSpawn (bo, self->radius); + } + else ACTION_SET_RESULT(false); +} + + +//=========================================================================== +// +// A_Recoil +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Recoil) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_FIXED(xyvel, 0); + + angle_t angle = self->angle + ANG180; + angle >>= ANGLETOFINESHIFT; + self->velx += FixedMul (xyvel, finecosine[angle]); + self->vely += FixedMul (xyvel, finesine[angle]); +} + + +//=========================================================================== +// +// A_SelectWeapon +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SelectWeapon) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_CLASS(cls, 0); + + if (cls == NULL || self->player == NULL) + { + ACTION_SET_RESULT(false); + return; + } + + AWeapon * weaponitem = static_cast(self->FindInventory(cls)); + + if (weaponitem != NULL && weaponitem->IsKindOf(RUNTIME_CLASS(AWeapon))) + { + if (self->player->ReadyWeapon != weaponitem) + { + self->player->PendingWeapon = weaponitem; + } + } + else ACTION_SET_RESULT(false); + +} + + +//=========================================================================== +// +// A_Print +// +//=========================================================================== +EXTERN_CVAR(Float, con_midtime) + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Print) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_STRING(text, 0); + ACTION_PARAM_FLOAT(time, 1); + ACTION_PARAM_NAME(fontname, 2); + + if (text[0] == '$') text = GStrings(text+1); + if (self->CheckLocalView (consoleplayer) || + (self->target!=NULL && self->target->CheckLocalView (consoleplayer))) + { + float saved = con_midtime; + FFont *font = NULL; + + if (fontname != NAME_None) + { + font = V_GetFont(fontname); + } + if (time > 0) + { + con_midtime = time; + } + + FString formatted = strbin1(text); + C_MidPrint(font != NULL ? font : SmallFont, formatted.GetChars()); + con_midtime = saved; + } + ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! +} + +//=========================================================================== +// +// A_PrintBold +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PrintBold) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_STRING(text, 0); + ACTION_PARAM_FLOAT(time, 1); + ACTION_PARAM_NAME(fontname, 2); + + float saved = con_midtime; + FFont *font = NULL; + + if (text[0] == '$') text = GStrings(text+1); + if (fontname != NAME_None) + { + font = V_GetFont(fontname); + } + if (time > 0) + { + con_midtime = time; + } + + FString formatted = strbin1(text); + C_MidPrintBold(font != NULL ? font : SmallFont, formatted.GetChars()); + con_midtime = saved; + ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! +} + +//=========================================================================== +// +// A_Log +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Log) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STRING(text, 0); + + if (text[0] == '$') text = GStrings(text+1); + FString formatted = strbin1(text); + Printf("%s\n", formatted.GetChars()); + ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! +} + +//========================================================================= +// +// A_LogInt +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LogInt) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(num, 0); + Printf("%d\n", num); + ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! +} + +//=========================================================================== +// +// A_SetTranslucent +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTranslucent) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_FIXED(alpha, 0); + ACTION_PARAM_INT(mode, 1); + + mode = mode == 0 ? STYLE_Translucent : mode == 2 ? STYLE_Fuzzy : STYLE_Add; + + self->RenderStyle.Flags &= ~STYLEF_Alpha1; + self->alpha = clamp(alpha, 0, FRACUNIT); + self->RenderStyle = ERenderStyle(mode); +} + +//=========================================================================== +// +// A_FadeIn +// +// Fades the actor in +// +//=========================================================================== + +enum FadeFlags +{ + FTF_REMOVE = 1 << 0, + FTF_CLAMP = 1 << 1, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeIn) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_FIXED(reduce, 0); + ACTION_PARAM_INT(flags, 1); + + if (reduce == 0) + { + reduce = FRACUNIT / 10; + } + self->RenderStyle.Flags &= ~STYLEF_Alpha1; + self->alpha += reduce; + + if (self->alpha >= (FRACUNIT * 1)) + { + if (flags & FTF_CLAMP) + self->alpha = (FRACUNIT * 1); + if (flags & FTF_REMOVE) + self->Destroy(); + } +} + +//=========================================================================== +// +// A_FadeOut +// +// fades the actor out and destroys it when done +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeOut) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_FIXED(reduce, 0); + ACTION_PARAM_INT(flags, 1); + + if (reduce == 0) + { + reduce = FRACUNIT/10; + } + self->RenderStyle.Flags &= ~STYLEF_Alpha1; + self->alpha -= reduce; + if (self->alpha <= 0) + { + if (flags & FTF_CLAMP) + self->alpha = 0; + if (flags & FTF_REMOVE) + self->Destroy(); + } +} + +//=========================================================================== +// +// A_FadeTo +// +// fades the actor to a specified transparency by a specified amount and +// destroys it if so desired +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeTo) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_FIXED(target, 0); + ACTION_PARAM_FIXED(amount, 1); + ACTION_PARAM_INT(flags, 2); + + self->RenderStyle.Flags &= ~STYLEF_Alpha1; + + if (self->alpha > target) + { + self->alpha -= amount; + + if (self->alpha < target) + { + self->alpha = target; + } + } + else if (self->alpha < target) + { + self->alpha += amount; + + if (self->alpha > target) + { + self->alpha = target; + } + } + if (flags & FTF_CLAMP) + { + if (self->alpha > (FRACUNIT * 1)) + self->alpha = (FRACUNIT * 1); + else if (self->alpha < 0) + self->alpha = 0; + } + if (self->alpha == target && (flags & FTF_REMOVE)) + { + self->Destroy(); + } +} + +//=========================================================================== +// +// A_Scale(float scalex, optional float scaley) +// +// Scales the actor's graphics. If scaley is 0, use scalex. +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetScale) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_FIXED(scalex, 0); + ACTION_PARAM_FIXED(scaley, 1); + ACTION_PARAM_INT(ptr, 2); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } + + ref->scaleX = scalex; + ref->scaleY = scaley ? scaley : scalex; +} + +//=========================================================================== +// +// A_SetMass(int mass) +// +// Sets the actor's mass. +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetMass) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(mass, 0); + + self->Mass = mass; +} + +//=========================================================================== +// +// A_SpawnDebris +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnDebris) +{ + int i; + AActor * mo; + + ACTION_PARAM_START(4); + ACTION_PARAM_CLASS(debris, 0); + ACTION_PARAM_BOOL(transfer_translation, 1); + ACTION_PARAM_FIXED(mult_h, 2); + ACTION_PARAM_FIXED(mult_v, 3); + + if (debris == NULL) return; + + // only positive values make sense here + if (mult_v<=0) mult_v=FRACUNIT; + if (mult_h<=0) mult_h=FRACUNIT; + + for (i = 0; i < GetDefaultByType(debris)->health; i++) + { + mo = Spawn(debris, self->x+((pr_spawndebris()-128)<<12), + self->y + ((pr_spawndebris()-128)<<12), + self->z + (pr_spawndebris()*self->height/256+self->GetBobOffset()), ALLOW_REPLACE); + if (mo) + { + if (transfer_translation) + { + mo->Translation = self->Translation; + } + if (i < mo->GetClass()->ActorInfo->NumOwnedStates) + { + mo->SetState(mo->GetClass()->ActorInfo->OwnedStates + i); + } + mo->velz = FixedMul(mult_v, ((pr_spawndebris()&7)+5)*FRACUNIT); + mo->velx = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6)); + mo->vely = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6)); + } + } +} + + +//=========================================================================== +// +// A_CheckSight +// jumps if no player can see this actor +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSight) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + for (int i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { + // Always check sight from each player. + if (P_CheckSight(players[i].mo, self, SF_IGNOREVISIBILITY)) + { + return; + } + // If a player is viewing from a non-player, then check that too. + if (players[i].camera != NULL && players[i].camera->player == NULL && + P_CheckSight(players[i].camera, self, SF_IGNOREVISIBILITY)) + { + return; + } + } + } + + ACTION_JUMP(jump); +} + +//=========================================================================== +// +// A_CheckSightOrRange +// Jumps if this actor is out of range of all players *and* out of sight. +// Useful for maps with many multi-actor special effects. +// +//=========================================================================== +static bool DoCheckSightOrRange(AActor *self, AActor *camera, double range, bool twodi) +{ + if (camera == NULL) + { + return false; + } + // Check distance first, since it's cheaper than checking sight. + double dx = self->x - camera->x; + double dy = self->y - camera->y; + double dz; + fixed_t eyez = (camera->z + camera->height - (camera->height>>2)); // same eye height as P_CheckSight + if (eyez > self->z + self->height) + { + dz = self->z + self->height - eyez; + } + else if (eyez < self->z) + { + dz = self->z - eyez; + } + else + { + dz = 0; + } + double distance = (dx * dx) + (dy * dy) + (twodi == 0? (dz * dz) : 0); + if (distance <= range){ + // Within range + return true; + } + + // Now check LOS. + if (P_CheckSight(camera, self, SF_IGNOREVISIBILITY)) + { // Visible + return true; + } + return false; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSightOrRange) +{ + ACTION_PARAM_START(3); + double range = EvalExpressionF(ParameterIndex+0, self); + ACTION_PARAM_STATE(jump, 1); + ACTION_PARAM_BOOL(twodi, 2); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots + for (int i = 0; i < MAXPLAYERS; ++i) + { + if (playeringame[i]) + { + // Always check from each player. + if (DoCheckSightOrRange(self, players[i].mo, range, twodi)) + { + return; + } + // If a player is viewing from a non-player, check that too. + if (players[i].camera != NULL && players[i].camera->player == NULL && + DoCheckSightOrRange(self, players[i].camera, range, twodi)) + { + return; + } + } + } + ACTION_JUMP(jump); +} + +//=========================================================================== +// +// A_CheckRange +// Jumps if this actor is out of range of all players. +// +//=========================================================================== +static bool DoCheckRange(AActor *self, AActor *camera, double range, bool twodi) +{ + if (camera == NULL) + { + return false; + } + // Check distance first, since it's cheaper than checking sight. + double dx = self->x - camera->x; + double dy = self->y - camera->y; + double dz; + fixed_t eyez = (camera->z + camera->height - (camera->height>>2)); // same eye height as P_CheckSight + if (eyez > self->z + self->height){ + dz = self->z + self->height - eyez; + } + else if (eyez < self->z){ + dz = self->z - eyez; + } + else{ + dz = 0; + } + double distance = (dx * dx) + (dy * dy) + (twodi == 0? (dz * dz) : 0); + if (distance <= range){ + // Within range + return true; + } + return false; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckRange) +{ + ACTION_PARAM_START(3); + double range = EvalExpressionF(ParameterIndex+0, self); + ACTION_PARAM_STATE(jump, 1); + ACTION_PARAM_BOOL(twodi, 2); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots + for (int i = 0; i < MAXPLAYERS; ++i) + { + if (playeringame[i]) + { + // Always check from each player. + if (DoCheckRange(self, players[i].mo, range, twodi)) + { + return; + } + // If a player is viewing from a non-player, check that too. + if (players[i].camera != NULL && players[i].camera->player == NULL && + DoCheckRange(self, players[i].camera, range, twodi)) + { + return; + } + } + } + ACTION_JUMP(jump); +} + + +//=========================================================================== +// +// Inventory drop +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropInventory) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_CLASS(drop, 0); + + if (drop) + { + AInventory * inv = self->FindInventory(drop); + if (inv) + { + self->DropInventory(inv); + } + } +} + + +//=========================================================================== +// +// A_SetBlend +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetBlend) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_COLOR(color, 0); + ACTION_PARAM_FLOAT(alpha, 1); + ACTION_PARAM_INT(tics, 2); + ACTION_PARAM_COLOR(color2, 3); + + if (color == MAKEARGB(255,255,255,255)) color=0; + if (color2 == MAKEARGB(255,255,255,255)) color2=0; + if (!color2.a) + color2 = color; + + new DFlashFader(color.r/255.0f, color.g/255.0f, color.b/255.0f, alpha, + color2.r/255.0f, color2.g/255.0f, color2.b/255.0f, 0, + (float)tics/TICRATE, self); +} + + +//=========================================================================== +// +// A_JumpIf +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIf) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_BOOL(expression, 0); + ACTION_PARAM_STATE(jump, 1); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (expression) ACTION_JUMP(jump); + +} + +//=========================================================================== +// +// A_CountdownArg +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CountdownArg) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(cnt, 0); + ACTION_PARAM_STATE(state, 1); + + if (cnt<0 || cnt>=5) return; + if (!self->args[cnt]--) + { + if (self->flags&MF_MISSILE) + { + P_ExplodeMissile(self, NULL, NULL); + } + else if (self->flags&MF_SHOOTABLE) + { + P_DamageMobj (self, NULL, NULL, self->health, NAME_None, DMG_FORCED); + } + else + { + // can't use "Death" as default parameter with current DECORATE parser. + if (state == NULL) state = self->FindState(NAME_Death); + self->SetState(state); + } + } + +} + +//============================================================================ +// +// A_Burst +// +//============================================================================ + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Burst) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_CLASS(chunk, 0); + + int i, numChunks; + AActor * mo; + + if (chunk == NULL) return; + + self->velx = self->vely = self->velz = 0; + self->height = self->GetDefault()->height; + + // [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 (4, (self->radius>>FRACBITS)*(self->height>>FRACBITS)/32); + i = (pr_burst.Random2()) % (numChunks/4); + for (i = MAX (24, numChunks + i); i >= 0; i--) + { + mo = Spawn(chunk, + self->x + (((pr_burst()-128)*self->radius)>>7), + self->y + (((pr_burst()-128)*self->radius)>>7), + self->z + (pr_burst()*self->height/255 + self->GetBobOffset()), ALLOW_REPLACE); + + if (mo) + { + mo->velz = FixedDiv(mo->z - self->z, self->height)<<2; + mo->velx = pr_burst.Random2 () << (FRACBITS-7); + mo->vely = pr_burst.Random2 () << (FRACBITS-7); + mo->RenderStyle = self->RenderStyle; + mo->alpha = self->alpha; + mo->CopyFriendliness(self, true); + } + } + + // [RH] Do some stuff to make this more useful outside Hexen + if (self->flags4 & MF4_BOSSDEATH) + { + CALL_ACTION(A_BossDeath, self); + } + A_Unblock(self, true); + + self->Destroy (); +} + +//=========================================================================== +// +// A_CheckFloor +// [GRB] Jumps if actor is standing on floor +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFloor) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (self->z <= self->floorz) + { + ACTION_JUMP(jump); + } + +} + +//=========================================================================== +// +// A_CheckCeiling +// [GZ] Totally copied on A_CheckFloor, jumps if actor touches ceiling +// + +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckCeiling) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + ACTION_SET_RESULT(false); + if (self->z+self->height >= self->ceilingz) // Height needs to be counted + { + ACTION_JUMP(jump); + } + +} + +//=========================================================================== +// +// A_Stop +// resets all velocity of the actor to 0 +// +//=========================================================================== +DEFINE_ACTION_FUNCTION(AActor, A_Stop) +{ + self->velx = self->vely = self->velz = 0; + if (self->player && self->player->mo == self && !(self->player->cheats & CF_PREDICTING)) + { + self->player->mo->PlayIdle(); + self->player->velx = self->player->vely = 0; + } +} + +static void CheckStopped(AActor *self) +{ + if (self->player != NULL && + self->player->mo == self && + !(self->player->cheats & CF_PREDICTING) && + !(self->velx | self->vely | self->velz)) + { + self->player->mo->PlayIdle(); + self->player->velx = self->player->vely = 0; + } +} + +//=========================================================================== +// +// A_Respawn +// +//=========================================================================== + +extern void AF_A_RestoreSpecialPosition(DECLARE_PARAMINFO); + +enum RS_Flags +{ + RSF_FOG=1, + RSF_KEEPTARGET=2, + RSF_TELEFRAG=4, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Respawn) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(flags, 0); + bool oktorespawn = false; + fixed_t oldx = self->x; + fixed_t oldy = self->y; + fixed_t oldz = self->z; + self->flags |= MF_SOLID; + self->height = self->GetDefault()->height; + CALL_ACTION(A_RestoreSpecialPosition, self); + + if (flags & RSF_TELEFRAG) + { + // [KS] DIE DIE DIE DIE erm *ahem* =) + oktorespawn = P_TeleportMove(self, self->x, self->y, self->z, true); + if (oktorespawn) + { // Need to do this over again, since P_TeleportMove() will redo + // it with the proper point-on-side calculation. + self->UnlinkFromWorld(); + self->LinkToWorld(true); + sector_t *sec = self->Sector; + self->dropoffz = + self->floorz = sec->floorplane.ZatPoint(self->x, self->y); + self->ceilingz = sec->ceilingplane.ZatPoint(self->x, self->y); + P_FindFloorCeiling(self, FFCF_ONLYSPAWNPOS); + } + } + else + { + oktorespawn = P_CheckPosition(self, self->x, self->y, true); + } + + if (oktorespawn) + { + AActor *defs = self->GetDefault(); + self->health = defs->health; + + // [KS] Don't keep target, because it could be self if the monster committed suicide + // ...Actually it's better off an option, so you have better control over monster behavior. + if (!(flags & RSF_KEEPTARGET)) + { + self->target = NULL; + self->LastHeard = NULL; + self->lastenemy = NULL; + } + else + { + // Don't attack yourself (Re: "Marine targets itself after suicide") + if (self->target == self) self->target = NULL; + if (self->lastenemy == self) self->lastenemy = NULL; + } + + self->flags = (defs->flags & ~MF_FRIENDLY) | (self->flags & MF_FRIENDLY); + self->flags2 = defs->flags2; + self->flags3 = (defs->flags3 & ~(MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)) | (self->flags3 & (MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)); + self->flags4 = (defs->flags4 & ~MF4_NOHATEPLAYERS) | (self->flags4 & MF4_NOHATEPLAYERS); + self->flags5 = defs->flags5; + self->flags6 = defs->flags6; + self->flags7 = defs->flags7; + self->SetState (self->SpawnState); + self->renderflags &= ~RF_INVISIBLE; + + if (flags & RSF_FOG) + { + P_SpawnTeleportFog(self, oldx, oldy, oldz, true); + P_SpawnTeleportFog(self, self->x, self->y, self->z, false); + } + if (self->CountsAsKill()) + { + level.total_monsters++; + } + } + else + { + self->flags &= ~MF_SOLID; + } +} + + +//========================================================================== +// +// A_PlayerSkinCheck +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayerSkinCheck) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (self->player != NULL && + skins[self->player->userinfo.GetSkin()].othergame) + { + ACTION_JUMP(jump); + } +} + +//=========================================================================== +// +// A_SetGravity +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetGravity) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_FIXED(val, 0); + + self->gravity = clamp (val, 0, FRACUNIT*10); +} + + +// [KS] *** Start of my modifications *** + +//=========================================================================== +// +// A_ClearTarget +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION(AActor, A_ClearTarget) +{ + self->target = NULL; + self->LastHeard = NULL; + self->lastenemy = NULL; +} + +//========================================================================== +// +// A_CheckLOF (state jump, int flags = CRF_AIM_VERT|CRF_AIM_HOR, +// fixed range = 0, angle angle = 0, angle pitch = 0, +// fixed offsetheight = 32, fixed offsetwidth = 0, +// int ptr_target = AAPTR_DEFAULT (target) ) +// +//========================================================================== + +enum CLOF_flags +{ + CLOFF_NOAIM_VERT = 0x00000001, + CLOFF_NOAIM_HORZ = 0x00000002, + + CLOFF_JUMPENEMY = 0x00000004, + CLOFF_JUMPFRIEND = 0x00000008, + CLOFF_JUMPOBJECT = 0x00000010, + CLOFF_JUMPNONHOSTILE = 0x00000020, + + CLOFF_SKIPENEMY = 0x00000040, + CLOFF_SKIPFRIEND = 0x00000080, + CLOFF_SKIPOBJECT = 0x00000100, + CLOFF_SKIPNONHOSTILE = 0x00000200, + + CLOFF_MUSTBESHOOTABLE = 0x00000400, + + CLOFF_SKIPTARGET = 0x00000800, + CLOFF_ALLOWNULL = 0x00001000, + CLOFF_CHECKPARTIAL = 0x00002000, + + CLOFF_MUSTBEGHOST = 0x00004000, + CLOFF_IGNOREGHOST = 0x00008000, + + CLOFF_MUSTBESOLID = 0x00010000, + CLOFF_BEYONDTARGET = 0x00020000, + + CLOFF_FROMBASE = 0x00040000, + CLOFF_MUL_HEIGHT = 0x00080000, + CLOFF_MUL_WIDTH = 0x00100000, + + CLOFF_JUMP_ON_MISS = 0x00200000, + CLOFF_AIM_VERT_NOOFFSET = 0x00400000, + + CLOFF_SETTARGET = 0x00800000, + CLOFF_SETMASTER = 0x01000000, + CLOFF_SETTRACER = 0x02000000, +}; + +struct LOFData +{ + AActor *Self; + AActor *Target; + int Flags; + bool BadActor; +}; + +ETraceStatus CheckLOFTraceFunc(FTraceResults &trace, void *userdata) +{ + LOFData *data = (LOFData *)userdata; + int flags = data->Flags; + + if (trace.HitType != TRACE_HitActor) + { + return TRACE_Stop; + } + if (trace.Actor == data->Target) + { + if (flags & CLOFF_SKIPTARGET) + { + if (flags & CLOFF_BEYONDTARGET) + { + return TRACE_Skip; + } + return TRACE_Abort; + } + return TRACE_Stop; + } + if (flags & CLOFF_MUSTBESHOOTABLE) + { // all shootability checks go here + if (!(trace.Actor->flags & MF_SHOOTABLE)) + { + return TRACE_Skip; + } + if (trace.Actor->flags2 & MF2_NONSHOOTABLE) + { + return TRACE_Skip; + } + } + if ((flags & CLOFF_MUSTBESOLID) && !(trace.Actor->flags & MF_SOLID)) + { + return TRACE_Skip; + } + if (flags & CLOFF_MUSTBEGHOST) + { + if (!(trace.Actor->flags3 & MF3_GHOST)) + { + return TRACE_Skip; + } + } + else if (flags & CLOFF_IGNOREGHOST) + { + if (trace.Actor->flags3 & MF3_GHOST) + { + return TRACE_Skip; + } + } + if ( + ((flags & CLOFF_JUMPENEMY) && data->Self->IsHostile(trace.Actor)) || + ((flags & CLOFF_JUMPFRIEND) && data->Self->IsFriend(trace.Actor)) || + ((flags & CLOFF_JUMPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) || + ((flags & CLOFF_JUMPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor)) + ) + { + return TRACE_Stop; + } + if ( + ((flags & CLOFF_SKIPENEMY) && data->Self->IsHostile(trace.Actor)) || + ((flags & CLOFF_SKIPFRIEND) && data->Self->IsFriend(trace.Actor)) || + ((flags & CLOFF_SKIPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) || + ((flags & CLOFF_SKIPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor)) + ) + { + return TRACE_Skip; + } + data->BadActor = true; + return TRACE_Abort; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckLOF) +{ + // Check line of fire + + /* + Not accounted for / I don't know how it works: FLOORCLIP + */ + + AActor *target; + fixed_t + x1, y1, z1, + vx, vy, vz; + + ACTION_PARAM_START(9); + + ACTION_PARAM_STATE(jump, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_FIXED(range, 2); + ACTION_PARAM_FIXED(minrange, 3); + { + ACTION_PARAM_ANGLE(angle, 4); + ACTION_PARAM_ANGLE(pitch, 5); + ACTION_PARAM_FIXED(offsetheight, 6); + ACTION_PARAM_FIXED(offsetwidth, 7); + ACTION_PARAM_INT(ptr_target, 8); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + target = COPY_AAPTR(self, ptr_target == AAPTR_DEFAULT ? AAPTR_TARGET|AAPTR_PLAYER_GETTARGET|AAPTR_NULL : ptr_target); // no player-support by default + + if (flags & CLOFF_MUL_HEIGHT) + { + if (self->player != NULL) + { + // Synced with hitscan: self->player->mo->height is strangely conscientious about getting the right actor for player + offsetheight = FixedMul(offsetheight, FixedMul (self->player->mo->height, self->player->crouchfactor)); + } + else + { + offsetheight = FixedMul(offsetheight, self->height); + } + } + if (flags & CLOFF_MUL_WIDTH) + { + offsetwidth = FixedMul(self->radius, offsetwidth); + } + + x1 = self->x; + y1 = self->y; + z1 = self->z + offsetheight - self->floorclip; + + if (!(flags & CLOFF_FROMBASE)) + { // default to hitscan origin + + // Synced with hitscan: self->height is strangely NON-conscientious about getting the right actor for player + z1 += (self->height >> 1); + if (self->player != NULL) + { + z1 += FixedMul (self->player->mo->AttackZOffset, self->player->crouchfactor); + } + else + { + z1 += 8*FRACUNIT; + } + } + + if (target) + { + FVector2 xyvec(target->x - x1, target->y - y1); + fixed_t distance = P_AproxDistance((fixed_t)xyvec.Length(), target->z - z1); + + if (range && !(flags & CLOFF_CHECKPARTIAL)) + { + if (distance > range) return; + } + + { + angle_t ang; + + if (flags & CLOFF_NOAIM_HORZ) + { + ang = self->angle; + } + else ang = R_PointToAngle2 (x1, y1, target->x, target->y); + + angle += ang; + + ang >>= ANGLETOFINESHIFT; + x1 += FixedMul(offsetwidth, finesine[ang]); + y1 -= FixedMul(offsetwidth, finecosine[ang]); + } + + if (flags & CLOFF_NOAIM_VERT) + { + pitch += self->pitch; + } + else if (flags & CLOFF_AIM_VERT_NOOFFSET) + { + pitch += R_PointToAngle2 (0,0, (fixed_t)xyvec.Length(), target->z - z1 + offsetheight + target->height / 2); + } + else + { + pitch += R_PointToAngle2 (0,0, (fixed_t)xyvec.Length(), target->z - z1 + target->height / 2); + } + } + else if (flags & CLOFF_ALLOWNULL) + { + angle += self->angle; + pitch += self->pitch; + + angle_t ang = self->angle >> ANGLETOFINESHIFT; + x1 += FixedMul(offsetwidth, finesine[ang]); + y1 -= FixedMul(offsetwidth, finecosine[ang]); + } + else return; + + angle >>= ANGLETOFINESHIFT; + pitch = (0-pitch)>>ANGLETOFINESHIFT; + + vx = FixedMul (finecosine[pitch], finecosine[angle]); + vy = FixedMul (finecosine[pitch], finesine[angle]); + vz = -finesine[pitch]; + } + + /* Variable set: + + jump, flags, target + x1,y1,z1 (trace point of origin) + vx,vy,vz (trace unit vector) + range + */ + + sector_t *sec = P_PointInSector(x1, y1); + + if (range == 0) + { + range = (self->player != NULL) ? PLAYERMISSILERANGE : MISSILERANGE; + } + + FTraceResults trace; + LOFData lof_data; + + lof_data.Self = self; + lof_data.Target = target; + lof_data.Flags = flags; + lof_data.BadActor = false; + + Trace(x1, y1, z1, sec, vx, vy, vz, range, 0xFFFFFFFF, ML_BLOCKEVERYTHING, self, trace, 0, + CheckLOFTraceFunc, &lof_data); + + if (trace.HitType == TRACE_HitActor || + ((flags & CLOFF_JUMP_ON_MISS) && !lof_data.BadActor && trace.HitType != TRACE_HitNone)) + { + if (minrange > 0 && trace.Distance < minrange) + { + return; + } + if ((trace.HitType == TRACE_HitActor) && (trace.Actor != NULL) && !(lof_data.BadActor)) + { + if (flags & (CLOFF_SETTARGET)) self->target = trace.Actor; + if (flags & (CLOFF_SETMASTER)) self->master = trace.Actor; + if (flags & (CLOFF_SETTRACER)) self->tracer = trace.Actor; + } + + ACTION_JUMP(jump); + } +} + +//========================================================================== +// +// A_JumpIfTargetInLOS (state label, optional fixed fov, optional int flags, +// optional fixed dist_max, optional fixed dist_close) +// +// Jumps if the actor can see its target, or if the player has a linetarget. +// ProjectileTarget affects how projectiles are treated. If set, it will use +// the target of the projectile for seekers, and ignore the target for +// normal projectiles. If not set, it will use the missile's owner instead +// (the default). ProjectileTarget is now flag JLOSF_PROJECTILE. dist_max +// sets the maximum distance that actor can see, 0 means forever. dist_close +// uses special behavior if certain flags are set, 0 means no checks. +// +//========================================================================== + +enum JLOS_flags +{ + JLOSF_PROJECTILE = 1, + JLOSF_NOSIGHT = 1 << 1, + JLOSF_CLOSENOFOV = 1 << 2, + JLOSF_CLOSENOSIGHT = 1 << 3, + JLOSF_CLOSENOJUMP = 1 << 4, + JLOSF_DEADNOJUMP = 1 << 5, + JLOSF_CHECKMASTER = 1 << 6, + JLOSF_TARGETLOS = 1 << 7, + JLOSF_FLIPFOV = 1 << 8, + JLOSF_ALLYNOJUMP = 1 << 9, + JLOSF_COMBATANTONLY = 1 << 10, + JLOSF_NOAUTOAIM = 1 << 11, + JLOSF_CHECKTRACER = 1 << 12, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_STATE(jump, 0); + ACTION_PARAM_ANGLE(fov, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_FIXED(dist_max, 3); + ACTION_PARAM_FIXED(dist_close, 4); + + angle_t an; + AActor *target, *viewport; + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + bool doCheckSight; + + if (!self->player) + { + if (flags & JLOSF_CHECKMASTER) + { + target = self->master; + } + else if ((self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) || (flags & JLOSF_CHECKTRACER)) + { + if ((self->flags2 & MF2_SEEKERMISSILE) || (flags & JLOSF_CHECKTRACER)) + target = self->tracer; + else + target = NULL; + } + else + { + target = self->target; + } + + if (!target) return; // [KS] Let's not call P_CheckSight unnecessarily in this case. + + if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) return; + + doCheckSight = !(flags & JLOSF_NOSIGHT); + } + else + { + // Does the player aim at something that can be shot? + P_AimLineAttack(self, self->angle, MISSILERANGE, &target, (flags & JLOSF_NOAUTOAIM) ? ANGLE_1/2 : 0); + + if (!target) return; + + switch (flags & (JLOSF_TARGETLOS|JLOSF_FLIPFOV)) + { + case JLOSF_TARGETLOS|JLOSF_FLIPFOV: + // target makes sight check, player makes fov check; player has verified fov + fov = 0; + // fall-through + case JLOSF_TARGETLOS: + doCheckSight = !(flags & JLOSF_NOSIGHT); // The target is responsible for sight check and fov + break; + default: + // player has verified sight and fov + fov = 0; + // fall-through + case JLOSF_FLIPFOV: // Player has verified sight, but target must verify fov + doCheckSight = false; + break; + } + } + + // [FDARI] If target is not a combatant, don't jump + if ( (flags & JLOSF_COMBATANTONLY) && (!target->player) && !(target->flags3 & MF3_ISMONSTER)) return; + + // [FDARI] If actors share team, don't jump + if ((flags & JLOSF_ALLYNOJUMP) && self->IsFriend(target)) return; + + fixed_t distance = P_AproxDistance(target->x - self->x, target->y - self->y); + distance = P_AproxDistance(distance, target->z - self->z); + + if (dist_max && (distance > dist_max)) return; + + if (dist_close && (distance < dist_close)) + { + if (flags & JLOSF_CLOSENOJUMP) + return; + + if (flags & JLOSF_CLOSENOFOV) + fov = 0; + + if (flags & JLOSF_CLOSENOSIGHT) + doCheckSight = false; + } + + if (flags & JLOSF_TARGETLOS) { viewport = target; target = self; } + else { viewport = self; } + + if (doCheckSight && !P_CheckSight (viewport, target, SF_IGNOREVISIBILITY)) + return; + + if (flags & JLOSF_FLIPFOV) + { + if (viewport == self) { viewport = target; target = self; } + else { target = viewport; viewport = self; } + } + + if (fov && (fov < ANGLE_MAX)) + { + an = R_PointToAngle2 (viewport->x, + viewport->y, + target->x, + target->y) + - viewport->angle; + + if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2))) + { + return; // [KS] Outside of FOV - return + } + + } + + ACTION_JUMP(jump); +} + + +//========================================================================== +// +// A_JumpIfInTargetLOS (state label, optional fixed fov, optional int flags +// optional fixed dist_max, optional fixed dist_close) +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetLOS) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_STATE(jump, 0); + ACTION_PARAM_ANGLE(fov, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_FIXED(dist_max, 3); + ACTION_PARAM_FIXED(dist_close, 4); + + angle_t an; + AActor *target; + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + if (flags & JLOSF_CHECKMASTER) + { + target = self->master; + } + else if (self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) + { + if (self->flags2 & MF2_SEEKERMISSILE) + target = self->tracer; + else + target = NULL; + } + else + { + target = self->target; + } + + if (!target) return; // [KS] Let's not call P_CheckSight unnecessarily in this case. + + if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) return; + + fixed_t distance = P_AproxDistance(target->x - self->x, target->y - self->y); + distance = P_AproxDistance(distance, target->z - self->z); + + if (dist_max && (distance > dist_max)) return; + + bool doCheckSight = !(flags & JLOSF_NOSIGHT); + + if (dist_close && (distance < dist_close)) + { + if (flags & JLOSF_CLOSENOJUMP) + return; + + if (flags & JLOSF_CLOSENOFOV) + fov = 0; + + if (flags & JLOSF_CLOSENOSIGHT) + doCheckSight = false; + } + + if (fov && (fov < ANGLE_MAX)) + { + an = R_PointToAngle2 (target->x, + target->y, + self->x, + self->y) + - target->angle; + + if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2))) + { + return; // [KS] Outside of FOV - return + } + } + + if (doCheckSight && !P_CheckSight (target, self, SF_IGNOREVISIBILITY)) + return; + + ACTION_JUMP(jump); +} + +//=========================================================================== +// +// Modified code pointer from Skulltag +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckForReload) +{ + if ( self->player == NULL || self->player->ReadyWeapon == NULL ) + return; + + ACTION_PARAM_START(2); + ACTION_PARAM_INT(count, 0); + ACTION_PARAM_STATE(jump, 1); + ACTION_PARAM_BOOL(dontincrement, 2) + + if (count <= 0) return; + + AWeapon *weapon = self->player->ReadyWeapon; + + int ReloadCounter = weapon->ReloadCounter; + if(!dontincrement || ReloadCounter != 0) + ReloadCounter = (weapon->ReloadCounter+1) % count; + else // 0 % 1 = 1? So how do we check if the weapon was never fired? We should only do this when we're not incrementing the counter though. + ReloadCounter = 1; + + // If we have not made our last shot... + if (ReloadCounter != 0) + { + // Go back to the refire frames, instead of continuing on to the reload frames. + ACTION_JUMP(jump); + } + else + { + // We need to reload. However, don't reload if we're out of ammo. + weapon->CheckAmmo( false, false ); + } + + if(!dontincrement) + weapon->ReloadCounter = ReloadCounter; +} + +//=========================================================================== +// +// Resets the counter for the above function +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION(AActor, A_ResetReloadCounter) +{ + if ( self->player == NULL || self->player->ReadyWeapon == NULL ) + return; + + AWeapon *weapon = self->player->ReadyWeapon; + weapon->ReloadCounter = 0; +} + +//=========================================================================== +// +// A_ChangeFlag +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeFlag) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_STRING(flagname, 0); + ACTION_PARAM_BOOL(expression, 1); + + const char *dot = strchr (flagname, '.'); + FFlagDef *fd; + const PClass *cls = self->GetClass(); + + if (dot != NULL) + { + FString part1(flagname, dot-flagname); + fd = FindFlag (cls, part1, dot+1); + } + else + { + fd = FindFlag (cls, flagname, NULL); + } + + if (fd != NULL) + { + bool kill_before, kill_after; + INTBOOL item_before, item_after; + INTBOOL secret_before, secret_after; + + kill_before = self->CountsAsKill(); + item_before = self->flags & MF_COUNTITEM; + secret_before = self->flags5 & MF5_COUNTSECRET; + + if (fd->structoffset == -1) + { + HandleDeprecatedFlags(self, cls->ActorInfo, expression, fd->flagbit); + } + else + { + DWORD *flagp = (DWORD*) (((char*)self) + fd->structoffset); + + // If these 2 flags get changed we need to update the blockmap and sector links. + bool linkchange = flagp == &self->flags && (fd->flagbit == MF_NOBLOCKMAP || fd->flagbit == MF_NOSECTOR); + + if (linkchange) self->UnlinkFromWorld(); + ModActorFlag(self, fd, expression); + if (linkchange) self->LinkToWorld(); + } + kill_after = self->CountsAsKill(); + item_after = self->flags & MF_COUNTITEM; + secret_after = self->flags5 & MF5_COUNTSECRET; + // Was this monster previously worth a kill but no longer is? + // Or vice versa? + if (kill_before != kill_after) + { + if (kill_after) + { // It counts as a kill now. + level.total_monsters++; + } + else + { // It no longer counts as a kill. + level.total_monsters--; + } + } + // same for items + if (item_before != item_after) + { + if (item_after) + { // It counts as an item now. + level.total_items++; + } + else + { // It no longer counts as an item + level.total_items--; + } + } + // and secretd + if (secret_before != secret_after) + { + if (secret_after) + { // It counts as an secret now. + level.total_secrets++; + } + else + { // It no longer counts as an secret + level.total_secrets--; + } + } + } + else + { + Printf("Unknown flag '%s' in '%s'\n", flagname, cls->TypeName.GetChars()); + } +} + +//=========================================================================== +// +// A_CheckFlag +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFlag) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_STRING(flagname, 0); + ACTION_PARAM_STATE(jumpto, 1); + ACTION_PARAM_INT(checkpointer, 2); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + AActor *owner; + + COPY_AAPTR_NOT_NULL(self, owner, checkpointer); + + if (CheckActorFlag(owner, flagname)) + { + ACTION_JUMP(jumpto); + } +} + +//=========================================================================== +// +// A_RaiseMaster +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseMaster) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_BOOL(copy, 0); + + if (self->master != NULL) + { + if (copy) + P_Thing_Raise(self->master, self); + else + P_Thing_Raise(self->master, NULL); + } +} + +//=========================================================================== +// +// A_RaiseChildren +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseChildren) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_BOOL(copy, 0); + TThinkerIterator it; + AActor *mo; + + while ((mo = it.Next()) != NULL) + { + if (mo->master == self) + { + if (copy) + P_Thing_Raise(mo, self); + else + P_Thing_Raise(mo, NULL); + } + } +} + +//=========================================================================== +// +// A_RaiseSiblings +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseSiblings) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_BOOL(copy, 0); + TThinkerIterator it; + AActor *mo; + + if (self->master != NULL) + { + while ((mo = it.Next()) != NULL) + { + if (mo->master == self->master && mo != self) + { + if (copy) + P_Thing_Raise(mo, self); + else + P_Thing_Raise(mo, NULL); + } + } + } +} + +//=========================================================================== +// +// [TP] A_FaceConsolePlayer +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS (AActor, A_FaceConsolePlayer) { + ACTION_PARAM_START (1); + ACTION_PARAM_ANGLE (MaxTurnAngle, 0); + // NOTE: It does nothing for zdoom. +} + +//=========================================================================== +// +// A_MonsterRefire +// +// Keep firing unless target got out of sight +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_MonsterRefire) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(prob, 0); + ACTION_PARAM_STATE(jump, 1); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + A_FaceTarget (self); + + if (pr_monsterrefire() < prob) + return; + + if (!self->target + || P_HitFriend (self) + || self->target->health <= 0 + || !P_CheckSight (self, self->target, SF_SEEPASTBLOCKEVERYTHING|SF_SEEPASTSHOOTABLELINES) ) + { + ACTION_JUMP(jump); + } +} + +//=========================================================================== +// +// A_SetAngle +// +// Set actor's angle (in degrees). +// +//=========================================================================== +enum +{ + SPF_FORCECLAMP = 1, // players always clamp + SPF_INTERPOLATE = 2, +}; + + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetAngle) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_ANGLE(angle, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_INT(ptr, 2); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } + ref->SetAngle(angle, !!(flags & SPF_INTERPOLATE)); +} + +//=========================================================================== +// +// A_SetPitch +// +// Set actor's pitch (in degrees). +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetPitch) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_ANGLE(pitch, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_INT(ptr, 2); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } + + if (ref->player != NULL || (flags & SPF_FORCECLAMP)) + { // clamp the pitch we set + int min, max; + + if (ref->player != NULL) + { + min = ref->player->MinPitch; + max = ref->player->MaxPitch; + } + else + { + min = -ANGLE_90 + (1 << ANGLETOFINESHIFT); + max = ANGLE_90 - (1 << ANGLETOFINESHIFT); + } + pitch = clamp(pitch, min, max); + } + ref->SetPitch(pitch, !!(flags & SPF_INTERPOLATE)); +} + //=========================================================================== // // [Nash] A_SetRoll @@ -4028,1830 +4013,1830 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRoll) ACTION_PARAM_START(3); ACTION_PARAM_ANGLE(roll, 0); ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_INT(ptr, 2); - - AActor *ref = COPY_AAPTR(self, ptr); - - if (!ref) - { - ACTION_SET_RESULT(false); - return; + ACTION_PARAM_INT(ptr, 2); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; } ref->SetRoll(roll, !!(flags & SPF_INTERPOLATE)); } - -//=========================================================================== -// -// A_ScaleVelocity -// -// Scale actor's velocity. -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ScaleVelocity) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_FIXED(scale, 0); - ACTION_PARAM_INT(ptr, 1); - - AActor *ref = COPY_AAPTR(self, ptr); - - if (!ref) - { - ACTION_SET_RESULT(false); - return; - } - - INTBOOL was_moving = self->velx | self->vely | self->velz; - - ref->velx = FixedMul(ref->velx, scale); - ref->vely = FixedMul(ref->vely, scale); - ref->velz = FixedMul(ref->velz, scale); - - // If the actor was previously moving but now is not, and is a player, - // update its player variables. (See A_Stop.) - if (was_moving) - { - CheckStopped(ref); - } -} - -//=========================================================================== -// -// A_ChangeVelocity -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeVelocity) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_FIXED(x, 0); - ACTION_PARAM_FIXED(y, 1); - ACTION_PARAM_FIXED(z, 2); - ACTION_PARAM_INT(flags, 3); - ACTION_PARAM_INT(ptr, 4); - - AActor *ref = COPY_AAPTR(self, ptr); - - if (!ref) - { - ACTION_SET_RESULT(false); - return; - } - - INTBOOL was_moving = ref->velx | ref->vely | ref->velz; - - fixed_t vx = x, vy = y, vz = z; - fixed_t sina = finesine[ref->angle >> ANGLETOFINESHIFT]; - fixed_t cosa = finecosine[ref->angle >> ANGLETOFINESHIFT]; - - if (flags & 1) // relative axes - make x, y relative to actor's current angle - { - vx = DMulScale16(x, cosa, -y, sina); - vy = DMulScale16(x, sina, y, cosa); - } - if (flags & 2) // discard old velocity - replace old velocity with new velocity - { - ref->velx = vx; - ref->vely = vy; - ref->velz = vz; - } - else // add new velocity to old velocity - { - ref->velx += vx; - ref->vely += vy; - ref->velz += vz; - } - - if (was_moving) - { - CheckStopped(self); - } -} - -//=========================================================================== -// -// A_SetArg -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetArg) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(pos, 0); - ACTION_PARAM_INT(value, 1); - - // Set the value of the specified arg - if ((size_t)pos < countof(self->args)) - { - self->args[pos] = value; - } -} - -//=========================================================================== -// -// A_SetSpecial -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpecial) -{ - ACTION_PARAM_START(6); - ACTION_PARAM_INT(spec, 0); - ACTION_PARAM_INT(arg0, 1); - ACTION_PARAM_INT(arg1, 2); - ACTION_PARAM_INT(arg2, 3); - ACTION_PARAM_INT(arg3, 4); - ACTION_PARAM_INT(arg4, 5); - - self->special = spec; - self->args[0] = arg0; - self->args[1] = arg1; - self->args[2] = arg2; - self->args[3] = arg3; - self->args[4] = arg4; -} - -//=========================================================================== -// -// A_SetUserVar -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserVar) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_NAME(varname, 0); - ACTION_PARAM_INT(value, 1); - - PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); - PSymbolVariable *var; - - if (sym == NULL || sym->SymbolType != SYM_Variable || - !(var = static_cast(sym))->bUserVar || - var->ValueType.Type != VAL_Int) - { - Printf("%s is not a user variable in class %s\n", varname.GetChars(), - self->GetClass()->TypeName.GetChars()); - return; - } - // Set the value of the specified user variable. - *(int *)(reinterpret_cast(self) + var->offset) = value; -} - -//=========================================================================== -// -// A_SetUserArray -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserArray) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_NAME(varname, 0); - ACTION_PARAM_INT(pos, 1); - ACTION_PARAM_INT(value, 2); - - PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); - PSymbolVariable *var; - - if (sym == NULL || sym->SymbolType != SYM_Variable || - !(var = static_cast(sym))->bUserVar || - var->ValueType.Type != VAL_Array || var->ValueType.BaseType != VAL_Int) - { - Printf("%s is not a user array in class %s\n", varname.GetChars(), - self->GetClass()->TypeName.GetChars()); - return; - } - if (pos < 0 || pos >= var->ValueType.size) - { - Printf("%d is out of bounds in array %s in class %s\n", pos, varname.GetChars(), - self->GetClass()->TypeName.GetChars()); - return; - } - // Set the value of the specified user array at index pos. - ((int *)(reinterpret_cast(self) + var->offset))[pos] = value; -} - -//=========================================================================== -// -// A_Teleport(optional state teleportstate, optional class targettype, -// optional class fogtype, optional int flags, optional fixed mindist, -// optional fixed maxdist) -// -// Attempts to teleport to a targettype at least mindist away and at most -// maxdist away (0 means unlimited). If successful, spawn a fogtype at old -// location and place calling actor in teleportstate. -// -//=========================================================================== -enum T_Flags -{ - TF_TELEFRAG = 0x00000001, // Allow telefrag in order to teleport. - TF_RANDOMDECIDE = 0x00000002, // Randomly fail based on health. (A_Srcr2Decide) - TF_FORCED = 0x00000004, // Forget what's in the way. TF_Telefrag takes precedence though. - TF_KEEPVELOCITY = 0x00000008, // Preserve velocity. - TF_KEEPANGLE = 0x00000010, // Keep angle. - TF_USESPOTZ = 0x00000020, // Set the z to the spot's z, instead of the floor. - TF_NOSRCFOG = 0x00000040, // Don't leave any fog behind when teleporting. - TF_NODESTFOG = 0x00000080, // Don't spawn any fog at the arrival position. - TF_USEACTORFOG = 0x00000100, // Use the actor's TeleFogSourceType and TeleFogDestType fogs. - TF_NOJUMP = 0x00000200, // Don't jump after teleporting. -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport) -{ - ACTION_PARAM_START(6); - ACTION_PARAM_STATE(TeleportState, 0); - ACTION_PARAM_CLASS(TargetType, 1); - ACTION_PARAM_CLASS(FogType, 2); - ACTION_PARAM_INT(Flags, 3); - ACTION_PARAM_FIXED(MinDist, 4); - ACTION_PARAM_FIXED(MaxDist, 5); - - // Randomly choose not to teleport like A_Srcr2Decide. - if (Flags & TF_RANDOMDECIDE) - { - static const int chance[] = - { - 192, 120, 120, 120, 64, 64, 32, 16, 0 - }; - - unsigned int chanceindex = self->health / ((self->SpawnHealth()/8 == 0) ? 1 : self->SpawnHealth()/8); - - if (chanceindex >= countof(chance)) - { - chanceindex = countof(chance) - 1; - } - - if (pr_teleport() >= chance[chanceindex]) return; - } - - DSpotState *state = DSpotState::GetSpotState(); - if (state == NULL) return; - - if (!TargetType) TargetType = PClass::FindClass("BossSpot"); - - AActor * spot = state->GetSpotWithMinMaxDistance(TargetType, self->x, self->y, MinDist, MaxDist); - if (spot == NULL) return; - - fixed_t prevX = self->x; - fixed_t prevY = self->y; - fixed_t prevZ = self->z; - fixed_t aboveFloor = spot->z - spot->floorz; - fixed_t finalz = spot->floorz + aboveFloor; - - if (spot->z + self->height > spot->ceilingz) - finalz = spot->ceilingz - self->height; - else if (spot->z < spot->floorz) - finalz = spot->floorz; - - - //Take precedence and cooperate with telefragging first. - bool teleResult = P_TeleportMove(self, spot->x, spot->y, finalz, Flags & TF_TELEFRAG); - - if (Flags & TF_FORCED) - { - //If for some reason the original move didn't work, regardless of telefrag, force it to move. - self->SetOrigin(spot->x, spot->y, finalz); - teleResult = true; - } - - if (teleResult) - { - //If a fog type is defined in the parameter, or the user wants to use the actor's predefined fogs, - //and if there's no desire to be fogless, spawn a fog based upon settings. - if (FogType || (Flags & TF_USEACTORFOG)) - { - if (!(Flags & TF_NOSRCFOG)) - { - if (Flags & TF_USEACTORFOG) - P_SpawnTeleportFog(self, prevX, prevY, prevZ, true); - else - Spawn(FogType, prevX, prevY, prevZ, ALLOW_REPLACE); - } - if (!(Flags & TF_NODESTFOG)) - { - if (Flags & TF_USEACTORFOG) - P_SpawnTeleportFog(self, self->x, self->y, self->z, false); - else - Spawn(FogType, self->x, self->y, self->z, ALLOW_REPLACE); - } - } - - if (Flags & TF_USESPOTZ) - self->z = spot->z; - else - self->z = self->floorz; - - if (!(Flags & TF_KEEPANGLE)) - self->angle = spot->angle; - - if (!(Flags & TF_KEEPVELOCITY)) - self->velx = self->vely = self->velz = 0; - - if (!(Flags & TF_NOJUMP)) - { - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (TeleportState == NULL) - { - // Default to Teleport. - TeleportState = self->FindState("Teleport"); - // If still nothing, then return. - if (!TeleportState) return; - } - ACTION_JUMP(TeleportState); - return; - } - } - ACTION_SET_RESULT(teleResult); -} - -//=========================================================================== -// -// A_Turn -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Turn) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_ANGLE(angle, 0); - self->angle += angle; -} - -//=========================================================================== -// -// A_Quake -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Quake) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(intensity, 0); - ACTION_PARAM_INT(duration, 1); - ACTION_PARAM_INT(damrad, 2); - ACTION_PARAM_INT(tremrad, 3); - ACTION_PARAM_SOUND(sound, 4); - P_StartQuake(self, 0, intensity, duration, damrad, tremrad, sound); -} - -//=========================================================================== -// -// A_Weave -// -//=========================================================================== - -void A_Weave(AActor *self, int xyspeed, int zspeed, fixed_t xydist, fixed_t zdist) -{ - fixed_t newX, newY; - int weaveXY, weaveZ; - int angle; - fixed_t dist; - - weaveXY = self->WeaveIndexXY & 63; - weaveZ = self->WeaveIndexZ & 63; - angle = (self->angle + ANG90) >> ANGLETOFINESHIFT; - - if (xydist != 0 && xyspeed != 0) - { - dist = MulScale13(finesine[weaveXY << BOBTOFINESHIFT], xydist); - newX = self->x - FixedMul (finecosine[angle], dist); - newY = self->y - FixedMul (finesine[angle], dist); - weaveXY = (weaveXY + xyspeed) & 63; - dist = MulScale13(finesine[weaveXY << BOBTOFINESHIFT], xydist); - newX += FixedMul (finecosine[angle], dist); - newY += FixedMul (finesine[angle], dist); - if (!(self->flags5 & MF5_NOINTERACTION)) - { - P_TryMove (self, newX, newY, true); - } - else - { - self->UnlinkFromWorld (); - self->flags |= MF_NOBLOCKMAP; - self->x = newX; - self->y = newY; - self->LinkToWorld (); - } - self->WeaveIndexXY = weaveXY; - } - if (zdist != 0 && zspeed != 0) - { - self->z -= MulScale13(finesine[weaveZ << BOBTOFINESHIFT], zdist); - weaveZ = (weaveZ + zspeed) & 63; - self->z += MulScale13(finesine[weaveZ << BOBTOFINESHIFT], zdist); - self->WeaveIndexZ = weaveZ; - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Weave) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_INT(xspeed, 0); - ACTION_PARAM_INT(yspeed, 1); - ACTION_PARAM_FIXED(xdist, 2); - ACTION_PARAM_FIXED(ydist, 3); - A_Weave(self, xspeed, yspeed, xdist, ydist); -} - - - - -//=========================================================================== -// -// A_LineEffect -// -// This allows linedef effects to be activated inside deh frames. -// -//=========================================================================== - - -void P_TranslateLineDef (line_t *ld, maplinedef_t *mld); -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LineEffect) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(special, 0); - ACTION_PARAM_INT(tag, 1); - - line_t junk; maplinedef_t oldjunk; - bool res = false; - if (!(self->flags6 & MF6_LINEDONE)) // Unless already used up - { - if ((oldjunk.special = special)) // Linedef type - { - oldjunk.tag = tag; // Sector tag for linedef - P_TranslateLineDef(&junk, &oldjunk); // Turn into native type - res = !!P_ExecuteSpecial(junk.special, NULL, self, false, junk.args[0], - junk.args[1], junk.args[2], junk.args[3], junk.args[4]); - if (res && !(junk.flags & ML_REPEAT_SPECIAL)) // If only once, - self->flags6 |= MF6_LINEDONE; // no more for this thing - } - } - ACTION_SET_RESULT(res); -} - -//========================================================================== -// -// A Wolf3D-style attack codepointer -// -//========================================================================== -enum WolfAttackFlags -{ - WAF_NORANDOM = 1, - WAF_USEPUFF = 2, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_WolfAttack) -{ - ACTION_PARAM_START(9); - ACTION_PARAM_INT(flags, 0); - ACTION_PARAM_SOUND(sound, 1); - ACTION_PARAM_FIXED(snipe, 2); - ACTION_PARAM_INT(maxdamage, 3); - ACTION_PARAM_INT(blocksize, 4); - ACTION_PARAM_INT(pointblank, 5); - ACTION_PARAM_INT(longrange, 6); - ACTION_PARAM_FIXED(runspeed, 7); - ACTION_PARAM_CLASS(pufftype, 8); - - if (!self->target) - return; - - // Enemy can't see target - if (!P_CheckSight(self, self->target)) - return; - - A_FaceTarget (self); - - - // Target can dodge if it can see enemy - angle_t angle = R_PointToAngle2(self->target->x, self->target->y, self->x, self->y) - self->target->angle; - angle >>= 24; - bool dodge = (P_CheckSight(self->target, self) && (angle>226 || angle<30)); - - // Distance check is simplistic - fixed_t dx = abs (self->x - self->target->x); - fixed_t dy = abs (self->y - self->target->y); - fixed_t dz; - fixed_t dist = dx > dy ? dx : dy; - - // Some enemies are more precise - dist = FixedMul(dist, snipe); - - // Convert distance into integer number of blocks - dist >>= FRACBITS; - dist /= blocksize; - - // Now for the speed accuracy thingie - fixed_t speed = FixedMul(self->target->velx, self->target->velx) - + FixedMul(self->target->vely, self->target->vely) - + FixedMul(self->target->velz, self->target->velz); - int hitchance = speed < runspeed ? 256 : 160; - - // Distance accuracy (factoring dodge) - hitchance -= dist * (dodge ? 16 : 8); - - // While we're here, we may as well do something for this: - if (self->target->flags & MF_SHADOW) - { - hitchance >>= 2; - } - - // The attack itself - if (pr_cabullet() < hitchance) - { - // Compute position for spawning blood/puff - dx = self->target->x; - dy = self->target->y; - dz = self->target->z + (self->target->height>>1); - angle = R_PointToAngle2(dx, dy, self->x, self->y); - - dx += FixedMul(self->target->radius, finecosine[angle>>ANGLETOFINESHIFT]); - dy += FixedMul(self->target->radius, finesine[angle>>ANGLETOFINESHIFT]); - - int damage = flags & WAF_NORANDOM ? maxdamage : (1 + (pr_cabullet() % maxdamage)); - if (dist >= pointblank) - damage >>= 1; - if (dist >= longrange) - damage >>= 1; - FName mod = NAME_None; - bool spawnblood = !((self->target->flags & MF_NOBLOOD) - || (self->target->flags2 & (MF2_INVULNERABLE|MF2_DORMANT))); - if (flags & WAF_USEPUFF && pufftype) - { - AActor * dpuff = GetDefaultByType(pufftype->GetReplacement()); - mod = dpuff->DamageType; - - if (dpuff->flags2 & MF2_THRUGHOST && self->target->flags3 & MF3_GHOST) - damage = 0; - - if ((0 && dpuff->flags3 & MF3_PUFFONACTORS) || !spawnblood) - { - spawnblood = false; - P_SpawnPuff(self, pufftype, dx, dy, dz, angle, 0); - } - } - else if (self->target->flags3 & MF3_GHOST) - damage >>= 2; - if (damage) - { - int newdam = P_DamageMobj(self->target, self, self, damage, mod, DMG_THRUSTLESS); - if (spawnblood) - { - P_SpawnBlood(dx, dy, dz, angle, newdam > 0 ? newdam : damage, self->target); - P_TraceBleed(newdam > 0 ? newdam : damage, self->target, R_PointToAngle2(self->x, self->y, dx, dy), 0); - } - } - } - - // And finally, let's play the sound - S_Sound (self, CHAN_WEAPON, sound, 1, ATTN_NORM); -} - - -//========================================================================== -// -// A_Warp -// -//========================================================================== - -enum WARPF -{ - WARPF_ABSOLUTEOFFSET = 0x1, - WARPF_ABSOLUTEANGLE = 0x2, - WARPF_USECALLERANGLE = 0x4, - - WARPF_NOCHECKPOSITION = 0x8, - - WARPF_INTERPOLATE = 0x10, - WARPF_WARPINTERPOLATION = 0x20, - WARPF_COPYINTERPOLATION = 0x40, - - WARPF_STOP = 0x80, - WARPF_TOFLOOR = 0x100, - WARPF_TESTONLY = 0x200, - WARPF_ABSOLUTEPOSITION = 0x400, - WARPF_BOB = 0x800, - WARPF_MOVEPTR = 0x1000, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Warp) -{ - ACTION_PARAM_START(7); - - ACTION_PARAM_INT(destination_selector, 0); - ACTION_PARAM_FIXED(xofs, 1); - ACTION_PARAM_FIXED(yofs, 2); - ACTION_PARAM_FIXED(zofs, 3); - ACTION_PARAM_ANGLE(angle, 4); - ACTION_PARAM_INT(flags, 5); - ACTION_PARAM_STATE(success_state, 6); - - AActor *reference = COPY_AAPTR(self, destination_selector); - - //If there is no actor to warp to, fail. - if (!reference) - { - ACTION_SET_RESULT(false); - return; - } - - AActor *caller = self; - - if (flags & WARPF_MOVEPTR) - { - AActor *temp = reference; - reference = caller; - caller = temp; - } - - fixed_t oldx = caller->x; - fixed_t oldy = caller->y; - fixed_t oldz = caller->z; - - if (!(flags & WARPF_ABSOLUTEANGLE)) - { - angle += (flags & WARPF_USECALLERANGLE) ? caller->angle : reference->angle; - } - if (!(flags & WARPF_ABSOLUTEPOSITION)) - { - if (!(flags & WARPF_ABSOLUTEOFFSET)) - { - angle_t fineangle = angle >> ANGLETOFINESHIFT; - fixed_t xofs1 = xofs; - - // (borrowed from A_SpawnItemEx, assumed workable) - // in relative mode negative y values mean 'left' and positive ones mean 'right' - // This is the inverse orientation of the absolute mode! - - xofs = FixedMul(xofs1, finecosine[fineangle]) + FixedMul(yofs, finesine[fineangle]); - yofs = FixedMul(xofs1, finesine[fineangle]) - FixedMul(yofs, finecosine[fineangle]); - } - - if (flags & WARPF_TOFLOOR) - { - // set correct xy - - caller->SetOrigin( - reference->x + xofs, - reference->y + yofs, - reference->z); - - // now the caller's floorz should be appropriate for the assigned xy-position - // assigning position again with - - if (zofs) - { - // extra unlink, link and environment calculation - caller->SetOrigin( - caller->x, - caller->y, - caller->floorz + zofs); - } - else - { - // if there is no offset, there should be no ill effect from moving down to the - // already identified floor - - // A_Teleport does the same thing anyway - caller->z = caller->floorz; - } - } - else - { - caller->SetOrigin( - reference->x + xofs, - reference->y + yofs, - reference->z + zofs); - } - } - else //[MC] The idea behind "absolute" is meant to be "absolute". Override everything, just like A_SpawnItemEx's. - { - if (flags & WARPF_TOFLOOR) - { - caller->SetOrigin(xofs, yofs, caller->floorz + zofs); - } - else - { - caller->SetOrigin(xofs, yofs, zofs); - } - } - - if ((flags & WARPF_NOCHECKPOSITION) || P_TestMobjLocation(caller)) - { - if (flags & WARPF_TESTONLY) - { - caller->SetOrigin(oldx, oldy, oldz); - } - else - { - caller->angle = angle; - - if (flags & WARPF_STOP) - { - caller->velx = 0; - caller->vely = 0; - caller->velz = 0; - } - - if (flags & WARPF_WARPINTERPOLATION) - { - caller->PrevX += caller->x - oldx; - caller->PrevY += caller->y - oldy; - caller->PrevZ += caller->z - oldz; - } - else if (flags & WARPF_COPYINTERPOLATION) - { - caller->PrevX = caller->x + reference->PrevX - reference->x; - caller->PrevY = caller->y + reference->PrevY - reference->y; - caller->PrevZ = caller->z + reference->PrevZ - reference->z; - } - else if (!(flags & WARPF_INTERPOLATE)) - { - caller->PrevX = caller->x; - caller->PrevY = caller->y; - caller->PrevZ = caller->z; - } - - if ((flags & WARPF_BOB) && (reference->flags2 & MF2_FLOATBOB)) - { - caller->z += reference->GetBobOffset(); - } - } - - - if (success_state) - { - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - // in this case, you have the statejump to help you handle all the success anyway. - ACTION_JUMP(success_state); - return; - } - - ACTION_SET_RESULT(true); - } - else - { - caller->SetOrigin(oldx, oldy, oldz); - ACTION_SET_RESULT(false); - } - -} - -//========================================================================== -// -// ACS_Named* stuff - -// -// These are exactly like their un-named line special equivalents, except -// they take strings instead of integers to indicate which script to run. -// Some of these probably aren't very useful, but they are included for -// the sake of completeness. -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteWithResult) -{ - ACTION_PARAM_START(5); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(arg1, 1); - ACTION_PARAM_INT(arg2, 2); - ACTION_PARAM_INT(arg3, 3); - ACTION_PARAM_INT(arg4, 4); - - bool res = !!P_ExecuteSpecial(ACS_ExecuteWithResult, NULL, self, false, -scriptname, arg1, arg2, arg3, arg4); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecute) -{ - ACTION_PARAM_START(5); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - ACTION_PARAM_INT(arg1, 2); - ACTION_PARAM_INT(arg2, 3); - ACTION_PARAM_INT(arg3, 4); - - bool res = !!P_ExecuteSpecial(ACS_Execute, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteAlways) -{ - ACTION_PARAM_START(5); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - ACTION_PARAM_INT(arg1, 2); - ACTION_PARAM_INT(arg2, 3); - ACTION_PARAM_INT(arg3, 4); - - bool res = !!P_ExecuteSpecial(ACS_ExecuteAlways, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecute) -{ - ACTION_PARAM_START(5); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - ACTION_PARAM_INT(arg1, 2); - ACTION_PARAM_INT(arg2, 3); - ACTION_PARAM_INT(lock, 4); - - bool res = !!P_ExecuteSpecial(ACS_LockedExecute, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecuteDoor) -{ - ACTION_PARAM_START(5); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - ACTION_PARAM_INT(arg1, 2); - ACTION_PARAM_INT(arg2, 3); - ACTION_PARAM_INT(lock, 4); - - bool res = !!P_ExecuteSpecial(ACS_LockedExecuteDoor, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedSuspend) -{ - ACTION_PARAM_START(2); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - - bool res = !!P_ExecuteSpecial(ACS_Suspend, NULL, self, false, -scriptname, mapnum, 0, 0, 0); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedTerminate) -{ - ACTION_PARAM_START(2); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - - bool res = !!P_ExecuteSpecial(ACS_Terminate, NULL, self, false, -scriptname, mapnum, 0, 0, 0); - - ACTION_SET_RESULT(res); -} - - -//========================================================================== -// -// A_RadiusGive -// -// Uses code roughly similar to A_Explode (but without all the compatibility -// baggage and damage computation code to give an item to all eligible mobjs -// in range. -// -//========================================================================== -enum RadiusGiveFlags -{ - RGF_GIVESELF = 1 << 0, - RGF_PLAYERS = 1 << 1, - RGF_MONSTERS = 1 << 2, - RGF_OBJECTS = 1 << 3, - RGF_VOODOO = 1 << 4, - RGF_CORPSES = 1 << 5, - RGF_MASK = 2111, - RGF_NOTARGET = 1 << 6, - RGF_NOTRACER = 1 << 7, - RGF_NOMASTER = 1 << 8, - RGF_CUBE = 1 << 9, - RGF_NOSIGHT = 1 << 10, - RGF_MISSILES = 1 << 11, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusGive) -{ - ACTION_PARAM_START(7); - ACTION_PARAM_CLASS(item, 0); - ACTION_PARAM_FIXED(distance, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_INT(amount, 3); - - // We need a valid item, valid targets, and a valid range - if (item == NULL || (flags & RGF_MASK) == 0 || distance <= 0) - { - return; - } - if (amount == 0) - { - amount = 1; - } - FBlockThingsIterator it(FBoundingBox(self->x, self->y, distance)); - double distsquared = double(distance) * double(distance); - - AActor *thing; - while ((thing = it.Next())) - { - // Don't give to inventory items - if (thing->flags & MF_SPECIAL) - { - continue; - } - // Avoid giving to self unless requested - if (thing == self && !(flags & RGF_GIVESELF)) - { - continue; - } - // Avoiding special pointers if requested - if (((thing == self->target) && (flags & RGF_NOTARGET)) || - ((thing == self->tracer) && (flags & RGF_NOTRACER)) || - ((thing == self->master) && (flags & RGF_NOMASTER))) - { - continue; - } - // Don't give to dead thing unless requested - if (thing->flags & MF_CORPSE) - { - if (!(flags & RGF_CORPSES)) - { - continue; - } - } - else if (thing->health <= 0 || thing->flags6 & MF6_KILLED) - { - continue; - } - // Players, monsters, and other shootable objects - if (thing->player) - { - if ((thing->player->mo == thing) && !(flags & RGF_PLAYERS)) - { - continue; - } - if ((thing->player->mo != thing) && !(flags & RGF_VOODOO)) - { - continue; - } - } - else if (thing->flags3 & MF3_ISMONSTER) - { - if (!(flags & RGF_MONSTERS)) - { - continue; - } - } - else if (thing->flags & MF_SHOOTABLE || thing->flags6 & MF6_VULNERABLE) - { - if (!(flags & RGF_OBJECTS)) - { - continue; - } - } - else if (thing->flags & MF_MISSILE) - { - if (!(flags & RGF_MISSILES)) - { - continue; - } - } - else - { - continue; - } - - if (flags & RGF_CUBE) - { // check if inside a cube - if (abs(thing->x - self->x) > distance || - abs(thing->y - self->y) > distance || - abs((thing->z + thing->height/2) - (self->z + self->height/2)) > distance) - { - continue; - } - } - else - { // check if inside a sphere - TVector3 tpos(thing->x, thing->y, thing->z + thing->height/2); - TVector3 spos(self->x, self->y, self->z + self->height/2); - if ((tpos - spos).LengthSquared() > distsquared) - { - continue; - } - } - fixed_t dz = abs ((thing->z + thing->height/2) - (self->z + self->height/2)); - - if ((flags & RGF_NOSIGHT) || P_CheckSight (thing, self, SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY)) - { // OK to give; target is in direct path, or the monster doesn't care about it being in line of sight. - AInventory *gift = static_cast(Spawn (item, 0, 0, 0, NO_REPLACE)); - if (gift->IsKindOf(RUNTIME_CLASS(AHealth))) - { - gift->Amount *= amount; - } - else - { - gift->Amount = amount; - } - gift->flags |= MF_DROPPED; - gift->ClearCounters(); - if (!gift->CallTryPickup (thing)) - { - gift->Destroy (); - } - } - } -} - - -//========================================================================== -// -// A_SetTics -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTics) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(tics_to_set, 0); - - if (stateowner != self && self->player != NULL && stateowner->IsKindOf(RUNTIME_CLASS(AWeapon))) - { // Is this a weapon? Need to check psp states for a match, then. Blah. - for (int i = 0; i < NUMPSPRITES; ++i) - { - if (self->player->psprites[i].state == CallingState) - { - self->player->psprites[i].tics = tics_to_set; - return; - } - } - } - // Just set tics for self. - self->tics = tics_to_set; -} - -//========================================================================== -// -// A_SetDamageType -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetDamageType) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_NAME(damagetype, 0); - - self->DamageType = damagetype; -} - -//========================================================================== -// -// A_DropItem -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropItem) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_CLASS(spawntype, 0); - ACTION_PARAM_INT(amount, 1); - ACTION_PARAM_INT(chance, 2); - - P_DropItem(self, spawntype, amount, chance); -} - -//========================================================================== -// -// A_SetSpeed -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpeed) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_FIXED(speed, 0); - ACTION_PARAM_INT(ptr, 1); - - AActor *ref = COPY_AAPTR(self, ptr); - - if (!ref) - { - ACTION_SET_RESULT(false); - return; - } - - ref->Speed = speed; -} - -static bool DoCheckSpecies(AActor *mo, FName species, bool exclude) -{ - return (!(species) || mo->Species == NAME_None || (species && ((exclude) ? (mo->Species != species) : (mo->Species == species)))); -} - -static bool DoCheckFilter(AActor *mo, const PClass *filter, bool exclude) -{ - const PClass *c1 = mo->GetClass(); - return (!(filter) || (filter == NULL) || (filter && ((exclude) ? (c1 != filter) : (c1 == filter)))); -} - -//=========================================================================== -// -// Common A_Damage handler -// -// A_Damage* (int amount, str damagetype, int flags, str filter, str species) -// Damages the specified actor by the specified amount. Negative values heal. -// Flags: See below. -// Filter: Specified actor is the only type allowed to be affected. -// Species: Specified species is the only type allowed to be affected. -// -// Examples: -// A_Damage(20,"Normal",DMSS_FOILINVUL,0,"DemonicSpecies") <--Only actors -// with a species "DemonicSpecies" will be affected. Use 0 to not filter by actor. -// -//=========================================================================== - -enum DMSS -{ - DMSS_FOILINVUL = 1, //Foil invulnerability - DMSS_AFFECTARMOR = 2, //Make it affect armor - DMSS_KILL = 4, //Damages them for their current health - DMSS_NOFACTOR = 8, //Ignore DamageFactors - DMSS_FOILBUDDHA = 16, //Can kill actors with Buddha flag, except the player. - DMSS_NOPROTECT = 32, //Ignores PowerProtection entirely - DMSS_EXFILTER = 64, //Changes filter into a blacklisted class instead of whitelisted. - DMSS_EXSPECIES = 128, // ^ but with species instead. - DMSS_EITHER = 256, //Allow either type or species to be affected. -}; - -static void DoDamage(AActor *dmgtarget, AActor *self, int amount, FName DamageType, int flags, const PClass *filter, FName species) -{ - bool filterpass = DoCheckFilter(dmgtarget, filter, (flags & DMSS_EXFILTER) ? true : false), - speciespass = DoCheckSpecies(dmgtarget, species, (flags & DMSS_EXSPECIES) ? true : false); - if ((flags & DMSS_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) - { - int dmgFlags = 0; - if (flags & DMSS_FOILINVUL) - dmgFlags += DMG_FOILINVUL; - if (flags & DMSS_FOILBUDDHA) - dmgFlags += DMG_FOILBUDDHA; - if ((flags & DMSS_KILL) || (flags & DMSS_NOFACTOR)) //Kill implies NoFactor - dmgFlags += DMG_NO_FACTOR; - if (!(flags & DMSS_AFFECTARMOR) || (flags & DMSS_KILL)) //Kill overrides AffectArmor - dmgFlags += DMG_NO_ARMOR; - if (flags & DMSS_KILL) //Kill adds the value of the damage done to it. Allows for more controlled extreme death types. - amount += dmgtarget->health; - if (flags & DMSS_NOPROTECT) //Ignore PowerProtection. - dmgFlags += DMG_NO_PROTECT; - - if (amount > 0) - P_DamageMobj(dmgtarget, self, self, amount, DamageType, dmgFlags); //Should wind up passing them through just fine. - - else if (amount < 0) - { - amount = -amount; - P_GiveBody(dmgtarget, amount); - } - } -} - -//=========================================================================== -// -// -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSelf) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(amount, 0); - ACTION_PARAM_NAME(DamageType, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_CLASS(filter, 3); - ACTION_PARAM_NAME(species, 4); - - DoDamage(self, self, amount, DamageType, flags, filter, species); -} - -//=========================================================================== -// -// -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTarget) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(amount, 0); - ACTION_PARAM_NAME(DamageType, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_CLASS(filter, 3); - ACTION_PARAM_NAME(species, 4); - - if (self->target != NULL) - { - DoDamage(self->target, self, amount, DamageType, flags, filter, species); - } -} - -//=========================================================================== -// -// -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTracer) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(amount, 0); - ACTION_PARAM_NAME(DamageType, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_CLASS(filter, 3); - ACTION_PARAM_NAME(species, 4); - - if (self->tracer != NULL) - { - DoDamage(self->tracer, self, amount, DamageType, flags, filter, species); - } -} - -//=========================================================================== -// -// -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageMaster) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(amount, 0); - ACTION_PARAM_NAME(DamageType, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_CLASS(filter, 3); - ACTION_PARAM_NAME(species, 4); - - if (self->master != NULL) - { - DoDamage(self->master, self, amount, DamageType, flags, filter, species); - } -} - -//=========================================================================== -// -// -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageChildren) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(amount, 0); - ACTION_PARAM_NAME(DamageType, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_CLASS(filter, 3); - ACTION_PARAM_NAME(species, 4); - - TThinkerIterator it; - AActor * mo; - - while ( (mo = it.Next()) ) - { - if (mo->master == self) - { - DoDamage(mo, self, amount, DamageType, flags, filter, species); - } - } -} - -//=========================================================================== -// -// -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSiblings) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(amount, 0); - ACTION_PARAM_NAME(DamageType, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_CLASS(filter, 3); - ACTION_PARAM_NAME(species, 4); - - TThinkerIterator it; - AActor * mo; - - if (self->master != NULL) - { - while ((mo = it.Next())) - { - if (mo->master == self->master && mo != self) - { - DoDamage(mo, self, amount, DamageType, flags, filter, species); - } - } - } -} - - -//=========================================================================== -// -// A_Kill*(damagetype, int flags) -// -//=========================================================================== -enum KILS -{ - KILS_FOILINVUL = 1 << 0, - KILS_KILLMISSILES = 1 << 1, - KILS_NOMONSTERS = 1 << 2, - KILS_FOILBUDDHA = 1 << 3, - KILS_EXFILTER = 1 << 4, - KILS_EXSPECIES = 1 << 5, - KILS_EITHER = 1 << 6, -}; - -static void DoKill(AActor *killtarget, AActor *self, FName damagetype, int flags, const PClass *filter, FName species) -{ - bool filterpass = DoCheckFilter(killtarget, filter, (flags & KILS_EXFILTER) ? true : false), - speciespass = DoCheckSpecies(killtarget, species, (flags & KILS_EXSPECIES) ? true : false); - if ((flags & KILS_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) //Check this first. I think it'll save the engine a lot more time this way. - { - int dmgFlags = DMG_NO_ARMOR + DMG_NO_FACTOR; - - if (KILS_FOILINVUL) - dmgFlags += DMG_FOILINVUL; - if (KILS_FOILBUDDHA) - dmgFlags += DMG_FOILBUDDHA; - - - if ((killtarget->flags & MF_MISSILE) && (flags & KILS_KILLMISSILES)) - { - //[MC] Now that missiles can set masters, lets put in a check to properly destroy projectiles. BUT FIRST! New feature~! - //Check to see if it's invulnerable. Disregarded if foilinvul is on, but never works on a missile with NODAMAGE - //since that's the whole point of it. - if ((!(killtarget->flags2 & MF2_INVULNERABLE) || (flags & KILS_FOILINVUL)) && - (!(killtarget->flags2 & MF7_BUDDHA) || (flags & KILS_FOILBUDDHA)) && !(killtarget->flags5 & MF5_NODAMAGE)) - { - P_ExplodeMissile(killtarget, NULL, NULL); - } - } - if (!(flags & KILS_NOMONSTERS)) - { - P_DamageMobj(killtarget, self, self, killtarget->health, damagetype, dmgFlags); - } - } -} - - -//=========================================================================== -// -// A_KillTarget(damagetype, int flags) -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillTarget) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_NAME(damagetype, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_CLASS(filter, 2); - ACTION_PARAM_NAME(species, 3); - - if (self->target != NULL) - { - DoKill(self->target, self, damagetype, flags, filter, species); - } -} - -//=========================================================================== -// -// A_KillTracer(damagetype, int flags) -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillTracer) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_NAME(damagetype, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_CLASS(filter, 2); - ACTION_PARAM_NAME(species, 3); - - if (self->tracer != NULL) - { - DoKill(self->tracer, self, damagetype, flags, filter, species); - } -} - -//=========================================================================== -// -// A_KillMaster(damagetype, int flags) -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillMaster) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_NAME(damagetype, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_CLASS(filter, 2); - ACTION_PARAM_NAME(species, 3); - - if (self->master != NULL) - { - DoKill(self->master, self, damagetype, flags, filter, species); - } -} - -//=========================================================================== -// -// A_KillChildren(damagetype, int flags) -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillChildren) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_NAME(damagetype, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_CLASS(filter, 2); - ACTION_PARAM_NAME(species, 3); - - TThinkerIterator it; - AActor *mo; - - while ( (mo = it.Next()) ) - { - if (mo->master == self) - { - DoKill(mo, self, damagetype, flags, filter, species); - } - } -} - -//=========================================================================== -// -// A_KillSiblings(damagetype, int flags) -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillSiblings) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_NAME(damagetype, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_CLASS(filter, 2); - ACTION_PARAM_NAME(species, 3); - - TThinkerIterator it; - AActor *mo; - - if (self->master != NULL) - { - while ( (mo = it.Next()) ) - { - if (mo->master == self->master && mo != self) - { - DoKill(mo, self, damagetype, flags, filter, species); - } - } - } -} - -//=========================================================================== -// -// DoRemove -// -//=========================================================================== - -enum RMVF_flags -{ - RMVF_MISSILES = 1 << 0, - RMVF_NOMONSTERS = 1 << 1, - RMVF_MISC = 1 << 2, - RMVF_EVERYTHING = 1 << 3, - RMVF_EXFILTER = 1 << 4, - RMVF_EXSPECIES = 1 << 5, - RMVF_EITHER = 1 << 6, -}; - -static void DoRemove(AActor *removetarget, int flags, const PClass *filter, FName species) -{ - bool filterpass = DoCheckFilter(removetarget, filter, (flags & RMVF_EXFILTER) ? true : false), - speciespass = DoCheckSpecies(removetarget, species, (flags & RMVF_EXSPECIES) ? true : false); - if ((flags & RMVF_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) - { - if ((flags & RMVF_EVERYTHING)) - { - P_RemoveThing(removetarget); - } - if ((flags & RMVF_MISC) && !((removetarget->flags3 & MF3_ISMONSTER) && (removetarget->flags & MF_MISSILE))) - { - P_RemoveThing(removetarget); - } - if ((removetarget->flags3 & MF3_ISMONSTER) && !(flags & RMVF_NOMONSTERS)) - { - P_RemoveThing(removetarget); - } - if ((removetarget->flags & MF_MISSILE) && (flags & RMVF_MISSILES)) - { - P_RemoveThing(removetarget); - } - } -} - -//=========================================================================== -// -// A_RemoveTarget -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveTarget) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(flags, 0); - ACTION_PARAM_CLASS(filter, 1); - ACTION_PARAM_NAME(species, 2); - - if (self->target != NULL) - { - DoRemove(self->target, flags, filter, species); - } -} - -//=========================================================================== -// -// A_RemoveTracer -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveTracer) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(flags, 0); - ACTION_PARAM_CLASS(filter, 1); - ACTION_PARAM_NAME(species, 2); - - if (self->tracer != NULL) - { - DoRemove(self->tracer, flags, filter, species); - } -} - -//=========================================================================== -// -// A_RemoveMaster -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveMaster) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(flags, 0); - ACTION_PARAM_CLASS(filter, 1); - ACTION_PARAM_NAME(species, 2); - - if (self->master != NULL) - { - DoRemove(self->master, flags, filter, species); - } -} - -//=========================================================================== -// -// A_RemoveChildren -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveChildren) -{ - TThinkerIterator it; - AActor *mo; - ACTION_PARAM_START(4); - ACTION_PARAM_BOOL(removeall, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_CLASS(filter, 2); - ACTION_PARAM_NAME(species, 3); - - - while ((mo = it.Next()) != NULL) - { - if (mo->master == self && (mo->health <= 0 || removeall)) - { - DoRemove(mo, flags, filter, species); - } - } -} - -//=========================================================================== -// -// A_RemoveSiblings -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveSiblings) -{ - TThinkerIterator it; - AActor *mo; - ACTION_PARAM_START(4); - ACTION_PARAM_BOOL(removeall, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_CLASS(filter, 2); - ACTION_PARAM_NAME(species, 3); - - if (self->master != NULL) - { - while ((mo = it.Next()) != NULL) - { - if (mo->master == self->master && mo != self && (mo->health <= 0 || removeall)) - { - DoRemove(mo, flags, filter, species); - } - } - } -} - -//=========================================================================== -// -// A_Remove -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Remove) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_INT(removee, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_CLASS(filter, 2); - ACTION_PARAM_NAME(species, 3); - - AActor *reference = COPY_AAPTR(self, removee); - if (reference != NULL) - { - DoRemove(reference, flags, filter, species); - } -} - -//=========================================================================== -// -// A_SetTeleFog -// -// Sets the teleport fog(s) for the calling actor. -// Takes a name of the classes for te source and destination. -// Can set both at the same time. Use "" to retain the previous fog without -// changing it. -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTeleFog) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_NAME(oldpos, 0); - ACTION_PARAM_NAME(newpos, 1); - const PClass *check = PClass::FindClass(oldpos); - if (check == NULL || !stricmp(oldpos, "none") || !stricmp(oldpos, "null")) - self->TeleFogSourceType = NULL; - else if (!stricmp(oldpos, "")) - { //Don't change it if it's just "" - } - else - self->TeleFogSourceType = check; - - check = PClass::FindClass(newpos); - if (check == NULL || !stricmp(newpos, "none") || !stricmp(newpos, "null")) - self->TeleFogDestType = NULL; - else if (!stricmp(newpos, "")) - { //Don't change it if it's just "" - } - else - self->TeleFogDestType = check; -} - -//=========================================================================== -// -// A_SwapTeleFog -// -// Switches the source and dest telefogs around. -//=========================================================================== - -DEFINE_ACTION_FUNCTION(AActor, A_SwapTeleFog) -{ - if ((self->TeleFogSourceType != self->TeleFogDestType)) //Does nothing if they're the same. - { - const PClass *temp = self->TeleFogSourceType; - self->TeleFogSourceType = self->TeleFogDestType; - self->TeleFogDestType = temp; - } -} - -//=========================================================================== -// -// A_SetFloatBobPhase -// -// Changes the FloatBobPhase of the actor. -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetFloatBobPhase) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(bob, 0); - - //Respect float bob phase limits. - if (self && (bob >= 0 && bob <= 63)) - self->FloatBobPhase = bob; -} - -//=========================================================================== -// A_SetHealth -// -// Changes the health of the actor. -// Takes a pointer as well. -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetHealth) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(health, 0); - ACTION_PARAM_INT(ptr, 1); - - AActor *mobj = COPY_AAPTR(self, ptr); - - if (!mobj) - { - return; - } - - player_t *player = mobj->player; - if (player) - { - if (health <= 0) - player->mo->health = mobj->health = player->health = 1; //Copied from the buddha cheat. - else - player->mo->health = mobj->health = player->health = health; - } - else if (mobj) - { - if (health <= 0) - mobj->health = 1; - else - mobj->health = health; - } -} - -//=========================================================================== -// A_ResetHealth -// -// Resets the health of the actor to default, except if their dead. -// Takes a pointer. -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ResetHealth) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(ptr, 0); - - AActor *mobj = COPY_AAPTR(self, ptr); - - if (!mobj) - { - return; - } - - player_t *player = mobj->player; - if (player && (player->mo->health > 0)) - { - player->health = player->mo->health = player->mo->GetDefault()->health; //Copied from the resurrect cheat. - } - else if (mobj && (mobj->health > 0)) - { - mobj->health = mobj->SpawnHealth(); - } -} - -//=========================================================================== -// -// A_SetRipperLevel(int level) -// -// Sets the ripper level/requirement of the calling actor. -// Also sets the minimum and maximum levels to rip through. -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipperLevel) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(level, 0); - self->RipperLevel = level; -} - -//=========================================================================== -// -// A_SetRipMin(int min) -// -// Sets the ripper level/requirement of the calling actor. -// Also sets the minimum and maximum levels to rip through. -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipMin) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(min, 1); - self->RipLevelMin = min; -} - -//=========================================================================== -// -// A_SetRipMin(int min) -// -// Sets the ripper level/requirement of the calling actor. -// Also sets the minimum and maximum levels to rip through. -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipMax) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(max, 1); - self->RipLevelMax = max; + +//=========================================================================== +// +// A_ScaleVelocity +// +// Scale actor's velocity. +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ScaleVelocity) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_FIXED(scale, 0); + ACTION_PARAM_INT(ptr, 1); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } + + INTBOOL was_moving = self->velx | self->vely | self->velz; + + ref->velx = FixedMul(ref->velx, scale); + ref->vely = FixedMul(ref->vely, scale); + ref->velz = FixedMul(ref->velz, scale); + + // If the actor was previously moving but now is not, and is a player, + // update its player variables. (See A_Stop.) + if (was_moving) + { + CheckStopped(ref); + } +} + +//=========================================================================== +// +// A_ChangeVelocity +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeVelocity) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_FIXED(x, 0); + ACTION_PARAM_FIXED(y, 1); + ACTION_PARAM_FIXED(z, 2); + ACTION_PARAM_INT(flags, 3); + ACTION_PARAM_INT(ptr, 4); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } + + INTBOOL was_moving = ref->velx | ref->vely | ref->velz; + + fixed_t vx = x, vy = y, vz = z; + fixed_t sina = finesine[ref->angle >> ANGLETOFINESHIFT]; + fixed_t cosa = finecosine[ref->angle >> ANGLETOFINESHIFT]; + + if (flags & 1) // relative axes - make x, y relative to actor's current angle + { + vx = DMulScale16(x, cosa, -y, sina); + vy = DMulScale16(x, sina, y, cosa); + } + if (flags & 2) // discard old velocity - replace old velocity with new velocity + { + ref->velx = vx; + ref->vely = vy; + ref->velz = vz; + } + else // add new velocity to old velocity + { + ref->velx += vx; + ref->vely += vy; + ref->velz += vz; + } + + if (was_moving) + { + CheckStopped(self); + } +} + +//=========================================================================== +// +// A_SetArg +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetArg) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(pos, 0); + ACTION_PARAM_INT(value, 1); + + // Set the value of the specified arg + if ((size_t)pos < countof(self->args)) + { + self->args[pos] = value; + } +} + +//=========================================================================== +// +// A_SetSpecial +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpecial) +{ + ACTION_PARAM_START(6); + ACTION_PARAM_INT(spec, 0); + ACTION_PARAM_INT(arg0, 1); + ACTION_PARAM_INT(arg1, 2); + ACTION_PARAM_INT(arg2, 3); + ACTION_PARAM_INT(arg3, 4); + ACTION_PARAM_INT(arg4, 5); + + self->special = spec; + self->args[0] = arg0; + self->args[1] = arg1; + self->args[2] = arg2; + self->args[3] = arg3; + self->args[4] = arg4; +} + +//=========================================================================== +// +// A_SetUserVar +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserVar) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_NAME(varname, 0); + ACTION_PARAM_INT(value, 1); + + PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); + PSymbolVariable *var; + + if (sym == NULL || sym->SymbolType != SYM_Variable || + !(var = static_cast(sym))->bUserVar || + var->ValueType.Type != VAL_Int) + { + Printf("%s is not a user variable in class %s\n", varname.GetChars(), + self->GetClass()->TypeName.GetChars()); + return; + } + // Set the value of the specified user variable. + *(int *)(reinterpret_cast(self) + var->offset) = value; +} + +//=========================================================================== +// +// A_SetUserArray +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserArray) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_NAME(varname, 0); + ACTION_PARAM_INT(pos, 1); + ACTION_PARAM_INT(value, 2); + + PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); + PSymbolVariable *var; + + if (sym == NULL || sym->SymbolType != SYM_Variable || + !(var = static_cast(sym))->bUserVar || + var->ValueType.Type != VAL_Array || var->ValueType.BaseType != VAL_Int) + { + Printf("%s is not a user array in class %s\n", varname.GetChars(), + self->GetClass()->TypeName.GetChars()); + return; + } + if (pos < 0 || pos >= var->ValueType.size) + { + Printf("%d is out of bounds in array %s in class %s\n", pos, varname.GetChars(), + self->GetClass()->TypeName.GetChars()); + return; + } + // Set the value of the specified user array at index pos. + ((int *)(reinterpret_cast(self) + var->offset))[pos] = value; +} + +//=========================================================================== +// +// A_Teleport(optional state teleportstate, optional class targettype, +// optional class fogtype, optional int flags, optional fixed mindist, +// optional fixed maxdist) +// +// Attempts to teleport to a targettype at least mindist away and at most +// maxdist away (0 means unlimited). If successful, spawn a fogtype at old +// location and place calling actor in teleportstate. +// +//=========================================================================== +enum T_Flags +{ + TF_TELEFRAG = 0x00000001, // Allow telefrag in order to teleport. + TF_RANDOMDECIDE = 0x00000002, // Randomly fail based on health. (A_Srcr2Decide) + TF_FORCED = 0x00000004, // Forget what's in the way. TF_Telefrag takes precedence though. + TF_KEEPVELOCITY = 0x00000008, // Preserve velocity. + TF_KEEPANGLE = 0x00000010, // Keep angle. + TF_USESPOTZ = 0x00000020, // Set the z to the spot's z, instead of the floor. + TF_NOSRCFOG = 0x00000040, // Don't leave any fog behind when teleporting. + TF_NODESTFOG = 0x00000080, // Don't spawn any fog at the arrival position. + TF_USEACTORFOG = 0x00000100, // Use the actor's TeleFogSourceType and TeleFogDestType fogs. + TF_NOJUMP = 0x00000200, // Don't jump after teleporting. +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport) +{ + ACTION_PARAM_START(6); + ACTION_PARAM_STATE(TeleportState, 0); + ACTION_PARAM_CLASS(TargetType, 1); + ACTION_PARAM_CLASS(FogType, 2); + ACTION_PARAM_INT(Flags, 3); + ACTION_PARAM_FIXED(MinDist, 4); + ACTION_PARAM_FIXED(MaxDist, 5); + + // Randomly choose not to teleport like A_Srcr2Decide. + if (Flags & TF_RANDOMDECIDE) + { + static const int chance[] = + { + 192, 120, 120, 120, 64, 64, 32, 16, 0 + }; + + unsigned int chanceindex = self->health / ((self->SpawnHealth()/8 == 0) ? 1 : self->SpawnHealth()/8); + + if (chanceindex >= countof(chance)) + { + chanceindex = countof(chance) - 1; + } + + if (pr_teleport() >= chance[chanceindex]) return; + } + + DSpotState *state = DSpotState::GetSpotState(); + if (state == NULL) return; + + if (!TargetType) TargetType = PClass::FindClass("BossSpot"); + + AActor * spot = state->GetSpotWithMinMaxDistance(TargetType, self->x, self->y, MinDist, MaxDist); + if (spot == NULL) return; + + fixed_t prevX = self->x; + fixed_t prevY = self->y; + fixed_t prevZ = self->z; + fixed_t aboveFloor = spot->z - spot->floorz; + fixed_t finalz = spot->floorz + aboveFloor; + + if (spot->z + self->height > spot->ceilingz) + finalz = spot->ceilingz - self->height; + else if (spot->z < spot->floorz) + finalz = spot->floorz; + + + //Take precedence and cooperate with telefragging first. + bool teleResult = P_TeleportMove(self, spot->x, spot->y, finalz, Flags & TF_TELEFRAG); + + if (Flags & TF_FORCED) + { + //If for some reason the original move didn't work, regardless of telefrag, force it to move. + self->SetOrigin(spot->x, spot->y, finalz); + teleResult = true; + } + + if (teleResult) + { + //If a fog type is defined in the parameter, or the user wants to use the actor's predefined fogs, + //and if there's no desire to be fogless, spawn a fog based upon settings. + if (FogType || (Flags & TF_USEACTORFOG)) + { + if (!(Flags & TF_NOSRCFOG)) + { + if (Flags & TF_USEACTORFOG) + P_SpawnTeleportFog(self, prevX, prevY, prevZ, true); + else + Spawn(FogType, prevX, prevY, prevZ, ALLOW_REPLACE); + } + if (!(Flags & TF_NODESTFOG)) + { + if (Flags & TF_USEACTORFOG) + P_SpawnTeleportFog(self, self->x, self->y, self->z, false); + else + Spawn(FogType, self->x, self->y, self->z, ALLOW_REPLACE); + } + } + + if (Flags & TF_USESPOTZ) + self->z = spot->z; + else + self->z = self->floorz; + + if (!(Flags & TF_KEEPANGLE)) + self->angle = spot->angle; + + if (!(Flags & TF_KEEPVELOCITY)) + self->velx = self->vely = self->velz = 0; + + if (!(Flags & TF_NOJUMP)) + { + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (TeleportState == NULL) + { + // Default to Teleport. + TeleportState = self->FindState("Teleport"); + // If still nothing, then return. + if (!TeleportState) return; + } + ACTION_JUMP(TeleportState); + return; + } + } + ACTION_SET_RESULT(teleResult); +} + +//=========================================================================== +// +// A_Turn +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Turn) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_ANGLE(angle, 0); + self->angle += angle; +} + +//=========================================================================== +// +// A_Quake +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Quake) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(intensity, 0); + ACTION_PARAM_INT(duration, 1); + ACTION_PARAM_INT(damrad, 2); + ACTION_PARAM_INT(tremrad, 3); + ACTION_PARAM_SOUND(sound, 4); + P_StartQuake(self, 0, intensity, duration, damrad, tremrad, sound); +} + +//=========================================================================== +// +// A_Weave +// +//=========================================================================== + +void A_Weave(AActor *self, int xyspeed, int zspeed, fixed_t xydist, fixed_t zdist) +{ + fixed_t newX, newY; + int weaveXY, weaveZ; + int angle; + fixed_t dist; + + weaveXY = self->WeaveIndexXY & 63; + weaveZ = self->WeaveIndexZ & 63; + angle = (self->angle + ANG90) >> ANGLETOFINESHIFT; + + if (xydist != 0 && xyspeed != 0) + { + dist = MulScale13(finesine[weaveXY << BOBTOFINESHIFT], xydist); + newX = self->x - FixedMul (finecosine[angle], dist); + newY = self->y - FixedMul (finesine[angle], dist); + weaveXY = (weaveXY + xyspeed) & 63; + dist = MulScale13(finesine[weaveXY << BOBTOFINESHIFT], xydist); + newX += FixedMul (finecosine[angle], dist); + newY += FixedMul (finesine[angle], dist); + if (!(self->flags5 & MF5_NOINTERACTION)) + { + P_TryMove (self, newX, newY, true); + } + else + { + self->UnlinkFromWorld (); + self->flags |= MF_NOBLOCKMAP; + self->x = newX; + self->y = newY; + self->LinkToWorld (); + } + self->WeaveIndexXY = weaveXY; + } + if (zdist != 0 && zspeed != 0) + { + self->z -= MulScale13(finesine[weaveZ << BOBTOFINESHIFT], zdist); + weaveZ = (weaveZ + zspeed) & 63; + self->z += MulScale13(finesine[weaveZ << BOBTOFINESHIFT], zdist); + self->WeaveIndexZ = weaveZ; + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Weave) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_INT(xspeed, 0); + ACTION_PARAM_INT(yspeed, 1); + ACTION_PARAM_FIXED(xdist, 2); + ACTION_PARAM_FIXED(ydist, 3); + A_Weave(self, xspeed, yspeed, xdist, ydist); +} + + + + +//=========================================================================== +// +// A_LineEffect +// +// This allows linedef effects to be activated inside deh frames. +// +//=========================================================================== + + +void P_TranslateLineDef (line_t *ld, maplinedef_t *mld); +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LineEffect) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(special, 0); + ACTION_PARAM_INT(tag, 1); + + line_t junk; maplinedef_t oldjunk; + bool res = false; + if (!(self->flags6 & MF6_LINEDONE)) // Unless already used up + { + if ((oldjunk.special = special)) // Linedef type + { + oldjunk.tag = tag; // Sector tag for linedef + P_TranslateLineDef(&junk, &oldjunk); // Turn into native type + res = !!P_ExecuteSpecial(junk.special, NULL, self, false, junk.args[0], + junk.args[1], junk.args[2], junk.args[3], junk.args[4]); + if (res && !(junk.flags & ML_REPEAT_SPECIAL)) // If only once, + self->flags6 |= MF6_LINEDONE; // no more for this thing + } + } + ACTION_SET_RESULT(res); +} + +//========================================================================== +// +// A Wolf3D-style attack codepointer +// +//========================================================================== +enum WolfAttackFlags +{ + WAF_NORANDOM = 1, + WAF_USEPUFF = 2, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_WolfAttack) +{ + ACTION_PARAM_START(9); + ACTION_PARAM_INT(flags, 0); + ACTION_PARAM_SOUND(sound, 1); + ACTION_PARAM_FIXED(snipe, 2); + ACTION_PARAM_INT(maxdamage, 3); + ACTION_PARAM_INT(blocksize, 4); + ACTION_PARAM_INT(pointblank, 5); + ACTION_PARAM_INT(longrange, 6); + ACTION_PARAM_FIXED(runspeed, 7); + ACTION_PARAM_CLASS(pufftype, 8); + + if (!self->target) + return; + + // Enemy can't see target + if (!P_CheckSight(self, self->target)) + return; + + A_FaceTarget (self); + + + // Target can dodge if it can see enemy + angle_t angle = R_PointToAngle2(self->target->x, self->target->y, self->x, self->y) - self->target->angle; + angle >>= 24; + bool dodge = (P_CheckSight(self->target, self) && (angle>226 || angle<30)); + + // Distance check is simplistic + fixed_t dx = abs (self->x - self->target->x); + fixed_t dy = abs (self->y - self->target->y); + fixed_t dz; + fixed_t dist = dx > dy ? dx : dy; + + // Some enemies are more precise + dist = FixedMul(dist, snipe); + + // Convert distance into integer number of blocks + dist >>= FRACBITS; + dist /= blocksize; + + // Now for the speed accuracy thingie + fixed_t speed = FixedMul(self->target->velx, self->target->velx) + + FixedMul(self->target->vely, self->target->vely) + + FixedMul(self->target->velz, self->target->velz); + int hitchance = speed < runspeed ? 256 : 160; + + // Distance accuracy (factoring dodge) + hitchance -= dist * (dodge ? 16 : 8); + + // While we're here, we may as well do something for this: + if (self->target->flags & MF_SHADOW) + { + hitchance >>= 2; + } + + // The attack itself + if (pr_cabullet() < hitchance) + { + // Compute position for spawning blood/puff + dx = self->target->x; + dy = self->target->y; + dz = self->target->z + (self->target->height>>1); + angle = R_PointToAngle2(dx, dy, self->x, self->y); + + dx += FixedMul(self->target->radius, finecosine[angle>>ANGLETOFINESHIFT]); + dy += FixedMul(self->target->radius, finesine[angle>>ANGLETOFINESHIFT]); + + int damage = flags & WAF_NORANDOM ? maxdamage : (1 + (pr_cabullet() % maxdamage)); + if (dist >= pointblank) + damage >>= 1; + if (dist >= longrange) + damage >>= 1; + FName mod = NAME_None; + bool spawnblood = !((self->target->flags & MF_NOBLOOD) + || (self->target->flags2 & (MF2_INVULNERABLE|MF2_DORMANT))); + if (flags & WAF_USEPUFF && pufftype) + { + AActor * dpuff = GetDefaultByType(pufftype->GetReplacement()); + mod = dpuff->DamageType; + + if (dpuff->flags2 & MF2_THRUGHOST && self->target->flags3 & MF3_GHOST) + damage = 0; + + if ((0 && dpuff->flags3 & MF3_PUFFONACTORS) || !spawnblood) + { + spawnblood = false; + P_SpawnPuff(self, pufftype, dx, dy, dz, angle, 0); + } + } + else if (self->target->flags3 & MF3_GHOST) + damage >>= 2; + if (damage) + { + int newdam = P_DamageMobj(self->target, self, self, damage, mod, DMG_THRUSTLESS); + if (spawnblood) + { + P_SpawnBlood(dx, dy, dz, angle, newdam > 0 ? newdam : damage, self->target); + P_TraceBleed(newdam > 0 ? newdam : damage, self->target, R_PointToAngle2(self->x, self->y, dx, dy), 0); + } + } + } + + // And finally, let's play the sound + S_Sound (self, CHAN_WEAPON, sound, 1, ATTN_NORM); +} + + +//========================================================================== +// +// A_Warp +// +//========================================================================== + +enum WARPF +{ + WARPF_ABSOLUTEOFFSET = 0x1, + WARPF_ABSOLUTEANGLE = 0x2, + WARPF_USECALLERANGLE = 0x4, + + WARPF_NOCHECKPOSITION = 0x8, + + WARPF_INTERPOLATE = 0x10, + WARPF_WARPINTERPOLATION = 0x20, + WARPF_COPYINTERPOLATION = 0x40, + + WARPF_STOP = 0x80, + WARPF_TOFLOOR = 0x100, + WARPF_TESTONLY = 0x200, + WARPF_ABSOLUTEPOSITION = 0x400, + WARPF_BOB = 0x800, + WARPF_MOVEPTR = 0x1000, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Warp) +{ + ACTION_PARAM_START(7); + + ACTION_PARAM_INT(destination_selector, 0); + ACTION_PARAM_FIXED(xofs, 1); + ACTION_PARAM_FIXED(yofs, 2); + ACTION_PARAM_FIXED(zofs, 3); + ACTION_PARAM_ANGLE(angle, 4); + ACTION_PARAM_INT(flags, 5); + ACTION_PARAM_STATE(success_state, 6); + + AActor *reference = COPY_AAPTR(self, destination_selector); + + //If there is no actor to warp to, fail. + if (!reference) + { + ACTION_SET_RESULT(false); + return; + } + + AActor *caller = self; + + if (flags & WARPF_MOVEPTR) + { + AActor *temp = reference; + reference = caller; + caller = temp; + } + + fixed_t oldx = caller->x; + fixed_t oldy = caller->y; + fixed_t oldz = caller->z; + + if (!(flags & WARPF_ABSOLUTEANGLE)) + { + angle += (flags & WARPF_USECALLERANGLE) ? caller->angle : reference->angle; + } + if (!(flags & WARPF_ABSOLUTEPOSITION)) + { + if (!(flags & WARPF_ABSOLUTEOFFSET)) + { + angle_t fineangle = angle >> ANGLETOFINESHIFT; + fixed_t xofs1 = xofs; + + // (borrowed from A_SpawnItemEx, assumed workable) + // in relative mode negative y values mean 'left' and positive ones mean 'right' + // This is the inverse orientation of the absolute mode! + + xofs = FixedMul(xofs1, finecosine[fineangle]) + FixedMul(yofs, finesine[fineangle]); + yofs = FixedMul(xofs1, finesine[fineangle]) - FixedMul(yofs, finecosine[fineangle]); + } + + if (flags & WARPF_TOFLOOR) + { + // set correct xy + + caller->SetOrigin( + reference->x + xofs, + reference->y + yofs, + reference->z); + + // now the caller's floorz should be appropriate for the assigned xy-position + // assigning position again with + + if (zofs) + { + // extra unlink, link and environment calculation + caller->SetOrigin( + caller->x, + caller->y, + caller->floorz + zofs); + } + else + { + // if there is no offset, there should be no ill effect from moving down to the + // already identified floor + + // A_Teleport does the same thing anyway + caller->z = caller->floorz; + } + } + else + { + caller->SetOrigin( + reference->x + xofs, + reference->y + yofs, + reference->z + zofs); + } + } + else //[MC] The idea behind "absolute" is meant to be "absolute". Override everything, just like A_SpawnItemEx's. + { + if (flags & WARPF_TOFLOOR) + { + caller->SetOrigin(xofs, yofs, caller->floorz + zofs); + } + else + { + caller->SetOrigin(xofs, yofs, zofs); + } + } + + if ((flags & WARPF_NOCHECKPOSITION) || P_TestMobjLocation(caller)) + { + if (flags & WARPF_TESTONLY) + { + caller->SetOrigin(oldx, oldy, oldz); + } + else + { + caller->angle = angle; + + if (flags & WARPF_STOP) + { + caller->velx = 0; + caller->vely = 0; + caller->velz = 0; + } + + if (flags & WARPF_WARPINTERPOLATION) + { + caller->PrevX += caller->x - oldx; + caller->PrevY += caller->y - oldy; + caller->PrevZ += caller->z - oldz; + } + else if (flags & WARPF_COPYINTERPOLATION) + { + caller->PrevX = caller->x + reference->PrevX - reference->x; + caller->PrevY = caller->y + reference->PrevY - reference->y; + caller->PrevZ = caller->z + reference->PrevZ - reference->z; + } + else if (!(flags & WARPF_INTERPOLATE)) + { + caller->PrevX = caller->x; + caller->PrevY = caller->y; + caller->PrevZ = caller->z; + } + + if ((flags & WARPF_BOB) && (reference->flags2 & MF2_FLOATBOB)) + { + caller->z += reference->GetBobOffset(); + } + } + + + if (success_state) + { + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + // in this case, you have the statejump to help you handle all the success anyway. + ACTION_JUMP(success_state); + return; + } + + ACTION_SET_RESULT(true); + } + else + { + caller->SetOrigin(oldx, oldy, oldz); + ACTION_SET_RESULT(false); + } + +} + +//========================================================================== +// +// ACS_Named* stuff + +// +// These are exactly like their un-named line special equivalents, except +// they take strings instead of integers to indicate which script to run. +// Some of these probably aren't very useful, but they are included for +// the sake of completeness. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteWithResult) +{ + ACTION_PARAM_START(5); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(arg1, 1); + ACTION_PARAM_INT(arg2, 2); + ACTION_PARAM_INT(arg3, 3); + ACTION_PARAM_INT(arg4, 4); + + bool res = !!P_ExecuteSpecial(ACS_ExecuteWithResult, NULL, self, false, -scriptname, arg1, arg2, arg3, arg4); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecute) +{ + ACTION_PARAM_START(5); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + ACTION_PARAM_INT(arg1, 2); + ACTION_PARAM_INT(arg2, 3); + ACTION_PARAM_INT(arg3, 4); + + bool res = !!P_ExecuteSpecial(ACS_Execute, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteAlways) +{ + ACTION_PARAM_START(5); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + ACTION_PARAM_INT(arg1, 2); + ACTION_PARAM_INT(arg2, 3); + ACTION_PARAM_INT(arg3, 4); + + bool res = !!P_ExecuteSpecial(ACS_ExecuteAlways, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecute) +{ + ACTION_PARAM_START(5); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + ACTION_PARAM_INT(arg1, 2); + ACTION_PARAM_INT(arg2, 3); + ACTION_PARAM_INT(lock, 4); + + bool res = !!P_ExecuteSpecial(ACS_LockedExecute, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecuteDoor) +{ + ACTION_PARAM_START(5); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + ACTION_PARAM_INT(arg1, 2); + ACTION_PARAM_INT(arg2, 3); + ACTION_PARAM_INT(lock, 4); + + bool res = !!P_ExecuteSpecial(ACS_LockedExecuteDoor, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedSuspend) +{ + ACTION_PARAM_START(2); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + + bool res = !!P_ExecuteSpecial(ACS_Suspend, NULL, self, false, -scriptname, mapnum, 0, 0, 0); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedTerminate) +{ + ACTION_PARAM_START(2); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + + bool res = !!P_ExecuteSpecial(ACS_Terminate, NULL, self, false, -scriptname, mapnum, 0, 0, 0); + + ACTION_SET_RESULT(res); +} + + +//========================================================================== +// +// A_RadiusGive +// +// Uses code roughly similar to A_Explode (but without all the compatibility +// baggage and damage computation code to give an item to all eligible mobjs +// in range. +// +//========================================================================== +enum RadiusGiveFlags +{ + RGF_GIVESELF = 1 << 0, + RGF_PLAYERS = 1 << 1, + RGF_MONSTERS = 1 << 2, + RGF_OBJECTS = 1 << 3, + RGF_VOODOO = 1 << 4, + RGF_CORPSES = 1 << 5, + RGF_MASK = 2111, + RGF_NOTARGET = 1 << 6, + RGF_NOTRACER = 1 << 7, + RGF_NOMASTER = 1 << 8, + RGF_CUBE = 1 << 9, + RGF_NOSIGHT = 1 << 10, + RGF_MISSILES = 1 << 11, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusGive) +{ + ACTION_PARAM_START(7); + ACTION_PARAM_CLASS(item, 0); + ACTION_PARAM_FIXED(distance, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_INT(amount, 3); + + // We need a valid item, valid targets, and a valid range + if (item == NULL || (flags & RGF_MASK) == 0 || distance <= 0) + { + return; + } + if (amount == 0) + { + amount = 1; + } + FBlockThingsIterator it(FBoundingBox(self->x, self->y, distance)); + double distsquared = double(distance) * double(distance); + + AActor *thing; + while ((thing = it.Next())) + { + // Don't give to inventory items + if (thing->flags & MF_SPECIAL) + { + continue; + } + // Avoid giving to self unless requested + if (thing == self && !(flags & RGF_GIVESELF)) + { + continue; + } + // Avoiding special pointers if requested + if (((thing == self->target) && (flags & RGF_NOTARGET)) || + ((thing == self->tracer) && (flags & RGF_NOTRACER)) || + ((thing == self->master) && (flags & RGF_NOMASTER))) + { + continue; + } + // Don't give to dead thing unless requested + if (thing->flags & MF_CORPSE) + { + if (!(flags & RGF_CORPSES)) + { + continue; + } + } + else if (thing->health <= 0 || thing->flags6 & MF6_KILLED) + { + continue; + } + // Players, monsters, and other shootable objects + if (thing->player) + { + if ((thing->player->mo == thing) && !(flags & RGF_PLAYERS)) + { + continue; + } + if ((thing->player->mo != thing) && !(flags & RGF_VOODOO)) + { + continue; + } + } + else if (thing->flags3 & MF3_ISMONSTER) + { + if (!(flags & RGF_MONSTERS)) + { + continue; + } + } + else if (thing->flags & MF_SHOOTABLE || thing->flags6 & MF6_VULNERABLE) + { + if (!(flags & RGF_OBJECTS)) + { + continue; + } + } + else if (thing->flags & MF_MISSILE) + { + if (!(flags & RGF_MISSILES)) + { + continue; + } + } + else + { + continue; + } + + if (flags & RGF_CUBE) + { // check if inside a cube + if (abs(thing->x - self->x) > distance || + abs(thing->y - self->y) > distance || + abs((thing->z + thing->height/2) - (self->z + self->height/2)) > distance) + { + continue; + } + } + else + { // check if inside a sphere + TVector3 tpos(thing->x, thing->y, thing->z + thing->height/2); + TVector3 spos(self->x, self->y, self->z + self->height/2); + if ((tpos - spos).LengthSquared() > distsquared) + { + continue; + } + } + fixed_t dz = abs ((thing->z + thing->height/2) - (self->z + self->height/2)); + + if ((flags & RGF_NOSIGHT) || P_CheckSight (thing, self, SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY)) + { // OK to give; target is in direct path, or the monster doesn't care about it being in line of sight. + AInventory *gift = static_cast(Spawn (item, 0, 0, 0, NO_REPLACE)); + if (gift->IsKindOf(RUNTIME_CLASS(AHealth))) + { + gift->Amount *= amount; + } + else + { + gift->Amount = amount; + } + gift->flags |= MF_DROPPED; + gift->ClearCounters(); + if (!gift->CallTryPickup (thing)) + { + gift->Destroy (); + } + } + } +} + + +//========================================================================== +// +// A_SetTics +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTics) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(tics_to_set, 0); + + if (stateowner != self && self->player != NULL && stateowner->IsKindOf(RUNTIME_CLASS(AWeapon))) + { // Is this a weapon? Need to check psp states for a match, then. Blah. + for (int i = 0; i < NUMPSPRITES; ++i) + { + if (self->player->psprites[i].state == CallingState) + { + self->player->psprites[i].tics = tics_to_set; + return; + } + } + } + // Just set tics for self. + self->tics = tics_to_set; +} + +//========================================================================== +// +// A_SetDamageType +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetDamageType) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_NAME(damagetype, 0); + + self->DamageType = damagetype; +} + +//========================================================================== +// +// A_DropItem +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropItem) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_CLASS(spawntype, 0); + ACTION_PARAM_INT(amount, 1); + ACTION_PARAM_INT(chance, 2); + + P_DropItem(self, spawntype, amount, chance); +} + +//========================================================================== +// +// A_SetSpeed +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpeed) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_FIXED(speed, 0); + ACTION_PARAM_INT(ptr, 1); + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return; + } + + ref->Speed = speed; +} + +static bool DoCheckSpecies(AActor *mo, FName species, bool exclude) +{ + return (!(species) || mo->Species == NAME_None || (species && ((exclude) ? (mo->Species != species) : (mo->Species == species)))); +} + +static bool DoCheckFilter(AActor *mo, const PClass *filter, bool exclude) +{ + const PClass *c1 = mo->GetClass(); + return (!(filter) || (filter == NULL) || (filter && ((exclude) ? (c1 != filter) : (c1 == filter)))); +} + +//=========================================================================== +// +// Common A_Damage handler +// +// A_Damage* (int amount, str damagetype, int flags, str filter, str species) +// Damages the specified actor by the specified amount. Negative values heal. +// Flags: See below. +// Filter: Specified actor is the only type allowed to be affected. +// Species: Specified species is the only type allowed to be affected. +// +// Examples: +// A_Damage(20,"Normal",DMSS_FOILINVUL,0,"DemonicSpecies") <--Only actors +// with a species "DemonicSpecies" will be affected. Use 0 to not filter by actor. +// +//=========================================================================== + +enum DMSS +{ + DMSS_FOILINVUL = 1, //Foil invulnerability + DMSS_AFFECTARMOR = 2, //Make it affect armor + DMSS_KILL = 4, //Damages them for their current health + DMSS_NOFACTOR = 8, //Ignore DamageFactors + DMSS_FOILBUDDHA = 16, //Can kill actors with Buddha flag, except the player. + DMSS_NOPROTECT = 32, //Ignores PowerProtection entirely + DMSS_EXFILTER = 64, //Changes filter into a blacklisted class instead of whitelisted. + DMSS_EXSPECIES = 128, // ^ but with species instead. + DMSS_EITHER = 256, //Allow either type or species to be affected. +}; + +static void DoDamage(AActor *dmgtarget, AActor *self, int amount, FName DamageType, int flags, const PClass *filter, FName species) +{ + bool filterpass = DoCheckFilter(dmgtarget, filter, (flags & DMSS_EXFILTER) ? true : false), + speciespass = DoCheckSpecies(dmgtarget, species, (flags & DMSS_EXSPECIES) ? true : false); + if ((flags & DMSS_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) + { + int dmgFlags = 0; + if (flags & DMSS_FOILINVUL) + dmgFlags += DMG_FOILINVUL; + if (flags & DMSS_FOILBUDDHA) + dmgFlags += DMG_FOILBUDDHA; + if ((flags & DMSS_KILL) || (flags & DMSS_NOFACTOR)) //Kill implies NoFactor + dmgFlags += DMG_NO_FACTOR; + if (!(flags & DMSS_AFFECTARMOR) || (flags & DMSS_KILL)) //Kill overrides AffectArmor + dmgFlags += DMG_NO_ARMOR; + if (flags & DMSS_KILL) //Kill adds the value of the damage done to it. Allows for more controlled extreme death types. + amount += dmgtarget->health; + if (flags & DMSS_NOPROTECT) //Ignore PowerProtection. + dmgFlags += DMG_NO_PROTECT; + + if (amount > 0) + P_DamageMobj(dmgtarget, self, self, amount, DamageType, dmgFlags); //Should wind up passing them through just fine. + + else if (amount < 0) + { + amount = -amount; + P_GiveBody(dmgtarget, amount); + } + } +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSelf) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + DoDamage(self, self, amount, DamageType, flags, filter, species); +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTarget) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + if (self->target != NULL) + { + DoDamage(self->target, self, amount, DamageType, flags, filter, species); + } +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTracer) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + if (self->tracer != NULL) + { + DoDamage(self->tracer, self, amount, DamageType, flags, filter, species); + } +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageMaster) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + if (self->master != NULL) + { + DoDamage(self->master, self, amount, DamageType, flags, filter, species); + } +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageChildren) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + TThinkerIterator it; + AActor * mo; + + while ( (mo = it.Next()) ) + { + if (mo->master == self) + { + DoDamage(mo, self, amount, DamageType, flags, filter, species); + } + } +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSiblings) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + TThinkerIterator it; + AActor * mo; + + if (self->master != NULL) + { + while ((mo = it.Next())) + { + if (mo->master == self->master && mo != self) + { + DoDamage(mo, self, amount, DamageType, flags, filter, species); + } + } + } +} + + +//=========================================================================== +// +// A_Kill*(damagetype, int flags) +// +//=========================================================================== +enum KILS +{ + KILS_FOILINVUL = 1 << 0, + KILS_KILLMISSILES = 1 << 1, + KILS_NOMONSTERS = 1 << 2, + KILS_FOILBUDDHA = 1 << 3, + KILS_EXFILTER = 1 << 4, + KILS_EXSPECIES = 1 << 5, + KILS_EITHER = 1 << 6, +}; + +static void DoKill(AActor *killtarget, AActor *self, FName damagetype, int flags, const PClass *filter, FName species) +{ + bool filterpass = DoCheckFilter(killtarget, filter, (flags & KILS_EXFILTER) ? true : false), + speciespass = DoCheckSpecies(killtarget, species, (flags & KILS_EXSPECIES) ? true : false); + if ((flags & KILS_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) //Check this first. I think it'll save the engine a lot more time this way. + { + int dmgFlags = DMG_NO_ARMOR + DMG_NO_FACTOR; + + if (KILS_FOILINVUL) + dmgFlags += DMG_FOILINVUL; + if (KILS_FOILBUDDHA) + dmgFlags += DMG_FOILBUDDHA; + + + if ((killtarget->flags & MF_MISSILE) && (flags & KILS_KILLMISSILES)) + { + //[MC] Now that missiles can set masters, lets put in a check to properly destroy projectiles. BUT FIRST! New feature~! + //Check to see if it's invulnerable. Disregarded if foilinvul is on, but never works on a missile with NODAMAGE + //since that's the whole point of it. + if ((!(killtarget->flags2 & MF2_INVULNERABLE) || (flags & KILS_FOILINVUL)) && + (!(killtarget->flags2 & MF7_BUDDHA) || (flags & KILS_FOILBUDDHA)) && !(killtarget->flags5 & MF5_NODAMAGE)) + { + P_ExplodeMissile(killtarget, NULL, NULL); + } + } + if (!(flags & KILS_NOMONSTERS)) + { + P_DamageMobj(killtarget, self, self, killtarget->health, damagetype, dmgFlags); + } + } +} + + +//=========================================================================== +// +// A_KillTarget(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillTarget) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_NAME(damagetype, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + if (self->target != NULL) + { + DoKill(self->target, self, damagetype, flags, filter, species); + } +} + +//=========================================================================== +// +// A_KillTracer(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillTracer) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_NAME(damagetype, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + if (self->tracer != NULL) + { + DoKill(self->tracer, self, damagetype, flags, filter, species); + } +} + +//=========================================================================== +// +// A_KillMaster(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillMaster) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_NAME(damagetype, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + if (self->master != NULL) + { + DoKill(self->master, self, damagetype, flags, filter, species); + } +} + +//=========================================================================== +// +// A_KillChildren(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillChildren) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_NAME(damagetype, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + TThinkerIterator it; + AActor *mo; + + while ( (mo = it.Next()) ) + { + if (mo->master == self) + { + DoKill(mo, self, damagetype, flags, filter, species); + } + } +} + +//=========================================================================== +// +// A_KillSiblings(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillSiblings) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_NAME(damagetype, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + TThinkerIterator it; + AActor *mo; + + if (self->master != NULL) + { + while ( (mo = it.Next()) ) + { + if (mo->master == self->master && mo != self) + { + DoKill(mo, self, damagetype, flags, filter, species); + } + } + } +} + +//=========================================================================== +// +// DoRemove +// +//=========================================================================== + +enum RMVF_flags +{ + RMVF_MISSILES = 1 << 0, + RMVF_NOMONSTERS = 1 << 1, + RMVF_MISC = 1 << 2, + RMVF_EVERYTHING = 1 << 3, + RMVF_EXFILTER = 1 << 4, + RMVF_EXSPECIES = 1 << 5, + RMVF_EITHER = 1 << 6, +}; + +static void DoRemove(AActor *removetarget, int flags, const PClass *filter, FName species) +{ + bool filterpass = DoCheckFilter(removetarget, filter, (flags & RMVF_EXFILTER) ? true : false), + speciespass = DoCheckSpecies(removetarget, species, (flags & RMVF_EXSPECIES) ? true : false); + if ((flags & RMVF_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) + { + if ((flags & RMVF_EVERYTHING)) + { + P_RemoveThing(removetarget); + } + if ((flags & RMVF_MISC) && !((removetarget->flags3 & MF3_ISMONSTER) && (removetarget->flags & MF_MISSILE))) + { + P_RemoveThing(removetarget); + } + if ((removetarget->flags3 & MF3_ISMONSTER) && !(flags & RMVF_NOMONSTERS)) + { + P_RemoveThing(removetarget); + } + if ((removetarget->flags & MF_MISSILE) && (flags & RMVF_MISSILES)) + { + P_RemoveThing(removetarget); + } + } +} + +//=========================================================================== +// +// A_RemoveTarget +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveTarget) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(flags, 0); + ACTION_PARAM_CLASS(filter, 1); + ACTION_PARAM_NAME(species, 2); + + if (self->target != NULL) + { + DoRemove(self->target, flags, filter, species); + } +} + +//=========================================================================== +// +// A_RemoveTracer +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveTracer) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(flags, 0); + ACTION_PARAM_CLASS(filter, 1); + ACTION_PARAM_NAME(species, 2); + + if (self->tracer != NULL) + { + DoRemove(self->tracer, flags, filter, species); + } +} + +//=========================================================================== +// +// A_RemoveMaster +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveMaster) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(flags, 0); + ACTION_PARAM_CLASS(filter, 1); + ACTION_PARAM_NAME(species, 2); + + if (self->master != NULL) + { + DoRemove(self->master, flags, filter, species); + } +} + +//=========================================================================== +// +// A_RemoveChildren +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveChildren) +{ + TThinkerIterator it; + AActor *mo; + ACTION_PARAM_START(4); + ACTION_PARAM_BOOL(removeall, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + + while ((mo = it.Next()) != NULL) + { + if (mo->master == self && (mo->health <= 0 || removeall)) + { + DoRemove(mo, flags, filter, species); + } + } +} + +//=========================================================================== +// +// A_RemoveSiblings +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveSiblings) +{ + TThinkerIterator it; + AActor *mo; + ACTION_PARAM_START(4); + ACTION_PARAM_BOOL(removeall, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + if (self->master != NULL) + { + while ((mo = it.Next()) != NULL) + { + if (mo->master == self->master && mo != self && (mo->health <= 0 || removeall)) + { + DoRemove(mo, flags, filter, species); + } + } + } +} + +//=========================================================================== +// +// A_Remove +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Remove) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_INT(removee, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + AActor *reference = COPY_AAPTR(self, removee); + if (reference != NULL) + { + DoRemove(reference, flags, filter, species); + } +} + +//=========================================================================== +// +// A_SetTeleFog +// +// Sets the teleport fog(s) for the calling actor. +// Takes a name of the classes for te source and destination. +// Can set both at the same time. Use "" to retain the previous fog without +// changing it. +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTeleFog) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_NAME(oldpos, 0); + ACTION_PARAM_NAME(newpos, 1); + const PClass *check = PClass::FindClass(oldpos); + if (check == NULL || !stricmp(oldpos, "none") || !stricmp(oldpos, "null")) + self->TeleFogSourceType = NULL; + else if (!stricmp(oldpos, "")) + { //Don't change it if it's just "" + } + else + self->TeleFogSourceType = check; + + check = PClass::FindClass(newpos); + if (check == NULL || !stricmp(newpos, "none") || !stricmp(newpos, "null")) + self->TeleFogDestType = NULL; + else if (!stricmp(newpos, "")) + { //Don't change it if it's just "" + } + else + self->TeleFogDestType = check; +} + +//=========================================================================== +// +// A_SwapTeleFog +// +// Switches the source and dest telefogs around. +//=========================================================================== + +DEFINE_ACTION_FUNCTION(AActor, A_SwapTeleFog) +{ + if ((self->TeleFogSourceType != self->TeleFogDestType)) //Does nothing if they're the same. + { + const PClass *temp = self->TeleFogSourceType; + self->TeleFogSourceType = self->TeleFogDestType; + self->TeleFogDestType = temp; + } +} + +//=========================================================================== +// +// A_SetFloatBobPhase +// +// Changes the FloatBobPhase of the actor. +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetFloatBobPhase) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(bob, 0); + + //Respect float bob phase limits. + if (self && (bob >= 0 && bob <= 63)) + self->FloatBobPhase = bob; +} + +//=========================================================================== +// A_SetHealth +// +// Changes the health of the actor. +// Takes a pointer as well. +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetHealth) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(health, 0); + ACTION_PARAM_INT(ptr, 1); + + AActor *mobj = COPY_AAPTR(self, ptr); + + if (!mobj) + { + return; + } + + player_t *player = mobj->player; + if (player) + { + if (health <= 0) + player->mo->health = mobj->health = player->health = 1; //Copied from the buddha cheat. + else + player->mo->health = mobj->health = player->health = health; + } + else if (mobj) + { + if (health <= 0) + mobj->health = 1; + else + mobj->health = health; + } +} + +//=========================================================================== +// A_ResetHealth +// +// Resets the health of the actor to default, except if their dead. +// Takes a pointer. +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ResetHealth) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(ptr, 0); + + AActor *mobj = COPY_AAPTR(self, ptr); + + if (!mobj) + { + return; + } + + player_t *player = mobj->player; + if (player && (player->mo->health > 0)) + { + player->health = player->mo->health = player->mo->GetDefault()->health; //Copied from the resurrect cheat. + } + else if (mobj && (mobj->health > 0)) + { + mobj->health = mobj->SpawnHealth(); + } +} + +//=========================================================================== +// +// A_SetRipperLevel(int level) +// +// Sets the ripper level/requirement of the calling actor. +// Also sets the minimum and maximum levels to rip through. +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipperLevel) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(level, 0); + self->RipperLevel = level; +} + +//=========================================================================== +// +// A_SetRipMin(int min) +// +// Sets the ripper level/requirement of the calling actor. +// Also sets the minimum and maximum levels to rip through. +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipMin) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(min, 1); + self->RipLevelMin = min; +} + +//=========================================================================== +// +// A_SetRipMin(int min) +// +// Sets the ripper level/requirement of the calling actor. +// Also sets the minimum and maximum levels to rip through. +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipMax) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(max, 1); + self->RipLevelMax = max; } \ No newline at end of file From b69edbbec12e8b483fb4e977dfeb041084f1b6ae Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Thu, 29 Jan 2015 19:38:16 -0600 Subject: [PATCH 18/19] Redo commit a4c07a9 - Line endings are inconsistant in this file, but at least the commit doesn't touch the whole file. --- src/thingdef/thingdef_codeptr.cpp | 49 ++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index be3921877c..9db52f728c 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -1670,14 +1670,17 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) // //=========================================================================== -static void DoGiveInventory(AActor * receiver, DECLARE_PARAMINFO) +static void DoGiveInventory(AActor * receiver, bool use_aaptr, DECLARE_PARAMINFO) { - ACTION_PARAM_START(3); + ACTION_PARAM_START(2+use_aaptr); ACTION_PARAM_CLASS(mi, 0); ACTION_PARAM_INT(amount, 1); - ACTION_PARAM_INT(setreceiver, 2); - COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver); + if (use_aaptr) + { + ACTION_PARAM_INT(setreceiver, 2); + COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver); + } bool res=true; @@ -1685,6 +1688,11 @@ static void DoGiveInventory(AActor * receiver, DECLARE_PARAMINFO) if (mi) { AInventory *item = static_cast(Spawn (mi, 0, 0, 0, NO_REPLACE)); + if (!item) + { + ACTION_SET_RESULT(false); + return; + } if (item->IsKindOf(RUNTIME_CLASS(AHealth))) { item->Amount *= amount; @@ -1709,12 +1717,12 @@ static void DoGiveInventory(AActor * receiver, DECLARE_PARAMINFO) DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveInventory) { - DoGiveInventory(self, PUSH_PARAMINFO); + DoGiveInventory(self, true, PUSH_PARAMINFO); } DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToTarget) { - DoGiveInventory(self->target, PUSH_PARAMINFO); + DoGiveInventory(self->target, true, PUSH_PARAMINFO); } DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToChildren) @@ -1724,7 +1732,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToChildren) while ((mo = it.Next())) { - if (mo->master == self) DoGiveInventory(mo, PUSH_PARAMINFO); + if (mo->master == self) DoGiveInventory(mo, false, PUSH_PARAMINFO); } } @@ -1737,7 +1745,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToSiblings) { while ((mo = it.Next())) { - if (mo->master == self->master && mo != self) DoGiveInventory(mo, PUSH_PARAMINFO); + if (mo->master == self->master && mo != self) DoGiveInventory(mo, false, PUSH_PARAMINFO); } } } @@ -1753,16 +1761,23 @@ enum TIF_NOTAKEINFINITE = 1, }; -void DoTakeInventory(AActor * receiver, DECLARE_PARAMINFO) +void DoTakeInventory(AActor * receiver, bool use_aaptr, DECLARE_PARAMINFO) { - ACTION_PARAM_START(4); + ACTION_PARAM_START(3+use_aaptr); ACTION_PARAM_CLASS(item, 0); ACTION_PARAM_INT(amount, 1); ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_INT(setreceiver, 3); - if (!item) return; - COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver); + if (!item) + { + ACTION_SET_RESULT(false); + return; + } + if (use_aaptr) + { + ACTION_PARAM_INT(setreceiver, 3); + COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver); + } bool res = false; @@ -1794,12 +1809,12 @@ void DoTakeInventory(AActor * receiver, DECLARE_PARAMINFO) DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeInventory) { - DoTakeInventory(self, PUSH_PARAMINFO); + DoTakeInventory(self, true, PUSH_PARAMINFO); } DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromTarget) { - DoTakeInventory(self->target, PUSH_PARAMINFO); + DoTakeInventory(self->target, true, PUSH_PARAMINFO); } DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromChildren) @@ -1809,7 +1824,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromChildren) while ((mo = it.Next())) { - if (mo->master == self) DoTakeInventory(mo, PUSH_PARAMINFO); + if (mo->master == self) DoTakeInventory(mo, false, PUSH_PARAMINFO); } } @@ -1822,7 +1837,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromSiblings) { while ((mo = it.Next())) { - if (mo->master == self->master && mo != self) DoTakeInventory(mo, PUSH_PARAMINFO); + if (mo->master == self->master && mo != self) DoTakeInventory(mo, false, PUSH_PARAMINFO); } } } From dc6b45804d22ea84d2f7a506c4bc2ac79d87a9be Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Sat, 31 Jan 2015 20:10:18 -0600 Subject: [PATCH 19/19] Don't lose the cursor when scrolling up in option menus - Fixed: If the menu cursor was on the topmost-displayed item, pressing up would not scroll the view up. The check for scrolling only tested if the newly selected item was the topmost one, since the menu code had assumed the only time the cursor would be on the topmost visible line was when it was the very first line of the menu. Using PgDn breaks this assumption. --- src/menu/optionmenu.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/menu/optionmenu.cpp b/src/menu/optionmenu.cpp index eff53fc496..9a77152659 100644 --- a/src/menu/optionmenu.cpp +++ b/src/menu/optionmenu.cpp @@ -196,9 +196,9 @@ bool DOptionMenu::MenuEvent (int mkey, bool fromcontroller) --mDesc->mSelectedItem; if (mDesc->mScrollPos > 0 && - mDesc->mSelectedItem == mDesc->mScrollTop + mDesc->mScrollPos) + mDesc->mSelectedItem <= mDesc->mScrollTop + mDesc->mScrollPos) { - mDesc->mScrollPos--; + mDesc->mScrollPos = MAX(mDesc->mSelectedItem - mDesc->mScrollTop - 1, 0); } if (mDesc->mSelectedItem < 0)