gzdoom-gles/src/p_actionfunctions.cpp
Christoph Oelckers 8dba322775 - scriptified Heretic's Skull Rod.
- Took the opportunity and fixed the logic for the Skull Rod's rain spawner. The old code which was part of the 3D floor submission was unable to work with portals at all. The new approach no longer tries to hide the dead projectile in the ceiling, it leaves it where it is and changes a few flags, so that its z-position can be used as reference to get the actual ceiling. This works for line portals, but for sector portals still requires some changes to sector_t::NextHighestCeilingAt to work, but at least this can be made to work unlike the old code.
- added names for the player-related translations to A_SetTranslation.
- fixed: Failure to resolve a function argument was checked for, too late.
- made the parameter for A_SetTranslation a name instead of a string, because it is more efficient. We do not need full strings here.
2016-11-25 16:05:03 +01:00

6809 lines
168 KiB
C++
Raw Blame History

/*
** thingdef_codeptr.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_effect.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 "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 "p_setup.h"
#include "gstrings.h"
#include "d_player.h"
#include "p_maputl.h"
#include "p_spec.h"
#include "templates.h"
#include "v_text.h"
#include "thingdef.h"
#include "math/cmath.h"
AActor *SingleActorFromTID(int tid, AActor *defactor);
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");
static FRandom pr_bfgselfdamage("BFGSelfDamage");
//==========================================================================
//
// ACustomInventory :: CallStateChain
//
// Executes the code pointers in a chain of states
// until there is no next state
//
//==========================================================================
bool ACustomInventory::CallStateChain (AActor *actor, FState *state)
{
INTBOOL result = false;
int counter = 0;
VMValue params[3] = { actor, this, 0 };
// We accept return types of `state`, `(int|bool)` or `state, (int|bool)`.
// The last one is for the benefit of A_Warp and A_Teleport.
int retval, numret;
FState *nextstate;
VMReturn ret[2];
ret[0].PointerAt((void **)&nextstate);
ret[1].IntAt(&retval);
FState *savedstate = this->state;
while (state != NULL)
{
if (!(state->UseFlags & SUF_ITEM))
{
auto so = FState::StaticFindStateOwner(state);
Printf(TEXTCOLOR_RED "State %s.%d not flagged for use in CustomInventory state chains.\n", so->TypeName.GetChars(), int(state - so->OwnedStates));
return false;
}
this->state = state;
nextstate = NULL; // assume no jump
if (state->ActionFunc != NULL)
{
if (state->ActionFunc->Unsafe)
{
// If an unsafe function (i.e. one that accesses user variables) is being detected, print a warning once and remove the bogus function. We may not call it because that would inevitably crash.
auto owner = FState::StaticFindStateOwner(state);
Printf(TEXTCOLOR_RED "Unsafe state call in state %s.%d to %s which accesses user variables. The action function has been removed from this state\n",
owner->TypeName.GetChars(), state - owner->OwnedStates, state->ActionFunc->PrintableName.GetChars());
state->ActionFunc = nullptr;
}
VMFrameStack stack;
PPrototype *proto = state->ActionFunc->Proto;
VMReturn *wantret;
FStateParamInfo stp = { state, STATE_StateChain, PSP_WEAPON };
params[2] = VMValue(&stp, ATAG_GENERIC);
retval = true; // assume success
wantret = NULL; // assume no return value wanted
numret = 0;
// For functions that return nothing (or return some type
// we don't care about), we pretend they return true,
// thanks to the values set just above.
if (proto->ReturnTypes.Size() == 1)
{
if (proto->ReturnTypes[0] == TypeState)
{ // Function returns a state
wantret = &ret[0];
retval = false; // this is a jump function which never affects the success state.
}
else if (proto->ReturnTypes[0] == TypeSInt32 || proto->ReturnTypes[0] == TypeBool)
{ // Function returns an int or bool
wantret = &ret[1];
}
numret = 1;
}
else if (proto->ReturnTypes.Size() == 2)
{
if (proto->ReturnTypes[0] == TypeState &&
(proto->ReturnTypes[1] == TypeSInt32 || proto->ReturnTypes[1] == TypeBool))
{ // Function returns a state and an int or bool
wantret = &ret[0];
numret = 2;
}
}
stack.Call(state->ActionFunc, params, state->ActionFunc->ImplicitArgs, wantret, numret);
// As long as even one state succeeds, the whole chain succeeds unless aborted below.
// A state that wants to jump does not count as "succeeded".
if (nextstate == NULL)
{
result |= retval;
}
}
// Since there are no delays it is a good idea to check for infinite loops here!
counter++;
if (counter >= 10000) break;
if (nextstate == NULL)
{
nextstate = state->GetNextState();
if (state == nextstate)
{ // Abort immediately if the state jumps to itself!
result = false;
break;
}
}
state = nextstate;
}
this->state = savedstate;
return !!result;
}
//==========================================================================
//
// GetPointer
//
// resolve AAPTR_*
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, GetPointer)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(ptr);
ACTION_RETURN_OBJECT(COPY_AAPTR(self, ptr));
}
//==========================================================================
//
// CheckClass
//
// NON-ACTION function to check a pointer's class.
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, CheckClass)
{
if (numret > 0)
{
assert(ret != NULL);
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS (checktype, AActor);
PARAM_INT_DEF (pick_pointer);
PARAM_BOOL_DEF (match_superclass);
self = COPY_AAPTR(self, pick_pointer);
if (self == NULL)
{
ret->SetInt(false);
}
else if (match_superclass)
{
ret->SetInt(self->IsKindOf(checktype));
}
else
{
ret->SetInt(self->GetClass() == checktype);
}
return 1;
}
return 0;
}
//==========================================================================
//
// CheckClass
//
// NON-ACTION function to calculate missile damage for the given actor
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, GetMissileDamage)
{
if (numret > 0)
{
assert(ret != NULL);
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(mask);
PARAM_INT(add);
PARAM_INT_DEF(pick_pointer);
self = COPY_AAPTR(self, pick_pointer);
if (self == NULL)
{
ret->SetInt(0);
}
else
{
ret->SetInt(self->GetMissileDamage(mask, add));
}
return 1;
}
return 0;
}
//==========================================================================
//
// CountInv
//
// NON-ACTION function to return the inventory count of an item.
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, CountInv)
{
if (numret > 0)
{
assert(ret != NULL);
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS(itemtype, AInventory);
PARAM_INT_DEF(pick_pointer);
self = COPY_AAPTR(self, pick_pointer);
if (self == NULL || itemtype == NULL)
{
ret->SetInt(0);
}
else
{
AInventory *item = self->FindInventory(itemtype);
ret->SetInt(item ? item->Amount : 0);
}
return 1;
}
return 0;
}
//==========================================================================
//
// GetDistance
//
// NON-ACTION function to get the distance in double.
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, GetDistance)
{
if (numret > 0)
{
assert(ret != NULL);
PARAM_SELF_PROLOGUE(AActor);
PARAM_BOOL(checkz);
PARAM_INT_DEF(ptr);
AActor *target = COPY_AAPTR(self, ptr);
if (!target || target == self)
{
ret->SetFloat(0);
}
else
{
DVector3 diff = self->Vec3To(target);
if (checkz)
diff.Z += (target->Height - self->Height) / 2;
else
diff.Z = 0.;
ret->SetFloat(diff.Length());
}
return 1;
}
return 0;
}
//==========================================================================
//
// GetAngle
//
// NON-ACTION function to get the angle in degrees (normalized to -180..180)
//
//==========================================================================
enum GAFlags
{
GAF_RELATIVE = 1,
GAF_SWITCH = 1 << 1,
};
DEFINE_ACTION_FUNCTION(AActor, GetAngle)
{
if (numret > 0)
{
assert(ret != NULL);
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(flags);
PARAM_INT_DEF(ptr)
AActor *target = COPY_AAPTR(self, ptr);
if (!target || target == self)
{
ret->SetFloat(0);
}
else
{
DVector3 diff = (flags & GAF_SWITCH) ? target->Vec3To(self) : self->Vec3To(target);
DAngle angto = diff.Angle();
DAngle yaw = (flags & GAF_SWITCH) ? target->Angles.Yaw : self->Angles.Yaw;
if (flags & GAF_RELATIVE) angto = deltaangle(yaw, angto);
ret->SetFloat(angto.Degrees);
}
return 1;
}
return 0;
}
//==========================================================================
//
// GetSpawnHealth
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, GetSpawnHealth)
{
if (numret > 0)
{
PARAM_SELF_PROLOGUE(AActor);
ret->SetInt(self->SpawnHealth());
return 1;
}
return 0;
}
//==========================================================================
//
// GetGibHealth
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, GetGibHealth)
{
if (numret > 0)
{
PARAM_SELF_PROLOGUE(AActor);
ret->SetInt(self->GetGibHealth());
return 1;
}
return 0;
}
//==========================================================================
//
// GetSpriteAngle
//
// NON-ACTION function returns the sprite angle of a pointer.
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, GetSpriteAngle)
{
if (numret > 0)
{
assert(ret != NULL);
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF(ptr);
AActor *target = COPY_AAPTR(self, ptr);
if (target == nullptr)
{
ret->SetFloat(0.0);
}
else
{
const double ang = target->SpriteAngle.Degrees;
ret->SetFloat(ang);
}
return 1;
}
return 0;
}
//==========================================================================
//
// GetSpriteRotation
//
// NON-ACTION function returns the sprite rotation of a pointer.
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, GetSpriteRotation)
{
if (numret > 0)
{
assert(ret != NULL);
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF(ptr);
AActor *target = COPY_AAPTR(self, ptr);
if (target == nullptr)
{
ret->SetFloat(0.0);
}
else
{
const double ang = target->SpriteRotation.Degrees;
ret->SetFloat(ang);
}
return 1;
}
return 0;
}
//==========================================================================
//
// GetZAt
//
// NON-ACTION function to get the floor or ceiling z at (x, y) with
// relativity being an option.
//==========================================================================
enum GZFlags
{
GZF_ABSOLUTEPOS = 1, // Use the absolute position instead of an offsetted one.
GZF_ABSOLUTEANG = 1 << 1, // Don't add the actor's angle to the parameter.
GZF_CEILING = 1 << 2, // Check the ceiling instead of the floor.
GZF_3DRESTRICT = 1 << 3, // Ignore midtextures and 3D floors above the pointer's z.
GZF_NOPORTALS = 1 << 4, // Don't pass through any portals.
GZF_NO3DFLOOR = 1 << 5, // Pass all 3D floors.
};
DEFINE_ACTION_FUNCTION(AActor, GetZAt)
{
if (numret > 0)
{
assert(ret != NULL);
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT_DEF(px);
PARAM_FLOAT_DEF(py);
PARAM_ANGLE_DEF(angle);
PARAM_INT_DEF(flags);
PARAM_INT_DEF(pick_pointer);
AActor *mobj = COPY_AAPTR(self, pick_pointer);
if (mobj == nullptr)
{
ret->SetFloat(0);
}
else
{
// [MC] At any time, the NextLowest/Highest functions could be changed to include
// more FFC flags to check. Don't risk it by just passing flags straight to it.
DVector2 pos = { px, py };
double z = 0.;
int pflags = (flags & GZF_3DRESTRICT) ? FFCF_3DRESTRICT : 0;
if (flags & GZF_NOPORTALS) pflags |= FFCF_NOPORTALS;
if (!(flags & GZF_ABSOLUTEPOS))
{
if (!(flags & GZF_ABSOLUTEANG))
{
angle += mobj->Angles.Yaw;
}
double s = angle.Sin();
double c = angle.Cos();
pos = mobj->Vec2Offset(pos.X * c + pos.Y * s, pos.X * s - pos.Y * c);
}
sector_t *sec = P_PointInSector(pos);
if (sec)
{
if (flags & GZF_CEILING)
{
if ((flags & GZF_NO3DFLOOR) && (flags & GZF_NOPORTALS))
{
z = sec->ceilingplane.ZatPoint(pos);
}
else if (flags & GZF_NO3DFLOOR)
{
z = sec->HighestCeilingAt(pos);
}
else
{ // [MC] Handle strict 3D floors and portal toggling via the flags passed to it.
z = sec->NextHighestCeilingAt(pos.X, pos.Y, mobj->Z(), mobj->Top(), pflags);
}
}
else
{
if ((flags & GZF_NO3DFLOOR) && (flags & GZF_NOPORTALS))
{
z = sec->floorplane.ZatPoint(pos);
}
else if (flags & GZF_NO3DFLOOR)
{
z = sec->LowestFloorAt(pos);
}
else
{
z = sec->NextLowestFloorAt(pos.X, pos.Y, mobj->Z(), pflags, mobj->MaxStepHeight);
}
}
}
ret->SetFloat(z);
return 1;
}
}
return 0;
}
//==========================================================================
//
// GetCrouchFactor
//
// NON-ACTION function to retrieve a player's crouching factor.
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, GetCrouchFactor)
{
if (numret > 0)
{
assert(ret != NULL);
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF(ptr);
AActor *mobj = COPY_AAPTR(self, ptr);
if (!mobj || !mobj->player)
{
ret->SetFloat(1);
}
else
{
ret->SetFloat(mobj->player->crouchfactor);
}
return 1;
}
return 0;
}
//==========================================================================
//
// GetCVar
//
// NON-ACTION function that works like ACS's GetCVar.
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, GetCVar)
{
if (numret > 0)
{
assert(ret != nullptr);
PARAM_SELF_PROLOGUE(AActor);
PARAM_STRING(cvarname);
FBaseCVar *cvar = GetCVar(self, cvarname);
if (cvar == nullptr)
{
ret->SetFloat(0);
}
else
{
ret->SetFloat(cvar->GetGenericRep(CVAR_Float).Float);
}
return 1;
}
return 0;
}
//==========================================================================
//
// GetPlayerInput
//
// NON-ACTION function that works like ACS's GetPlayerInput.
// Takes a pointer as anyone may or may not be a player.
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, GetPlayerInput)
{
if (numret > 0)
{
assert(ret != nullptr);
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT (inputnum);
PARAM_INT_DEF(ptr);
AActor *mobj = COPY_AAPTR(self, ptr);
//Need a player.
if (!mobj || !mobj->player)
{
ret->SetInt(0);
}
else
{
ret->SetInt(P_Thing_CheckInputNum(mobj->player, inputnum));
}
return 1;
}
return 0;
}
//==========================================================================
//
// CountProximity
//
// NON-ACTION function of A_CheckProximity that returns how much it counts.
// Takes a pointer as anyone may or may not be a player.
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, CountProximity)
{
if (numret > 0)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS(classname, AActor);
PARAM_FLOAT(distance);
PARAM_INT_DEF(flags);
PARAM_INT_DEF(ptr);
AActor *mobj = COPY_AAPTR(self, ptr);
if (mobj == nullptr)
{
ret->SetInt(0);
}
else
{
ret->SetInt(P_Thing_CheckProximity(self, classname, distance, 0, flags, ptr, true));
}
return 1;
}
return 0;
}
//===========================================================================
//
// __decorate_internal_int__
// __decorate_internal_bool__
// __decorate_internal_float__
//
// Placeholders for forcing DECORATE to cast numbers. If actually called,
// returns whatever was passed.
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, __decorate_internal_int__)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(returnme);
ACTION_RETURN_INT(returnme);
}
DEFINE_ACTION_FUNCTION(AActor, __decorate_internal_bool__)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_BOOL(returnme);
ACTION_RETURN_BOOL(returnme);
}
DEFINE_ACTION_FUNCTION(AActor, __decorate_internal_float__)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT(returnme);
if (numret > 0)
{
ret->SetFloat(returnme);
return 1;
}
return 0;
}
//==========================================================================
//
// 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(AActor, A_RearrangePointers)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT (ptr_target);
PARAM_INT_DEF (ptr_master);
PARAM_INT_DEF (ptr_tracer);
PARAM_INT_DEF (flags);
// 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;
}
return 0;
}
//==========================================================================
//
// 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(AActor, A_TransferPointer)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT (ptr_source);
PARAM_INT (ptr_recipient);
PARAM_INT (ptr_sourcefield);
PARAM_INT_DEF (ptr_recipientfield);
PARAM_INT_DEF (flags);
AActor *source, *recipient;
// Exchange pointers with actors to whom you have pointers (or with yourself, if you must)
source = COPY_AAPTR(self, ptr_source);
recipient = COPY_AAPTR(self, ptr_recipient); // pick an actor to store the provided pointer value
if (recipient == NULL)
{
return 0;
}
// convert source from dataprovider to data
source = COPY_AAPTR(source, ptr_sourcefield);
if (source == recipient)
{ // The recepient should not acquire a pointer to itself; will write NULL}
source = NULL;
}
if (ptr_recipientfield == AAPTR_DEFAULT)
{ // If default: Write to same field as data was read from
ptr_recipientfield = ptr_sourcefield;
}
ASSIGN_AAPTR(recipient, ptr_recipientfield, source, flags);
return 0;
}
//==========================================================================
//
// 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(AActor, A_CopyFriendliness)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF (ptr_source);
if (self->player != NULL)
{
return 0;
}
AActor *source = COPY_AAPTR(self, ptr_source);
if (source != NULL)
{ // No change in current target or health
self->CopyFriendliness(source, false, false);
}
return 0;
}
//==========================================================================
//
// Customizable attack functions which use actor parameters.
//
//==========================================================================
static void DoAttack (AActor *self, bool domelee, bool domissile,
int MeleeDamage, FSoundID MeleeSound, PClassActor *MissileType,double 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.
double add = MissileHeight + self->GetBobOffset() - 32;
self->AddZ(add);
AActor *missile = P_SpawnMissileXYZ (self->PosPlusZ(32.), self, self->target, MissileType, false);
self->AddZ(-add);
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)
{
PARAM_SELF_PROLOGUE(AActor);
int MeleeDamage = self->GetClass()->MeleeDamage;
FSoundID MeleeSound = self->GetClass()->MeleeSound;
DoAttack(self, true, false, MeleeDamage, MeleeSound, NULL, 0);
return 0;
}
DEFINE_ACTION_FUNCTION(AActor, A_MissileAttack)
{
PARAM_SELF_PROLOGUE(AActor);
PClassActor *MissileType = PClass::FindActor(self->GetClass()->MissileName);
DoAttack(self, false, true, 0, 0, MissileType, self->GetClass()->MissileHeight);
return 0;
}
DEFINE_ACTION_FUNCTION(AActor, A_ComboAttack)
{
PARAM_SELF_PROLOGUE(AActor);
int MeleeDamage = self->GetClass()->MeleeDamage;
FSoundID MeleeSound = self->GetClass()->MeleeSound;
PClassActor *MissileType = PClass::FindActor(self->GetClass()->MissileName);
DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, self->GetClass()->MissileHeight);
return 0;
}
DEFINE_ACTION_FUNCTION(AActor, A_BasicAttack)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT (melee_damage);
PARAM_SOUND (melee_sound);
PARAM_CLASS (missile_type, AActor);
PARAM_FLOAT (missile_height);
if (missile_type != NULL)
{
DoAttack(self, true, true, melee_damage, melee_sound, missile_type, missile_height);
}
return 0;
}
//==========================================================================
//
// Custom sound functions.
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_PlaySound)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_SOUND_DEF (soundid);
PARAM_INT_DEF (channel);
PARAM_FLOAT_DEF (volume);
PARAM_BOOL_DEF (looping);
PARAM_FLOAT_DEF (attenuation);
PARAM_BOOL_DEF (local);
if (!looping)
{
S_PlaySound(self, channel, soundid, (float)volume, (float)attenuation, local);
}
else
{
if (!S_IsActorPlayingSomething (self, channel&7, soundid))
{
S_PlaySound(self, channel | CHAN_LOOP, soundid, (float)volume, (float)attenuation, local);
}
}
return 0;
}
DEFINE_ACTION_FUNCTION(AActor, A_StopSound)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF(slot);
S_StopSound(self, slot);
return 0;
}
//==========================================================================
//
// 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(AActor, A_PlaySoundEx)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_SOUND (soundid);
PARAM_NAME (channel);
PARAM_BOOL_DEF (looping);
PARAM_INT_DEF (attenuation_raw);
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);
}
}
return 0;
}
DEFINE_ACTION_FUNCTION(AActor, A_StopSoundEx)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME(channel);
if (channel > NAME_Auto && channel <= NAME_SoundSlot7)
{
S_StopSound (self, int(channel) - NAME_Auto);
}
return 0;
}
//==========================================================================
//
// Generic seeker missile function
//
//==========================================================================
static FRandom pr_seekermissile ("SeekerMissile");
enum
{
SMF_LOOK = 1,
SMF_PRECISE = 2,
SMF_CURSPEED = 4,
};
DEFINE_ACTION_FUNCTION(AActor, A_SeekerMissile)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(ang1);
PARAM_INT(ang2);
PARAM_INT_DEF(flags);
PARAM_INT_DEF(chance);
PARAM_INT_DEF(distance);
if ((flags & SMF_LOOK) && (self->tracer == 0) && (pr_seekermissile()<chance))
{
self->tracer = P_RoughMonsterSearch (self, distance, true);
}
if (!P_SeekerMissile(self, clamp<int>(ang1, 0, 90), clamp<int>(ang2, 0, 90), !!(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;
}
}
return 0;
}
//==========================================================================
//
// Hitscan attack with a customizable amount of bullets (specified in damage)
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_BulletAttack)
{
PARAM_SELF_PROLOGUE(AActor);
int i;
if (!self->target) return 0;
A_FaceTarget (self);
DAngle slope = P_AimLineAttack (self, self->Angles.Yaw, MISSILERANGE);
S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM);
for (i = self->GetMissileDamage (0, 1); i > 0; --i)
{
DAngle angle = self->Angles.Yaw + pr_cabullet.Random2() * (5.625 / 256.);
int damage = ((pr_cabullet()%5)+1)*3;
P_LineAttack(self, angle, MISSILERANGE, slope, damage,
NAME_Hitscan, NAME_BulletPuff);
}
return 0;
}
//==========================================================================
//
// State jump function
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_Jump)
{
PARAM_ACTION_PROLOGUE(AActor);
PARAM_INT(maxchance);
paramnum++; // Increment paramnum to point at the first jump target
int count = numparam - paramnum;
if (count > 0 && (maxchance >= 256 || pr_cajump() < maxchance))
{
int jumpnum = (count == 1 ? 0 : (pr_cajump() % count));
PARAM_STATE_ACTION_AT(paramnum + jumpnum, jumpto);
ACTION_RETURN_STATE(jumpto);
}
ACTION_RETURN_STATE(NULL);
}
//==========================================================================
//
// State jump function
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, CheckInventory)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS (itemtype, AInventory);
PARAM_INT (itemamount);
PARAM_INT_DEF (setowner);
if (itemtype == nullptr)
{
ACTION_RETURN_BOOL(false);
}
AActor *owner = COPY_AAPTR(self, setowner);
if (owner == nullptr)
{
ACTION_RETURN_BOOL(false);
}
AInventory *item = owner->FindInventory(itemtype);
if (item)
{
if (itemamount > 0)
{
if (item->Amount >= itemamount)
{
ACTION_RETURN_BOOL(true);
}
}
else if (item->Amount >= item->MaxAmount)
{
ACTION_RETURN_BOOL(true);
}
}
ACTION_RETURN_BOOL(false);
}
//==========================================================================
//
// State jump function
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, CheckArmorType)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME (type);
PARAM_INT_DEF(amount);
ABasicArmor *armor = (ABasicArmor *)self->FindInventory(NAME_BasicArmor);
ACTION_RETURN_BOOL(armor && armor->ArmorType == type && armor->Amount >= amount);
}
//==========================================================================
//
// Parameterized version of A_Explode
//
//==========================================================================
enum
{
XF_HURTSOURCE = 1,
XF_NOTMISSILE = 4,
XF_NOACTORTYPE = 1 << 3,
XF_NOSPLASH = 16,
};
DEFINE_ACTION_FUNCTION(AActor, A_Explode)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF (damage);
PARAM_INT_DEF (distance);
PARAM_INT_DEF (flags);
PARAM_BOOL_DEF (alert);
PARAM_INT_DEF (fulldmgdistance);
PARAM_INT_DEF (nails);
PARAM_INT_DEF (naildamage);
PARAM_CLASS_DEF (pufftype, AActor);
PARAM_NAME_DEF (damagetype);
if (damage < 0) // get parameters from metadata
{
damage = self->GetClass()->ExplosionDamage;
distance = self->GetClass()->ExplosionRadius;
flags = !self->GetClass()->DontHurtShooter;
alert = false;
}
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)
{
DAngle ang;
for (int i = 0; i < nails; i++)
{
ang = i*360./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);
}
}
if (!(flags & XF_NOACTORTYPE) && damagetype == NAME_None)
{
damagetype = self->DamageType;
}
int pflags = 0;
if (flags & XF_HURTSOURCE) pflags |= RADF_HURTSOURCE;
if (flags & XF_NOTMISSILE) pflags |= RADF_SOURCEISSPOT;
int count = P_RadiusAttack (self, self->target, damage, distance, damagetype, pflags, fulldmgdistance);
if (!(flags & XF_NOSPLASH)) P_CheckSplash(self, distance);
if (alert && self->target != NULL && self->target->player != NULL)
{
P_NoiseAlert(self->target, self);
}
ACTION_RETURN_INT(count);
}
//==========================================================================
//
// A_RadiusThrust
//
//==========================================================================
enum
{
RTF_AFFECTSOURCE = 1,
RTF_NOIMPACTDAMAGE = 2,
RTF_NOTMISSILE = 4,
};
DEFINE_ACTION_FUNCTION(AActor, A_RadiusThrust)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF (force);
PARAM_INT_DEF (distance);
PARAM_INT_DEF (flags);
PARAM_INT_DEF (fullthrustdistance);
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);
if (sourcenothrust)
{
self->target->flags2 |= MF2_NODMGTHRUST;
}
return 0;
}
//==========================================================================
//
// A_RadiusDamageSelf
//
//==========================================================================
enum
{
RDSF_BFGDAMAGE = 1,
};
DEFINE_ACTION_FUNCTION(AActor, A_RadiusDamageSelf)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF(damage);
PARAM_FLOAT_DEF(distance);
PARAM_INT_DEF(flags);
PARAM_CLASS_DEF(flashtype, AActor);
int i;
int damageSteps;
int actualDamage;
double actualDistance;
actualDistance = self->Distance3D(self->target);
if (actualDistance < distance)
{
// [XA] Decrease damage with distance. Use the BFG damage
// calculation formula if the flag is set (essentially
// a generalization of SMMU's BFG11K behavior, used
// with fraggle's blessing.)
damageSteps = damage - int(damage * actualDistance / distance);
if (flags & RDSF_BFGDAMAGE)
{
actualDamage = 0;
for (i = 0; i < damageSteps; ++i)
actualDamage += (pr_bfgselfdamage() & 7) + 1;
}
else
{
actualDamage = damageSteps;
}
// optional "flash" effect -- spawn an actor on
// the player to indicate bad things happened.
AActor *flash = NULL;
if(flashtype != NULL)
flash = Spawn(flashtype, self->target->PosPlusZ(self->target->Height / 4), ALLOW_REPLACE);
int dmgFlags = 0;
FName dmgType = NAME_BFGSplash;
if (flash != NULL)
{
if (flash->flags5 & MF5_PUFFGETSOWNER) flash->target = self->target;
if (flash->flags3 & MF3_FOILINVUL) dmgFlags |= DMG_FOILINVUL;
if (flash->flags7 & MF7_FOILBUDDHA) dmgFlags |= DMG_FOILBUDDHA;
dmgType = flash->DamageType;
}
int newdam = P_DamageMobj(self->target, self, self->target, actualDamage, dmgType, dmgFlags);
P_TraceBleed(newdam > 0 ? newdam : actualDamage, self->target, self);
}
return 0;
}
//==========================================================================
//
// Execute a line special / script
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_CallSpecial)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT (special);
PARAM_INT_DEF (arg1);
PARAM_INT_DEF (arg2);
PARAM_INT_DEF (arg3);
PARAM_INT_DEF (arg4);
PARAM_INT_DEF (arg5);
bool res = !!P_ExecuteSpecial(special, NULL, self, false, arg1, arg2, arg3, arg4, arg5);
ACTION_RETURN_BOOL(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(AActor, A_CustomMissile)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS (ti, AActor);
PARAM_FLOAT_DEF (Spawnheight);
PARAM_FLOAT_DEF (Spawnofs_xy);
PARAM_ANGLE_DEF (Angle);
PARAM_INT_DEF (flags);
PARAM_ANGLE_DEF (Pitch);
PARAM_INT_DEF (ptr);
AActor *ref = COPY_AAPTR(self, ptr);
int aimmode = flags & CMF_AIMMODE;
AActor * targ;
AActor * missile;
if (ref != NULL || aimmode == 2)
{
if (ti)
{
DAngle angle = self->Angles.Yaw - 90;
double x = Spawnofs_xy * angle.Cos();
double y = Spawnofs_xy * angle.Sin();
double z = Spawnheight + self->GetBobOffset() - 32 + (self->player? self->player->crouchoffset : 0.);
DVector3 pos = self->Pos();
switch (aimmode)
{
case 0:
default:
// same adjustment as above (in all 3 directions this time) - for better aiming!
self->SetXYZ(self->Vec3Offset(x, y, z));
missile = P_SpawnMissileXYZ(self->PosPlusZ(32.), self, ref, ti, false);
self->SetXYZ(pos);
break;
case 1:
missile = P_SpawnMissileXYZ(self->Vec3Offset(x, y, self->GetBobOffset() + Spawnheight), self, ref, ti, false);
break;
case 2:
self->SetXYZ(self->Vec3Offset(x, y, 0.));
missile = P_SpawnMissileAngleZSpeed(self, self->Z() + self->GetBobOffset() + Spawnheight, ti, self->Angles.Yaw, 0, GetDefaultByType(ti)->Speed, self, false);
self->SetXYZ(pos);
flags |= CMF_ABSOLUTEPITCH;
break;
}
if (missile != NULL)
{
// Use the actual velocity instead of the missile's Speed property
// so that this can handle missiles with a high vertical velocity
// component properly.
double missilespeed;
if ( (CMF_ABSOLUTEPITCH|CMF_OFFSETPITCH) & flags)
{
if (CMF_OFFSETPITCH & flags)
{
Pitch += missile->Vel.Pitch();
}
missilespeed = fabs(Pitch.Cos() * missile->Speed);
missile->Vel.Z = Pitch.Sin() * missile->Speed;
}
else
{
missilespeed = missile->VelXYToSpeed();
}
if (CMF_SAVEPITCH & flags)
{
missile->Angles.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->Angles.Yaw = (CMF_ABSOLUTEANGLE & flags) ? Angle : missile->Angles.Yaw + Angle;
missile->VelFromAngle(missilespeed);
// 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->flags2 & 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);
}
return 0;
}
//==========================================================================
//
// An even more customizable hitscan attack
//
//==========================================================================
enum CBA_Flags
{
CBAF_AIMFACING = 1,
CBAF_NORANDOM = 2,
CBAF_EXPLICITANGLE = 4,
CBAF_NOPITCH = 8,
CBAF_NORANDOMPUFFZ = 16,
CBAF_PUFFTARGET = 32,
CBAF_PUFFMASTER = 64,
CBAF_PUFFTRACER = 128,
};
static void AimBulletMissile(AActor *proj, AActor *puff, int flags, bool temp, bool cba);
DEFINE_ACTION_FUNCTION(AActor, A_CustomBulletAttack)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_ANGLE (spread_xy);
PARAM_ANGLE (spread_z);
PARAM_INT (numbullets);
PARAM_INT (damageperbullet);
PARAM_CLASS_DEF (pufftype, AActor);
PARAM_FLOAT_DEF (range);
PARAM_INT_DEF (flags);
PARAM_INT_DEF (ptr);
PARAM_CLASS_DEF (missile, AActor);
PARAM_FLOAT_DEF (Spawnheight);
PARAM_FLOAT_DEF (Spawnofs_xy);
AActor *ref = COPY_AAPTR(self, ptr);
if (range == 0)
range = MISSILERANGE;
int i;
DAngle bangle;
DAngle bslope = 0.;
int laflags = (flags & CBAF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0;
if (ref != NULL || (flags & CBAF_AIMFACING))
{
if (!(flags & CBAF_AIMFACING))
{
A_Face(self, ref);
}
bangle = self->Angles.Yaw;
if (!(flags & CBAF_NOPITCH)) bslope = P_AimLineAttack (self, bangle, MISSILERANGE);
if (pufftype == nullptr) pufftype = PClass::FindActor(NAME_BulletPuff);
S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM);
for (i = 0; i < numbullets; i++)
{
DAngle angle = bangle;
DAngle slope = bslope;
if (flags & CBAF_EXPLICITANGLE)
{
angle += spread_xy;
slope += spread_z;
}
else
{
angle += spread_xy * (pr_cwbullet.Random2() / 255.);
slope += spread_z * (pr_cwbullet.Random2() / 255.);
}
int damage = damageperbullet;
if (!(flags & CBAF_NORANDOM))
damage *= ((pr_cabullet()%3)+1);
AActor *puff = P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype, laflags);
if (missile != nullptr && pufftype != nullptr)
{
double x = Spawnofs_xy * angle.Cos();
double y = Spawnofs_xy * angle.Sin();
DVector3 pos = self->Pos();
self->SetXYZ(self->Vec3Offset(x, y, 0.));
AActor *proj = P_SpawnMissileAngleZSpeed(self, self->Z() + self->GetBobOffset() + Spawnheight, missile, self->Angles.Yaw, 0, GetDefaultByType(missile)->Speed, self, false);
self->SetXYZ(pos);
if (proj)
{
bool temp = (puff == nullptr);
if (!puff)
{
puff = P_LineAttack(self, angle, range, slope, 0, NAME_Hitscan, pufftype, laflags | LAF_NOINTERACT);
}
if (puff)
{
AimBulletMissile(proj, puff, flags, temp, true);
}
}
}
}
}
return 0;
}
//==========================================================================
//
// A fully customizable melee attack
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_CustomMeleeAttack)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF (damage);
PARAM_SOUND_DEF (meleesound);
PARAM_SOUND_DEF (misssound);
PARAM_NAME_DEF (damagetype);
PARAM_BOOL_DEF (bleed);
if (damagetype == NAME_None)
damagetype = NAME_Melee; // Melee is the default type
if (!self->target)
return 0;
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);
}
return 0;
}
//==========================================================================
//
// A fully customizable combo attack
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_CustomComboAttack)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS (ti, AActor);
PARAM_FLOAT (spawnheight);
PARAM_INT (damage);
PARAM_SOUND_DEF (meleesound);
PARAM_NAME_DEF (damagetype);
PARAM_BOOL_DEF (bleed);
if (!self->target)
return 0;
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.
double add = spawnheight + self->GetBobOffset() - 32;
self->AddZ(add);
AActor *missile = P_SpawnMissileXYZ (self->PosPlusZ(32.), self, self->target, ti, false);
self->AddZ(-add);
if (missile)
{
// automatic handling of seeker missiles
if (missile->flags2 & MF2_SEEKERMISSILE)
{
missile->tracer = self->target;
}
P_CheckMissileSpawn(missile, self->radius);
}
}
return 0;
}
//==========================================================================
//
// State jump function
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_JumpIfNoAmmo)
{
PARAM_ACTION_PROLOGUE(AActor);
PARAM_STATE_ACTION(jump);
if (!ACTION_CALL_FROM_PSPRITE() || self->player->ReadyWeapon == nullptr)
{
ACTION_RETURN_STATE(NULL);
}
if (!self->player->ReadyWeapon->CheckAmmo(self->player->ReadyWeapon->bAltFire, false, true))
{
ACTION_RETURN_STATE(jump);
}
ACTION_RETURN_STATE(NULL);
}
//==========================================================================
//
// 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,
FBF_PUFFTARGET = 64,
FBF_PUFFMASTER = 128,
FBF_PUFFTRACER = 256,
};
static void AimBulletMissile(AActor *proj, AActor *puff, int flags, bool temp, bool cba)
{
if (proj && puff)
{
if (proj)
{
// FAF_BOTTOM = 1
// Aim for the base of the puff as that's where blood puffs will spawn... roughly.
A_Face(proj, puff, 0., 0., 0., 0., 1);
proj->Vel3DFromAngle(-proj->Angles.Pitch, proj->Speed);
if (!temp)
{
if (cba)
{
if (flags & CBAF_PUFFTARGET) proj->target = puff;
if (flags & CBAF_PUFFMASTER) proj->master = puff;
if (flags & CBAF_PUFFTRACER) proj->tracer = puff;
}
else
{
if (flags & FBF_PUFFTARGET) proj->target = puff;
if (flags & FBF_PUFFMASTER) proj->master = puff;
if (flags & FBF_PUFFTRACER) proj->tracer = puff;
}
}
}
}
if (puff && temp)
{
puff->Destroy();
}
}
DEFINE_ACTION_FUNCTION(AActor, A_FireBullets)
{
PARAM_ACTION_PROLOGUE(AActor);
PARAM_ANGLE (spread_xy);
PARAM_ANGLE (spread_z);
PARAM_INT (numbullets);
PARAM_INT (damageperbullet);
PARAM_CLASS_DEF (pufftype, AActor);
PARAM_INT_DEF (flags);
PARAM_FLOAT_DEF (range);
PARAM_CLASS_DEF (missile, AActor);
PARAM_FLOAT_DEF (Spawnheight);
PARAM_FLOAT_DEF (Spawnofs_xy);
if (!self->player) return 0;
player_t *player = self->player;
AWeapon *weapon = player->ReadyWeapon;
int i;
DAngle bangle;
DAngle bslope = 0.;
int laflags = (flags & FBF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0;
if ((flags & FBF_USEAMMO) && weapon && ACTION_CALL_FROM_PSPRITE())
{
if (!weapon->DepleteAmmo(weapon->bAltFire, true))
return 0; // out of ammo
}
if (range == 0) range = PLAYERMISSILERANGE;
if (!(flags & FBF_NOFLASH)) static_cast<APlayerPawn *>(self)->PlayAttacking2 ();
if (!(flags & FBF_NOPITCH)) bslope = P_BulletSlope(self);
bangle = self->Angles.Yaw;
if (pufftype == NULL) pufftype = PClass::FindActor(NAME_BulletPuff);
if (weapon != NULL)
{
S_Sound(self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM);
}
if ((numbullets == 1 && !player->refire) || numbullets == 0)
{
int damage = damageperbullet;
if (!(flags & FBF_NORANDOM))
damage *= ((pr_cwbullet()%3)+1);
AActor *puff = P_LineAttack(self, bangle, range, bslope, damage, NAME_Hitscan, pufftype, laflags);
if (missile != nullptr)
{
bool temp = false;
DAngle ang = self->Angles.Yaw - 90;
DVector2 ofs = ang.ToVector(Spawnofs_xy);
AActor *proj = P_SpawnPlayerMissile(self, ofs.X, ofs.Y, Spawnheight, missile, bangle, nullptr, nullptr, false, true);
if (proj)
{
if (!puff)
{
temp = true;
puff = P_LineAttack(self, bangle, range, bslope, 0, NAME_Hitscan, pufftype, laflags | LAF_NOINTERACT);
}
AimBulletMissile(proj, puff, flags, temp, false);
}
}
}
else
{
if (numbullets < 0)
numbullets = 1;
for (i = 0; i < numbullets; i++)
{
DAngle angle = bangle;
DAngle slope = bslope;
if (flags & FBF_EXPLICITANGLE)
{
angle += spread_xy;
slope += spread_z;
}
else
{
angle += spread_xy * (pr_cwbullet.Random2() / 255.);
slope += spread_z * (pr_cwbullet.Random2() / 255.);
}
int damage = damageperbullet;
if (!(flags & FBF_NORANDOM))
damage *= ((pr_cwbullet()%3)+1);
AActor *puff = P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype, laflags);
if (missile != nullptr)
{
bool temp = false;
DAngle ang = self->Angles.Yaw - 90;
DVector2 ofs = ang.ToVector(Spawnofs_xy);
AActor *proj = P_SpawnPlayerMissile(self, ofs.X, ofs.Y, Spawnheight, missile, angle, nullptr, nullptr, false, true);
if (proj)
{
if (!puff)
{
temp = true;
puff = P_LineAttack(self, angle, range, slope, 0, NAME_Hitscan, pufftype, laflags | LAF_NOINTERACT);
}
AimBulletMissile(proj, puff, flags, temp, false);
}
}
}
}
return 0;
}
//==========================================================================
//
// A_FireProjectile
//
//==========================================================================
enum FP_Flags
{
FPF_AIMATANGLE = 1,
FPF_TRANSFERTRANSLATION = 2,
FPF_NOAUTOAIM = 4,
};
DEFINE_ACTION_FUNCTION(AActor, A_FireCustomMissile)
{
PARAM_ACTION_PROLOGUE(AActor);
PARAM_CLASS (ti, AActor);
PARAM_ANGLE_DEF (angle);
PARAM_BOOL_DEF (useammo);
PARAM_FLOAT_DEF (spawnofs_xy);
PARAM_FLOAT_DEF (spawnheight);
PARAM_INT_DEF (flags);
PARAM_ANGLE_DEF (pitch);
if (!self->player)
return 0;
player_t *player = self->player;
AWeapon *weapon = player->ReadyWeapon;
FTranslatedLineTarget t;
// Only use ammo if called from a weapon
if (useammo && ACTION_CALL_FROM_PSPRITE() && weapon)
{
if (!weapon->DepleteAmmo(weapon->bAltFire, true))
return 0; // out of ammo
}
if (ti)
{
DAngle ang = self->Angles.Yaw - 90;
DVector2 ofs = ang.ToVector(spawnofs_xy);
DAngle shootangle = self->Angles.Yaw;
if (flags & FPF_AIMATANGLE) shootangle += angle;
// Temporarily adjusts the pitch
DAngle saved_player_pitch = self->Angles.Pitch;
self->Angles.Pitch -= pitch;
AActor * misl=P_SpawnPlayerMissile (self, ofs.X, ofs.Y, spawnheight, ti, shootangle, &t, NULL, false, (flags & FPF_NOAUTOAIM) != 0);
self->Angles.Pitch = saved_player_pitch;
// automatic handling of seeker missiles
if (misl)
{
if (flags & FPF_TRANSFERTRANSLATION)
misl->Translation = self->Translation;
if (t.linetarget && !t.unlinked && (misl->flags2 & MF2_SEEKERMISSILE))
misl->tracer = t.linetarget;
if (!(flags & FPF_AIMATANGLE))
{
// This original implementation is to aim straight ahead and then offset
// the angle from the resulting direction.
misl->Angles.Yaw += angle;
misl->VelFromAngle(misl->VelXYToSpeed());
}
}
}
return 0;
}
//==========================================================================
//
// 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(AActor, A_CustomPunch)
{
PARAM_ACTION_PROLOGUE(AActor);
PARAM_INT (damage);
PARAM_BOOL_DEF (norandom);
PARAM_INT_DEF (flags);
PARAM_CLASS_DEF (pufftype, AActor);
PARAM_FLOAT_DEF (range);
PARAM_FLOAT_DEF (lifesteal);
PARAM_INT_DEF (lifestealmax);
PARAM_CLASS_DEF (armorbonustype, ABasicArmorBonus);
PARAM_SOUND_DEF (MeleeSound);
PARAM_SOUND_DEF (MissSound);
if (!self->player)
return 0;
player_t *player = self->player;
AWeapon *weapon = player->ReadyWeapon;
DAngle angle;
DAngle pitch;
FTranslatedLineTarget t;
int actualdamage;
if (!norandom)
damage *= pr_cwpunch() % 8 + 1;
angle = self->Angles.Yaw + pr_cwpunch.Random2() * (5.625 / 256);
if (range == 0) range = MELEERANGE;
pitch = P_AimLineAttack (self, angle, range, &t);
// only use ammo when actually hitting something!
if ((flags & CPF_USEAMMO) && t.linetarget && weapon && ACTION_CALL_FROM_PSPRITE())
{
if (!weapon->DepleteAmmo(weapon->bAltFire, true))
return 0; // out of ammo
}
if (pufftype == NULL)
pufftype = PClass::FindActor(NAME_BulletPuff);
int puffFlags = LAF_ISMELEEATTACK | ((flags & CPF_NORANDOMPUFFZ) ? LAF_NORANDOMPUFFZ : 0);
P_LineAttack (self, angle, range, pitch, damage, NAME_Melee, pufftype, puffFlags, &t, &actualdamage);
if (!t.linetarget)
{
if (MissSound) S_Sound(self, CHAN_WEAPON, MissSound, 1, ATTN_NORM);
}
else
{
if (lifesteal > 0 && !(t.linetarget->flags5 & MF5_DONTDRAIN))
{
if (flags & CPF_STEALARMOR)
{
if (armorbonustype == NULL)
{
armorbonustype = dyn_cast<ABasicArmorBonus::MetaClass>(PClass::FindClass("ArmorBonus"));
}
if (armorbonustype != NULL)
{
assert(armorbonustype->IsDescendantOf(RUNTIME_CLASS(ABasicArmorBonus)));
ABasicArmorBonus *armorbonus = static_cast<ABasicArmorBonus *>(Spawn(armorbonustype));
armorbonus->SaveAmount *= int(actualdamage * lifesteal);
armorbonus->MaxSaveAmount = lifestealmax <= 0 ? armorbonus->MaxSaveAmount : lifestealmax;
armorbonus->flags |= MF_DROPPED;
armorbonus->ClearCounters();
if (!armorbonus->CallTryPickup(self))
{
armorbonus->Destroy ();
}
}
}
else
{
P_GiveBody (self, int(actualdamage * lifesteal), lifestealmax);
}
}
if (weapon != NULL)
{
if (MeleeSound) S_Sound(self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM);
else S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM);
}
if (!(flags & CPF_NOTURN))
{
// turn to face target
self->Angles.Yaw = t.angleFromSource;
}
if (flags & CPF_PULLIN) self->flags |= MF_JUSTATTACKED;
if (flags & CPF_DAGGER) P_DaggerAlert (self, t.linetarget);
}
return 0;
}
//==========================================================================
//
// customizable railgun attack function
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_RailAttack)
{
PARAM_ACTION_PROLOGUE(AActor);
PARAM_INT (damage);
PARAM_INT_DEF (spawnofs_xy);
PARAM_BOOL_DEF (useammo);
PARAM_COLOR_DEF (color1);
PARAM_COLOR_DEF (color2);
PARAM_INT_DEF (flags);
PARAM_FLOAT_DEF (maxdiff);
PARAM_CLASS_DEF (pufftype, AActor);
PARAM_ANGLE_DEF (spread_xy);
PARAM_ANGLE_DEF (spread_z);
PARAM_FLOAT_DEF (range) ;
PARAM_INT_DEF (duration);
PARAM_FLOAT_DEF (sparsity);
PARAM_FLOAT_DEF (driftspeed);
PARAM_CLASS_DEF (spawnclass, AActor);
PARAM_FLOAT_DEF (spawnofs_z);
PARAM_INT_DEF (SpiralOffset);
PARAM_INT_DEF (limit);
if (range == 0) range = 8192;
if (sparsity == 0) sparsity=1.0;
if (self->player == NULL)
return 0;
AWeapon *weapon = self->player->ReadyWeapon;
// only use ammo when actually hitting something!
if (useammo && weapon != NULL && ACTION_CALL_FROM_PSPRITE())
{
if (!weapon->DepleteAmmo(weapon->bAltFire, true))
return 0; // out of ammo
}
if (!(flags & RAF_EXPLICITANGLE))
{
spread_xy = spread_xy * pr_crailgun.Random2() / 255;
spread_z = spread_z * pr_crailgun.Random2() / 255;
}
FRailParams p;
p.source = self;
p.damage = damage;
p.offset_xy = spawnofs_xy;
p.offset_z = spawnofs_z;
p.color1 = color1;
p.color2 = color2;
p.maxdiff = maxdiff;
p.flags = flags;
p.puff = pufftype;
p.angleoffset = spread_xy;
p.pitchoffset = spread_z;
p.distance = range;
p.duration = duration;
p.sparsity = sparsity;
p.drift = driftspeed;
p.spawnclass = spawnclass;
p.SpiralOffset = SpiralOffset;
p.limit = limit;
P_RailAttack(&p);
return 0;
}
//==========================================================================
//
// also for monsters
//
//==========================================================================
enum
{
CRF_DONTAIM = 0,
CRF_AIMPARALLEL = 1,
CRF_AIMDIRECT = 2,
CRF_EXPLICITANGLE = 4,
};
DEFINE_ACTION_FUNCTION(AActor, A_CustomRailgun)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT (damage);
PARAM_INT_DEF (spawnofs_xy)
PARAM_COLOR_DEF (color1)
PARAM_COLOR_DEF (color2)
PARAM_INT_DEF (flags)
PARAM_INT_DEF (aim)
PARAM_FLOAT_DEF (maxdiff)
PARAM_CLASS_DEF (pufftype, AActor)
PARAM_ANGLE_DEF (spread_xy)
PARAM_ANGLE_DEF (spread_z)
PARAM_FLOAT_DEF (range)
PARAM_INT_DEF (duration)
PARAM_FLOAT_DEF (sparsity)
PARAM_FLOAT_DEF (driftspeed)
PARAM_CLASS_DEF (spawnclass, AActor)
PARAM_FLOAT_DEF (spawnofs_z)
PARAM_INT_DEF (SpiralOffset)
PARAM_INT_DEF (limit)
if (range == 0) range = 8192.;
if (sparsity == 0) sparsity = 1;
FTranslatedLineTarget t;
DVector3 savedpos = self->Pos();
DAngle saved_angle = self->Angles.Yaw;
DAngle saved_pitch = self->Angles.Pitch;
if (aim && self->target == NULL)
{
return 0;
}
// [RH] Andy Baker's stealth monsters
if (self->flags & MF_STEALTH)
{
self->visdir = 1;
}
self->flags &= ~MF_AMBUSH;
if (aim)
{
self->Angles.Yaw = self->AngleTo(self->target);
}
self->Angles.Pitch = P_AimLineAttack (self, self->Angles.Yaw, MISSILERANGE, &t, 60., 0, aim ? self->target : NULL);
if (t.linetarget == NULL && aim)
{
// We probably won't hit the target, but aim at it anyway so we don't look stupid.
DVector2 xydiff = self->Vec2To(self->target);
double zdiff = self->target->Center() - self->Center() - self->Floorclip;
self->Angles.Pitch = VecToAngle(xydiff.Length(), zdiff);
}
// Let the aim trail behind the player
if (aim)
{
saved_angle = self->Angles.Yaw = self->AngleTo(self->target, -self->target->Vel.X * 3, -self->target->Vel.Y * 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->SetXY(self->Vec2Offset(
spawnofs_xy * self->Angles.Yaw.Cos(),
spawnofs_xy * self->Angles.Yaw.Sin()));
spawnofs_xy = 0;
self->Angles.Yaw = self->AngleTo(self->target,- self->target->Vel.X * 3, -self->target->Vel.Y * 3);
}
if (self->target->flags & MF_SHADOW)
{
DAngle rnd = pr_crailgun.Random2() * (45. / 256.);
self->Angles.Yaw += rnd;
}
}
if (!(flags & CRF_EXPLICITANGLE))
{
spread_xy = spread_xy * pr_crailgun.Random2() / 255;
spread_z = spread_z * pr_crailgun.Random2() / 255;
}
FRailParams p;
p.source = self;
p.damage = damage;
p.offset_xy = spawnofs_xy;
p.offset_z = spawnofs_z;
p.color1 = color1;
p.color2 = color2;
p.maxdiff = maxdiff;
p.flags = flags;
p.puff = pufftype;
p.angleoffset = spread_xy;
p.pitchoffset = spread_z;
p.distance = range;
p.duration = duration;
p.sparsity = sparsity;
p.drift = driftspeed;
p.spawnclass = spawnclass;
p.SpiralOffset = SpiralOffset;
p.limit = 0;
P_RailAttack(&p);
self->SetXYZ(savedpos);
self->Angles.Yaw = saved_angle;
self->Angles.Pitch = saved_pitch;
return 0;
}
//===========================================================================
//
// DoGiveInventory
//
//===========================================================================
static bool DoGiveInventory(AActor *receiver, bool orresult, VM_ARGS)
{
int paramnum = 0;
PARAM_CLASS (mi, AInventory);
PARAM_INT_DEF (amount)
if (!orresult)
{
PARAM_INT_DEF(setreceiver)
receiver = COPY_AAPTR(receiver, setreceiver);
}
if (receiver == NULL)
{ // If there's nothing to receive it, it's obviously a fail, right?
return false;
}
// Owned inventory items cannot own anything because their Inventory pointer is repurposed for the owner's linked list.
if (receiver->IsKindOf(RUNTIME_CLASS(AInventory)) && static_cast<AInventory*>(receiver)->Owner != nullptr)
{
return false;
}
if (amount <= 0)
{
amount = 1;
}
if (mi)
{
AInventory *item = static_cast<AInventory *>(Spawn(mi));
if (item == NULL)
{
return false;
}
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();
return false;
}
else
{
return true;
}
}
return false;
}
DEFINE_ACTION_FUNCTION(AActor, A_GiveInventory)
{
PARAM_SELF_PROLOGUE(AActor);
ACTION_RETURN_BOOL(DoGiveInventory(self, false, VM_ARGS_NAMES));
}
DEFINE_ACTION_FUNCTION(AActor, A_GiveToTarget)
{
PARAM_SELF_PROLOGUE(AActor);
ACTION_RETURN_BOOL(DoGiveInventory(self->target, false, VM_ARGS_NAMES));
}
DEFINE_ACTION_FUNCTION(AActor, A_GiveToChildren)
{
PARAM_SELF_PROLOGUE(AActor);
TThinkerIterator<AActor> it;
AActor *mo;
int count = 0;
while ((mo = it.Next()))
{
if (mo->master == self)
{
count += DoGiveInventory(mo, true, VM_ARGS_NAMES);
}
}
ACTION_RETURN_INT(count);
}
DEFINE_ACTION_FUNCTION(AActor, A_GiveToSiblings)
{
PARAM_SELF_PROLOGUE(AActor);
TThinkerIterator<AActor> it;
AActor *mo;
int count = 0;
if (self->master != NULL)
{
while ((mo = it.Next()))
{
if (mo->master == self->master && mo != self)
{
count += DoGiveInventory(mo, true, VM_ARGS_NAMES);
}
}
}
ACTION_RETURN_INT(count);
}
//===========================================================================
//
// A_SetInventory
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_SetInventory)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS(itemtype, AInventory);
PARAM_INT(amount);
PARAM_INT_DEF(ptr);
PARAM_BOOL_DEF(beyondMax);
bool res = false;
if (itemtype == nullptr)
{
ACTION_RETURN_BOOL(false);
}
AActor *mobj = COPY_AAPTR(self, ptr);
if (mobj == nullptr)
{
ACTION_RETURN_BOOL(false);
}
AInventory *item = mobj->FindInventory(itemtype);
if (item != nullptr)
{
// A_SetInventory sets the absolute amount.
// Subtract or set the appropriate amount as necessary.
if (amount == item->Amount)
{
// Nothing was changed.
ACTION_RETURN_BOOL(false);
}
else if (amount <= 0)
{
//Remove it all.
res = (mobj->TakeInventory(itemtype, item->Amount, true, false));
ACTION_RETURN_BOOL(res);
}
else if (amount < item->Amount)
{
int amt = abs(item->Amount - amount);
res = (mobj->TakeInventory(itemtype, amt, true, false));
ACTION_RETURN_BOOL(res);
}
else
{
item->Amount = (beyondMax ? amount : clamp(amount, 0, item->MaxAmount));
ACTION_RETURN_BOOL(true);
}
}
else
{
if (amount <= 0)
{
ACTION_RETURN_BOOL(false);
}
item = static_cast<AInventory *>(Spawn(itemtype));
if (item == nullptr)
{
ACTION_RETURN_BOOL(false);
}
else
{
item->Amount = amount;
item->flags |= MF_DROPPED;
item->ItemFlags |= IF_IGNORESKILL;
item->ClearCounters();
if (!item->CallTryPickup(mobj))
{
item->Destroy();
ACTION_RETURN_BOOL(false);
}
ACTION_RETURN_BOOL(true);
}
}
ACTION_RETURN_BOOL(false);
}
//===========================================================================
//
// A_TakeInventory
//
//===========================================================================
enum
{
TIF_NOTAKEINFINITE = 1,
};
bool DoTakeInventory(AActor *receiver, bool orresult, VM_ARGS)
{
int paramnum = 0;
PARAM_CLASS (itemtype, AInventory);
PARAM_INT_DEF (amount);
PARAM_INT_DEF (flags);
if (itemtype == NULL)
{
return false;
}
if (!orresult)
{
PARAM_INT_DEF(setreceiver);
receiver = COPY_AAPTR(receiver, setreceiver);
}
if (receiver == NULL)
{
return false;
}
return receiver->TakeInventory(itemtype, amount, true, (flags & TIF_NOTAKEINFINITE) != 0);
}
DEFINE_ACTION_FUNCTION(AActor, A_TakeInventory)
{
PARAM_SELF_PROLOGUE(AActor);
ACTION_RETURN_BOOL(DoTakeInventory(self, false, VM_ARGS_NAMES));
}
DEFINE_ACTION_FUNCTION(AActor, A_TakeFromTarget)
{
PARAM_SELF_PROLOGUE(AActor);
ACTION_RETURN_BOOL(DoTakeInventory(self->target, false, VM_ARGS_NAMES));
}
DEFINE_ACTION_FUNCTION(AActor, A_TakeFromChildren)
{
PARAM_SELF_PROLOGUE(AActor);
TThinkerIterator<AActor> it;
AActor *mo;
int count = 0;
while ((mo = it.Next()))
{
if (mo->master == self)
{
count += DoTakeInventory(mo, true, VM_ARGS_NAMES);
}
}
ACTION_RETURN_INT(count);
}
DEFINE_ACTION_FUNCTION(AActor, A_TakeFromSiblings)
{
PARAM_SELF_PROLOGUE(AActor);
TThinkerIterator<AActor> it;
AActor *mo;
int count = 0;
if (self->master != NULL)
{
while ((mo = it.Next()))
{
if (mo->master == self->master && mo != self)
{
count += DoTakeInventory(mo, true, VM_ARGS_NAMES);
}
}
}
ACTION_RETURN_INT(count);
}
//===========================================================================
//
// Common code for A_SpawnItem and A_SpawnItemEx
//
//===========================================================================
enum SIX_Flags
{
SIXF_TRANSFERTRANSLATION = 0x00000001,
SIXF_ABSOLUTEPOSITION = 0x00000002,
SIXF_ABSOLUTEANGLE = 0x00000004,
SIXF_ABSOLUTEVELOCITY = 0x00000008,
SIXF_SETMASTER = 0x00000010,
SIXF_NOCHECKPOSITION = 0x00000020,
SIXF_TELEFRAG = 0x00000040,
SIXF_CLIENTSIDE = 0x00000080, // only used by Skulldronum
SIXF_TRANSFERAMBUSHFLAG = 0x00000100,
SIXF_TRANSFERPITCH = 0x00000200,
SIXF_TRANSFERPOINTERS = 0x00000400,
SIXF_USEBLOODCOLOR = 0x00000800,
SIXF_CLEARCALLERTID = 0x00001000,
SIXF_MULTIPLYSPEED = 0x00002000,
SIXF_TRANSFERSCALE = 0x00004000,
SIXF_TRANSFERSPECIAL = 0x00008000,
SIXF_CLEARCALLERSPECIAL = 0x00010000,
SIXF_TRANSFERSTENCILCOL = 0x00020000,
SIXF_TRANSFERALPHA = 0x00040000,
SIXF_TRANSFERRENDERSTYLE = 0x00080000,
SIXF_SETTARGET = 0x00100000,
SIXF_SETTRACER = 0x00200000,
SIXF_NOPOINTERS = 0x00400000,
SIXF_ORIGINATOR = 0x00800000,
SIXF_TRANSFERSPRITEFRAME = 0x01000000,
SIXF_TRANSFERROLL = 0x02000000,
SIXF_ISTARGET = 0x04000000,
SIXF_ISMASTER = 0x08000000,
SIXF_ISTRACER = 0x10000000,
};
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->Angles.Yaw = self->Angles.Yaw;
if (flags & SIXF_TRANSFERPITCH)
{
mo->Angles.Pitch = self->Angles.Pitch;
}
if (!(flags & SIXF_ORIGINATOR))
{
while (originator && originator->isMissile())
{
originator = originator->target;
}
}
if (flags & SIXF_TELEFRAG)
{
P_TeleportMove(mo, mo->Pos(), 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)
{ // don't let it attack you (optional)!
mo->master = originator;
}
if (flags & SIXF_SETTARGET)
{
mo->target = originator;
}
if (flags & SIXF_SETTRACER)
{
mo->tracer = originator;
}
if (flags & SIXF_TRANSFERSCALE)
{
mo->Scale = self->Scale;
}
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;
}
if (flags & SIXF_TRANSFERSPRITEFRAME)
{
mo->sprite = self->sprite;
mo->frame = self->frame;
}
if (flags & SIXF_TRANSFERROLL)
{
mo->Angles.Roll = self->Angles.Roll;
}
if (flags & SIXF_ISTARGET)
{
self->target = mo;
}
if (flags & SIXF_ISMASTER)
{
self->master = mo;
}
if (flags & SIXF_ISTRACER)
{
self->tracer = mo;
}
return true;
}
//===========================================================================
//
// A_SpawnItem
//
// Spawns an item in front of the caller like Heretic's time bomb
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_SpawnItem)
{
PARAM_ACTION_PROLOGUE(AActor);
PARAM_CLASS_DEF (missile, AActor)
PARAM_FLOAT_DEF (distance)
PARAM_FLOAT_DEF (zheight)
PARAM_BOOL_DEF (useammo)
PARAM_BOOL_DEF (transfer_translation)
if (missile == NULL)
{
ACTION_RETURN_BOOL(false);
}
// Don't spawn monsters if this actor has been massacred
if (self->DamageType == NAME_Massacre && (GetDefaultByType(missile)->flags3 & MF3_ISMONSTER))
{
ACTION_RETURN_BOOL(true);
}
if (ACTION_CALL_FROM_PSPRITE())
{
// Used from a weapon, so use some ammo
AWeapon *weapon = self->player->ReadyWeapon;
if (weapon == NULL)
{
ACTION_RETURN_BOOL(true);
}
if (useammo && !weapon->DepleteAmmo(weapon->bAltFire))
{
ACTION_RETURN_BOOL(true);
}
}
AActor *mo = Spawn( missile, self->Vec3Angle(distance, self->Angles.Yaw, -self->Floorclip + self->GetBobOffset() + zheight), ALLOW_REPLACE);
int flags = (transfer_translation ? SIXF_TRANSFERTRANSLATION : 0) + (useammo ? SIXF_SETMASTER : 0);
ACTION_RETURN_BOOL(InitSpawnedItem(self, mo, flags)); // for an inventory item's use state
}
//===========================================================================
//
// A_SpawnItemEx
//
// Enhanced spawning function
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_SpawnItemEx)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS (missile, AActor);
PARAM_FLOAT_DEF (xofs)
PARAM_FLOAT_DEF (yofs)
PARAM_FLOAT_DEF (zofs)
PARAM_FLOAT_DEF (xvel)
PARAM_FLOAT_DEF (yvel)
PARAM_FLOAT_DEF (zvel)
PARAM_ANGLE_DEF (angle)
PARAM_INT_DEF (flags)
PARAM_INT_DEF (chance)
PARAM_INT_DEF (tid)
if (missile == NULL)
{
ACTION_RETURN_BOOL(false);
}
if (chance > 0 && pr_spawnitemex() < chance)
{
ACTION_RETURN_BOOL(true);
}
// Don't spawn monsters if this actor has been massacred
if (self->DamageType == NAME_Massacre && (GetDefaultByType(missile)->flags3 & MF3_ISMONSTER))
{
ACTION_RETURN_BOOL(true);
}
DVector2 pos;
if (!(flags & SIXF_ABSOLUTEANGLE))
{
angle += self->Angles.Yaw;
}
double s = angle.Sin();
double c = angle.Cos();
if (flags & SIXF_ABSOLUTEPOSITION)
{
pos = self->Vec2Offset(xofs, yofs);
}
else
{
// in relative mode negative y values mean 'left' and positive ones mean 'right'
// This is the inverse orientation of the absolute mode!
pos = self->Vec2Offset(xofs * c + yofs * s, xofs * s - yofs*c);
}
if (!(flags & SIXF_ABSOLUTEVELOCITY))
{
// Same orientation issue here!
double newxvel = xvel * c + yvel * s;
yvel = xvel * s - yvel * c;
xvel = newxvel;
}
AActor *mo = Spawn(missile, DVector3(pos, self->Z() - self->Floorclip + self->GetBobOffset() + zofs), ALLOW_REPLACE);
bool res = InitSpawnedItem(self, mo, flags);
if (res)
{
if (tid != 0)
{
assert(mo->tid == 0);
mo->tid = tid;
mo->AddToHash();
}
mo->Vel = {xvel, yvel, zvel};
if (flags & SIXF_MULTIPLYSPEED)
{
mo->Vel *= mo->Speed;
}
mo->Angles.Yaw = angle;
}
ACTION_RETURN_BOOL(res); // for an inventory item's use state
}
//===========================================================================
//
// A_ThrowGrenade
//
// Throws a grenade (like Hexen's fighter flechette)
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_ThrowGrenade)
{
PARAM_ACTION_PROLOGUE(AActor);
PARAM_CLASS (missile, AActor);
PARAM_FLOAT_DEF (zheight)
PARAM_FLOAT_DEF (xyvel)
PARAM_FLOAT_DEF (zvel)
PARAM_BOOL_DEF (useammo)
if (missile == NULL)
{
ACTION_RETURN_BOOL(true);
}
if (ACTION_CALL_FROM_PSPRITE())
{
// Used from a weapon, so use some ammo
AWeapon *weapon = self->player->ReadyWeapon;
if (weapon == NULL)
{
ACTION_RETURN_BOOL(true);
}
if (useammo && !weapon->DepleteAmmo(weapon->bAltFire))
{
ACTION_RETURN_BOOL(true);
}
}
AActor *bo;
bo = Spawn(missile,
self->PosPlusZ(-self->Floorclip + self->GetBobOffset() + zheight + 35 + (self->player? self->player->crouchoffset : 0.)),
ALLOW_REPLACE);
if (bo)
{
P_PlaySpawnSound(bo, self);
if (xyvel != 0)
bo->Speed = xyvel;
bo->Angles.Yaw = self->Angles.Yaw + (((pr_grenade()&7) - 4) * (360./256.));
DAngle pitch = -self->Angles.Pitch;
DAngle angle = bo->Angles.Yaw;
// 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.
double xy_xyscale = bo->Speed * pitch.Cos();
double xy_velz = bo->Speed * pitch.Sin();
double xy_velx = xy_xyscale * angle.Cos();
double xy_vely = xy_xyscale * angle.Sin();
pitch = self->Angles.Pitch;
double z_xyscale = zvel * pitch.Sin();
double z_velz = zvel * pitch.Cos();
double z_velx = z_xyscale * angle.Cos();
double z_vely = z_xyscale * angle.Sin();
bo->Vel.X = xy_velx + z_velx + self->Vel.X / 2;
bo->Vel.Y = xy_vely + z_vely + self->Vel.Y / 2;
bo->Vel.Z = xy_velz + z_velz;
bo->target = self;
P_CheckMissileSpawn (bo, self->radius);
}
else
{
ACTION_RETURN_BOOL(false);
}
ACTION_RETURN_BOOL(true);
}
//===========================================================================
//
// A_Recoil
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_Recoil)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT(xyvel);
self->Thrust(self->Angles.Yaw + 180., xyvel);
return 0;
}
//===========================================================================
//
// A_SelectWeapon
//
//===========================================================================
enum SW_Flags
{
SWF_SELECTPRIORITY = 1,
};
DEFINE_ACTION_FUNCTION(AActor, A_SelectWeapon)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS(cls, AWeapon);
PARAM_INT_DEF(flags);
bool selectPriority = !!(flags & SWF_SELECTPRIORITY);
if ((!selectPriority && cls == NULL) || self->player == NULL)
{
ACTION_RETURN_BOOL(false);
}
AWeapon *weaponitem = static_cast<AWeapon*>(self->FindInventory(cls));
if (weaponitem != NULL && weaponitem->IsKindOf(RUNTIME_CLASS(AWeapon)))
{
if (self->player->ReadyWeapon != weaponitem)
{
self->player->PendingWeapon = weaponitem;
}
ACTION_RETURN_BOOL(true);
}
else if (selectPriority)
{
// [XA] if the named weapon cannot be found (or is a dummy like 'None'),
// select the next highest priority weapon. This is basically
// the same as A_CheckReload minus the ammo check. Handy.
self->player->mo->PickNewWeapon(NULL);
ACTION_RETURN_BOOL(true);
}
else
{
ACTION_RETURN_BOOL(false);
}
}
//===========================================================================
//
// A_Print
//
//===========================================================================
EXTERN_CVAR(Float, con_midtime)
DEFINE_ACTION_FUNCTION(AActor, A_Print)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_STRING (text);
PARAM_FLOAT_DEF (time);
PARAM_NAME_DEF (fontname);
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 = float(time);
}
FString formatted = strbin1(text);
C_MidPrint(font != NULL ? font : SmallFont, formatted.GetChars());
con_midtime = saved;
}
return 0;
}
//===========================================================================
//
// A_PrintBold
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_PrintBold)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_STRING (text);
PARAM_FLOAT_DEF (time);
PARAM_NAME_DEF (fontname);
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 = float(time);
}
FString formatted = strbin1(text);
C_MidPrintBold(font != NULL ? font : SmallFont, formatted.GetChars());
con_midtime = saved;
return 0;
}
//===========================================================================
//
// A_Log
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_Log)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_STRING(text);
if (text[0] == '$') text = GStrings(&text[1]);
FString formatted = strbin1(text);
Printf("%s\n", formatted.GetChars());
return 0;
}
//=========================================================================
//
// A_LogInt
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_LogInt)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(num);
Printf("%d\n", num);
return 0;
}
//=========================================================================
//
// A_LogFloat
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_LogFloat)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT(num);
IGNORE_FORMAT_PRE
Printf("%H\n", num);
IGNORE_FORMAT_POST
return 0;
}
//===========================================================================
//
// A_SetTranslucent
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_SetTranslucent)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT (alpha);
PARAM_INT_DEF (mode);
mode = mode == 0 ? STYLE_Translucent : mode == 2 ? STYLE_Fuzzy : STYLE_Add;
self->RenderStyle.Flags &= ~STYLEF_Alpha1;
self->Alpha = clamp(alpha, 0., 1.);
self->RenderStyle = ERenderStyle(mode);
return 0;
}
//===========================================================================
//
// A_SetRenderStyle
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_SetRenderStyle)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT(alpha);
PARAM_INT_DEF(mode);
self->Alpha = clamp(alpha, 0., 1.);
self->RenderStyle = ERenderStyle(mode);
return 0;
}
//===========================================================================
//
// A_FadeIn
//
// Fades the actor in
//
//===========================================================================
enum FadeFlags
{
FTF_REMOVE = 1 << 0,
FTF_CLAMP = 1 << 1,
};
DEFINE_ACTION_FUNCTION(AActor, A_FadeIn)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT_DEF(reduce);
PARAM_INT_DEF(flags);
if (reduce == 0)
{
reduce = 0.1;
}
self->RenderStyle.Flags &= ~STYLEF_Alpha1;
self->Alpha += reduce;
if (self->Alpha >= 1.)
{
if (flags & FTF_CLAMP)
{
self->Alpha = 1.;
}
if (flags & FTF_REMOVE)
{
P_RemoveThing(self);
}
}
return 0;
}
//===========================================================================
//
// A_FadeOut
//
// fades the actor out and destroys it when done
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_FadeOut)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT_DEF(reduce);
PARAM_INT_DEF(flags);
if (reduce == 0)
{
reduce = 0.1;
}
self->RenderStyle.Flags &= ~STYLEF_Alpha1;
self->Alpha -= reduce;
if (self->Alpha <= 0)
{
if (flags & FTF_CLAMP)
{
self->Alpha = 0;
}
if (flags & FTF_REMOVE)
{
P_RemoveThing(self);
}
}
return 0;
}
//===========================================================================
//
// A_FadeTo
//
// fades the actor to a specified transparency by a specified amount and
// destroys it if so desired
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_FadeTo)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT (target);
PARAM_FLOAT_DEF (amount);
PARAM_INT_DEF (flags);
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)
{
self->Alpha = clamp(self->Alpha, 0., 1.);
}
if (self->Alpha == target && (flags & FTF_REMOVE))
{
P_RemoveThing(self);
}
return 0;
}
//===========================================================================
//
// A_SpawnDebris
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_SpawnDebris)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS (debris, AActor);
PARAM_BOOL_DEF (transfer_translation)
PARAM_FLOAT_DEF (mult_h)
PARAM_FLOAT_DEF (mult_v)
int i;
AActor *mo;
if (debris == NULL)
return 0;
// only positive values make sense here
if (mult_v <= 0) mult_v = 1;
if (mult_h <= 0) mult_h = 1;
for (i = 0; i < GetDefaultByType(debris)->health; i++)
{
double xo = (pr_spawndebris() - 128) / 16.;
double yo = (pr_spawndebris() - 128) / 16.;
double zo = pr_spawndebris()*self->Height / 256 + self->GetBobOffset();
mo = Spawn(debris, self->Vec3Offset(xo, yo, zo), ALLOW_REPLACE);
if (mo)
{
if (transfer_translation)
{
mo->Translation = self->Translation;
}
if (i < mo->GetClass()->NumOwnedStates)
{
mo->SetState (mo->GetClass()->OwnedStates + i);
}
mo->Vel.X = mult_h * pr_spawndebris.Random2() / 64.;
mo->Vel.Y = mult_h * pr_spawndebris.Random2() / 64.;
mo->Vel.Z = mult_v * ((pr_spawndebris() & 7) + 5);
}
}
return 0;
}
//===========================================================================
//
// A_SpawnParticle
//
//===========================================================================
enum SPFflag
{
SPF_FULLBRIGHT = 1,
SPF_RELPOS = 1 << 1,
SPF_RELVEL = 1 << 2,
SPF_RELACCEL = 1 << 3,
SPF_RELANG = 1 << 4,
SPF_NOTIMEFREEZE = 1 << 5,
};
DEFINE_ACTION_FUNCTION(AActor, A_SpawnParticle)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_COLOR (color);
PARAM_INT_DEF (flags)
PARAM_INT_DEF (lifetime)
PARAM_FLOAT_DEF (size)
PARAM_ANGLE_DEF (angle)
PARAM_FLOAT_DEF (xoff)
PARAM_FLOAT_DEF (yoff)
PARAM_FLOAT_DEF (zoff)
PARAM_FLOAT_DEF (xvel)
PARAM_FLOAT_DEF (yvel)
PARAM_FLOAT_DEF (zvel)
PARAM_FLOAT_DEF (accelx)
PARAM_FLOAT_DEF (accely)
PARAM_FLOAT_DEF (accelz)
PARAM_FLOAT_DEF (startalpha)
PARAM_FLOAT_DEF (fadestep)
PARAM_FLOAT_DEF (sizestep)
startalpha = clamp(startalpha, 0., 1.);
if (fadestep > 0) fadestep = clamp(fadestep, 0., 1.);
size = fabs(size);
if (lifetime != 0)
{
if (flags & SPF_RELANG) angle += self->Angles.Yaw;
double s = angle.Sin();
double c = angle.Cos();
DVector3 pos(xoff, yoff, zoff);
DVector3 vel(xvel, yvel, zvel);
DVector3 acc(accelx, accely, accelz);
//[MC] Code ripped right out of A_SpawnItemEx.
if (flags & SPF_RELPOS)
{
// in relative mode negative y values mean 'left' and positive ones mean 'right'
// This is the inverse orientation of the absolute mode!
pos.X = xoff * c + yoff * s;
pos.Y = xoff * s - yoff * c;
}
if (flags & SPF_RELVEL)
{
vel.X = xvel * c + yvel * s;
vel.Y = xvel * s - yvel * c;
}
if (flags & SPF_RELACCEL)
{
acc.X = accelx * c + accely * s;
acc.Y = accelx * s - accely * c;
}
P_SpawnParticle(self->Vec3Offset(pos), vel, acc, color, startalpha, lifetime, size, fadestep, sizestep, flags);
}
return 0;
}
//===========================================================================
//
// A_CheckSight
// jumps if no player can see this actor
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, CheckIfSeen)
{
PARAM_SELF_PROLOGUE(AActor);
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))
{
ACTION_RETURN_BOOL(false);
}
// 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))
{
ACTION_RETURN_BOOL(false);
}
}
}
ACTION_RETURN_BOOL(true);
}
//===========================================================================
//
// 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, bool checksight)
{
if (camera == NULL)
{
return false;
}
// Check distance first, since it's cheaper than checking sight.
DVector2 pos = camera->Vec2To(self);
double dz;
double eyez = camera->Center();
if (eyez > self->Top())
{
dz = self->Top() - eyez;
}
else if (eyez < self->Z())
{
dz = self->Z() - eyez;
}
else
{
dz = 0;
}
double distance = DVector3(pos, twodi? 0. : dz).LengthSquared();
if (distance <= range)
{
// Within range
return true;
}
// Now check LOS.
if (checksight && P_CheckSight(camera, self, SF_IGNOREVISIBILITY))
{ // Visible
return true;
}
return false;
}
DEFINE_ACTION_FUNCTION(AActor, CheckSightOrRange)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT(range);
PARAM_BOOL_DEF(twodi);
range *= range;
for (int i = 0; i < MAXPLAYERS; ++i)
{
if (playeringame[i])
{
// Always check from each player.
if (DoCheckSightOrRange(self, players[i].mo, range, twodi, true))
{
ACTION_RETURN_BOOL(false);
}
// 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, true))
{
ACTION_RETURN_BOOL(false);
}
}
}
ACTION_RETURN_BOOL(true);
}
DEFINE_ACTION_FUNCTION(AActor, CheckRange)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT(range);
PARAM_BOOL_DEF(twodi);
range *= range;
for (int i = 0; i < MAXPLAYERS; ++i)
{
if (playeringame[i])
{
// Always check from each player.
if (DoCheckSightOrRange(self, players[i].mo, range, twodi, false))
{
ACTION_RETURN_BOOL(false);
}
// 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, false))
{
ACTION_RETURN_BOOL(false);
}
}
}
ACTION_RETURN_BOOL(true);
}
//===========================================================================
//
// Inventory drop
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_DropInventory)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS(drop, AInventory);
if (drop)
{
AInventory *inv = self->FindInventory(drop);
if (inv)
{
self->DropInventory(inv);
}
}
return 0;
}
//===========================================================================
//
// A_SetBlend
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_SetBlend)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_COLOR (color);
PARAM_FLOAT (alpha);
PARAM_INT (tics);
PARAM_COLOR_DEF (color2);
if (color == MAKEARGB(255,255,255,255))
color = 0;
if (color2 == MAKEARGB(255,255,255,255))
color2 = 0;
if (color2.a == 0)
color2 = color;
new DFlashFader(color.r/255.f, color.g/255.f, color.b/255.f, float(alpha),
color2.r/255.f, color2.g/255.f, color2.b/255.f, 0,
float(tics)/TICRATE, self);
return 0;
}
//===========================================================================
//
// A_CountdownArg
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_CountdownArg)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(cnt);
PARAM_STATE_DEF(state)
if (cnt<0 || cnt >= 5) return 0;
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
{
if (state == nullptr) state = self->FindState(NAME_Death);
self->SetState(state);
}
}
return 0;
}
//============================================================================
//
// A_Burst
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_Burst)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS(chunk, AActor);
int i, numChunks;
AActor * mo;
if (chunk == NULL)
{
return 0;
}
self->Vel.Zero();
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<int> (4, int(self->radius * self->Height)/32);
i = (pr_burst.Random2()) % (numChunks/4);
for (i = MAX (24, numChunks + i); i >= 0; i--)
{
double xo = (pr_burst() - 128) * self->radius / 128;
double yo = (pr_burst() - 128) * self->radius / 128;
double zo = (pr_burst() * self->Height / 255);
mo = Spawn(chunk, self->Vec3Offset(xo, yo, zo), ALLOW_REPLACE);
if (mo)
{
mo->Vel.Z = 4 * (mo->Z() - self->Z()) / self->Height;
mo->Vel.X = pr_burst.Random2() / 128.;
mo->Vel.Y = pr_burst.Random2() / 128.;
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)
{
A_BossDeath(self);
}
A_Unblock(self, true);
self->Destroy ();
return 0;
}
//===========================================================================
//
// A_Stop
// resets all velocity of the actor to 0
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_Stop)
{
PARAM_SELF_PROLOGUE(AActor);
self->Vel.Zero();
if (self->player && self->player->mo == self && !(self->player->cheats & CF_PREDICTING))
{
self->player->mo->PlayIdle();
self->player->Vel.Zero();
}
return 0;
}
static void CheckStopped(AActor *self)
{
if (self->player != NULL &&
self->player->mo == self &&
!(self->player->cheats & CF_PREDICTING) && !self->Vel.isZero())
{
self->player->mo->PlayIdle();
self->player->Vel.Zero();
}
}
//===========================================================================
//
// A_Respawn
//
//===========================================================================
DECLARE_ACTION(A_RestoreSpecialPosition)
enum RS_Flags
{
RSF_FOG=1,
RSF_KEEPTARGET=2,
RSF_TELEFRAG=4,
};
DEFINE_ACTION_FUNCTION(AActor, A_Respawn)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF(flags);
bool oktorespawn = false;
DVector3 pos = self->Pos();
self->flags |= MF_SOLID;
self->Height = self->GetDefault()->Height;
self->radius = self->GetDefault()->radius;
CALL_ACTION(A_RestoreSpecialPosition, self);
if (flags & RSF_TELEFRAG)
{
// [KS] DIE DIE DIE DIE erm *ahem* =)
oktorespawn = P_TeleportMove(self, self->Pos(), true, false);
}
else
{
oktorespawn = P_CheckPosition(self, self->Pos(), 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, pos, true, true);
P_SpawnTeleportFog(self, self->Pos(), false, true);
}
if (self->CountsAsKill())
{
level.total_monsters++;
}
}
else
{
self->flags &= ~MF_SOLID;
}
return 0;
}
//==========================================================================
//
// A_PlayerSkinCheck
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, PlayerSkinCheck)
{
PARAM_SELF_PROLOGUE(AActor);
ACTION_RETURN_BOOL(self->player != NULL &&
skins[self->player->userinfo.GetSkin()].othergame);
}
// [KS] *** Start of my modifications ***
//==========================================================================
//
// 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(AActor, CheckLOF)
{
// Check line of fire
/*
Not accounted for / I don't know how it works: FLOORCLIP
*/
AActor *target;
DVector3 pos;
DVector3 vel;
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF (flags)
PARAM_FLOAT_DEF (range)
PARAM_FLOAT_DEF (minrange)
PARAM_ANGLE_DEF (angle)
PARAM_ANGLE_DEF (pitch)
PARAM_FLOAT_DEF (offsetheight)
PARAM_FLOAT_DEF (offsetwidth)
PARAM_INT_DEF (ptr_target)
PARAM_FLOAT_DEF (offsetforward)
DAngle ang;
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 *= self->player->mo->Height * self->player->crouchfactor;
}
else
{
offsetheight *= self->Height;
}
}
if (flags & CLOFF_MUL_WIDTH)
{
offsetforward *= self->radius;
offsetwidth *= self->radius;
}
pos = self->PosPlusZ(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
pos.Z += self->Height *0.5;
if (self->player != NULL)
{
pos.Z += self->player->mo->AttackZOffset * self->player->crouchfactor;
}
else
{
pos.Z += 8;
}
}
if (target)
{
if (range > 0 && !(flags & CLOFF_CHECKPARTIAL))
{
double distance = self->Distance3D(target);
if (distance > range)
{
ACTION_RETURN_BOOL(false);
}
}
if (flags & CLOFF_NOAIM_HORZ)
{
ang = self->Angles.Yaw;
}
else ang = self->AngleTo (target);
angle += ang;
double s = ang.Sin();
double c = ang.Cos();
DVector2 xy = self->Vec2Offset(offsetforward * c + offsetwidth * s, offsetforward * s - offsetwidth * c);
pos.X = xy.X;
pos.Y = xy.Y;
double xydist = self->Distance2D(target);
if (flags & CLOFF_NOAIM_VERT)
{
pitch += self->Angles.Pitch;
}
else if (flags & CLOFF_AIM_VERT_NOOFFSET)
{
pitch -= VecToAngle(xydist, target->Center() - pos.Z + offsetheight);
}
else
{
pitch -= VecToAngle(xydist, target->Center() - pos.Z);
}
}
else if (flags & CLOFF_ALLOWNULL)
{
angle += self->Angles.Yaw;
pitch += self->Angles.Pitch;
double s = angle.Sin();
double c = angle.Cos();
DVector2 xy = self->Vec2Offset(offsetforward * c + offsetwidth * s, offsetforward * s - offsetwidth * c);
pos.X = xy.X;
pos.Y = xy.Y;
}
else
{
ACTION_RETURN_BOOL(false);
}
double cp = pitch.Cos();
vel = { cp * angle.Cos(), cp * angle.Sin(), -pitch.Sin() };
/* Variable set:
jump, flags, target
pos (trace point of origin)
vel (trace unit vector)
range
*/
sector_t *sec = P_PointInSector(pos);
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(pos, sec, vel, range, ActorFlags::FromInt(0xFFFFFFFF), ML_BLOCKEVERYTHING, self, trace, TRACE_PortalRestrict,
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)
{
ACTION_RETURN_BOOL(false);
}
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_RETURN_BOOL(true);
}
ACTION_RETURN_BOOL(false);
}
//==========================================================================
//
// 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 << 0,
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(AActor, CheckIfTargetInLOS)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_ANGLE_DEF (fov)
PARAM_INT_DEF (flags)
PARAM_FLOAT_DEF (dist_max)
PARAM_FLOAT_DEF (dist_close)
AActor *target, *viewport;
FTranslatedLineTarget t;
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 == NULL)
{ // [KS] Let's not call P_CheckSight unnecessarily in this case.
ACTION_RETURN_BOOL(false);
}
if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0))
{
ACTION_RETURN_BOOL(false);
}
doCheckSight = !(flags & JLOSF_NOSIGHT);
}
else
{
// Does the player aim at something that can be shot?
P_AimLineAttack(self, self->Angles.Yaw, MISSILERANGE, &t, (flags & JLOSF_NOAUTOAIM) ? 0.5 : 0., ALF_PORTALRESTRICT);
if (!t.linetarget)
{
ACTION_RETURN_BOOL(false);
}
target = t.linetarget;
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))
{
ACTION_RETURN_BOOL(false);
}
// [FDARI] If actors share team, don't jump
if ((flags & JLOSF_ALLYNOJUMP) && self->IsFriend(target))
{
ACTION_RETURN_BOOL(false);
}
double distance = self->Distance3D(target);
if (dist_max && (distance > dist_max))
{
ACTION_RETURN_BOOL(false);
}
if (dist_close && (distance < dist_close))
{
if (flags & JLOSF_CLOSENOJUMP)
{
ACTION_RETURN_BOOL(false);
}
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))
{
ACTION_RETURN_BOOL(false);
}
if (flags & JLOSF_FLIPFOV)
{
if (viewport == self) { viewport = target; target = self; }
else { target = viewport; viewport = self; }
}
fov = MIN<DAngle>(fov, 360.);
if (fov > 0)
{
DAngle an = absangle(viewport->AngleTo(target), viewport->Angles.Yaw);
if (an > (fov / 2))
{
ACTION_RETURN_BOOL(false); // [KS] Outside of FOV - return
}
}
ACTION_RETURN_BOOL(true);
}
//==========================================================================
//
// A_JumpIfInTargetLOS (state label, optional fixed fov, optional int flags
// optional fixed dist_max, optional fixed dist_close)
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, CheckIfInTargetLOS)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_ANGLE_DEF (fov)
PARAM_INT_DEF (flags)
PARAM_FLOAT_DEF (dist_max)
PARAM_FLOAT_DEF (dist_close)
AActor *target;
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 == NULL)
{ // [KS] Let's not call P_CheckSight unnecessarily in this case.
ACTION_RETURN_BOOL(false);
}
if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0))
{
ACTION_RETURN_BOOL(false);
}
double distance = self->Distance3D(target);
if (dist_max && (distance > dist_max))
{
ACTION_RETURN_BOOL(false);
}
bool doCheckSight = !(flags & JLOSF_NOSIGHT);
if (dist_close && (distance < dist_close))
{
if (flags & JLOSF_CLOSENOJUMP)
{
ACTION_RETURN_BOOL(false);
}
if (flags & JLOSF_CLOSENOFOV)
fov = 0.;
if (flags & JLOSF_CLOSENOSIGHT)
doCheckSight = false;
}
if (fov > 0 && (fov < 360.))
{
DAngle an = absangle(target->AngleTo(self), target->Angles.Yaw);
if (an > (fov / 2))
{
ACTION_RETURN_BOOL(false); // [KS] Outside of FOV - return
}
}
if (doCheckSight && !P_CheckSight (target, self, SF_IGNOREVISIBILITY))
{
ACTION_RETURN_BOOL(false);
}
ACTION_RETURN_BOOL(true);
}
//===========================================================================
//
// Modified code pointer from Skulltag
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_CheckForReload)
{
PARAM_ACTION_PROLOGUE(AActor);
if ( self->player == NULL || self->player->ReadyWeapon == NULL )
{
ACTION_RETURN_STATE(NULL);
}
PARAM_INT (count);
PARAM_STATE_ACTION (jump);
PARAM_BOOL_DEF (dontincrement);
if (numret > 0)
{
ret->SetPointer(NULL, ATAG_STATE);
numret = 1;
}
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.
if (numret != 0)
{
ret->SetPointer(jump, ATAG_STATE);
}
}
else
{
// We need to reload. However, don't reload if we're out of ammo.
weapon->CheckAmmo(false, false);
}
if (!dontincrement)
{
weapon->ReloadCounter = ReloadCounter;
}
return numret;
}
//===========================================================================
//
// Resets the counter for the above function
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_ResetReloadCounter)
{
PARAM_ACTION_PROLOGUE(AActor);
if (self->player == NULL || self->player->ReadyWeapon == NULL)
return 0;
AWeapon *weapon = self->player->ReadyWeapon;
weapon->ReloadCounter = 0;
return 0;
}
//===========================================================================
//
// A_ChangeFlag
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_ChangeFlag)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_STRING (flagname);
PARAM_BOOL (value);
ModActorFlag(self, flagname, value);
return 0;
}
//===========================================================================
//
// A_CheckFlag
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, CheckFlag)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_STRING (flagname);
PARAM_INT_DEF (checkpointer);
AActor *owner = COPY_AAPTR(self, checkpointer);
ACTION_RETURN_BOOL(owner != nullptr && CheckActorFlag(owner, flagname));
}
DEFINE_ACTION_FUNCTION(AActor, A_ChangeCountFlags)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF(kill);
PARAM_INT_DEF(item);
PARAM_INT_DEF(secret);
if (self->CountsAsKill() && self->health > 0) --level.total_monsters;
if (self->flags & MF_COUNTITEM) --level.total_items;
if (self->flags5 & MF5_COUNTSECRET) --level.total_secrets;
if (kill != -1)
{
if (kill == 0) self->flags &= ~MF_COUNTKILL;
else self->flags |= MF_COUNTKILL;
}
if (item != -1)
{
if (item == 0) self->flags &= ~MF_COUNTITEM;
else self->flags |= MF_COUNTITEM;
}
if (secret != -1)
{
if (secret == 0) self->flags5 &= ~MF5_COUNTSECRET;
else self->flags5 |= MF5_COUNTSECRET;
}
if (self->CountsAsKill() && self->health > 0) ++level.total_monsters;
if (self->flags & MF_COUNTITEM) ++level.total_items;
if (self->flags5 & MF5_COUNTSECRET) ++level.total_secrets;
return 0;
}
//===========================================================================
//
// A_RaiseMaster
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_RaiseMaster)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_BOOL_DEF(copy);
if (self->master != NULL)
{
P_Thing_Raise(self->master, copy ? self : NULL);
}
return 0;
}
//===========================================================================
//
// A_RaiseChildren
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_RaiseChildren)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_BOOL_DEF(copy);
TThinkerIterator<AActor> it;
AActor *mo;
while ((mo = it.Next()) != NULL)
{
if (mo->master == self)
{
P_Thing_Raise(mo, copy ? self : NULL);
}
}
return 0;
}
//===========================================================================
//
// A_RaiseSiblings
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_RaiseSiblings)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_BOOL_DEF(copy);
TThinkerIterator<AActor> it;
AActor *mo;
if (self->master != NULL)
{
while ((mo = it.Next()) != NULL)
{
if (mo->master == self->master && mo != self)
{
P_Thing_Raise(mo, copy ? self : NULL);
}
}
}
return 0;
}
//===========================================================================
//
// A_MonsterRefire
//
// Keep firing unless target got out of sight
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_MonsterRefire)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT (prob);
PARAM_STATE (jump);
A_FaceTarget(self);
if (pr_monsterrefire() < prob)
{
ACTION_RETURN_STATE(NULL);
}
if (self->target == NULL
|| P_HitFriend (self)
|| self->target->health <= 0
|| !P_CheckSight (self, self->target, SF_SEEPASTBLOCKEVERYTHING|SF_SEEPASTSHOOTABLELINES) )
{
ACTION_RETURN_STATE(jump);
}
ACTION_RETURN_STATE(NULL);
}
//===========================================================================
//
// A_SetAngle
//
// Set actor's angle (in degrees).
//
//===========================================================================
enum
{
SPF_FORCECLAMP = 1, // players always clamp
SPF_INTERPOLATE = 2,
};
DEFINE_ACTION_FUNCTION(AActor, A_SetAngle)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT_DEF(angle);
PARAM_INT_DEF(flags);
PARAM_INT_DEF(ptr);
AActor *ref = COPY_AAPTR(self, ptr);
if (ref != NULL)
{
ref->SetAngle(angle, !!(flags & SPF_INTERPOLATE));
}
return 0;
}
//===========================================================================
//
// A_SetPitch
//
// Set actor's pitch (in degrees).
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_SetPitch)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT(pitch);
PARAM_INT_DEF(flags);
PARAM_INT_DEF(ptr);
AActor *ref = COPY_AAPTR(self, ptr);
if (ref != NULL)
{
ref->SetPitch(pitch, !!(flags & SPF_INTERPOLATE), !!(flags & SPF_FORCECLAMP));
}
return 0;
}
//===========================================================================
//
// [Nash] A_SetRoll
//
// Set actor's roll (in degrees).
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_SetRoll)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT (roll);
PARAM_INT_DEF (flags);
PARAM_INT_DEF (ptr) ;
AActor *ref = COPY_AAPTR(self, ptr);
if (ref != NULL)
{
ref->SetRoll(roll, !!(flags & SPF_INTERPOLATE));
}
return 0;
}
//===========================================================================
//
// A_ScaleVelocity
//
// Scale actor's velocity.
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_ScaleVelocity)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT(scale);
PARAM_INT_DEF(ptr);
AActor *ref = COPY_AAPTR(self, ptr);
if (ref == NULL)
{
return 0;
}
bool was_moving = !ref->Vel.isZero();
ref->Vel *= 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);
}
return 0;
}
//===========================================================================
//
// A_ChangeVelocity
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_ChangeVelocity)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT_DEF (x)
PARAM_FLOAT_DEF (y)
PARAM_FLOAT_DEF (z)
PARAM_INT_DEF (flags)
PARAM_INT_DEF (ptr)
AActor *ref = COPY_AAPTR(self, ptr);
if (ref == NULL)
{
return 0;
}
INTBOOL was_moving = !ref->Vel.isZero();
DVector3 vel(x, y, z);
double sina = ref->Angles.Yaw.Sin();
double cosa = ref->Angles.Yaw.Cos();
if (flags & 1) // relative axes - make x, y relative to actor's current angle
{
vel.X = x*cosa - y*sina;
vel.Y = x*sina + y*cosa;
}
if (flags & 2) // discard old velocity - replace old velocity with new velocity
{
ref->Vel = vel;
}
else // add new velocity to old velocity
{
ref->Vel += vel;
}
if (was_moving)
{
CheckStopped(ref);
}
return 0;
}
//===========================================================================
//
// A_SetUserVar
//
//===========================================================================
static PField *GetVar(DObject *self, FName varname)
{
PField *var = dyn_cast<PField>(self->GetClass()->Symbols.FindSymbol(varname, true));
if (var == NULL || (var->Flags & (VARF_Native | VARF_Private | VARF_Protected | VARF_Static)) || !var->Type->IsKindOf(RUNTIME_CLASS(PBasicType)))
{
Printf("%s is not a user variable in class %s\n", varname.GetChars(),
self->GetClass()->TypeName.GetChars());
return nullptr;
}
return var;
}
DEFINE_ACTION_FUNCTION(AActor, A_SetUserVar)
{
PARAM_SELF_PROLOGUE(DObject);
PARAM_NAME (varname);
PARAM_INT (value);
// Set the value of the specified user variable.
PField *var = GetVar(self, varname);
if (var != nullptr)
{
var->Type->SetValue(reinterpret_cast<BYTE *>(self) + var->Offset, value);
}
return 0;
}
DEFINE_ACTION_FUNCTION(AActor, A_SetUserVarFloat)
{
PARAM_SELF_PROLOGUE(DObject);
PARAM_NAME (varname);
PARAM_FLOAT (value);
// Set the value of the specified user variable.
PField *var = GetVar(self, varname);
if (var != nullptr)
{
var->Type->SetValue(reinterpret_cast<BYTE *>(self) + var->Offset, value);
}
return 0;
}
//===========================================================================
//
// A_SetUserArray
//
//===========================================================================
static PField *GetArrayVar(DObject *self, FName varname, int pos)
{
PField *var = dyn_cast<PField>(self->GetClass()->Symbols.FindSymbol(varname, true));
if (var == NULL || (var->Flags & (VARF_Native | VARF_Private | VARF_Protected | VARF_Static)) ||
!var->Type->IsKindOf(RUNTIME_CLASS(PArray)) ||
!static_cast<PArray *>(var->Type)->ElementType->IsKindOf(RUNTIME_CLASS(PBasicType)))
{
Printf("%s is not a user array in class %s\n", varname.GetChars(),
self->GetClass()->TypeName.GetChars());
return nullptr;
}
if ((unsigned)pos >= static_cast<PArray *>(var->Type)->ElementCount)
{
Printf("%d is out of bounds in array %s in class %s\n", pos, varname.GetChars(),
self->GetClass()->TypeName.GetChars());
return nullptr;
}
return var;
}
DEFINE_ACTION_FUNCTION(AActor, A_SetUserArray)
{
PARAM_SELF_PROLOGUE(DObject);
PARAM_NAME (varname);
PARAM_INT (pos);
PARAM_INT (value);
// Set the value of the specified user array at index pos.
PField *var = GetArrayVar(self, varname, pos);
if (var != nullptr)
{
PArray *arraytype = static_cast<PArray *>(var->Type);
arraytype->ElementType->SetValue(reinterpret_cast<BYTE *>(self) + var->Offset + arraytype->ElementSize * pos, value);
}
return 0;
}
DEFINE_ACTION_FUNCTION(AActor, A_SetUserArrayFloat)
{
PARAM_SELF_PROLOGUE(DObject);
PARAM_NAME (varname);
PARAM_INT (pos);
PARAM_FLOAT (value);
// Set the value of the specified user array at index pos.
PField *var = GetArrayVar(self, varname, pos);
if (var != nullptr)
{
PArray *arraytype = static_cast<PArray *>(var->Type);
arraytype->ElementType->SetValue(reinterpret_cast<BYTE *>(self) + var->Offset + arraytype->ElementSize * pos, value);
}
return 0;
}
//===========================================================================
//
// A_Teleport([state teleportstate, [class targettype,
// [class fogtype, [int flags, [fixed mindist,
// [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.
TF_OVERRIDE = 0x00000400, // Ignore NOTELEPORT.
TF_SENSITIVEZ = 0x00000800, // Fail if the actor wouldn't fit in the position (for Z).
};
DEFINE_ACTION_FUNCTION(AActor, A_Teleport)
{
PARAM_ACTION_PROLOGUE(AActor);
PARAM_STATE_DEF (teleport_state)
PARAM_CLASS_DEF (target_type, ASpecialSpot)
PARAM_CLASS_DEF (fog_type, AActor)
PARAM_INT_DEF (flags)
PARAM_FLOAT_DEF (mindist)
PARAM_FLOAT_DEF (maxdist)
PARAM_INT_DEF (ptr)
AActor *ref = COPY_AAPTR(self, ptr);
// A_Teleport and A_Warp were the only codepointers that can state jump
// *AND* have a meaningful inventory state chain result. Grrr.
if (numret > 1)
{
ret[1].SetInt(false);
numret = 2;
}
if (numret > 0)
{
ret[0].SetPointer(NULL, ATAG_STATE);
}
if (!ref)
{
return numret;
}
if ((ref->flags2 & MF2_NOTELEPORT) && !(flags & TF_OVERRIDE))
{
return numret;
}
// 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 = ref->health / ((ref->SpawnHealth()/8 == 0) ? 1 : ref->SpawnHealth()/8);
if (chanceindex >= countof(chance))
{
chanceindex = countof(chance) - 1;
}
if (pr_teleport() >= chance[chanceindex])
{
return numret;
}
}
DSpotState *state = DSpotState::GetSpotState();
if (state == NULL)
{
return numret;
}
if (target_type == NULL)
{
target_type = PClass::FindActor("BossSpot");
}
AActor *spot = state->GetSpotWithMinMaxDistance(target_type, ref->X(), ref->Y(), mindist, maxdist);
if (spot == NULL)
{
return numret;
}
// [MC] By default, the function adjusts the actor's Z if it's below the floor or above the ceiling.
// This can be an issue as actors designed to maintain specific z positions wind up teleporting
// anyway when they should not, such as a floor rising above or ceiling lowering below the position
// of the spot.
if (flags & TF_SENSITIVEZ)
{
double posz = (flags & TF_USESPOTZ) ? spot->Z() : spot->floorz;
if ((posz + ref->Height > spot->ceilingz) || (posz < spot->floorz))
{
return numret;
}
}
DVector3 prev = ref->Pos();
double aboveFloor = spot->Z() - spot->floorz;
double finalz = spot->floorz + aboveFloor;
if (spot->Top() > spot->ceilingz)
finalz = spot->ceilingz - ref->Height;
else if (spot->Z() < spot->floorz)
finalz = spot->floorz;
DVector3 tpos = spot->PosAtZ(finalz);
//Take precedence and cooperate with telefragging first.
bool tele_result = P_TeleportMove(ref, tpos, !!(flags & TF_TELEFRAG));
if (!tele_result && (flags & TF_FORCED))
{
//If for some reason the original move didn't work, regardless of telefrag, force it to move.
ref->SetOrigin(tpos, false);
tele_result = true;
}
AActor *fog1 = NULL, *fog2 = NULL;
if (tele_result)
{
//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 (fog_type || (flags & TF_USEACTORFOG))
{
if (!(flags & TF_NOSRCFOG))
{
if (flags & TF_USEACTORFOG)
P_SpawnTeleportFog(ref, prev, true, true);
else
{
fog1 = Spawn(fog_type, prev, ALLOW_REPLACE);
if (fog1 != NULL)
fog1->target = ref;
}
}
if (!(flags & TF_NODESTFOG))
{
if (flags & TF_USEACTORFOG)
P_SpawnTeleportFog(ref, ref->Pos(), false, true);
else
{
fog2 = Spawn(fog_type, ref->Pos(), ALLOW_REPLACE);
if (fog2 != NULL)
fog2->target = ref;
}
}
}
ref->SetZ((flags & TF_USESPOTZ) ? spot->Z() : ref->floorz, false);
if (!(flags & TF_KEEPANGLE))
ref->Angles.Yaw = spot->Angles.Yaw;
if (!(flags & TF_KEEPVELOCITY)) ref->Vel.Zero();
if (!(flags & TF_NOJUMP)) //The state jump should only happen with the calling actor.
{
if (teleport_state == NULL)
{
// Default to Teleport.
teleport_state = self->FindState("Teleport");
// If still nothing, then return.
if (teleport_state == NULL)
{
return numret;
}
}
if (numret > 0)
{
ret[0].SetPointer(teleport_state, ATAG_STATE);
}
return numret;
}
}
if (numret > 1)
{
ret[1].SetInt(tele_result);
}
return numret;
}
//===========================================================================
//
// A_Quake
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_Quake)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT (intensity);
PARAM_INT (duration);
PARAM_INT (damrad);
PARAM_INT (tremrad);
PARAM_SOUND_DEF (sound);
P_StartQuake(self, 0, intensity, duration, damrad, tremrad, sound);
return 0;
}
//===========================================================================
//
// A_QuakeEx
//
// Extended version of A_Quake. Takes individual axis into account and can
// take flags.
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_QuakeEx)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(intensityX);
PARAM_INT(intensityY);
PARAM_INT(intensityZ);
PARAM_INT(duration);
PARAM_INT(damrad);
PARAM_INT(tremrad);
PARAM_SOUND_DEF(sound);
PARAM_INT_DEF(flags);
PARAM_FLOAT_DEF(mulWaveX);
PARAM_FLOAT_DEF(mulWaveY);
PARAM_FLOAT_DEF(mulWaveZ);
PARAM_INT_DEF(falloff);
PARAM_INT_DEF(highpoint);
PARAM_FLOAT_DEF(rollIntensity);
PARAM_FLOAT_DEF(rollWave);
P_StartQuakeXYZ(self, 0, intensityX, intensityY, intensityZ, duration, damrad, tremrad, sound, flags, mulWaveX, mulWaveY, mulWaveZ, falloff, highpoint,
rollIntensity, rollWave);
return 0;
}
//===========================================================================
//
// A_Weave
//
//===========================================================================
void A_Weave(AActor *self, int xyspeed, int zspeed, double xydist, double zdist)
{
DVector2 newpos;
int weaveXY, weaveZ;
DAngle angle;
double dist;
weaveXY = self->WeaveIndexXY & 63;
weaveZ = self->WeaveIndexZ & 63;
angle = self->Angles.Yaw + 90;
if (xydist != 0 && xyspeed != 0)
{
dist = BobSin(weaveXY) * xydist;
newpos = self->Pos().XY() - angle.ToVector(dist);
weaveXY = (weaveXY + xyspeed) & 63;
dist = BobSin(weaveXY) * xydist;
newpos += angle.ToVector(dist);
if (!(self->flags5 & MF5_NOINTERACTION))
{
P_TryMove (self, newpos, true);
}
else
{
self->UnlinkFromWorld ();
self->flags |= MF_NOBLOCKMAP;
// We need to do portal offsetting here explicitly, because SetXY cannot do that.
newpos -= self->Pos().XY();
self->SetXY(self->Vec2Offset(newpos.X, newpos.Y));
self->LinkToWorld ();
}
self->WeaveIndexXY = weaveXY;
}
if (zdist != 0 && zspeed != 0)
{
self->AddZ(-BobSin(weaveZ) * zdist);
weaveZ = (weaveZ + zspeed) & 63;
self->AddZ(BobSin(weaveZ) * zdist);
self->WeaveIndexZ = weaveZ;
}
}
DEFINE_ACTION_FUNCTION(AActor, A_Weave)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT (xspeed);
PARAM_INT (yspeed);
PARAM_FLOAT (xdist);
PARAM_FLOAT (ydist);
A_Weave(self, xspeed, yspeed, xdist, ydist);
return 0;
}
//===========================================================================
//
// A_LineEffect
//
// This allows linedef effects to be activated inside deh frames.
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_LineEffect)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF(special);
PARAM_INT_DEF(tag);
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_RETURN_BOOL(res);
}
//==========================================================================
//
// A Wolf3D-style attack codepointer
//
//==========================================================================
enum WolfAttackFlags
{
WAF_NORANDOM = 1,
WAF_USEPUFF = 2,
};
DEFINE_ACTION_FUNCTION(AActor, A_WolfAttack)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF (flags)
PARAM_SOUND_DEF (sound)
PARAM_FLOAT_DEF (snipe)
PARAM_INT_DEF (maxdamage)
PARAM_INT_DEF (blocksize)
PARAM_INT_DEF (pointblank)
PARAM_INT_DEF (longrange)
PARAM_FLOAT_DEF (runspeed)
PARAM_CLASS_DEF (pufftype, AActor)
if (!self->target)
return 0;
// Enemy can't see target
if (!P_CheckSight(self, self->target))
return 0;
A_FaceTarget (self);
// Target can dodge if it can see enemy
DAngle angle = absangle(self->target->Angles.Yaw, self->target->AngleTo(self));
bool dodge = (P_CheckSight(self->target, self) && angle < 30. * 256. / 360.); // 30 byteangles ~ 21<32>
// Distance check is simplistic
DVector2 vec = self->Vec2To(self->target);
double dx = fabs (vec.X);
double dy = fabs (vec.Y);
double dist = dx > dy ? dx : dy;
// Some enemies are more precise
dist *= snipe;
// Convert distance into integer number of blocks
int idist = int(dist / blocksize);
// Now for the speed accuracy thingie
double speed = self->target->Vel.LengthSquared();
int hitchance = speed < runspeed ? 256 : 160;
// Distance accuracy (factoring dodge)
hitchance -= idist * (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
DAngle angle = self->target->AngleTo(self);
DVector3 BloodPos = self->target->Vec3Angle(self->target->radius, angle, self->target->Height/2);
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, BloodPos, angle, 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(BloodPos, angle, newdam > 0 ? newdam : damage, self->target);
P_TraceBleed(newdam > 0 ? newdam : damage, self->target, self);
}
}
}
// And finally, let's play the sound
S_Sound (self, CHAN_WEAPON, sound, 1, ATTN_NORM);
return 0;
}
//==========================================================================
//
// A_Warp
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_Warp)
{
PARAM_ACTION_PROLOGUE(AActor);
PARAM_INT(destination_selector);
PARAM_FLOAT_DEF(xofs)
PARAM_FLOAT_DEF(yofs)
PARAM_FLOAT_DEF(zofs)
PARAM_ANGLE_DEF(angle)
PARAM_INT_DEF(flags)
PARAM_STATE_DEF(success_state)
PARAM_FLOAT_DEF(heightoffset)
PARAM_FLOAT_DEF(radiusoffset)
PARAM_ANGLE_DEF(pitch)
AActor *reference;
// A_Teleport and A_Warp were the only codepointers that can state jump
// *AND* have a meaningful inventory state chain result. Grrr.
if (numret > 1)
{
ret[1].SetInt(false);
numret = 2;
}
if (numret > 0)
{
ret[0].SetPointer(NULL, ATAG_STATE);
}
if ((flags & WARPF_USETID))
{
reference = SingleActorFromTID(destination_selector, self);
}
else
{
reference = COPY_AAPTR(self, destination_selector);
}
//If there is no actor to warp to, fail.
if (!reference)
{
return numret;
}
if (P_Thing_Warp(self, reference, xofs, yofs, zofs, angle, flags, heightoffset, radiusoffset, pitch))
{
if (success_state)
{
// 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.
if (numret > 0)
{
ret[0].SetPointer(success_state, ATAG_STATE);
}
}
else if (numret > 1)
{
ret[1].SetInt(true);
}
}
return numret;
}
static bool DoCheckSpecies(AActor *mo, FName filterSpecies, bool exclude)
{
FName actorSpecies = mo->GetSpecies();
if (filterSpecies == NAME_None) return true;
return exclude ? (actorSpecies != filterSpecies) : (actorSpecies == filterSpecies);
}
static bool DoCheckClass(AActor *mo, PClassActor *filterClass, bool exclude)
{
const PClass *actorClass = mo->GetClass();
if (filterClass == NULL) return true;
return exclude ? (actorClass != filterClass) : (actorClass == filterClass);
}
//==========================================================================
//
// A_RadiusGive(item, distance, flags, amount, filter, species)
//
// 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_NOTARGET = 1 << 6,
RGF_NOTRACER = 1 << 7,
RGF_NOMASTER = 1 << 8,
RGF_CUBE = 1 << 9,
RGF_NOSIGHT = 1 << 10,
RGF_MISSILES = 1 << 11,
RGF_INCLUSIVE = 1 << 12,
RGF_ITEMS = 1 << 13,
RGF_KILLED = 1 << 14,
RGF_EXFILTER = 1 << 15,
RGF_EXSPECIES = 1 << 16,
RGF_EITHER = 1 << 17,
RGF_MASK = /*2111*/
RGF_GIVESELF |
RGF_PLAYERS |
RGF_MONSTERS |
RGF_OBJECTS |
RGF_VOODOO |
RGF_CORPSES |
RGF_KILLED |
RGF_MISSILES |
RGF_ITEMS,
};
static bool DoRadiusGive(AActor *self, AActor *thing, PClassActor *item, int amount, double distance, int flags, PClassActor *filter, FName species, double mindist)
{
bool doPass = false;
// Always allow self to give, no matter what other flags are specified. Otherwise, not at all.
if (thing == self)
{
if (!(flags & RGF_GIVESELF))
return false;
doPass = true;
}
else if (thing->flags & MF_MISSILE)
{
if (!(flags & RGF_MISSILES))
return false;
doPass = true;
}
else if (((flags & RGF_ITEMS) && thing->IsKindOf(RUNTIME_CLASS(AInventory))) ||
((flags & RGF_CORPSES) && thing->flags & MF_CORPSE) ||
((flags & RGF_KILLED) && thing->flags6 & MF6_KILLED))
{
doPass = true;
}
else if ((flags & (RGF_MONSTERS | RGF_OBJECTS | RGF_PLAYERS | RGF_VOODOO)))
{
// Make sure it's alive as we're not looking for corpses or killed here.
if (!doPass && thing->health > 0)
{
if (thing->player != nullptr)
{
if (((flags & RGF_PLAYERS) && (thing->player->mo == thing)) ||
((flags & RGF_VOODOO) && (thing->player->mo != thing)))
{
doPass = true;
}
}
else
{
if (((flags & RGF_MONSTERS) && (thing->flags3 & MF3_ISMONSTER)) ||
((flags & RGF_OBJECTS) && (!(thing->flags3 & MF3_ISMONSTER)) &&
(thing->flags & MF_SHOOTABLE || thing->flags6 & MF6_VULNERABLE)))
{
doPass = true;
}
}
}
}
// Nothing matched up so don't bother with the rest.
if (!doPass)
return false;
//[MC] Check for a filter, species, and the related exfilter/expecies/either flag(s).
bool filterpass = DoCheckClass(thing, filter, !!(flags & RGF_EXFILTER)),
speciespass = DoCheckSpecies(thing, species, !!(flags & RGF_EXSPECIES));
if ((flags & RGF_EITHER) ? (!(filterpass || speciespass)) : (!(filterpass && speciespass)))
{
if (thing != self) //Don't let filter and species obstruct RGF_GIVESELF.
return false;
}
if ((thing != self) && (flags & (RGF_NOTARGET | RGF_NOMASTER | RGF_NOTRACER)))
{
//Check for target, master, and tracer flagging.
bool targetPass = true;
bool masterPass = true;
bool tracerPass = true;
bool ptrPass = false;
if ((thing == self->target) && (flags & RGF_NOTARGET))
targetPass = false;
if ((thing == self->master) && (flags & RGF_NOMASTER))
masterPass = false;
if ((thing == self->tracer) && (flags & RGF_NOTRACER))
tracerPass = false;
ptrPass = (flags & RGF_INCLUSIVE) ? (targetPass || masterPass || tracerPass) : (targetPass && masterPass && tracerPass);
//We should not care about what the actor is here. It's safe to abort this actor.
if (!ptrPass)
return false;
}
if (doPass)
{
DVector3 diff = self->Vec3To(thing);
diff.Z += thing->Height *0.5;
if (flags & RGF_CUBE)
{ // check if inside a cube
double dx = fabs(diff.X);
double dy = fabs(diff.Y);
double dz = fabs(diff.Z);
if ((dx > distance || dy > distance || dz > distance) || (mindist && (dx < mindist && dy < mindist && dz < mindist)))
{
return false;
}
}
else
{ // check if inside a sphere
double lengthsquared = diff.LengthSquared();
if (lengthsquared > distance*distance || (mindist && (lengthsquared < mindist*mindist)))
{
return false;
}
}
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<AInventory *>(Spawn(item));
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();
return false;
}
else
{
return true;
}
}
}
return false;
}
DEFINE_ACTION_FUNCTION(AActor, A_RadiusGive)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS (item, AInventory);
PARAM_FLOAT (distance);
PARAM_INT (flags);
PARAM_INT_DEF (amount);
PARAM_CLASS_DEF (filter, AActor);
PARAM_NAME_DEF (species);
PARAM_FLOAT_DEF (mindist);
PARAM_INT_DEF (limit);
// We need a valid item, valid targets, and a valid range
if (item == nullptr || (flags & RGF_MASK) == 0 || !flags || distance <= 0 || mindist >= distance)
{
ACTION_RETURN_INT(0);
}
bool unlimited = (limit <= 0);
if (amount == 0)
{
amount = 1;
}
AActor *thing;
int given = 0;
if (flags & RGF_MISSILES)
{
TThinkerIterator<AActor> it;
while ((thing = it.Next()) && ((unlimited) || (given < limit)))
{
given += DoRadiusGive(self, thing, item, amount, distance, flags, filter, species, mindist);
}
}
else
{
FPortalGroupArray check(FPortalGroupArray::PGA_Full3d);
double mid = self->Center();
FMultiBlockThingsIterator it(check, self->X(), self->Y(), mid-distance, mid+distance, distance, false, self->Sector);
FMultiBlockThingsIterator::CheckResult cres;
while ((it.Next(&cres)) && ((unlimited) || (given < limit)))
{
given += DoRadiusGive(self, cres.thing, item, amount, distance, flags, filter, species, mindist);
}
}
ACTION_RETURN_INT(given);
}
//==========================================================================
//
// A_SetTics
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_SetTics)
{
PARAM_ACTION_PROLOGUE(AActor);
PARAM_INT(tics_to_set);
if (ACTION_CALL_FROM_PSPRITE())
{
DPSprite *pspr = self->player->FindPSprite(stateinfo->mPSPIndex);
if (pspr != nullptr)
{
pspr->Tics = tics_to_set;
return 0;
}
}
else if (ACTION_CALL_FROM_ACTOR())
{
// Just set tics for self.
self->tics = tics_to_set;
}
// for inventory state chains this needs to be ignored.
return 0;
}
//==========================================================================
//
// A_DropItem
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_DropItem)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS (spawntype, AActor);
PARAM_INT_DEF(amount);
PARAM_INT_DEF(chance);
P_DropItem(self, spawntype, amount, chance);
return 0;
}
//===========================================================================
//
// 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.
DMSS_INFLICTORDMGTYPE = 512, //Ignore the passed damagetype and use the inflictor's instead.
};
static void DoDamage(AActor *dmgtarget, AActor *inflictor, AActor *source, int amount, FName DamageType, int flags, PClassActor *filter, FName species)
{
bool filterpass = DoCheckClass(dmgtarget, filter, !!(flags & DMSS_EXFILTER)),
speciespass = DoCheckSpecies(dmgtarget, species, !!(flags & DMSS_EXSPECIES));
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 | 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)
{ //Should wind up passing them through just fine.
if (flags & DMSS_INFLICTORDMGTYPE)
DamageType = inflictor->DamageType;
P_DamageMobj(dmgtarget, inflictor, source, amount, DamageType, dmgFlags);
}
else if (amount < 0)
{
amount = -amount;
P_GiveBody(dmgtarget, amount);
}
}
}
//===========================================================================
//
//
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_DamageSelf)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT (amount);
PARAM_NAME_DEF (damagetype)
PARAM_INT_DEF (flags)
PARAM_CLASS_DEF (filter, AActor)
PARAM_NAME_DEF (species)
PARAM_INT_DEF (src)
PARAM_INT_DEF (inflict)
AActor *source = COPY_AAPTR(self, src);
AActor *inflictor = COPY_AAPTR(self, inflict);
DoDamage(self, inflictor, source, amount, damagetype, flags, filter, species);
return 0;
}
//===========================================================================
//
//
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_DamageTarget)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT (amount);
PARAM_NAME_DEF (damagetype)
PARAM_INT_DEF (flags)
PARAM_CLASS_DEF (filter, AActor)
PARAM_NAME_DEF (species)
PARAM_INT_DEF (src)
PARAM_INT_DEF (inflict)
AActor *source = COPY_AAPTR(self, src);
AActor *inflictor = COPY_AAPTR(self, inflict);
if (self->target != NULL)
DoDamage(self->target, inflictor, source, amount, damagetype, flags, filter, species);
return 0;
}
//===========================================================================
//
//
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_DamageTracer)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT (amount);
PARAM_NAME_DEF (damagetype)
PARAM_INT_DEF (flags)
PARAM_CLASS_DEF (filter, AActor)
PARAM_NAME_DEF (species)
PARAM_INT_DEF (src)
PARAM_INT_DEF (inflict)
AActor *source = COPY_AAPTR(self, src);
AActor *inflictor = COPY_AAPTR(self, inflict);
if (self->tracer != NULL)
DoDamage(self->tracer, inflictor, source, amount, damagetype, flags, filter, species);
return 0;
}
//===========================================================================
//
//
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_DamageMaster)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT (amount);
PARAM_NAME_DEF (damagetype)
PARAM_INT_DEF (flags)
PARAM_CLASS_DEF (filter, AActor)
PARAM_NAME_DEF (species)
PARAM_INT_DEF (src)
PARAM_INT_DEF (inflict)
AActor *source = COPY_AAPTR(self, src);
AActor *inflictor = COPY_AAPTR(self, inflict);
if (self->master != NULL)
DoDamage(self->master, inflictor, source, amount, damagetype, flags, filter, species);
return 0;
}
//===========================================================================
//
//
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_DamageChildren)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT (amount);
PARAM_NAME_DEF (damagetype)
PARAM_INT_DEF (flags)
PARAM_CLASS_DEF (filter, AActor)
PARAM_NAME_DEF (species)
PARAM_INT_DEF (src)
PARAM_INT_DEF (inflict)
AActor *source = COPY_AAPTR(self, src);
AActor *inflictor = COPY_AAPTR(self, inflict);
TThinkerIterator<AActor> it;
AActor *mo;
while ( (mo = it.Next()) )
{
if (mo->master == self)
DoDamage(mo, inflictor, source, amount, damagetype, flags, filter, species);
}
return 0;
}
//===========================================================================
//
//
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_DamageSiblings)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT (amount);
PARAM_NAME_DEF (damagetype)
PARAM_INT_DEF (flags)
PARAM_CLASS_DEF (filter, AActor)
PARAM_NAME_DEF (species)
PARAM_INT_DEF (src)
PARAM_INT_DEF (inflict)
AActor *source = COPY_AAPTR(self, src);
AActor *inflictor = COPY_AAPTR(self, inflict);
TThinkerIterator<AActor> it;
AActor *mo;
if (self->master != NULL)
{
while ((mo = it.Next()))
{
if (mo->master == self->master && mo != self)
DoDamage(mo, inflictor, source, amount, damagetype, flags, filter, species);
}
}
return 0;
}
//===========================================================================
//
// 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 *inflictor, AActor *source, FName damagetype, int flags, PClassActor *filter, FName species)
{
bool filterpass = DoCheckClass(killtarget, filter, !!(flags & KILS_EXFILTER)),
speciespass = DoCheckSpecies(killtarget, species, !!(flags & KILS_EXSPECIES));
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->flags7 & MF7_BUDDHA) || (flags & KILS_FOILBUDDHA)) &&
!(killtarget->flags5 & MF5_NODAMAGE))
{
P_ExplodeMissile(killtarget, NULL, NULL);
}
}
if (!(flags & KILS_NOMONSTERS))
{
P_DamageMobj(killtarget, inflictor, source, killtarget->health, damagetype, dmgFlags);
}
}
}
//===========================================================================
//
// A_KillTarget(damagetype, int flags)
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_KillTarget)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME_DEF (damagetype)
PARAM_INT_DEF (flags)
PARAM_CLASS_DEF (filter, AActor)
PARAM_NAME_DEF (species)
PARAM_INT_DEF (src)
PARAM_INT_DEF (inflict)
AActor *source = COPY_AAPTR(self, src);
AActor *inflictor = COPY_AAPTR(self, inflict);
if (self->target != NULL)
DoKill(self->target, inflictor, source, damagetype, flags, filter, species);
return 0;
}
//===========================================================================
//
// A_KillTracer(damagetype, int flags)
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_KillTracer)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME_DEF (damagetype)
PARAM_INT_DEF (flags)
PARAM_CLASS_DEF (filter, AActor)
PARAM_NAME_DEF (species)
PARAM_INT_DEF (src)
PARAM_INT_DEF (inflict)
AActor *source = COPY_AAPTR(self, src);
AActor *inflictor = COPY_AAPTR(self, inflict);
if (self->tracer != NULL)
DoKill(self->tracer, inflictor, source, damagetype, flags, filter, species);
return 0;
}
//===========================================================================
//
// A_KillMaster(damagetype, int flags)
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_KillMaster)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME_DEF (damagetype)
PARAM_INT_DEF (flags)
PARAM_CLASS_DEF (filter, AActor)
PARAM_NAME_DEF (species)
PARAM_INT_DEF (src)
PARAM_INT_DEF (inflict)
AActor *source = COPY_AAPTR(self, src);
AActor *inflictor = COPY_AAPTR(self, inflict);
if (self->master != NULL)
DoKill(self->master, inflictor, source, damagetype, flags, filter, species);
return 0;
}
//===========================================================================
//
// A_KillChildren(damagetype, int flags)
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_KillChildren)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME_DEF (damagetype)
PARAM_INT_DEF (flags)
PARAM_CLASS_DEF (filter, AActor)
PARAM_NAME_DEF (species)
PARAM_INT_DEF (src)
PARAM_INT_DEF (inflict)
AActor *source = COPY_AAPTR(self, src);
AActor *inflictor = COPY_AAPTR(self, inflict);
TThinkerIterator<AActor> it;
AActor *mo;
while ( (mo = it.Next()) )
{
if (mo->master == self)
{
DoKill(mo, inflictor, source, damagetype, flags, filter, species);
}
}
return 0;
}
//===========================================================================
//
// A_KillSiblings(damagetype, int flags)
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_KillSiblings)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME_DEF (damagetype)
PARAM_INT_DEF (flags)
PARAM_CLASS_DEF (filter, AActor)
PARAM_NAME_DEF (species)
PARAM_INT_DEF (src)
PARAM_INT_DEF (inflict)
AActor *source = COPY_AAPTR(self, src);
AActor *inflictor = COPY_AAPTR(self, inflict);
TThinkerIterator<AActor> it;
AActor *mo;
if (self->master != NULL)
{
while ( (mo = it.Next()) )
{
if (mo->master == self->master && mo != self)
{
DoKill(mo, inflictor, source, damagetype, flags, filter, species);
}
}
}
return 0;
}
//===========================================================================
//
// 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, PClassActor *filter, FName species)
{
bool filterpass = DoCheckClass(removetarget, filter, !!(flags & RMVF_EXFILTER)),
speciespass = DoCheckSpecies(removetarget, species, !!(flags & RMVF_EXSPECIES));
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(AActor, A_RemoveTarget)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF(flags);
PARAM_CLASS_DEF(filter, AActor);
PARAM_NAME_DEF(species);
if (self->target != NULL)
{
DoRemove(self->target, flags, filter, species);
}
return 0;
}
//===========================================================================
//
// A_RemoveTracer
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_RemoveTracer)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF(flags);
PARAM_CLASS_DEF(filter, AActor);
PARAM_NAME_DEF(species);
if (self->tracer != NULL)
{
DoRemove(self->tracer, flags, filter, species);
}
return 0;
}
//===========================================================================
//
// A_RemoveMaster
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_RemoveMaster)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF(flags);
PARAM_CLASS_DEF(filter, AActor);
PARAM_NAME_DEF(species);
if (self->master != NULL)
{
DoRemove(self->master, flags, filter, species);
}
return 0;
}
//===========================================================================
//
// A_RemoveChildren
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_RemoveChildren)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_BOOL_DEF(removeall);
PARAM_INT_DEF(flags);
PARAM_CLASS_DEF(filter, AActor);
PARAM_NAME_DEF(species);
TThinkerIterator<AActor> it;
AActor *mo;
while ((mo = it.Next()) != NULL)
{
if (mo->master == self && (mo->health <= 0 || removeall))
{
DoRemove(mo, flags, filter, species);
}
}
return 0;
}
//===========================================================================
//
// A_RemoveSiblings
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_RemoveSiblings)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_BOOL_DEF(removeall);
PARAM_INT_DEF(flags);
PARAM_CLASS_DEF(filter, AActor);
PARAM_NAME_DEF(species);
TThinkerIterator<AActor> it;
AActor *mo;
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);
}
}
}
return 0;
}
//===========================================================================
//
// A_Remove
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_Remove)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(removee);
PARAM_INT_DEF(flags);
PARAM_CLASS_DEF(filter, AActor);
PARAM_NAME_DEF(species);
AActor *reference = COPY_AAPTR(self, removee);
if (reference != NULL)
{
DoRemove(reference, flags, filter, species);
}
return 0;
}
//===========================================================================
//
// A_SetTeleFog
//
// Sets the teleport fog(s) for the calling actor.
// Takes a name of the classes for the source and destination.
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_SetTeleFog)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS(oldpos, AActor);
PARAM_CLASS(newpos, AActor);
self->TeleFogSourceType = oldpos;
self->TeleFogDestType = newpos;
return 0;
}
//===========================================================================
//
// A_SwapTeleFog
//
// Switches the source and dest telefogs around.
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_SwapTeleFog)
{
PARAM_SELF_PROLOGUE(AActor);
if ((self->TeleFogSourceType != self->TeleFogDestType)) //Does nothing if they're the same.
{
PClassActor *temp = self->TeleFogSourceType;
self->TeleFogSourceType = self->TeleFogDestType;
self->TeleFogDestType = temp;
}
return 0;
}
//===========================================================================
//
// A_SetFloatBobPhase
//
// Changes the FloatBobPhase of the actor.
//===========================================================================
//===========================================================================
// A_SetHealth
//
// Changes the health of the actor.
// Takes a pointer as well.
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_SetHealth)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT (health);
PARAM_INT_DEF (ptr);
AActor *mobj = COPY_AAPTR(self, ptr);
if (!mobj)
{
return 0;
}
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;
}
return 0;
}
//===========================================================================
// A_ResetHealth
//
// Resets the health of the actor to default, except if their dead.
// Takes a pointer.
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_ResetHealth)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF(ptr);
AActor *mobj = COPY_AAPTR(self, ptr);
if (!mobj)
{
return 0;
}
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();
}
return 0;
}
//===========================================================================
// A_SetSpecies(str species, ptr)
//
// Sets the species of the calling actor('s pointer).
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_SetSpecies)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME(species);
PARAM_INT_DEF(ptr);
AActor *mobj = COPY_AAPTR(self, ptr);
if (!mobj)
{
return 0;
}
mobj->Species = species;
return 0;
}
//===========================================================================
//
// A_SetChaseThreshold(int threshold, bool def, int ptr)
//
// Sets the current chase threshold of the actor (pointer). If def is true,
// changes the default threshold which the actor resets to once it switches
// targets and doesn't have the +QUICKTORETALIATE flag.
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_SetChaseThreshold)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(threshold);
PARAM_BOOL_DEF(def);
PARAM_INT_DEF(ptr);
AActor *mobj = COPY_AAPTR(self, ptr);
if (!mobj)
{
return 0;
}
if (def)
mobj->DefThreshold = (threshold >= 0) ? threshold : 0;
else
mobj->threshold = (threshold >= 0) ? threshold : 0;
return 0;
}
//==========================================================================
//
// A_CheckProximity(jump, classname, distance, count, flags, ptr)
//
// Checks to see if a certain actor class is close to the
// actor/pointer within distance, in numbers.
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, CheckProximity)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS(classname, AActor);
PARAM_FLOAT(distance);
PARAM_INT_DEF(count);
PARAM_INT_DEF(flags);
PARAM_INT_DEF(ptr);
ACTION_RETURN_BOOL(!!P_Thing_CheckProximity(self, classname, distance, count, flags, ptr));
}
/*===========================================================================
A_CheckBlock
(state block, int flags, int ptr)
Checks if something is blocking the actor('s pointer) 'ptr'.
The SET pointer flags only affect the caller, not the pointer.
===========================================================================*/
enum CBF
{
CBF_NOLINES = 1 << 0, //Don't check lines.
CBF_SETTARGET = 1 << 1, //Sets the caller/pointer's target to the actor blocking it. Actors only.
CBF_SETMASTER = 1 << 2, //^ but with master.
CBF_SETTRACER = 1 << 3, //^ but with tracer.
CBF_SETONPTR = 1 << 4, //Sets the pointer change on the actor doing the checking instead of self.
CBF_DROPOFF = 1 << 5, //Check for dropoffs.
CBF_NOACTORS = 1 << 6, //Don't check actors.
CBF_ABSOLUTEPOS = 1 << 7, //Absolute position for offsets.
CBF_ABSOLUTEANGLE = 1 << 8, //Absolute angle for offsets.
};
DEFINE_ACTION_FUNCTION(AActor, CheckBlock)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF(flags)
PARAM_INT_DEF(ptr)
PARAM_FLOAT_DEF(xofs)
PARAM_FLOAT_DEF(yofs)
PARAM_FLOAT_DEF(zofs)
PARAM_ANGLE_DEF(angle)
AActor *mobj = COPY_AAPTR(self, ptr);
//Needs at least one state jump to work.
if (!mobj)
{
ACTION_RETURN_BOOL(false);
}
if (!(flags & CBF_ABSOLUTEANGLE))
{
angle += self->Angles.Yaw;
}
DVector3 oldpos = mobj->Pos();
DVector3 pos;
if (flags & CBF_ABSOLUTEPOS)
{
pos = { xofs, yofs, zofs };
}
else
{
double s = angle.Sin();
double c = angle.Cos();
pos = mobj->Vec3Offset(xofs * c + yofs * s, xofs * s - yofs * c, zofs);
}
// Next, try checking the position based on the sensitivity desired.
// If checking for dropoffs, set the z so we can have maximum flexibility.
// Otherwise, set origin and set it back after testing.
bool checker = false;
if (flags & CBF_DROPOFF)
{
// Unfortunately, whenever P_CheckMove returned false, that means it could
// ignore a variety of flags mainly because of P_CheckPosition. This
// results in picking up false positives due to actors or lines being in the way
// when they clearly should not be.
int fpass = PCM_DROPOFF;
if (flags & CBF_NOACTORS) fpass |= PCM_NOACTORS;
if (flags & CBF_NOLINES) fpass |= PCM_NOLINES;
mobj->SetZ(pos.Z);
checker = P_CheckMove(mobj, pos, fpass);
mobj->SetZ(oldpos.Z);
}
else
{
mobj->SetOrigin(pos, true);
checker = P_TestMobjLocation(mobj);
mobj->SetOrigin(oldpos, true);
}
if (checker)
{
ACTION_RETURN_BOOL(false);
}
if (mobj->BlockingMobj)
{
AActor *setter = (flags & CBF_SETONPTR) ? mobj : self;
if (setter)
{
if (flags & CBF_SETTARGET) setter->target = mobj->BlockingMobj;
if (flags & CBF_SETMASTER) setter->master = mobj->BlockingMobj;
if (flags & CBF_SETTRACER) setter->tracer = mobj->BlockingMobj;
}
}
//[MC] I don't know why I let myself be persuaded not to include a flag.
//If an actor is loaded with pointers, they don't really have any options to spare.
//Also, fail if a dropoff or a step is too great to pass over when checking for dropoffs.
ACTION_RETURN_BOOL((!(flags & CBF_NOACTORS) && (mobj->BlockingMobj)) || (!(flags & CBF_NOLINES) && mobj->BlockingLine != NULL) ||
((flags & CBF_DROPOFF) && !checker));
}
//===========================================================================
//
// A_FaceMovementDirection(angle offset, bool pitch, ptr)
//
// Sets the actor('s pointer) to face the direction of travel.
//===========================================================================
enum FMDFlags
{
FMDF_NOPITCH = 1 << 0,
FMDF_INTERPOLATE = 1 << 1,
FMDF_NOANGLE = 1 << 2,
};
DEFINE_ACTION_FUNCTION(AActor, A_FaceMovementDirection)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_ANGLE_DEF(offset)
PARAM_ANGLE_DEF(anglelimit)
PARAM_ANGLE_DEF(pitchlimit)
PARAM_INT_DEF(flags)
PARAM_INT_DEF(ptr)
AActor *mobj = COPY_AAPTR(self, ptr);
//Need an actor.
if (!mobj || ((flags & FMDF_NOPITCH) && (flags & FMDF_NOANGLE)))
{
ACTION_RETURN_BOOL(false);
}
//Don't bother calculating this if we don't have any horizontal movement.
if (!(flags & FMDF_NOANGLE) && (mobj->Vel.X != 0 || mobj->Vel.Y != 0))
{
DAngle current = mobj->Angles.Yaw;
DAngle angle = mobj->Vel.Angle();
//Done because using anglelimit directly causes a signed/unsigned mismatch.
//Code borrowed from A_Face*.
if (anglelimit > 0)
{
DAngle delta = -deltaangle(current, angle);
if (fabs(delta) > anglelimit)
{
if (delta < 0)
{
current += anglelimit + offset;
}
else if (delta > 0)
{
current -= anglelimit + offset;
}
mobj->SetAngle(current, !!(flags & FMDF_INTERPOLATE));
}
else
mobj->SetAngle(angle + offset, !!(flags & FMDF_INTERPOLATE));
}
else
mobj->SetAngle(angle + offset, !!(flags & FMDF_INTERPOLATE));
}
if (!(flags & FMDF_NOPITCH))
{
DAngle current = mobj->Angles.Pitch;
const DVector2 velocity = mobj->Vel.XY();
DAngle pitch = VecToAngle(velocity.Length(), mobj->Vel.Z);
if (pitchlimit > 0)
{
DAngle pdelta = deltaangle(-current, pitch);
if (fabs(pdelta) > pitchlimit)
{
if (pdelta > 0)
{
current -= MIN(pitchlimit, pdelta);
}
else //if (pdelta < 0)
{
current += MIN(pitchlimit, -pdelta);
}
mobj->SetPitch(current, !!(flags & FMDF_INTERPOLATE));
}
else
{
mobj->SetPitch(pitch, !!(flags & FMDF_INTERPOLATE));
}
}
else
{
mobj->SetPitch(pitch, !!(flags & FMDF_INTERPOLATE));
}
}
ACTION_RETURN_BOOL(true);
}
//==========================================================================
//
// A_CopySpriteFrame(from, to, flags)
//
// Copies the sprite and/or frame from one pointer to another.
//==========================================================================
enum CPSFFlags
{
CPSF_NOSPRITE = 1,
CPSF_NOFRAME = 1 << 1,
};
DEFINE_ACTION_FUNCTION(AActor, A_CopySpriteFrame)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(from);
PARAM_INT(to);
PARAM_INT_DEF(flags);
AActor *copyfrom = COPY_AAPTR(self, from);
AActor *copyto = COPY_AAPTR(self, to);
if (copyfrom == copyto || copyfrom == nullptr || copyto == nullptr || ((flags & CPSF_NOSPRITE) && (flags & CPSF_NOFRAME)))
{
ACTION_RETURN_BOOL(false);
}
if (!(flags & CPSF_NOSPRITE)) copyto->sprite = copyfrom->sprite;
if (!(flags & CPSF_NOFRAME)) copyto->frame = copyfrom->frame;
ACTION_RETURN_BOOL(true);
}
//==========================================================================
//
// A_SetMaskRotation(anglestart, angleend, pitchstart, pitchend, flags, ptr)
//
// Specifies how much to fake a sprite rotation.
//==========================================================================
enum VRFFlags
{
VRF_NOANGLESTART = 1,
VRF_NOANGLEEND = 1 << 1,
VRF_NOPITCHSTART = 1 << 2,
VRF_NOPITCHEND = 1 << 3,
};
DEFINE_ACTION_FUNCTION(AActor, A_SetVisibleRotation)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_ANGLE_DEF(anglestart)
PARAM_ANGLE_DEF(angleend)
PARAM_ANGLE_DEF(pitchstart)
PARAM_ANGLE_DEF(pitchend)
PARAM_INT_DEF(flags)
PARAM_INT_DEF(ptr)
AActor *mobj = COPY_AAPTR(self, ptr);
if (mobj == nullptr)
{
ACTION_RETURN_BOOL(false);
}
if (!(flags & VRF_NOANGLESTART))
{
mobj->VisibleStartAngle = anglestart;
}
if (!(flags & VRF_NOANGLEEND))
{
mobj->VisibleEndAngle = angleend;
}
if (!(flags & VRF_NOPITCHSTART))
{
mobj->VisibleStartPitch = pitchstart;
}
if (!(flags & VRF_NOPITCHEND))
{
mobj->VisibleEndPitch = pitchend;
}
ACTION_RETURN_BOOL(true);
}
//==========================================================================
//
//
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_SetTranslation)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME(trname);
self->SetTranslation(trname);
return 0;
}