2015-01-30 01:30:26 +00:00
|
|
|
/*
|
2016-02-04 21:15:29 +00:00
|
|
|
** thingdef_codeptr.cpp
|
2015-01-30 01:30:26 +00:00
|
|
|
**
|
|
|
|
** 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"
|
2015-12-08 09:58:24 +00:00
|
|
|
#include "p_effect.h"
|
2015-01-30 01:30:26 +00:00
|
|
|
#include "p_enemy.h"
|
|
|
|
#include "a_action.h"
|
|
|
|
#include "decallib.h"
|
|
|
|
#include "m_random.h"
|
|
|
|
#include "i_system.h"
|
|
|
|
#include "p_local.h"
|
|
|
|
#include "c_console.h"
|
|
|
|
#include "doomerrors.h"
|
|
|
|
#include "a_sharedglobal.h"
|
|
|
|
#include "thingdef/thingdef.h"
|
|
|
|
#include "v_video.h"
|
|
|
|
#include "v_font.h"
|
|
|
|
#include "doomstat.h"
|
|
|
|
#include "v_palette.h"
|
|
|
|
#include "g_shared/a_specialspot.h"
|
|
|
|
#include "actorptrselect.h"
|
|
|
|
#include "m_bbox.h"
|
|
|
|
#include "r_data/r_translate.h"
|
|
|
|
#include "p_trace.h"
|
2015-04-15 18:10:27 +00:00
|
|
|
#include "p_setup.h"
|
2015-01-30 01:30:26 +00:00
|
|
|
#include "gstrings.h"
|
2016-02-15 01:14:34 +00:00
|
|
|
#include "d_player.h"
|
|
|
|
#include "p_maputl.h"
|
|
|
|
#include "p_spec.h"
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
AActor *SingleActorFromTID(int tid, AActor *defactor);
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
static FRandom pr_camissile ("CustomActorfire");
|
|
|
|
static FRandom pr_camelee ("CustomMelee");
|
|
|
|
static FRandom pr_cabullet ("CustomBullet");
|
|
|
|
static FRandom pr_cajump ("CustomJump");
|
|
|
|
static FRandom pr_cwbullet ("CustomWpBullet");
|
|
|
|
static FRandom pr_cwjump ("CustomWpJump");
|
|
|
|
static FRandom pr_cwpunch ("CustomWpPunch");
|
|
|
|
static FRandom pr_grenade ("ThrowGrenade");
|
|
|
|
static FRandom pr_crailgun ("CustomRailgun");
|
|
|
|
static FRandom pr_spawndebris ("SpawnDebris");
|
|
|
|
static FRandom pr_spawnitemex ("SpawnItemEx");
|
|
|
|
static FRandom pr_burst ("Burst");
|
|
|
|
static FRandom pr_monsterrefire ("MonsterRefire");
|
|
|
|
static FRandom pr_teleport("A_Teleport");
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// ACustomInventory :: CallStateChain
|
|
|
|
//
|
|
|
|
// Executes the code pointers in a chain of states
|
|
|
|
// until there is no next state
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
bool ACustomInventory::CallStateChain (AActor *actor, FState *state)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
INTBOOL result = false;
|
2015-01-30 01:30:26 +00:00
|
|
|
int counter = 0;
|
2016-02-04 21:15:29 +00:00
|
|
|
VMValue params[3] = { actor, this, 0 };
|
|
|
|
|
2016-02-19 04:05:10 +00:00
|
|
|
// 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);
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
this->flags5 |= MF5_INSTATECALL;
|
|
|
|
FState *savedstate = this->state;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
while (state != NULL)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
this->state = state;
|
2016-02-19 04:05:10 +00:00
|
|
|
nextstate = NULL; // assume no jump
|
2016-02-04 21:15:29 +00:00
|
|
|
|
|
|
|
if (state->ActionFunc != NULL)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
VMFrameStack stack;
|
2016-02-19 04:05:10 +00:00
|
|
|
PPrototype *proto = state->ActionFunc->Proto;
|
|
|
|
VMReturn *wantret;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
params[2] = VMValue(state, ATAG_STATE);
|
2016-02-19 04:05:10 +00:00
|
|
|
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];
|
2016-03-04 14:11:20 +00:00
|
|
|
retval = false; // this is a jump function which never affects the success state.
|
2016-02-19 04:05:10 +00:00
|
|
|
}
|
|
|
|
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, countof(params), wantret, numret);
|
2016-02-04 21:15:29 +00:00
|
|
|
// As long as even one state succeeds, the whole chain succeeds unless aborted below.
|
2016-02-19 04:05:10 +00:00
|
|
|
// A state that wants to jump does not count as "succeeded".
|
2016-02-20 04:09:35 +00:00
|
|
|
if (nextstate == NULL)
|
2016-02-19 04:05:10 +00:00
|
|
|
{
|
|
|
|
result |= retval;
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
// Since there are no delays it is a good idea to check for infinite loops here!
|
|
|
|
counter++;
|
|
|
|
if (counter >= 10000) break;
|
|
|
|
|
2016-02-19 04:05:10 +00:00
|
|
|
if (nextstate == NULL)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-19 04:05:10 +00:00
|
|
|
nextstate = state->GetNextState();
|
2016-02-04 21:15:29 +00:00
|
|
|
|
2016-02-19 04:05:10 +00:00
|
|
|
if (state == nextstate)
|
2016-02-04 21:15:29 +00:00
|
|
|
{ // Abort immediately if the state jumps to itself!
|
|
|
|
result = false;
|
|
|
|
break;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-19 04:05:10 +00:00
|
|
|
state = nextstate;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
this->flags5 &= ~MF5_INSTATECALL;
|
|
|
|
this->state = savedstate;
|
|
|
|
return !!result;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CheckClass
|
|
|
|
//
|
|
|
|
// NON-ACTION function to check a pointer's class.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, CheckClass)
|
|
|
|
{
|
|
|
|
if (numret > 0)
|
|
|
|
{
|
|
|
|
assert(ret != NULL);
|
|
|
|
PARAM_PROLOGUE;
|
|
|
|
PARAM_OBJECT (self, AActor);
|
|
|
|
PARAM_CLASS (checktype, AActor);
|
|
|
|
PARAM_INT_OPT (pick_pointer) { pick_pointer = AAPTR_DEFAULT; }
|
|
|
|
PARAM_BOOL_OPT (match_superclass) { match_superclass = false; }
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// IsPointerEqual
|
|
|
|
//
|
|
|
|
// NON-ACTION function to check if two pointers are equal.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, IsPointerEqual)
|
|
|
|
{
|
|
|
|
if (numret > 0)
|
|
|
|
{
|
|
|
|
assert(ret != NULL);
|
|
|
|
PARAM_PROLOGUE;
|
|
|
|
PARAM_OBJECT (self, AActor);
|
|
|
|
PARAM_INT (ptr_select1);
|
|
|
|
PARAM_INT (ptr_select2);
|
|
|
|
|
|
|
|
ret->SetInt(COPY_AAPTR(self, ptr_select1) == COPY_AAPTR(self, ptr_select2));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2016-02-05 03:45:31 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CountInv
|
|
|
|
//
|
|
|
|
// NON-ACTION function to return the inventory count of an item.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, CountInv)
|
|
|
|
{
|
|
|
|
if (numret > 0)
|
|
|
|
{
|
|
|
|
assert(ret != NULL);
|
|
|
|
PARAM_PROLOGUE;
|
|
|
|
PARAM_OBJECT(self, AActor);
|
|
|
|
PARAM_CLASS(itemtype, AInventory);
|
|
|
|
PARAM_INT_OPT(pick_pointer) { pick_pointer = AAPTR_DEFAULT; }
|
|
|
|
|
|
|
|
self = COPY_AAPTR(self, pick_pointer);
|
|
|
|
if (self == NULL || itemtype == NULL)
|
|
|
|
{
|
2016-02-09 04:21:42 +00:00
|
|
|
ret->SetInt(0);
|
2016-02-05 03:45:31 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
AInventory *item = self->FindInventory(itemtype);
|
|
|
|
ret->SetInt(item ? item->Amount : 0);
|
|
|
|
}
|
2016-02-09 04:21:42 +00:00
|
|
|
return 1;
|
2016-02-05 03:45:31 +00:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-02-10 23:13:50 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// GetDistance
|
|
|
|
//
|
|
|
|
// NON-ACTION function to get the distance in double.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, GetDistance)
|
|
|
|
{
|
|
|
|
if (numret > 0)
|
|
|
|
{
|
|
|
|
assert(ret != NULL);
|
|
|
|
PARAM_PROLOGUE;
|
|
|
|
PARAM_OBJECT(self, AActor);
|
|
|
|
PARAM_BOOL(checkz);
|
|
|
|
PARAM_INT_OPT(ptr) { ptr = AAPTR_TARGET; }
|
|
|
|
|
|
|
|
AActor *target = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
if (!target || target == self)
|
|
|
|
{
|
2016-02-11 00:04:59 +00:00
|
|
|
ret->SetFloat(0);
|
2016-02-10 23:13:50 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fixedvec3 diff = self->Vec3To(target);
|
2016-02-11 00:04:59 +00:00
|
|
|
if (checkz)
|
|
|
|
diff.z += (target->height - self->height) / 2;
|
|
|
|
|
2016-03-10 19:44:53 +00:00
|
|
|
const double length = DVector3(FIXED2DBL(diff.x), FIXED2DBL(diff.y), (checkz) ? FIXED2DBL(diff.z) : 0).Length();
|
2016-02-10 23:48:17 +00:00
|
|
|
ret->SetFloat(length);
|
2016-02-10 23:13:50 +00:00
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-02-25 16:08:08 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// GetSpawnHealth
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, GetSpawnHealth)
|
|
|
|
{
|
|
|
|
if (numret > 0)
|
|
|
|
{
|
|
|
|
PARAM_PROLOGUE;
|
|
|
|
PARAM_OBJECT(self, AActor);
|
|
|
|
ret->SetInt(self->SpawnHealth());
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// GetGibHealth
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, GetGibHealth)
|
|
|
|
{
|
|
|
|
if (numret > 0)
|
|
|
|
{
|
|
|
|
PARAM_PROLOGUE;
|
|
|
|
PARAM_OBJECT(self, AActor);
|
|
|
|
ret->SetInt(self->GetGibHealth());
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-02-19 06:29:19 +00:00
|
|
|
//===========================================================================
|
|
|
|
//
|
2016-02-21 04:05:17 +00:00
|
|
|
// __decorate_internal_state__
|
2016-03-01 17:36:15 +00:00
|
|
|
// __decorate_internal_int__
|
|
|
|
// __decorate_internal_bool__
|
|
|
|
// __decorate_internal_float__
|
2016-02-19 06:29:19 +00:00
|
|
|
//
|
2016-03-01 17:36:15 +00:00
|
|
|
// Placeholders for forcing DECORATE to cast numbers. If actually called,
|
|
|
|
// returns whatever was passed.
|
2016-02-19 06:29:19 +00:00
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
2016-02-21 04:05:17 +00:00
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, __decorate_internal_state__)
|
2016-02-19 06:29:19 +00:00
|
|
|
{
|
|
|
|
PARAM_PROLOGUE;
|
|
|
|
PARAM_OBJECT(self, AActor);
|
|
|
|
PARAM_STATE(returnme);
|
|
|
|
ACTION_RETURN_STATE(returnme);
|
|
|
|
}
|
|
|
|
|
2016-02-21 04:05:17 +00:00
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, __decorate_internal_int__)
|
2016-02-19 06:29:19 +00:00
|
|
|
{
|
|
|
|
PARAM_PROLOGUE;
|
|
|
|
PARAM_OBJECT(self, AActor);
|
|
|
|
PARAM_INT(returnme);
|
|
|
|
ACTION_RETURN_INT(returnme);
|
|
|
|
}
|
|
|
|
|
2016-02-21 04:05:17 +00:00
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, __decorate_internal_bool__)
|
2016-02-19 06:29:19 +00:00
|
|
|
{
|
|
|
|
PARAM_PROLOGUE;
|
|
|
|
PARAM_OBJECT(self, AActor);
|
|
|
|
PARAM_BOOL(returnme);
|
|
|
|
ACTION_RETURN_BOOL(returnme);
|
|
|
|
}
|
|
|
|
|
2016-03-01 17:36:15 +00:00
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, __decorate_internal_float__)
|
|
|
|
{
|
|
|
|
PARAM_PROLOGUE;
|
|
|
|
PARAM_OBJECT(self, AActor);
|
|
|
|
PARAM_FLOAT(returnme);
|
|
|
|
if (numret > 0)
|
|
|
|
{
|
|
|
|
ret->SetFloat(returnme);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_RearrangePointers
|
|
|
|
//
|
|
|
|
// Allow an actor to change its relationship to other actors by
|
|
|
|
// copying pointers freely between TARGET MASTER and TRACER.
|
|
|
|
// Can also assign null value, but does not duplicate A_ClearTarget.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RearrangePointers)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (ptr_target);
|
|
|
|
PARAM_INT_OPT (ptr_master) { ptr_master = AAPTR_DEFAULT; }
|
|
|
|
PARAM_INT_OPT (ptr_tracer) { ptr_tracer = AAPTR_TRACER; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_TransferPointer
|
|
|
|
//
|
|
|
|
// Copy one pointer (MASTER, TARGET or TRACER) from this actor (SELF),
|
|
|
|
// or from this actor's MASTER, TARGET or TRACER.
|
|
|
|
//
|
|
|
|
// You can copy any one of that actor's pointers
|
|
|
|
//
|
|
|
|
// Assign the copied pointer to any one pointer in SELF,
|
|
|
|
// MASTER, TARGET or TRACER.
|
|
|
|
//
|
|
|
|
// Any attempt to make an actor point to itself will replace the pointer
|
|
|
|
// with a null value.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TransferPointer)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (ptr_source);
|
|
|
|
PARAM_INT (ptr_recipient);
|
|
|
|
PARAM_INT (ptr_sourcefield);
|
|
|
|
PARAM_INT_OPT (ptr_recipientfield) { ptr_recipientfield = AAPTR_DEFAULT; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
AActor *source, *recipient;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
// Exchange pointers with actors to whom you have pointers (or with yourself, if you must)
|
|
|
|
source = COPY_AAPTR(self, ptr_source);
|
2016-02-04 21:15:29 +00:00
|
|
|
recipient = COPY_AAPTR(self, ptr_recipient); // pick an actor to store the provided pointer value
|
|
|
|
if (recipient == NULL)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
// convert source from dataprovider to data
|
|
|
|
source = COPY_AAPTR(source, ptr_sourcefield);
|
2016-02-04 21:15:29 +00:00
|
|
|
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;
|
|
|
|
}
|
2016-01-19 15:09:44 +00:00
|
|
|
ASSIGN_AAPTR(recipient, ptr_recipientfield, source, flags);
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_CopyFriendliness
|
|
|
|
//
|
|
|
|
// Join forces with one of the actors you are pointing to (MASTER by default)
|
|
|
|
//
|
|
|
|
// Normal CopyFriendliness reassigns health. This function will not.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CopyFriendliness)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT_OPT (ptr_source) { ptr_source = AAPTR_MASTER; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (self->player != NULL)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
AActor *source = COPY_AAPTR(self, ptr_source);
|
|
|
|
if (source != NULL)
|
|
|
|
{ // No change in current target or health
|
|
|
|
self->CopyFriendliness(source, false, false);
|
|
|
|
}
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Simple flag changers
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_SetSolid)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2015-01-30 01:30:26 +00:00
|
|
|
self->flags |= MF_SOLID;
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_UnsetSolid)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2015-01-30 01:30:26 +00:00
|
|
|
self->flags &= ~MF_SOLID;
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_SetFloat)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2015-01-30 01:30:26 +00:00
|
|
|
self->flags |= MF_FLOAT;
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_UnsetFloat)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2015-01-30 01:30:26 +00:00
|
|
|
self->flags &= ~(MF_FLOAT|MF_INFLOAT);
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Customizable attack functions which use actor parameters.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
static void DoAttack (AActor *self, bool domelee, bool domissile,
|
2016-02-04 21:15:29 +00:00
|
|
|
int MeleeDamage, FSoundID MeleeSound, PClassActor *MissileType,fixed_t MissileHeight)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
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.
|
2016-01-19 19:15:45 +00:00
|
|
|
self->AddZ(MissileHeight + self->GetBobOffset() - 32*FRACUNIT);
|
|
|
|
AActor *missile = P_SpawnMissileXYZ (self->PosPlusZ(32*FRACUNIT), self, self->target, MissileType, false);
|
|
|
|
self->AddZ(-(MissileHeight + self->GetBobOffset() - 32*FRACUNIT));
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
int MeleeDamage = self->GetClass()->MeleeDamage;
|
|
|
|
FSoundID MeleeSound = self->GetClass()->MeleeSound;
|
2015-01-30 01:30:26 +00:00
|
|
|
DoAttack(self, true, false, MeleeDamage, MeleeSound, NULL, 0);
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_MissileAttack)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PClassActor *MissileType = PClass::FindActor(self->GetClass()->MissileName);
|
|
|
|
fixed_t MissileHeight = self->GetClass()->MissileHeight;
|
2015-01-30 01:30:26 +00:00
|
|
|
DoAttack(self, false, true, 0, 0, MissileType, MissileHeight);
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_ComboAttack)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
int MeleeDamage = self->GetClass()->MeleeDamage;
|
|
|
|
FSoundID MeleeSound = self->GetClass()->MeleeSound;
|
|
|
|
PClassActor *MissileType = PClass::FindActor(self->GetClass()->MissileName);
|
|
|
|
fixed_t MissileHeight = self->GetClass()->MissileHeight;
|
2015-01-30 01:30:26 +00:00
|
|
|
DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight);
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BasicAttack)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (melee_damage);
|
|
|
|
PARAM_SOUND (melee_sound);
|
|
|
|
PARAM_CLASS (missile_type, AActor);
|
|
|
|
PARAM_FIXED (missile_height);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (missile_type != NULL)
|
|
|
|
{
|
|
|
|
DoAttack(self, true, true, melee_damage, melee_sound, missile_type, missile_height);
|
|
|
|
}
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Custom sound functions.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySound)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_SOUND_OPT (soundid) { soundid = "weapons/pistol"; }
|
|
|
|
PARAM_INT_OPT (channel) { channel = CHAN_BODY; }
|
|
|
|
PARAM_FLOAT_OPT (volume) { volume = 1; }
|
|
|
|
PARAM_BOOL_OPT (looping) { looping = false; }
|
|
|
|
PARAM_FLOAT_OPT (attenuation) { attenuation = ATTN_NORM; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (!looping)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
S_Sound (self, channel, soundid, (float)volume, (float)attenuation);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!S_IsActorPlayingSomething (self, channel&7, soundid))
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
S_Sound (self, channel | CHAN_LOOP, soundid, (float)volume, (float)attenuation);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSound)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT_OPT(slot) { slot = CHAN_VOICE; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
S_StopSound(self, slot);
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// These come from a time when DECORATE constants did not exist yet and
|
|
|
|
// the sound interface was less flexible. As a result the parameters are
|
|
|
|
// not optimal and these functions have been deprecated in favor of extending
|
|
|
|
// A_PlaySound and A_StopSound.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayWeaponSound)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_SOUND(soundid);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
S_Sound(self, CHAN_WEAPON, soundid, 1, ATTN_NORM);
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySoundEx)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_SOUND (soundid);
|
|
|
|
PARAM_NAME (channel);
|
|
|
|
PARAM_BOOL_OPT (looping) { looping = false; }
|
|
|
|
PARAM_INT_OPT (attenuation_raw) { attenuation_raw = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSoundEx)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_NAME(channel);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (channel > NAME_Auto && channel <= NAME_SoundSlot7)
|
|
|
|
{
|
|
|
|
S_StopSound (self, int(channel) - NAME_Auto);
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Generic seeker missile function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
static FRandom pr_seekermissile ("SeekerMissile");
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
SMF_LOOK = 1,
|
|
|
|
SMF_PRECISE = 2,
|
|
|
|
SMF_CURSPEED = 4,
|
|
|
|
};
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SeekerMissile)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT(ang1);
|
|
|
|
PARAM_INT(ang2);
|
|
|
|
PARAM_INT_OPT(flags) { flags = 0; }
|
|
|
|
PARAM_INT_OPT(chance) { chance = 50; }
|
|
|
|
PARAM_INT_OPT(distance) { distance = 10; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
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) * ANGLE_1, clamp<int>(ang2, 0, 90) * ANGLE_1, !!(flags & SMF_PRECISE), !!(flags & SMF_CURSPEED)))
|
|
|
|
{
|
|
|
|
if (flags & SMF_LOOK)
|
|
|
|
{ // This monster is no longer seekable, so let us look for another one next time.
|
|
|
|
self->tracer = NULL;
|
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Hitscan attack with a customizable amount of bullets (specified in damage)
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_BulletAttack)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
int i;
|
|
|
|
int bangle;
|
|
|
|
int slope;
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (!self->target) return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
A_FaceTarget (self);
|
|
|
|
bangle = self->angle;
|
|
|
|
|
|
|
|
slope = P_AimLineAttack (self, bangle, MISSILERANGE);
|
|
|
|
|
|
|
|
S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM);
|
|
|
|
for (i = self->GetMissileDamage (0, 1); i > 0; --i)
|
|
|
|
{
|
|
|
|
int angle = bangle + (pr_cabullet.Random2() << 20);
|
|
|
|
int damage = ((pr_cabullet()%5)+1)*3;
|
|
|
|
P_LineAttack(self, angle, MISSILERANGE, slope, damage,
|
|
|
|
NAME_Hitscan, NAME_BulletPuff);
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// State jump function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Jump)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT_OPT(maxchance) { maxchance = 256; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
paramnum++; // Increment paramnum to point at the first jump target
|
|
|
|
int count = numparam - paramnum;
|
|
|
|
if (count > 0 && (maxchance >= 256 || pr_cajump() < maxchance))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
int jumpnum = (count == 1 ? 0 : (pr_cajump() % count));
|
|
|
|
PARAM_STATE_AT(paramnum + jumpnum, jumpto);
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jumpto);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// State jump function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfHealthLower)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_INT (health);
|
|
|
|
PARAM_STATE (jump);
|
|
|
|
PARAM_INT_OPT (ptr_selector) { ptr_selector = AAPTR_DEFAULT; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
AActor *measured;
|
|
|
|
|
|
|
|
measured = COPY_AAPTR(self, ptr_selector);
|
2016-02-04 21:15:29 +00:00
|
|
|
|
|
|
|
if (measured != NULL && measured->health < health)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// State jump function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetOutsideMeleeRange)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (!self->CheckMeleeRange())
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// State jump function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInsideMeleeRange)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (self->CheckMeleeRange())
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// State jump function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
2016-02-04 21:15:29 +00:00
|
|
|
static int DoJumpIfCloser(AActor *target, VM_ARGS)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_FIXED (dist);
|
|
|
|
PARAM_STATE (jump);
|
|
|
|
PARAM_BOOL_OPT(noz) { noz = false; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2015-10-10 15:11:59 +00:00
|
|
|
if (!target)
|
2016-02-19 02:39:40 +00:00
|
|
|
{ // No target - no jump
|
|
|
|
ACTION_RETURN_STATE(NULL);
|
|
|
|
}
|
2016-01-10 16:52:41 +00:00
|
|
|
if (self->AproxDistance(target) < dist &&
|
2015-10-10 15:11:59 +00:00
|
|
|
(noz ||
|
2016-01-19 19:15:45 +00:00
|
|
|
((self->Z() > target->Z() && self->Z() - target->Top() < dist) ||
|
|
|
|
(self->Z() <= target->Z() && target->Z() - self->Top() < dist))))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfCloser)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
AActor *target;
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (self->player == NULL)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
target = self->target;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Does the player aim at something that can be shot?
|
2016-03-01 15:38:45 +00:00
|
|
|
FTranslatedLineTarget t;
|
|
|
|
P_BulletSlope(self, &t, ALF_PORTALRESTRICT);
|
|
|
|
target = t.linetarget;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return DoJumpIfCloser(target, VM_ARGS_NAMES);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTracerCloser)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
return DoJumpIfCloser(self->tracer, VM_ARGS_NAMES);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfMasterCloser)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
return DoJumpIfCloser(self->master, VM_ARGS_NAMES);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// State jump function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
2016-02-04 21:15:29 +00:00
|
|
|
int DoJumpIfInventory(AActor *owner, AActor *self, AActor *stateowner, FState *callingstate, VMValue *param, int numparam, VMReturn *ret, int numret)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
int paramnum = NAP-1;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_CLASS (itemtype, AInventory);
|
|
|
|
PARAM_INT (itemamount);
|
|
|
|
PARAM_STATE (label);
|
|
|
|
PARAM_INT_OPT (setowner) { setowner = AAPTR_DEFAULT; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (itemtype == NULL)
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
|
|
|
owner = COPY_AAPTR(owner, setowner);
|
|
|
|
if (owner == NULL)
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
AInventory *item = owner->FindInventory(itemtype);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (item)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
if (itemamount > 0)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
if (item->Amount >= itemamount)
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(label);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
else if (item->Amount >= item->MaxAmount)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(label);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInventory)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
return DoJumpIfInventory(self, self, stateowner, callingstate, param, numparam, ret, numret);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetInventory)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
return DoJumpIfInventory(self->target, self, stateowner, callingstate, param, numparam, ret, numret);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// State jump function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfArmorType)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_NAME (type);
|
|
|
|
PARAM_STATE (label);
|
|
|
|
PARAM_INT_OPT(amount) { amount = 1; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
ABasicArmor *armor = (ABasicArmor *)self->FindInventory(NAME_BasicArmor);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (armor && armor->ArmorType == type && armor->Amount >= amount)
|
2016-02-19 02:39:40 +00:00
|
|
|
{
|
|
|
|
ACTION_RETURN_STATE(label);
|
|
|
|
}
|
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Parameterized version of A_Explode
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
XF_HURTSOURCE = 1,
|
|
|
|
XF_NOTMISSILE = 4,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Explode)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT_OPT (damage) { damage = -1; }
|
|
|
|
PARAM_INT_OPT (distance) { distance = -1; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = XF_HURTSOURCE; }
|
|
|
|
PARAM_BOOL_OPT (alert) { alert = false; }
|
|
|
|
PARAM_INT_OPT (fulldmgdistance) { fulldmgdistance = 0; }
|
|
|
|
PARAM_INT_OPT (nails) { nails = 0; }
|
|
|
|
PARAM_INT_OPT (naildamage) { naildamage = 10; }
|
|
|
|
PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor(NAME_BulletPuff); }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (damage < 0) // get parameters from metadata
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
damage = self->GetClass()->ExplosionDamage;
|
|
|
|
distance = self->GetClass()->ExplosionRadius;
|
|
|
|
flags = !self->GetClass()->DontHurtShooter;
|
2015-01-30 01:30:26 +00:00
|
|
|
alert = false;
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
if (distance <= 0) distance = damage;
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
// NailBomb effect, from SMMU but not from its source code: instead it was implemented and
|
|
|
|
// generalized from the documentation at http://www.doomworld.com/eternity/engine/codeptrs.html
|
|
|
|
|
|
|
|
if (nails)
|
|
|
|
{
|
|
|
|
angle_t ang;
|
|
|
|
for (int i = 0; i < nails; i++)
|
|
|
|
{
|
|
|
|
ang = i*(ANGLE_MAX/nails);
|
|
|
|
// Comparing the results of a test wad with Eternity, it seems A_NailBomb does not aim
|
|
|
|
P_LineAttack (self, ang, MISSILERANGE, 0,
|
|
|
|
//P_AimLineAttack (self, ang, MISSILERANGE),
|
|
|
|
naildamage, NAME_Hitscan, pufftype);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
P_RadiusAttack (self, self->target, damage, distance, self->DamageType, flags, fulldmgdistance);
|
|
|
|
P_CheckSplash(self, distance<<FRACBITS);
|
|
|
|
if (alert && self->target != NULL && self->target->player != NULL)
|
|
|
|
{
|
|
|
|
validcount++;
|
|
|
|
P_RecursiveSound (self->Sector, self->target, false, 0);
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_RadiusThrust
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
RTF_AFFECTSOURCE = 1,
|
|
|
|
RTF_NOIMPACTDAMAGE = 2,
|
|
|
|
RTF_NOTMISSILE = 4,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusThrust)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT_OPT (force) { force = 128; }
|
|
|
|
PARAM_INT_OPT (distance) { distance = -1; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = RTF_AFFECTSOURCE; }
|
|
|
|
PARAM_INT_OPT (fullthrustdistance) { fullthrustdistance = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
bool sourcenothrust = false;
|
|
|
|
|
|
|
|
if (force == 0) force = 128;
|
|
|
|
if (distance <= 0) distance = abs(force);
|
|
|
|
|
|
|
|
// Temporarily negate MF2_NODMGTHRUST on the shooter, since it renders this function useless.
|
|
|
|
if (!(flags & RTF_NOTMISSILE) && self->target != NULL && self->target->flags2 & MF2_NODMGTHRUST)
|
|
|
|
{
|
|
|
|
sourcenothrust = true;
|
|
|
|
self->target->flags2 &= ~MF2_NODMGTHRUST;
|
|
|
|
}
|
|
|
|
|
|
|
|
P_RadiusAttack (self, self->target, force, distance, self->DamageType, flags | RADF_NODAMAGE, fullthrustdistance);
|
|
|
|
P_CheckSplash(self, distance << FRACBITS);
|
|
|
|
|
|
|
|
if (sourcenothrust)
|
|
|
|
{
|
|
|
|
self->target->flags2 |= MF2_NODMGTHRUST;
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Execute a line special / script
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CallSpecial)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (special);
|
|
|
|
PARAM_INT_OPT (arg1) { arg1 = 0; }
|
|
|
|
PARAM_INT_OPT (arg2) { arg2 = 0; }
|
|
|
|
PARAM_INT_OPT (arg3) { arg3 = 0; }
|
|
|
|
PARAM_INT_OPT (arg4) { arg4 = 0; }
|
|
|
|
PARAM_INT_OPT (arg5) { arg5 = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
bool res = !!P_ExecuteSpecial(special, NULL, self, false, arg1, arg2, arg3, arg4, arg5);
|
|
|
|
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(res);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// The ultimate code pointer: Fully customizable missiles!
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
enum CM_Flags
|
|
|
|
{
|
|
|
|
CMF_AIMMODE = 3,
|
|
|
|
CMF_TRACKOWNER = 4,
|
|
|
|
CMF_CHECKTARGETDEAD = 8,
|
|
|
|
|
|
|
|
CMF_ABSOLUTEPITCH = 16,
|
|
|
|
CMF_OFFSETPITCH = 32,
|
|
|
|
CMF_SAVEPITCH = 64,
|
|
|
|
|
|
|
|
CMF_ABSOLUTEANGLE = 128
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomMissile)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_CLASS (ti, AActor);
|
|
|
|
PARAM_FIXED_OPT (spawnheight) { spawnheight = 32*FRACUNIT; }
|
|
|
|
PARAM_INT_OPT (spawnofs_xy) { spawnofs_xy = 0; }
|
|
|
|
PARAM_ANGLE_OPT (angle) { angle = 0; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_ANGLE_OPT (pitch) { pitch = 0; }
|
|
|
|
PARAM_INT_OPT (ptr) { ptr = AAPTR_TARGET; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
int aimmode = flags & CMF_AIMMODE;
|
|
|
|
|
|
|
|
AActor * targ;
|
|
|
|
AActor * missile;
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (ref != NULL || aimmode == 2)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
if (ti)
|
|
|
|
{
|
|
|
|
angle_t ang = (self->angle - ANGLE_90) >> ANGLETOFINESHIFT;
|
2016-01-19 15:09:44 +00:00
|
|
|
fixed_t x = spawnofs_xy * finecosine[ang];
|
|
|
|
fixed_t y = spawnofs_xy * finesine[ang];
|
|
|
|
fixed_t z = spawnheight + self->GetBobOffset() - 32*FRACUNIT + (self->player? self->player->crouchoffset : 0);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 19:15:45 +00:00
|
|
|
fixedvec3 pos = self->Pos();
|
2015-01-30 01:30:26 +00:00
|
|
|
switch (aimmode)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
default:
|
|
|
|
// same adjustment as above (in all 3 directions this time) - for better aiming!
|
2016-01-19 19:15:45 +00:00
|
|
|
self->SetXYZ(self->Vec3Offset(x, y, z));
|
|
|
|
missile = P_SpawnMissileXYZ(self->PosPlusZ(32*FRACUNIT), self, ref, ti, false);
|
|
|
|
self->SetXYZ(pos);
|
2015-01-30 01:30:26 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
2016-01-19 19:15:45 +00:00
|
|
|
missile = P_SpawnMissileXYZ(self->Vec3Offset(x, y, self->GetBobOffset() + spawnheight), self, ref, ti, false);
|
2015-01-30 01:30:26 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 2:
|
2016-01-22 10:06:09 +00:00
|
|
|
self->SetXYZ(self->Vec3Offset(x, y, 0));
|
2016-01-19 19:15:45 +00:00
|
|
|
missile = P_SpawnMissileAngleZSpeed(self, self->Z() + self->GetBobOffset() + spawnheight, ti, self->angle, 0, GetDefaultByType(ti)->Speed, self, false);
|
|
|
|
self->SetXYZ(pos);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
flags |= CMF_ABSOLUTEPITCH;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (missile != NULL)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
// Use the actual velocity instead of the missile's Speed property
|
|
|
|
// so that this can handle missiles with a high vertical velocity
|
|
|
|
// component properly.
|
|
|
|
|
|
|
|
fixed_t missilespeed;
|
|
|
|
|
|
|
|
if ( (CMF_ABSOLUTEPITCH|CMF_OFFSETPITCH) & flags)
|
|
|
|
{
|
|
|
|
if (CMF_OFFSETPITCH & flags)
|
|
|
|
{
|
2016-03-10 19:44:53 +00:00
|
|
|
DVector2 velocity (missile->velx, missile->vely);
|
2016-02-11 19:01:12 +00:00
|
|
|
pitch += R_PointToAngle2(0,0, xs_CRoundToInt(velocity.Length()), missile->velz);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
ang = pitch >> ANGLETOFINESHIFT;
|
|
|
|
missilespeed = abs(FixedMul(finecosine[ang], missile->Speed));
|
|
|
|
missile->velz = FixedMul(finesine[ang], missile->Speed);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-03-10 19:44:53 +00:00
|
|
|
DVector2 velocity (missile->velx, missile->vely);
|
2016-02-11 19:01:12 +00:00
|
|
|
missilespeed = xs_CRoundToInt(velocity.Length());
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (CMF_SAVEPITCH & flags)
|
|
|
|
{
|
|
|
|
missile->pitch = pitch;
|
|
|
|
// In aimmode 0 and 1 without absolutepitch or offsetpitch, the pitch parameter
|
|
|
|
// contains the unapplied parameter. In that case, it is set as pitch without
|
|
|
|
// otherwise affecting the spawned actor.
|
|
|
|
}
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
missile->angle = (CMF_ABSOLUTEANGLE & flags) ? angle : missile->angle + angle ;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
ang = missile->angle >> ANGLETOFINESHIFT;
|
2016-01-19 15:09:44 +00:00
|
|
|
missile->velx = FixedMul(missilespeed, finecosine[ang]);
|
|
|
|
missile->vely = FixedMul(missilespeed, finesine[ang]);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
// handle projectile shooting projectiles - track the
|
|
|
|
// links back to a real owner
|
|
|
|
if (self->isMissile(!!(flags & CMF_TRACKOWNER)))
|
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
AActor *owner = self ;//->target;
|
|
|
|
while (owner->isMissile(!!(flags & CMF_TRACKOWNER)) && owner->target)
|
|
|
|
owner = owner->target;
|
|
|
|
targ = owner;
|
|
|
|
missile->target = owner;
|
2015-01-30 01:30:26 +00:00
|
|
|
// automatic handling of seeker missiles
|
2015-10-13 07:13:37 +00:00
|
|
|
if (self->flags2 & missile->flags2 & MF2_SEEKERMISSILE)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
missile->tracer = self->tracer;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
2016-01-19 15:09:44 +00:00
|
|
|
else if (missile->flags2 & MF2_SEEKERMISSILE)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
// automatic handling of seeker missiles
|
2016-01-19 15:09:44 +00:00
|
|
|
missile->tracer = self->target;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
// 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.
|
2016-01-19 15:09:44 +00:00
|
|
|
if (self->SeeState != NULL && (self->health > 0 || !(self->flags3 & MF3_ISMONSTER)))
|
|
|
|
self->SetState(self->SeeState);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// An even more customizable hitscan attack
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
enum CBA_Flags
|
|
|
|
{
|
|
|
|
CBAF_AIMFACING = 1,
|
|
|
|
CBAF_NORANDOM = 2,
|
|
|
|
CBAF_EXPLICITANGLE = 4,
|
|
|
|
CBAF_NOPITCH = 8,
|
|
|
|
CBAF_NORANDOMPUFFZ = 16,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomBulletAttack)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_ANGLE (spread_xy);
|
|
|
|
PARAM_ANGLE (spread_z);
|
|
|
|
PARAM_INT (numbullets);
|
|
|
|
PARAM_INT (damageperbullet);
|
|
|
|
PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor(NAME_BulletPuff); }
|
|
|
|
PARAM_FIXED_OPT (range) { range = MISSILERANGE; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_INT_OPT (ptr) { ptr = AAPTR_TARGET; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (range == 0)
|
|
|
|
range = MISSILERANGE;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
int i;
|
|
|
|
int bangle;
|
|
|
|
int bslope = 0;
|
2016-01-19 15:09:44 +00:00
|
|
|
int laflags = (flags & CBAF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (ref != NULL || (flags & CBAF_AIMFACING))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!(flags & CBAF_AIMFACING))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
A_Face(self, ref);
|
|
|
|
}
|
|
|
|
bangle = self->angle;
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!(flags & CBAF_NOPITCH)) bslope = P_AimLineAttack (self, bangle, MISSILERANGE);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM);
|
2016-01-19 15:09:44 +00:00
|
|
|
for (i = 0; i < numbullets; i++)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
int angle = bangle;
|
|
|
|
int slope = bslope;
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (flags & CBAF_EXPLICITANGLE)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
angle += spread_xy;
|
|
|
|
slope += spread_z;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
angle += pr_cwbullet.Random2() * (spread_xy / 255);
|
|
|
|
slope += pr_cwbullet.Random2() * (spread_z / 255);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
int damage = damageperbullet;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!(flags & CBAF_NORANDOM))
|
2015-01-30 01:30:26 +00:00
|
|
|
damage *= ((pr_cabullet()%3)+1);
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype, laflags);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A fully customizable melee attack
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomMeleeAttack)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT_OPT (damage) { damage = 0; }
|
|
|
|
PARAM_SOUND_OPT (meleesound) { meleesound = 0; }
|
|
|
|
PARAM_SOUND_OPT (misssound) { misssound = 0; }
|
|
|
|
PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; }
|
|
|
|
PARAM_BOOL_OPT (bleed) { bleed = true; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (damagetype == NAME_None)
|
|
|
|
damagetype = NAME_Melee; // Melee is the default type
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (!self->target)
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
A_FaceTarget (self);
|
|
|
|
if (self->CheckMeleeRange ())
|
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
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);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
if (misssound)
|
|
|
|
S_Sound (self, CHAN_WEAPON, misssound, 1, ATTN_NORM);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A fully customizable combo attack
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomComboAttack)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_CLASS (ti, AActor);
|
|
|
|
PARAM_FIXED (spawnheight);
|
|
|
|
PARAM_INT (damage);
|
|
|
|
PARAM_SOUND_OPT (meleesound) { meleesound = 0; }
|
|
|
|
PARAM_NAME_OPT (damagetype) { damagetype = NAME_Melee; }
|
|
|
|
PARAM_BOOL_OPT (bleed) { bleed = true; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (!self->target)
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
A_FaceTarget (self);
|
2016-01-19 15:09:44 +00:00
|
|
|
if (self->CheckMeleeRange())
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
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);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else if (ti)
|
|
|
|
{
|
|
|
|
// This seemingly senseless code is needed for proper aiming.
|
2016-01-19 19:15:45 +00:00
|
|
|
self->AddZ(spawnheight + self->GetBobOffset() - 32*FRACUNIT);
|
|
|
|
AActor *missile = P_SpawnMissileXYZ (self->PosPlusZ(32*FRACUNIT), self, self->target, ti, false);
|
|
|
|
self->AddZ(-(spawnheight + self->GetBobOffset() - 32*FRACUNIT));
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (missile)
|
|
|
|
{
|
|
|
|
// automatic handling of seeker missiles
|
2016-01-19 15:09:44 +00:00
|
|
|
if (missile->flags2 & MF2_SEEKERMISSILE)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
missile->tracer = self->target;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
P_CheckMissileSpawn(missile, self->radius);
|
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// State jump function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfNoAmmo)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (!ACTION_CALL_FROM_WEAPON())
|
2016-02-19 02:39:40 +00:00
|
|
|
{
|
|
|
|
ACTION_RETURN_STATE(NULL);
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (!self->player->ReadyWeapon->CheckAmmo(self->player->ReadyWeapon->bAltFire, false, true))
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// An even more customizable hitscan attack
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
enum FB_Flags
|
|
|
|
{
|
|
|
|
FBF_USEAMMO = 1,
|
|
|
|
FBF_NORANDOM = 2,
|
|
|
|
FBF_EXPLICITANGLE = 4,
|
|
|
|
FBF_NOPITCH = 8,
|
|
|
|
FBF_NOFLASH = 16,
|
|
|
|
FBF_NORANDOMPUFFZ = 32,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_ANGLE (spread_xy);
|
|
|
|
PARAM_ANGLE (spread_z);
|
|
|
|
PARAM_INT (numbullets);
|
|
|
|
PARAM_INT (damageperbullet);
|
|
|
|
PARAM_CLASS_OPT (pufftype, AActor) { pufftype = NULL; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = FBF_USEAMMO; }
|
|
|
|
PARAM_FIXED_OPT (range) { range = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (!self->player) return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
player_t *player = self->player;
|
|
|
|
AWeapon *weapon = player->ReadyWeapon;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
int i;
|
|
|
|
int bangle;
|
|
|
|
int bslope = 0;
|
2016-01-19 15:09:44 +00:00
|
|
|
int laflags = (flags & FBF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-26 08:43:47 +00:00
|
|
|
if ((flags & FBF_USEAMMO) && weapon && ACTION_CALL_FROM_WEAPON())
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!weapon->DepleteAmmo(weapon->bAltFire, true))
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0; // out of ammo
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (range == 0)
|
|
|
|
range = PLAYERMISSILERANGE;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!(flags & FBF_NOFLASH)) static_cast<APlayerPawn *>(self)->PlayAttacking2 ();
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!(flags & FBF_NOPITCH)) bslope = P_BulletSlope(self);
|
2015-01-30 01:30:26 +00:00
|
|
|
bangle = self->angle;
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (pufftype == NULL)
|
2016-02-04 21:15:29 +00:00
|
|
|
pufftype = PClass::FindActor(NAME_BulletPuff);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (weapon != NULL)
|
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
S_Sound(self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if ((numbullets == 1 && !player->refire) || numbullets == 0)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
int damage = damageperbullet;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!(flags & FBF_NORANDOM))
|
2015-01-30 01:30:26 +00:00
|
|
|
damage *= ((pr_cwbullet()%3)+1);
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
P_LineAttack(self, bangle, range, bslope, damage, NAME_Hitscan, pufftype, laflags);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
if (numbullets < 0)
|
|
|
|
numbullets = 1;
|
|
|
|
for (i = 0; i < numbullets; i++)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
int angle = bangle;
|
|
|
|
int slope = bslope;
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (flags & FBF_EXPLICITANGLE)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
angle += spread_xy;
|
|
|
|
slope += spread_z;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
angle += pr_cwbullet.Random2() * (spread_xy / 255);
|
|
|
|
slope += pr_cwbullet.Random2() * (spread_z / 255);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
int damage = damageperbullet;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!(flags & FBF_NORANDOM))
|
2015-01-30 01:30:26 +00:00
|
|
|
damage *= ((pr_cwbullet()%3)+1);
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype, laflags);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_FireProjectile
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
enum FP_Flags
|
|
|
|
{
|
|
|
|
FPF_AIMATANGLE = 1,
|
|
|
|
FPF_TRANSFERTRANSLATION = 2,
|
2015-09-08 15:40:21 +00:00
|
|
|
FPF_NOAUTOAIM = 4,
|
2015-01-30 01:30:26 +00:00
|
|
|
};
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireCustomMissile)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_CLASS (ti, AActor);
|
|
|
|
PARAM_ANGLE_OPT (angle) { angle = 0; }
|
|
|
|
PARAM_BOOL_OPT (useammo) { useammo = true; }
|
|
|
|
PARAM_INT_OPT (spawnofs_xy) { spawnofs_xy = 0; }
|
|
|
|
PARAM_FIXED_OPT (spawnheight) { spawnheight = 0; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_ANGLE_OPT (pitch) { pitch = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (!self->player)
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
player_t *player = self->player;
|
|
|
|
AWeapon *weapon = player->ReadyWeapon;
|
2016-03-01 15:38:45 +00:00
|
|
|
FTranslatedLineTarget t;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2015-03-10 15:58:10 +00:00
|
|
|
// Only use ammo if called from a weapon
|
2016-01-19 15:09:44 +00:00
|
|
|
if (useammo && ACTION_CALL_FROM_WEAPON() && weapon)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!weapon->DepleteAmmo(weapon->bAltFire, true))
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0; // out of ammo
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ti)
|
|
|
|
{
|
|
|
|
angle_t ang = (self->angle - ANGLE_90) >> ANGLETOFINESHIFT;
|
2016-01-19 15:09:44 +00:00
|
|
|
fixed_t x = spawnofs_xy * finecosine[ang];
|
|
|
|
fixed_t y = spawnofs_xy * finesine[ang];
|
|
|
|
fixed_t z = spawnheight;
|
2015-01-30 01:30:26 +00:00
|
|
|
fixed_t shootangle = self->angle;
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (flags & FPF_AIMATANGLE) shootangle += angle;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
// Temporarily adjusts the pitch
|
2016-01-19 15:09:44 +00:00
|
|
|
fixed_t saved_player_pitch = self->pitch;
|
2015-01-30 01:30:26 +00:00
|
|
|
self->pitch -= pitch;
|
2016-03-01 15:38:45 +00:00
|
|
|
AActor * misl=P_SpawnPlayerMissile (self, x, y, z, ti, shootangle, &t, NULL, false, (flags & FPF_NOAUTOAIM) != 0);
|
2016-01-19 15:09:44 +00:00
|
|
|
self->pitch = saved_player_pitch;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
// automatic handling of seeker missiles
|
|
|
|
if (misl)
|
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
if (flags & FPF_TRANSFERTRANSLATION)
|
|
|
|
misl->Translation = self->Translation;
|
2016-03-01 15:38:45 +00:00
|
|
|
if (t.linetarget && !t.unlinked && (misl->flags2 & MF2_SEEKERMISSILE))
|
|
|
|
misl->tracer = t.linetarget;
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!(flags & FPF_AIMATANGLE))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
// This original implementation is to aim straight ahead and then offset
|
|
|
|
// the angle from the resulting direction.
|
2016-03-10 19:44:53 +00:00
|
|
|
DVector3 velocity(misl->velx, misl->vely, 0);
|
2016-02-11 19:01:12 +00:00
|
|
|
fixed_t missilespeed = xs_CRoundToInt(velocity.Length());
|
2016-01-19 15:09:44 +00:00
|
|
|
misl->angle += angle;
|
2015-01-30 01:30:26 +00:00
|
|
|
angle_t an = misl->angle >> ANGLETOFINESHIFT;
|
|
|
|
misl->velx = FixedMul (missilespeed, finecosine[an]);
|
|
|
|
misl->vely = FixedMul (missilespeed, finesine[an]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_CustomPunch
|
|
|
|
//
|
|
|
|
// Berserk is not handled here. That can be done with A_CheckIfInventory
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
CPF_USEAMMO = 1,
|
|
|
|
CPF_DAGGER = 2,
|
|
|
|
CPF_PULLIN = 4,
|
|
|
|
CPF_NORANDOMPUFFZ = 8,
|
|
|
|
CPF_NOTURN = 16,
|
|
|
|
CPF_STEALARMOR = 32,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomPunch)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (damage);
|
|
|
|
PARAM_BOOL_OPT (norandom) { norandom = false; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = CPF_USEAMMO; }
|
|
|
|
PARAM_CLASS_OPT (pufftype, AActor) { pufftype = NULL; }
|
|
|
|
PARAM_FIXED_OPT (range) { range = 0; }
|
|
|
|
PARAM_FIXED_OPT (lifesteal) { lifesteal = 0; }
|
|
|
|
PARAM_INT_OPT (lifestealmax) { lifestealmax = 0; }
|
|
|
|
PARAM_CLASS_OPT (armorbonustype, ABasicArmorBonus) { armorbonustype = NULL; }
|
|
|
|
PARAM_SOUND_OPT (MeleeSound) { MeleeSound = ""; }
|
|
|
|
PARAM_SOUND_OPT (MissSound) { MissSound = ""; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (!self->player)
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
player_t *player = self->player;
|
|
|
|
AWeapon *weapon = player->ReadyWeapon;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
angle_t angle;
|
|
|
|
int pitch;
|
2016-03-01 15:38:45 +00:00
|
|
|
FTranslatedLineTarget t;
|
2015-01-30 01:30:26 +00:00
|
|
|
int actualdamage;
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!norandom)
|
|
|
|
damage *= pr_cwpunch() % 8 + 1;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
angle = self->angle + (pr_cwpunch.Random2() << 18);
|
2016-01-19 15:09:44 +00:00
|
|
|
if (range == 0)
|
|
|
|
range = MELEERANGE;
|
2016-03-01 15:38:45 +00:00
|
|
|
pitch = P_AimLineAttack (self, angle, range, &t);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
// only use ammo when actually hitting something!
|
2016-03-01 15:38:45 +00:00
|
|
|
if ((flags & CPF_USEAMMO) && t.linetarget && weapon && ACTION_CALL_FROM_WEAPON())
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!weapon->DepleteAmmo(weapon->bAltFire, true))
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0; // out of ammo
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (pufftype == NULL)
|
2016-02-04 21:15:29 +00:00
|
|
|
pufftype = PClass::FindActor(NAME_BulletPuff);
|
2015-01-30 01:30:26 +00:00
|
|
|
int puffFlags = LAF_ISMELEEATTACK | ((flags & CPF_NORANDOMPUFFZ) ? LAF_NORANDOMPUFFZ : 0);
|
|
|
|
|
2016-03-01 15:38:45 +00:00
|
|
|
P_LineAttack (self, angle, range, pitch, damage, NAME_Melee, pufftype, puffFlags, &t, &actualdamage);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-03-01 15:38:45 +00:00
|
|
|
if (!t.linetarget)
|
2015-11-30 17:42:08 +00:00
|
|
|
{
|
|
|
|
if (MissSound) S_Sound(self, CHAN_WEAPON, MissSound, 1, ATTN_NORM);
|
|
|
|
}
|
|
|
|
else
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-03-01 15:38:45 +00:00
|
|
|
if (lifesteal && !(t.linetarget->flags5 & MF5_DONTDRAIN))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
if (flags & CPF_STEALARMOR)
|
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
if (armorbonustype == NULL)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
armorbonustype = dyn_cast<ABasicArmorBonus::MetaClass>(PClass::FindClass("ArmorBonus"));
|
2016-01-19 15:09:44 +00:00
|
|
|
}
|
|
|
|
if (armorbonustype != NULL)
|
|
|
|
{
|
|
|
|
assert(armorbonustype->IsDescendantOf(RUNTIME_CLASS(ABasicArmorBonus)));
|
|
|
|
ABasicArmorBonus *armorbonus = static_cast<ABasicArmorBonus *>(Spawn(armorbonustype, 0,0,0, NO_REPLACE));
|
|
|
|
armorbonus->SaveAmount *= (actualdamage * lifesteal) >> FRACBITS;
|
2015-01-30 01:30:26 +00:00
|
|
|
armorbonus->MaxSaveAmount = lifestealmax <= 0 ? armorbonus->MaxSaveAmount : lifestealmax;
|
|
|
|
armorbonus->flags |= MF_DROPPED;
|
|
|
|
armorbonus->ClearCounters();
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!armorbonus->CallTryPickup(self))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
armorbonus->Destroy ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
P_GiveBody (self, (actualdamage * lifesteal) >> FRACBITS, lifestealmax);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (weapon != NULL)
|
|
|
|
{
|
2015-11-30 17:42:08 +00:00
|
|
|
if (MeleeSound) S_Sound(self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM);
|
|
|
|
else S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!(flags & CPF_NOTURN))
|
|
|
|
{
|
|
|
|
// turn to face target
|
2016-03-02 19:44:02 +00:00
|
|
|
self->angle = t.angleFromSource;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (flags & CPF_PULLIN) self->flags |= MF_JUSTATTACKED;
|
2016-03-01 15:38:45 +00:00
|
|
|
if (flags & CPF_DAGGER) P_DaggerAlert (self, t.linetarget);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// customizable railgun attack function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (damage);
|
|
|
|
PARAM_INT_OPT (spawnofs_xy) { spawnofs_xy = 0; }
|
|
|
|
PARAM_BOOL_OPT (useammo) { useammo = true; }
|
|
|
|
PARAM_COLOR_OPT (color1) { color1 = 0; }
|
|
|
|
PARAM_COLOR_OPT (color2) { color2 = 0; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_FLOAT_OPT (maxdiff) { maxdiff = 0; }
|
|
|
|
PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor(NAME_BulletPuff); }
|
|
|
|
PARAM_ANGLE_OPT (spread_xy) { spread_xy = 0; }
|
|
|
|
PARAM_ANGLE_OPT (spread_z) { spread_z = 0; }
|
|
|
|
PARAM_FIXED_OPT (range) { range = 0; }
|
|
|
|
PARAM_INT_OPT (duration) { duration = 0; }
|
|
|
|
PARAM_FLOAT_OPT (sparsity) { sparsity = 1; }
|
|
|
|
PARAM_FLOAT_OPT (driftspeed) { driftspeed = 1; }
|
|
|
|
PARAM_CLASS_OPT (spawnclass, AActor){ spawnclass = NULL; }
|
|
|
|
PARAM_FIXED_OPT (spawnofs_z) { spawnofs_z = 0; }
|
|
|
|
PARAM_INT_OPT (SpiralOffset) { SpiralOffset = 270; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (range == 0) range = 8192*FRACUNIT;
|
|
|
|
if (sparsity == 0) sparsity=1.0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (self->player == NULL)
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
AWeapon *weapon = self->player->ReadyWeapon;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
// only use ammo when actually hitting something!
|
2016-01-26 08:43:47 +00:00
|
|
|
if (useammo && weapon != NULL && ACTION_CALL_FROM_WEAPON())
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!weapon->DepleteAmmo(weapon->bAltFire, true))
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0; // out of ammo
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
angle_t angle;
|
|
|
|
angle_t slope;
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (flags & RAF_EXPLICITANGLE)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
angle = spread_xy;
|
|
|
|
slope = spread_z;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
angle = pr_crailgun.Random2() * (spread_xy / 255);
|
|
|
|
slope = pr_crailgun.Random2() * (spread_z / 255);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
P_RailAttack (self, damage, spawnofs_xy, spawnofs_z, color1, color2, maxdiff, flags, pufftype, angle, slope, range, duration, sparsity, driftspeed, spawnclass, SpiralOffset);
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// also for monsters
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
CRF_DONTAIM = 0,
|
|
|
|
CRF_AIMPARALLEL = 1,
|
|
|
|
CRF_AIMDIRECT = 2,
|
|
|
|
CRF_EXPLICITANGLE = 4,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (damage);
|
|
|
|
PARAM_INT_OPT (spawnofs_xy) { spawnofs_xy = 0; }
|
|
|
|
PARAM_COLOR_OPT (color1) { color1 = 0; }
|
|
|
|
PARAM_COLOR_OPT (color2) { color2 = 0; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_INT_OPT (aim) { aim = CRF_DONTAIM; }
|
|
|
|
PARAM_FLOAT_OPT (maxdiff) { maxdiff = 0; }
|
|
|
|
PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor(NAME_BulletPuff); }
|
|
|
|
PARAM_ANGLE_OPT (spread_xy) { spread_xy = 0; }
|
|
|
|
PARAM_ANGLE_OPT (spread_z) { spread_z = 0; }
|
|
|
|
PARAM_FIXED_OPT (range) { range = 0; }
|
|
|
|
PARAM_INT_OPT (duration) { duration = 0; }
|
|
|
|
PARAM_FLOAT_OPT (sparsity) { sparsity = 1; }
|
|
|
|
PARAM_FLOAT_OPT (driftspeed) { driftspeed = 1; }
|
|
|
|
PARAM_CLASS_OPT (spawnclass, AActor){ spawnclass = NULL; }
|
|
|
|
PARAM_FIXED_OPT (spawnofs_z) { spawnofs_z = 0; }
|
|
|
|
PARAM_INT_OPT (SpiralOffset) { SpiralOffset = 270; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (range == 0) range = 8192*FRACUNIT;
|
|
|
|
if (sparsity == 0) sparsity = 1;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-03-01 15:38:45 +00:00
|
|
|
FTranslatedLineTarget t;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 19:15:45 +00:00
|
|
|
fixedvec3 savedpos = self->Pos();
|
2015-01-30 01:30:26 +00:00
|
|
|
angle_t saved_angle = self->angle;
|
|
|
|
fixed_t saved_pitch = self->pitch;
|
|
|
|
|
|
|
|
if (aim && self->target == NULL)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
// [RH] Andy Baker's stealth monsters
|
|
|
|
if (self->flags & MF_STEALTH)
|
|
|
|
{
|
|
|
|
self->visdir = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
self->flags &= ~MF_AMBUSH;
|
|
|
|
|
|
|
|
|
|
|
|
if (aim)
|
|
|
|
{
|
2016-01-10 19:46:26 +00:00
|
|
|
self->angle = self->AngleTo(self->target);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-03-01 15:38:45 +00:00
|
|
|
self->pitch = P_AimLineAttack (self, self->angle, MISSILERANGE, &t, ANGLE_1*60, 0, aim ? self->target : NULL);
|
|
|
|
if (t.linetarget == NULL && aim)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
// We probably won't hit the target, but aim at it anyway so we don't look stupid.
|
2016-01-19 19:15:45 +00:00
|
|
|
fixedvec2 pos = self->Vec2To(self->target);
|
2016-03-10 19:44:53 +00:00
|
|
|
DVector2 xydiff(pos.x, pos.y);
|
2016-01-19 19:15:45 +00:00
|
|
|
double zdiff = (self->target->Z() + (self->target->height>>1)) -
|
|
|
|
(self->Z() + (self->height>>1) - self->floorclip);
|
2015-01-30 01:30:26 +00:00
|
|
|
self->pitch = int(atan2(zdiff, xydiff.Length()) * ANGLE_180 / -M_PI);
|
|
|
|
}
|
|
|
|
// Let the aim trail behind the player
|
|
|
|
if (aim)
|
|
|
|
{
|
2016-01-10 19:46:26 +00:00
|
|
|
saved_angle = self->angle = self->AngleTo(self->target, -self->target->velx * 3, -self->target->vely * 3);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
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.
|
2016-01-19 19:15:45 +00:00
|
|
|
self->SetXY(self->Vec2Offset(
|
|
|
|
spawnofs_xy * finecosine[self->angle],
|
|
|
|
spawnofs_xy * finesine[self->angle]));
|
2016-01-19 15:09:44 +00:00
|
|
|
spawnofs_xy = 0;
|
2016-01-10 19:46:26 +00:00
|
|
|
self->angle = self->AngleTo(self->target,- self->target->velx * 3, -self->target->vely * 3);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (self->target->flags & MF_SHADOW)
|
|
|
|
{
|
|
|
|
angle_t rnd = pr_crailgun.Random2() << 21;
|
|
|
|
self->angle += rnd;
|
|
|
|
saved_angle = rnd;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
angle_t angle = (self->angle - ANG90) >> ANGLETOFINESHIFT;
|
|
|
|
|
|
|
|
angle_t angleoffset;
|
|
|
|
angle_t slopeoffset;
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (flags & CRF_EXPLICITANGLE)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
angleoffset = spread_xy;
|
|
|
|
slopeoffset = spread_z;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
angleoffset = pr_crailgun.Random2() * (spread_xy / 255);
|
|
|
|
slopeoffset = pr_crailgun.Random2() * (spread_z / 255);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
P_RailAttack (self, damage, spawnofs_xy, spawnofs_z, color1, color2, maxdiff, flags, pufftype, angleoffset, slopeoffset, range, duration, sparsity, driftspeed, spawnclass,SpiralOffset);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 19:15:45 +00:00
|
|
|
self->SetXYZ(savedpos);
|
2015-01-30 01:30:26 +00:00
|
|
|
self->angle = saved_angle;
|
|
|
|
self->pitch = saved_pitch;
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// DoGiveInventory
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
2016-02-19 02:39:40 +00:00
|
|
|
static bool DoGiveInventory(AActor *receiver, bool orresult, VM_ARGS)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
int paramnum = NAP-1;
|
|
|
|
PARAM_CLASS (mi, AInventory);
|
|
|
|
PARAM_INT_OPT (amount) { amount = 1; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (!orresult)
|
2015-01-30 01:38:16 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_INT_OPT(setreceiver) { setreceiver = AAPTR_DEFAULT; }
|
|
|
|
receiver = COPY_AAPTR(receiver, setreceiver);
|
|
|
|
}
|
|
|
|
if (receiver == NULL)
|
|
|
|
{ // If there's nothing to receive it, it's obviously a fail, right?
|
2016-02-19 02:39:40 +00:00
|
|
|
return false;
|
2015-01-30 01:38:16 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (amount <= 0)
|
|
|
|
{
|
|
|
|
amount = 1;
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
if (mi)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
AInventory *item = static_cast<AInventory *>(Spawn(mi, 0, 0, 0, NO_REPLACE));
|
|
|
|
if (item == NULL)
|
2015-01-30 01:38:16 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
return false;
|
2015-01-30 01:38:16 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
if (item->IsKindOf(RUNTIME_CLASS(AHealth)))
|
|
|
|
{
|
|
|
|
item->Amount *= amount;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
item->Amount = amount;
|
|
|
|
}
|
|
|
|
item->flags |= MF_DROPPED;
|
|
|
|
item->ClearCounters();
|
2016-02-04 21:15:29 +00:00
|
|
|
if (!item->CallTryPickup(receiver))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
item->Destroy();
|
2016-02-19 02:39:40 +00:00
|
|
|
return false;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
else
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
return true;
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
return false;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveInventory)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-21 03:27:42 +00:00
|
|
|
ACTION_RETURN_BOOL(DoGiveInventory(self, false, VM_ARGS_NAMES));
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToTarget)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-21 03:27:42 +00:00
|
|
|
ACTION_RETURN_BOOL(DoGiveInventory(self->target, false, VM_ARGS_NAMES));
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToChildren)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
TThinkerIterator<AActor> it;
|
2016-02-04 21:15:29 +00:00
|
|
|
AActor *mo;
|
2016-02-19 02:39:40 +00:00
|
|
|
int count = 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
while ((mo = it.Next()))
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
if (mo->master == self)
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
count += DoGiveInventory(mo, true, VM_ARGS_NAMES);
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_INT(count);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToSiblings)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
TThinkerIterator<AActor> it;
|
2016-02-04 21:15:29 +00:00
|
|
|
AActor *mo;
|
2016-02-19 02:39:40 +00:00
|
|
|
int count = 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (self->master != NULL)
|
|
|
|
{
|
|
|
|
while ((mo = it.Next()))
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
if (mo->master == self->master && mo != self)
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
count += DoGiveInventory(mo, true, VM_ARGS_NAMES);
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_INT(count);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_TakeInventory
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
TIF_NOTAKEINFINITE = 1,
|
|
|
|
};
|
|
|
|
|
2016-02-19 02:39:40 +00:00
|
|
|
bool DoTakeInventory(AActor *receiver, bool orresult, VM_ARGS)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
int paramnum = NAP-1;
|
|
|
|
PARAM_CLASS (itemtype, AInventory);
|
|
|
|
PARAM_INT_OPT (amount) { amount = 0; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (itemtype == NULL)
|
2015-01-30 01:38:16 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
return true;
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
|
|
|
if (!orresult)
|
|
|
|
{
|
|
|
|
PARAM_INT_OPT(setreceiver) { setreceiver = AAPTR_DEFAULT; }
|
|
|
|
receiver = COPY_AAPTR(receiver, setreceiver);
|
2015-01-30 01:38:16 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
if (receiver == NULL)
|
2015-01-30 01:38:16 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
return false;
|
2015-01-30 01:38:16 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-19 02:39:40 +00:00
|
|
|
return receiver->TakeInventory(itemtype, amount, true, (flags & TIF_NOTAKEINFINITE) != 0);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeInventory)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-21 03:27:42 +00:00
|
|
|
ACTION_RETURN_BOOL(DoTakeInventory(self, false, VM_ARGS_NAMES));
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromTarget)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-21 03:27:42 +00:00
|
|
|
ACTION_RETURN_BOOL(DoTakeInventory(self->target, false, VM_ARGS_NAMES));
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromChildren)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2015-01-30 01:30:26 +00:00
|
|
|
TThinkerIterator<AActor> it;
|
2016-02-19 02:39:40 +00:00
|
|
|
AActor *mo;
|
|
|
|
int count = 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
while ((mo = it.Next()))
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
if (mo->master == self)
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
count += DoTakeInventory(mo, true, VM_ARGS_NAMES);
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_INT(count);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromSiblings)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2015-01-30 01:30:26 +00:00
|
|
|
TThinkerIterator<AActor> it;
|
2016-02-19 02:39:40 +00:00
|
|
|
AActor *mo;
|
|
|
|
int count = 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (self->master != NULL)
|
|
|
|
{
|
|
|
|
while ((mo = it.Next()))
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
if (mo->master == self->master && mo != self)
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
count += DoTakeInventory(mo, true, VM_ARGS_NAMES);
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_INT(count);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// Common code for A_SpawnItem and A_SpawnItemEx
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
enum SIX_Flags
|
|
|
|
{
|
2015-02-07 16:04:33 +00:00
|
|
|
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,
|
2015-02-14 22:51:05 +00:00
|
|
|
SIXF_TRANSFERROLL = 0x02000000,
|
2015-08-09 19:06:22 +00:00
|
|
|
SIXF_ISTARGET = 0x04000000,
|
|
|
|
SIXF_ISMASTER = 0x08000000,
|
|
|
|
SIXF_ISTRACER = 0x10000000,
|
2015-01-30 01:30:26 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static bool InitSpawnedItem(AActor *self, AActor *mo, int flags)
|
|
|
|
{
|
|
|
|
if (mo == NULL)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
AActor *originator = self;
|
|
|
|
|
|
|
|
if (!(mo->flags2 & MF2_DONTTRANSLATE))
|
|
|
|
{
|
|
|
|
if (flags & SIXF_TRANSFERTRANSLATION)
|
|
|
|
{
|
|
|
|
mo->Translation = self->Translation;
|
|
|
|
}
|
|
|
|
else if (flags & SIXF_USEBLOODCOLOR)
|
|
|
|
{
|
|
|
|
// [XA] Use the spawning actor's BloodColor to translate the newly-spawned object.
|
|
|
|
PalEntry bloodcolor = self->GetBloodColor();
|
|
|
|
mo->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (flags & SIXF_TRANSFERPOINTERS)
|
|
|
|
{
|
|
|
|
mo->target = self->target;
|
|
|
|
mo->master = self->master; // This will be overridden later if SIXF_SETMASTER is set
|
|
|
|
mo->tracer = self->tracer;
|
|
|
|
}
|
|
|
|
|
|
|
|
mo->angle = self->angle;
|
|
|
|
if (flags & SIXF_TRANSFERPITCH)
|
|
|
|
{
|
|
|
|
mo->pitch = self->pitch;
|
|
|
|
}
|
|
|
|
if (!(flags & SIXF_ORIGINATOR))
|
|
|
|
{
|
|
|
|
while (originator && originator->isMissile())
|
|
|
|
{
|
|
|
|
originator = originator->target;
|
|
|
|
}
|
2016-01-19 15:09:44 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
if (flags & SIXF_TELEFRAG)
|
|
|
|
{
|
2016-01-19 19:15:45 +00:00
|
|
|
P_TeleportMove(mo, mo->Pos(), true);
|
2015-01-30 01:30:26 +00:00
|
|
|
// 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)
|
2016-01-19 15:09:44 +00:00
|
|
|
{ // don't let it attack you (optional)!
|
2015-01-30 01:30:26 +00:00
|
|
|
mo->master = originator;
|
|
|
|
}
|
|
|
|
if (flags & SIXF_SETTARGET)
|
|
|
|
{
|
|
|
|
mo->target = originator;
|
|
|
|
}
|
|
|
|
if (flags & SIXF_SETTRACER)
|
|
|
|
{
|
|
|
|
mo->tracer = originator;
|
|
|
|
}
|
|
|
|
if (flags & SIXF_TRANSFERSCALE)
|
|
|
|
{
|
|
|
|
mo->scaleX = self->scaleX;
|
|
|
|
mo->scaleY = self->scaleY;
|
|
|
|
}
|
|
|
|
if (flags & SIXF_TRANSFERAMBUSHFLAG)
|
|
|
|
{
|
|
|
|
mo->flags = (mo->flags & ~MF_AMBUSH) | (self->flags & MF_AMBUSH);
|
|
|
|
}
|
|
|
|
if (flags & SIXF_CLEARCALLERTID)
|
|
|
|
{
|
|
|
|
self->RemoveFromHash();
|
|
|
|
self->tid = 0;
|
|
|
|
}
|
|
|
|
if (flags & SIXF_TRANSFERSPECIAL)
|
|
|
|
{
|
|
|
|
mo->special = self->special;
|
|
|
|
memcpy(mo->args, self->args, sizeof(self->args));
|
|
|
|
}
|
|
|
|
if (flags & SIXF_CLEARCALLERSPECIAL)
|
|
|
|
{
|
|
|
|
self->special = 0;
|
|
|
|
memset(self->args, 0, sizeof(self->args));
|
|
|
|
}
|
|
|
|
if (flags & SIXF_TRANSFERSTENCILCOL)
|
|
|
|
{
|
|
|
|
mo->fillcolor = self->fillcolor;
|
|
|
|
}
|
|
|
|
if (flags & SIXF_TRANSFERALPHA)
|
|
|
|
{
|
|
|
|
mo->alpha = self->alpha;
|
|
|
|
}
|
|
|
|
if (flags & SIXF_TRANSFERRENDERSTYLE)
|
|
|
|
{
|
|
|
|
mo->RenderStyle = self->RenderStyle;
|
|
|
|
}
|
2015-02-07 16:04:33 +00:00
|
|
|
|
2015-04-04 16:40:43 +00:00
|
|
|
if (flags & SIXF_TRANSFERSPRITEFRAME)
|
|
|
|
{
|
|
|
|
mo->sprite = self->sprite;
|
|
|
|
mo->frame = self->frame;
|
2015-02-07 16:04:33 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2015-02-14 22:51:05 +00:00
|
|
|
if (flags & SIXF_TRANSFERROLL)
|
|
|
|
{
|
|
|
|
mo->roll = self->roll;
|
|
|
|
}
|
|
|
|
|
2015-08-09 19:06:22 +00:00
|
|
|
if (flags & SIXF_ISTARGET)
|
|
|
|
{
|
|
|
|
self->target = mo;
|
|
|
|
}
|
|
|
|
if (flags & SIXF_ISMASTER)
|
|
|
|
{
|
|
|
|
self->master = mo;
|
|
|
|
}
|
|
|
|
if (flags & SIXF_ISTRACER)
|
|
|
|
{
|
|
|
|
self->tracer = mo;
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SpawnItem
|
|
|
|
//
|
|
|
|
// Spawns an item in front of the caller like Heretic's time bomb
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItem)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_CLASS_OPT (missile, AActor) { missile = PClass::FindActor("Unknown"); }
|
|
|
|
PARAM_FIXED_OPT (distance) { distance = 0; }
|
|
|
|
PARAM_FIXED_OPT (zheight) { zheight = 0; }
|
|
|
|
PARAM_BOOL_OPT (useammo) { useammo = true; }
|
|
|
|
PARAM_BOOL_OPT (transfer_translation) { transfer_translation = false; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (missile == NULL)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(false);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Don't spawn monsters if this actor has been massacred
|
2016-02-04 21:15:29 +00:00
|
|
|
if (self->DamageType == NAME_Massacre && (GetDefaultByType(missile)->flags3 & MF3_ISMONSTER))
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(true);
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (distance == 0)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
// use the minimum distance that does not result in an overlap
|
2016-02-04 21:15:29 +00:00
|
|
|
distance = (self->radius + GetDefaultByType(missile)->radius) >> FRACBITS;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ACTION_CALL_FROM_WEAPON())
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
// Used from a weapon, so use some ammo
|
|
|
|
AWeapon *weapon = self->player->ReadyWeapon;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (weapon == NULL)
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(true);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
if (useammo && !weapon->DepleteAmmo(weapon->bAltFire))
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(true);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
AActor *mo = Spawn( missile, self->Vec3Angle(distance, self->angle, -self->floorclip + self->GetBobOffset() + zheight), ALLOW_REPLACE);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
int flags = (transfer_translation ? SIXF_TRANSFERTRANSLATION : 0) + (useammo ? SIXF_SETMASTER : 0);
|
2016-02-21 03:27:42 +00:00
|
|
|
ACTION_RETURN_BOOL(InitSpawnedItem(self, mo, flags)); // for an inventory item's use state
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SpawnItemEx
|
|
|
|
//
|
|
|
|
// Enhanced spawning function
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItemEx)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_CLASS (missile, AActor);
|
|
|
|
PARAM_FIXED_OPT (xofs) { xofs = 0; }
|
|
|
|
PARAM_FIXED_OPT (yofs) { yofs = 0; }
|
|
|
|
PARAM_FIXED_OPT (zofs) { zofs = 0; }
|
|
|
|
PARAM_FIXED_OPT (xvel) { xvel = 0; }
|
|
|
|
PARAM_FIXED_OPT (yvel) { yvel = 0; }
|
|
|
|
PARAM_FIXED_OPT (zvel) { zvel = 0; }
|
|
|
|
PARAM_ANGLE_OPT (angle) { angle = 0; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_INT_OPT (chance) { chance = 0; }
|
|
|
|
PARAM_INT_OPT (tid) { tid = 0; }
|
|
|
|
|
|
|
|
if (missile == NULL)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(false);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
if (chance > 0 && pr_spawnitemex() < chance)
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(true);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
// Don't spawn monsters if this actor has been massacred
|
2016-02-04 21:15:29 +00:00
|
|
|
if (self->DamageType == NAME_Massacre && (GetDefaultByType(missile)->flags3 & MF3_ISMONSTER))
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(true);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 19:15:45 +00:00
|
|
|
fixedvec2 pos;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (!(flags & SIXF_ABSOLUTEANGLE))
|
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
angle += self->angle;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
angle_t ang = angle >> ANGLETOFINESHIFT;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (flags & SIXF_ABSOLUTEPOSITION)
|
|
|
|
{
|
2016-01-19 19:15:45 +00:00
|
|
|
pos = self->Vec2Offset(xofs, yofs);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// in relative mode negative y values mean 'left' and positive ones mean 'right'
|
|
|
|
// This is the inverse orientation of the absolute mode!
|
2016-01-19 19:15:45 +00:00
|
|
|
pos = self->Vec2Offset(
|
|
|
|
FixedMul(xofs, finecosine[ang]) + FixedMul(yofs, finesine[ang]),
|
|
|
|
FixedMul(xofs, finesine[ang]) - FixedMul(yofs, finecosine[ang]));
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!(flags & SIXF_ABSOLUTEVELOCITY))
|
|
|
|
{
|
|
|
|
// Same orientation issue here!
|
|
|
|
fixed_t newxvel = FixedMul(xvel, finecosine[ang]) + FixedMul(yvel, finesine[ang]);
|
|
|
|
yvel = FixedMul(xvel, finesine[ang]) - FixedMul(yvel, finecosine[ang]);
|
|
|
|
xvel = newxvel;
|
|
|
|
}
|
|
|
|
|
2016-01-19 19:15:45 +00:00
|
|
|
AActor *mo = Spawn(missile, pos.x, pos.y, self->Z() - self->floorclip + self->GetBobOffset() + zofs, ALLOW_REPLACE);
|
2015-01-30 01:30:26 +00:00
|
|
|
bool res = InitSpawnedItem(self, mo, flags);
|
|
|
|
if (res)
|
|
|
|
{
|
|
|
|
if (tid != 0)
|
|
|
|
{
|
|
|
|
assert(mo->tid == 0);
|
|
|
|
mo->tid = tid;
|
|
|
|
mo->AddToHash();
|
|
|
|
}
|
|
|
|
if (flags & SIXF_MULTIPLYSPEED)
|
|
|
|
{
|
|
|
|
mo->velx = FixedMul(xvel, mo->Speed);
|
|
|
|
mo->vely = FixedMul(yvel, mo->Speed);
|
|
|
|
mo->velz = FixedMul(zvel, mo->Speed);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mo->velx = xvel;
|
|
|
|
mo->vely = yvel;
|
|
|
|
mo->velz = zvel;
|
|
|
|
}
|
2016-01-19 15:09:44 +00:00
|
|
|
mo->angle = angle;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(res); // for an inventory item's use state
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_ThrowGrenade
|
|
|
|
//
|
|
|
|
// Throws a grenade (like Hexen's fighter flechette)
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ThrowGrenade)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_CLASS (missile, AActor);
|
|
|
|
PARAM_FIXED_OPT (zheight) { zheight = 0; }
|
|
|
|
PARAM_FIXED_OPT (xyvel) { xyvel = 0; }
|
|
|
|
PARAM_FIXED_OPT (zvel) { zvel = 0; }
|
|
|
|
PARAM_BOOL_OPT (useammo) { useammo = true; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (missile == NULL)
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(true);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
if (ACTION_CALL_FROM_WEAPON())
|
|
|
|
{
|
|
|
|
// Used from a weapon, so use some ammo
|
|
|
|
AWeapon *weapon = self->player->ReadyWeapon;
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (weapon == NULL)
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(true);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
if (useammo && !weapon->DepleteAmmo(weapon->bAltFire))
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(true);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
AActor *bo;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 19:15:45 +00:00
|
|
|
bo = Spawn(missile,
|
|
|
|
self->PosPlusZ(-self->floorclip + self->GetBobOffset() + zheight + 35*FRACUNIT + (self->player? self->player->crouchoffset : 0)),
|
2015-01-30 01:30:26 +00:00
|
|
|
ALLOW_REPLACE);
|
|
|
|
if (bo)
|
|
|
|
{
|
|
|
|
P_PlaySpawnSound(bo, self);
|
|
|
|
if (xyvel != 0)
|
|
|
|
bo->Speed = xyvel;
|
|
|
|
bo->angle = self->angle + (((pr_grenade()&7) - 4) << 24);
|
|
|
|
|
|
|
|
angle_t pitch = angle_t(-self->pitch) >> ANGLETOFINESHIFT;
|
|
|
|
angle_t angle = bo->angle >> ANGLETOFINESHIFT;
|
|
|
|
|
|
|
|
// There are two vectors we are concerned about here: xy and z. We rotate
|
|
|
|
// them separately according to the shooter's pitch and then sum them to
|
|
|
|
// get the final velocity vector to shoot with.
|
|
|
|
|
|
|
|
fixed_t xy_xyscale = FixedMul(bo->Speed, finecosine[pitch]);
|
|
|
|
fixed_t xy_velz = FixedMul(bo->Speed, finesine[pitch]);
|
|
|
|
fixed_t xy_velx = FixedMul(xy_xyscale, finecosine[angle]);
|
|
|
|
fixed_t xy_vely = FixedMul(xy_xyscale, finesine[angle]);
|
|
|
|
|
|
|
|
pitch = angle_t(self->pitch) >> ANGLETOFINESHIFT;
|
|
|
|
fixed_t z_xyscale = FixedMul(zvel, finesine[pitch]);
|
|
|
|
fixed_t z_velz = FixedMul(zvel, finecosine[pitch]);
|
|
|
|
fixed_t z_velx = FixedMul(z_xyscale, finecosine[angle]);
|
|
|
|
fixed_t z_vely = FixedMul(z_xyscale, finesine[angle]);
|
|
|
|
|
|
|
|
bo->velx = xy_velx + z_velx + (self->velx >> 1);
|
|
|
|
bo->vely = xy_vely + z_vely + (self->vely >> 1);
|
|
|
|
bo->velz = xy_velz + z_velz;
|
|
|
|
|
|
|
|
bo->target = self;
|
|
|
|
P_CheckMissileSpawn (bo, self->radius);
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
else
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(false);
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(true);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Recoil
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Recoil)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_FIXED(xyvel);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
angle_t angle = self->angle + ANG180;
|
|
|
|
angle >>= ANGLETOFINESHIFT;
|
2016-01-19 15:09:44 +00:00
|
|
|
self->velx += FixedMul(xyvel, finecosine[angle]);
|
|
|
|
self->vely += FixedMul(xyvel, finesine[angle]);
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SelectWeapon
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SelectWeapon)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_CLASS(cls, AWeapon);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (cls == NULL || self->player == NULL)
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(false);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
AWeapon *weaponitem = static_cast<AWeapon*>(self->FindInventory(cls));
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (weaponitem != NULL && weaponitem->IsKindOf(RUNTIME_CLASS(AWeapon)))
|
|
|
|
{
|
|
|
|
if (self->player->ReadyWeapon != weaponitem)
|
|
|
|
{
|
|
|
|
self->player->PendingWeapon = weaponitem;
|
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(true);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
else
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(false);
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Print
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
EXTERN_CVAR(Float, con_midtime)
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Print)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_STRING (text);
|
|
|
|
PARAM_FLOAT_OPT (time) { time = 0; }
|
|
|
|
PARAM_NAME_OPT (fontname) { fontname = NAME_None; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (text[0] == '$') text = GStrings(&text[1]);
|
2015-01-30 01:30:26 +00:00
|
|
|
if (self->CheckLocalView (consoleplayer) ||
|
2016-02-04 21:15:29 +00:00
|
|
|
(self->target != NULL && self->target->CheckLocalView (consoleplayer)))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
float saved = con_midtime;
|
|
|
|
FFont *font = NULL;
|
|
|
|
|
|
|
|
if (fontname != NAME_None)
|
|
|
|
{
|
|
|
|
font = V_GetFont(fontname);
|
|
|
|
}
|
|
|
|
if (time > 0)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
con_midtime = float(time);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
FString formatted = strbin1(text);
|
|
|
|
C_MidPrint(font != NULL ? font : SmallFont, formatted.GetChars());
|
|
|
|
con_midtime = saved;
|
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_PrintBold
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PrintBold)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_STRING (text);
|
|
|
|
PARAM_FLOAT_OPT (time) { time = 0; }
|
|
|
|
PARAM_NAME_OPT (fontname) { fontname = NAME_None; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
float saved = con_midtime;
|
|
|
|
FFont *font = NULL;
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (text[0] == '$') text = GStrings(&text[1]);
|
2015-01-30 01:30:26 +00:00
|
|
|
if (fontname != NAME_None)
|
|
|
|
{
|
|
|
|
font = V_GetFont(fontname);
|
|
|
|
}
|
|
|
|
if (time > 0)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
con_midtime = float(time);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
FString formatted = strbin1(text);
|
|
|
|
C_MidPrintBold(font != NULL ? font : SmallFont, formatted.GetChars());
|
|
|
|
con_midtime = saved;
|
2016-02-19 02:39:40 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Log
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Log)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_STRING(text);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (text[0] == '$') text = GStrings(&text[1]);
|
2015-01-30 01:30:26 +00:00
|
|
|
FString formatted = strbin1(text);
|
2015-02-26 14:46:01 +00:00
|
|
|
Printf("%s\n", formatted.GetChars());
|
2016-02-19 02:39:40 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================================
|
|
|
|
//
|
|
|
|
// A_LogInt
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LogInt)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT(num);
|
2015-01-30 01:30:26 +00:00
|
|
|
Printf("%d\n", num);
|
2016-02-19 02:39:40 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetTranslucent
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTranslucent)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_FIXED (alpha);
|
|
|
|
PARAM_INT_OPT (mode) { mode = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
mode = mode == 0 ? STYLE_Translucent : mode == 2 ? STYLE_Fuzzy : STYLE_Add;
|
|
|
|
|
|
|
|
self->RenderStyle.Flags &= ~STYLEF_Alpha1;
|
|
|
|
self->alpha = clamp<fixed_t>(alpha, 0, FRACUNIT);
|
|
|
|
self->RenderStyle = ERenderStyle(mode);
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_FadeIn
|
|
|
|
//
|
|
|
|
// Fades the actor in
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
enum FadeFlags
|
|
|
|
{
|
|
|
|
FTF_REMOVE = 1 << 0,
|
|
|
|
FTF_CLAMP = 1 << 1,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeIn)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_FIXED_OPT(reduce) { reduce = FRACUNIT/10; }
|
|
|
|
PARAM_INT_OPT(flags) { flags = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (reduce == 0)
|
|
|
|
{
|
|
|
|
reduce = FRACUNIT / 10;
|
|
|
|
}
|
|
|
|
self->RenderStyle.Flags &= ~STYLEF_Alpha1;
|
|
|
|
self->alpha += reduce;
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (self->alpha >= FRACUNIT)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
if (flags & FTF_CLAMP)
|
2016-01-19 15:09:44 +00:00
|
|
|
{
|
|
|
|
self->alpha = FRACUNIT;
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
if (flags & FTF_REMOVE)
|
2016-01-19 15:09:44 +00:00
|
|
|
{
|
2016-01-14 02:26:15 +00:00
|
|
|
P_RemoveThing(self);
|
2016-01-19 15:09:44 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_FadeOut
|
|
|
|
//
|
|
|
|
// fades the actor out and destroys it when done
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeOut)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_FIXED_OPT(reduce) { reduce = FRACUNIT/10; }
|
|
|
|
PARAM_INT_OPT(flags) { flags = FTF_REMOVE; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (reduce == 0)
|
|
|
|
{
|
|
|
|
reduce = FRACUNIT/10;
|
|
|
|
}
|
|
|
|
self->RenderStyle.Flags &= ~STYLEF_Alpha1;
|
|
|
|
self->alpha -= reduce;
|
|
|
|
if (self->alpha <= 0)
|
|
|
|
{
|
|
|
|
if (flags & FTF_CLAMP)
|
2016-01-19 15:09:44 +00:00
|
|
|
{
|
2015-01-30 01:30:26 +00:00
|
|
|
self->alpha = 0;
|
2016-01-19 15:09:44 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
if (flags & FTF_REMOVE)
|
2016-01-19 15:09:44 +00:00
|
|
|
{
|
2016-01-14 02:26:15 +00:00
|
|
|
P_RemoveThing(self);
|
2016-01-19 15:09:44 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_FadeTo
|
|
|
|
//
|
|
|
|
// fades the actor to a specified transparency by a specified amount and
|
|
|
|
// destroys it if so desired
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeTo)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_FIXED (target);
|
|
|
|
PARAM_FIXED_OPT (amount) { amount = fixed_t(0.1*FRACUNIT); }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
self->alpha = clamp(self->alpha, 0, FRACUNIT);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
if (self->alpha == target && (flags & FTF_REMOVE))
|
|
|
|
{
|
2016-01-14 02:26:15 +00:00
|
|
|
P_RemoveThing(self);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Scale(float scalex, optional float scaley)
|
|
|
|
//
|
|
|
|
// Scales the actor's graphics. If scaley is 0, use scalex.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetScale)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_FIXED (scalex);
|
|
|
|
PARAM_FIXED_OPT (scaley) { scaley = scalex; }
|
|
|
|
PARAM_INT_OPT (ptr) { ptr = AAPTR_DEFAULT; }
|
2016-03-07 15:02:34 +00:00
|
|
|
PARAM_BOOL_OPT (usezero) { usezero = false; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (ref != NULL)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-03-07 15:58:52 +00:00
|
|
|
if (scaley == 0 && !usezero)
|
|
|
|
{
|
|
|
|
scaley = scalex;
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
ref->scaleX = scalex;
|
2016-03-07 15:58:52 +00:00
|
|
|
ref->scaleY = scaley;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetMass(int mass)
|
|
|
|
//
|
|
|
|
// Sets the actor's mass.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetMass)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (mass);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
self->Mass = mass;
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SpawnDebris
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnDebris)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_CLASS (debris, AActor);
|
|
|
|
PARAM_BOOL_OPT (transfer_translation) { transfer_translation = false; }
|
|
|
|
PARAM_FIXED_OPT (mult_h) { mult_h = FRACUNIT; }
|
|
|
|
PARAM_FIXED_OPT (mult_v) { mult_v = FRACUNIT; }
|
2015-01-30 01:30:26 +00:00
|
|
|
int i;
|
2016-02-04 21:15:29 +00:00
|
|
|
AActor *mo;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (debris == NULL)
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
// only positive values make sense here
|
2016-01-19 15:09:44 +00:00
|
|
|
if (mult_v <= 0)
|
|
|
|
mult_v = FRACUNIT;
|
|
|
|
if (mult_h <= 0)
|
|
|
|
mult_h = FRACUNIT;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
for (i = 0; i < GetDefaultByType(debris)->health; i++)
|
|
|
|
{
|
2016-01-20 08:25:30 +00:00
|
|
|
fixed_t xo = ((pr_spawndebris() - 128) << 12);
|
|
|
|
fixed_t yo = ((pr_spawndebris() - 128) << 12);
|
|
|
|
fixed_t zo = (pr_spawndebris()*self->height / 256 + self->GetBobOffset());
|
|
|
|
mo = Spawn(debris, self->Vec3Offset(xo, yo, zo), ALLOW_REPLACE);
|
2015-01-30 01:30:26 +00:00
|
|
|
if (mo)
|
|
|
|
{
|
|
|
|
if (transfer_translation)
|
|
|
|
{
|
|
|
|
mo->Translation = self->Translation;
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
if (i < mo->GetClass()->NumOwnedStates)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
mo->SetState (mo->GetClass()->OwnedStates + i);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
mo->velz = FixedMul(mult_v, ((pr_spawndebris()&7)+5)*FRACUNIT);
|
|
|
|
mo->velx = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6));
|
|
|
|
mo->vely = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6));
|
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2015-12-08 09:58:24 +00:00
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SpawnParticle
|
|
|
|
//
|
|
|
|
//===========================================================================
|
2016-01-21 22:36:58 +00:00
|
|
|
enum SPFflag
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
SPF_FULLBRIGHT = 1,
|
|
|
|
SPF_RELPOS = 1 << 1,
|
|
|
|
SPF_RELVEL = 1 << 2,
|
|
|
|
SPF_RELACCEL = 1 << 3,
|
|
|
|
SPF_RELANG = 1 << 4,
|
2016-01-21 22:36:58 +00:00
|
|
|
};
|
2016-01-21 23:58:42 +00:00
|
|
|
|
2015-12-08 09:58:24 +00:00
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnParticle)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_COLOR (color);
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_INT_OPT (lifetime) { lifetime = 35; }
|
|
|
|
PARAM_INT_OPT (size) { size = 1; }
|
|
|
|
PARAM_ANGLE_OPT (angle) { angle = 0; }
|
|
|
|
PARAM_FIXED_OPT (xoff) { xoff = 0; }
|
|
|
|
PARAM_FIXED_OPT (yoff) { yoff = 0; }
|
|
|
|
PARAM_FIXED_OPT (zoff) { zoff = 0; }
|
|
|
|
PARAM_FIXED_OPT (xvel) { xvel = 0; }
|
|
|
|
PARAM_FIXED_OPT (yvel) { yvel = 0; }
|
|
|
|
PARAM_FIXED_OPT (zvel) { zvel = 0; }
|
|
|
|
PARAM_FIXED_OPT (accelx) { accelx = 0; }
|
|
|
|
PARAM_FIXED_OPT (accely) { accely = 0; }
|
|
|
|
PARAM_FIXED_OPT (accelz) { accelz = 0; }
|
|
|
|
PARAM_FIXED_OPT (startalphaf) { startalphaf = FRACUNIT; }
|
|
|
|
PARAM_FIXED_OPT (fadestepf) { fadestepf = -FRACUNIT; }
|
|
|
|
|
|
|
|
BYTE startalpha = (BYTE)(clamp(startalphaf, 0, FRACUNIT) * 255 / FRACUNIT);
|
|
|
|
int fadestep = fadestepf < 0 ? -1 : clamp(fadestepf, 0, FRACUNIT) * 255 / FRACUNIT;
|
|
|
|
lifetime = clamp<int>(lifetime, 0, 255); // Clamp to byte
|
|
|
|
size = clamp<int>(size, 0, 65535); // Clamp to word
|
2015-12-08 09:58:24 +00:00
|
|
|
|
|
|
|
if (lifetime != 0)
|
2016-01-21 16:04:52 +00:00
|
|
|
{
|
2016-01-21 22:36:58 +00:00
|
|
|
const angle_t ang = (angle + ((flags & SPF_RELANG) ? self->angle : 0)) >> ANGLETOFINESHIFT;
|
|
|
|
fixedvec3 pos;
|
|
|
|
//[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!
|
|
|
|
const fixed_t xof1 = xoff;
|
|
|
|
xoff = FixedMul(xof1, finecosine[ang]) + FixedMul(yoff, finesine[ang]);
|
|
|
|
yoff = FixedMul(xof1, finesine[ang]) - FixedMul(yoff, finecosine[ang]);
|
|
|
|
}
|
|
|
|
if (flags & SPF_RELVEL)
|
|
|
|
{
|
|
|
|
const fixed_t newxvel = FixedMul(xvel, finecosine[ang]) + FixedMul(yvel, finesine[ang]);
|
|
|
|
yvel = FixedMul(xvel, finesine[ang]) - FixedMul(yvel, finecosine[ang]);
|
|
|
|
xvel = newxvel;
|
|
|
|
}
|
|
|
|
if (flags & SPF_RELACCEL)
|
|
|
|
{
|
|
|
|
fixed_t newaccelx = FixedMul(accelx, finecosine[ang]) + FixedMul(accely, finesine[ang]);
|
|
|
|
accely = FixedMul(accelx, finesine[ang]) - FixedMul(accely, finecosine[ang]);
|
|
|
|
accelx = newaccelx;
|
|
|
|
}
|
|
|
|
pos = self->Vec3Offset(xoff, yoff, zoff);
|
|
|
|
P_SpawnParticle(pos.x, pos.y, pos.z, xvel, yvel, zvel, color, !!(flags & SPF_FULLBRIGHT), startalpha, lifetime, size, fadestep, accelx, accely, accelz);
|
2016-01-21 16:04:52 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-12-08 09:58:24 +00:00
|
|
|
}
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_CheckSight
|
|
|
|
// jumps if no player can see this actor
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSight)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
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))
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
// 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))
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_CheckSightOrRange
|
|
|
|
// Jumps if this actor is out of range of all players *and* out of sight.
|
|
|
|
// Useful for maps with many multi-actor special effects.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
static bool DoCheckSightOrRange(AActor *self, AActor *camera, double range, bool twodi)
|
|
|
|
{
|
|
|
|
if (camera == NULL)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Check distance first, since it's cheaper than checking sight.
|
2016-01-19 19:15:45 +00:00
|
|
|
fixedvec2 pos = camera->Vec2To(self);
|
|
|
|
fixed_t dz;
|
|
|
|
fixed_t eyez = (camera->Top() - (camera->height>>2)); // same eye height as P_CheckSight
|
|
|
|
if (eyez > self->Top())
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 19:15:45 +00:00
|
|
|
dz = self->Top() - eyez;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-01-19 19:15:45 +00:00
|
|
|
else if (eyez < self->Z())
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 19:15:45 +00:00
|
|
|
dz = self->Z() - eyez;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dz = 0;
|
|
|
|
}
|
2016-01-19 19:15:45 +00:00
|
|
|
double distance = ((double)pos.x * pos.x) + ((double)pos.y * pos.y) + (twodi == 0? ((double)dz * dz) : 0);
|
2015-01-30 01:30:26 +00:00
|
|
|
if (distance <= range){
|
|
|
|
// Within range
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now check LOS.
|
|
|
|
if (P_CheckSight(camera, self, SF_IGNOREVISIBILITY))
|
|
|
|
{ // Visible
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSightOrRange)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_FLOAT(range);
|
|
|
|
PARAM_STATE(jump);
|
|
|
|
PARAM_BOOL_OPT(twodi) { twodi = false; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots
|
|
|
|
for (int i = 0; i < MAXPLAYERS; ++i)
|
|
|
|
{
|
|
|
|
if (playeringame[i])
|
|
|
|
{
|
|
|
|
// Always check from each player.
|
|
|
|
if (DoCheckSightOrRange(self, players[i].mo, range, twodi))
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
// 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))
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_CheckRange
|
|
|
|
// Jumps if this actor is out of range of all players.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
static bool DoCheckRange(AActor *self, AActor *camera, double range, bool twodi)
|
|
|
|
{
|
|
|
|
if (camera == NULL)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Check distance first, since it's cheaper than checking sight.
|
2016-01-19 19:15:45 +00:00
|
|
|
fixedvec2 pos = camera->Vec2To(self);
|
|
|
|
fixed_t dz;
|
|
|
|
fixed_t eyez = (camera->Top() - (camera->height>>2)); // same eye height as P_CheckSight
|
|
|
|
if (eyez > self->Top())
|
|
|
|
{
|
|
|
|
dz = self->Top() - eyez;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-01-19 19:15:45 +00:00
|
|
|
else if (eyez < self->Z())
|
|
|
|
{
|
|
|
|
dz = self->Z() - eyez;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-01-19 19:15:45 +00:00
|
|
|
else
|
|
|
|
{
|
2015-01-30 01:30:26 +00:00
|
|
|
dz = 0;
|
|
|
|
}
|
2016-01-19 19:15:45 +00:00
|
|
|
double distance = ((double)pos.x * pos.x) + ((double)pos.y * pos.y) + (twodi == 0? ((double)dz * dz) : 0);
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
if (distance <= range){
|
|
|
|
// Within range
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckRange)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_FLOAT(range);
|
|
|
|
PARAM_STATE(jump);
|
|
|
|
PARAM_BOOL_OPT(twodi) { twodi = false; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots
|
|
|
|
for (int i = 0; i < MAXPLAYERS; ++i)
|
|
|
|
{
|
|
|
|
if (playeringame[i])
|
|
|
|
{
|
|
|
|
// Always check from each player.
|
|
|
|
if (DoCheckRange(self, players[i].mo, range, twodi))
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
// If a player is viewing from a non-player, check that too.
|
|
|
|
if (players[i].camera != NULL && players[i].camera->player == NULL &&
|
|
|
|
DoCheckRange(self, players[i].camera, range, twodi))
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// Inventory drop
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropInventory)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_CLASS(drop, AInventory);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (drop)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
AInventory *inv = self->FindInventory(drop);
|
2015-01-30 01:30:26 +00:00
|
|
|
if (inv)
|
|
|
|
{
|
|
|
|
self->DropInventory(inv);
|
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetBlend
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetBlend)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_COLOR (color);
|
|
|
|
PARAM_FLOAT (alpha);
|
|
|
|
PARAM_INT (tics);
|
|
|
|
PARAM_COLOR_OPT (color2) { color2 = 0; }
|
|
|
|
|
|
|
|
if (color == MAKEARGB(255,255,255,255))
|
|
|
|
color = 0;
|
|
|
|
if (color2 == MAKEARGB(255,255,255,255))
|
|
|
|
color2 = 0;
|
|
|
|
if (color2.a == 0)
|
2015-01-30 01:30:26 +00:00
|
|
|
color2 = color;
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
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;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_JumpIf
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIf)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_BOOL (condition);
|
|
|
|
PARAM_STATE (jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-21 03:27:42 +00:00
|
|
|
ACTION_RETURN_STATE(condition ? jump : NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_CountdownArg
|
|
|
|
//
|
|
|
|
//===========================================================================
|
2016-02-17 10:42:45 +00:00
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CountdownArg)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-17 10:42:45 +00:00
|
|
|
PARAM_INT(cnt);
|
|
|
|
PARAM_STATE_OPT(state) { state = self->FindState(NAME_Death); }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-17 11:36:56 +00:00
|
|
|
if (cnt<0 || cnt >= 5) return 0;
|
2016-02-17 10:42:45 +00:00
|
|
|
if (!self->args[cnt]--)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-17 10:42:45 +00:00
|
|
|
if (self->flags&MF_MISSILE)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-17 10:42:45 +00:00
|
|
|
P_ExplodeMissile(self, NULL, NULL);
|
|
|
|
}
|
|
|
|
else if (self->flags&MF_SHOOTABLE)
|
|
|
|
{
|
|
|
|
P_DamageMobj(self, NULL, NULL, self->health, NAME_None, DMG_FORCED);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
self->SetState(state);
|
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//============================================================================
|
|
|
|
//
|
|
|
|
// A_Burst
|
|
|
|
//
|
|
|
|
//============================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Burst)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_CLASS(chunk, AActor);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
int i, numChunks;
|
|
|
|
AActor * mo;
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (chunk == NULL)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
self->velx = self->vely = self->velz = 0;
|
|
|
|
self->height = self->GetDefault()->height;
|
|
|
|
|
|
|
|
// [RH] In Hexen, this creates a random number of shards (range [24,56])
|
|
|
|
// with no relation to the size of the self shattering. I think it should
|
|
|
|
// base the number of shards on the size of the dead thing, so bigger
|
|
|
|
// things break up into more shards than smaller things.
|
|
|
|
// An self with radius 20 and height 64 creates ~40 chunks.
|
|
|
|
numChunks = MAX<int> (4, (self->radius>>FRACBITS)*(self->height>>FRACBITS)/32);
|
|
|
|
i = (pr_burst.Random2()) % (numChunks/4);
|
|
|
|
for (i = MAX (24, numChunks + i); i >= 0; i--)
|
|
|
|
{
|
2016-01-20 08:25:30 +00:00
|
|
|
fixed_t xo = (((pr_burst() - 128)*self->radius) >> 7);
|
|
|
|
fixed_t yo = (((pr_burst() - 128)*self->radius) >> 7);
|
|
|
|
fixed_t zo = (pr_burst()*self->height / 255 + self->GetBobOffset());
|
|
|
|
mo = Spawn(chunk, self->Vec3Offset(xo, yo, zo), ALLOW_REPLACE);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (mo)
|
|
|
|
{
|
2016-01-19 19:15:45 +00:00
|
|
|
mo->velz = FixedDiv(mo->Z() - self->Z(), self->height)<<2;
|
2015-01-30 01:30:26 +00:00
|
|
|
mo->velx = pr_burst.Random2 () << (FRACBITS-7);
|
|
|
|
mo->vely = pr_burst.Random2 () << (FRACBITS-7);
|
|
|
|
mo->RenderStyle = self->RenderStyle;
|
|
|
|
mo->alpha = self->alpha;
|
|
|
|
mo->CopyFriendliness(self, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// [RH] Do some stuff to make this more useful outside Hexen
|
|
|
|
if (self->flags4 & MF4_BOSSDEATH)
|
|
|
|
{
|
2016-02-10 12:57:21 +00:00
|
|
|
A_BossDeath(self);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
A_Unblock(self, true);
|
|
|
|
|
|
|
|
self->Destroy ();
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_CheckFloor
|
|
|
|
// [GRB] Jumps if actor is standing on floor
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFloor)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 19:15:45 +00:00
|
|
|
if (self->Z() <= self->floorz)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_CheckCeiling
|
2016-02-04 21:15:29 +00:00
|
|
|
// [GZ] Totally copied from A_CheckFloor, jumps if actor touches ceiling
|
2015-01-30 01:30:26 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckCeiling)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 19:15:45 +00:00
|
|
|
if (self->Top() >= self->ceilingz) // Height needs to be counted
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Stop
|
|
|
|
// resets all velocity of the actor to 0
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_Stop)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2015-01-30 01:30:26 +00:00
|
|
|
self->velx = self->vely = self->velz = 0;
|
|
|
|
if (self->player && self->player->mo == self && !(self->player->cheats & CF_PREDICTING))
|
|
|
|
{
|
|
|
|
self->player->mo->PlayIdle();
|
|
|
|
self->player->velx = self->player->vely = 0;
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void CheckStopped(AActor *self)
|
|
|
|
{
|
|
|
|
if (self->player != NULL &&
|
|
|
|
self->player->mo == self &&
|
|
|
|
!(self->player->cheats & CF_PREDICTING) &&
|
|
|
|
!(self->velx | self->vely | self->velz))
|
|
|
|
{
|
|
|
|
self->player->mo->PlayIdle();
|
|
|
|
self->player->velx = self->player->vely = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Respawn
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
DECLARE_ACTION(A_RestoreSpecialPosition)
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
enum RS_Flags
|
|
|
|
{
|
|
|
|
RSF_FOG=1,
|
|
|
|
RSF_KEEPTARGET=2,
|
|
|
|
RSF_TELEFRAG=4,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Respawn)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT_OPT(flags) { flags = RSF_FOG; }
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
bool oktorespawn = false;
|
2016-01-19 19:15:45 +00:00
|
|
|
fixedvec3 pos = self->Pos();
|
2016-02-04 21:15:29 +00:00
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
self->flags |= MF_SOLID;
|
|
|
|
self->height = self->GetDefault()->height;
|
2015-08-11 15:12:16 +00:00
|
|
|
self->radius = self->GetDefault()->radius;
|
2015-01-30 01:30:26 +00:00
|
|
|
CALL_ACTION(A_RestoreSpecialPosition, self);
|
|
|
|
|
|
|
|
if (flags & RSF_TELEFRAG)
|
|
|
|
{
|
|
|
|
// [KS] DIE DIE DIE DIE erm *ahem* =)
|
2016-01-19 19:15:45 +00:00
|
|
|
oktorespawn = P_TeleportMove(self, self->Pos(), true, false);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-01-19 19:15:45 +00:00
|
|
|
oktorespawn = P_CheckPosition(self, self->X(), self->Y(), true);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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")
|
2016-01-19 15:09:44 +00:00
|
|
|
if (self->target == self)
|
|
|
|
self->target = NULL;
|
|
|
|
if (self->lastenemy == self)
|
|
|
|
self->lastenemy = NULL;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2016-01-19 19:15:45 +00:00
|
|
|
P_SpawnTeleportFog(self, pos, true, true);
|
|
|
|
P_SpawnTeleportFog(self, self->Pos(), false, true);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
if (self->CountsAsKill())
|
|
|
|
{
|
|
|
|
level.total_monsters++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
self->flags &= ~MF_SOLID;
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_PlayerSkinCheck
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayerSkinCheck)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (self->player != NULL &&
|
|
|
|
skins[self->player->userinfo.GetSkin()].othergame)
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetGravity
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetGravity)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_FIXED(gravity);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
self->gravity = clamp<fixed_t>(gravity, 0, FRACUNIT*10);
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// [KS] *** Start of my modifications ***
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_ClearTarget
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_ClearTarget)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2015-01-30 01:30:26 +00:00
|
|
|
self->target = NULL;
|
|
|
|
self->LastHeard = NULL;
|
|
|
|
self->lastenemy = NULL;
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_CheckLOF (state jump, int flags = CRF_AIM_VERT|CRF_AIM_HOR,
|
|
|
|
// fixed range = 0, angle angle = 0, angle pitch = 0,
|
|
|
|
// fixed offsetheight = 32, fixed offsetwidth = 0,
|
|
|
|
// int ptr_target = AAPTR_DEFAULT (target) )
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
enum CLOF_flags
|
|
|
|
{
|
|
|
|
CLOFF_NOAIM_VERT = 0x00000001,
|
|
|
|
CLOFF_NOAIM_HORZ = 0x00000002,
|
|
|
|
|
|
|
|
CLOFF_JUMPENEMY = 0x00000004,
|
|
|
|
CLOFF_JUMPFRIEND = 0x00000008,
|
|
|
|
CLOFF_JUMPOBJECT = 0x00000010,
|
|
|
|
CLOFF_JUMPNONHOSTILE = 0x00000020,
|
|
|
|
|
|
|
|
CLOFF_SKIPENEMY = 0x00000040,
|
|
|
|
CLOFF_SKIPFRIEND = 0x00000080,
|
|
|
|
CLOFF_SKIPOBJECT = 0x00000100,
|
|
|
|
CLOFF_SKIPNONHOSTILE = 0x00000200,
|
|
|
|
|
|
|
|
CLOFF_MUSTBESHOOTABLE = 0x00000400,
|
|
|
|
|
|
|
|
CLOFF_SKIPTARGET = 0x00000800,
|
|
|
|
CLOFF_ALLOWNULL = 0x00001000,
|
|
|
|
CLOFF_CHECKPARTIAL = 0x00002000,
|
|
|
|
|
|
|
|
CLOFF_MUSTBEGHOST = 0x00004000,
|
|
|
|
CLOFF_IGNOREGHOST = 0x00008000,
|
|
|
|
|
|
|
|
CLOFF_MUSTBESOLID = 0x00010000,
|
|
|
|
CLOFF_BEYONDTARGET = 0x00020000,
|
|
|
|
|
|
|
|
CLOFF_FROMBASE = 0x00040000,
|
|
|
|
CLOFF_MUL_HEIGHT = 0x00080000,
|
|
|
|
CLOFF_MUL_WIDTH = 0x00100000,
|
|
|
|
|
|
|
|
CLOFF_JUMP_ON_MISS = 0x00200000,
|
|
|
|
CLOFF_AIM_VERT_NOOFFSET = 0x00400000,
|
|
|
|
|
|
|
|
CLOFF_SETTARGET = 0x00800000,
|
|
|
|
CLOFF_SETMASTER = 0x01000000,
|
|
|
|
CLOFF_SETTRACER = 0x02000000,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct LOFData
|
|
|
|
{
|
|
|
|
AActor *Self;
|
|
|
|
AActor *Target;
|
|
|
|
int Flags;
|
|
|
|
bool BadActor;
|
|
|
|
};
|
|
|
|
|
|
|
|
ETraceStatus CheckLOFTraceFunc(FTraceResults &trace, void *userdata)
|
|
|
|
{
|
|
|
|
LOFData *data = (LOFData *)userdata;
|
|
|
|
int flags = data->Flags;
|
|
|
|
|
|
|
|
if (trace.HitType != TRACE_HitActor)
|
|
|
|
{
|
|
|
|
return TRACE_Stop;
|
|
|
|
}
|
|
|
|
if (trace.Actor == data->Target)
|
|
|
|
{
|
|
|
|
if (flags & CLOFF_SKIPTARGET)
|
|
|
|
{
|
|
|
|
if (flags & CLOFF_BEYONDTARGET)
|
|
|
|
{
|
|
|
|
return TRACE_Skip;
|
|
|
|
}
|
|
|
|
return TRACE_Abort;
|
|
|
|
}
|
|
|
|
return TRACE_Stop;
|
|
|
|
}
|
|
|
|
if (flags & CLOFF_MUSTBESHOOTABLE)
|
|
|
|
{ // all shootability checks go here
|
|
|
|
if (!(trace.Actor->flags & MF_SHOOTABLE))
|
|
|
|
{
|
|
|
|
return TRACE_Skip;
|
|
|
|
}
|
|
|
|
if (trace.Actor->flags2 & MF2_NONSHOOTABLE)
|
|
|
|
{
|
|
|
|
return TRACE_Skip;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((flags & CLOFF_MUSTBESOLID) && !(trace.Actor->flags & MF_SOLID))
|
|
|
|
{
|
|
|
|
return TRACE_Skip;
|
|
|
|
}
|
|
|
|
if (flags & CLOFF_MUSTBEGHOST)
|
|
|
|
{
|
|
|
|
if (!(trace.Actor->flags3 & MF3_GHOST))
|
|
|
|
{
|
|
|
|
return TRACE_Skip;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (flags & CLOFF_IGNOREGHOST)
|
|
|
|
{
|
|
|
|
if (trace.Actor->flags3 & MF3_GHOST)
|
|
|
|
{
|
|
|
|
return TRACE_Skip;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
((flags & CLOFF_JUMPENEMY) && data->Self->IsHostile(trace.Actor)) ||
|
|
|
|
((flags & CLOFF_JUMPFRIEND) && data->Self->IsFriend(trace.Actor)) ||
|
|
|
|
((flags & CLOFF_JUMPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) ||
|
|
|
|
((flags & CLOFF_JUMPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor))
|
|
|
|
)
|
|
|
|
{
|
|
|
|
return TRACE_Stop;
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
((flags & CLOFF_SKIPENEMY) && data->Self->IsHostile(trace.Actor)) ||
|
|
|
|
((flags & CLOFF_SKIPFRIEND) && data->Self->IsFriend(trace.Actor)) ||
|
|
|
|
((flags & CLOFF_SKIPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) ||
|
|
|
|
((flags & CLOFF_SKIPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor))
|
|
|
|
)
|
|
|
|
{
|
|
|
|
return TRACE_Skip;
|
|
|
|
}
|
|
|
|
data->BadActor = true;
|
|
|
|
return TRACE_Abort;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckLOF)
|
|
|
|
{
|
|
|
|
// Check line of fire
|
|
|
|
|
|
|
|
/*
|
|
|
|
Not accounted for / I don't know how it works: FLOORCLIP
|
|
|
|
*/
|
|
|
|
|
|
|
|
AActor *target;
|
2016-01-19 19:15:45 +00:00
|
|
|
fixedvec3 pos;
|
|
|
|
fixed_t vx, vy, vz;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_STATE (jump);
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_FIXED_OPT (range) { range = 0; }
|
|
|
|
PARAM_FIXED_OPT (minrange) { minrange = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ANGLE_OPT (angle) { angle = 0; }
|
|
|
|
PARAM_ANGLE_OPT (pitch) { pitch = 0; }
|
|
|
|
PARAM_FIXED_OPT (offsetheight) { offsetheight = 0; }
|
|
|
|
PARAM_FIXED_OPT (offsetwidth) { offsetwidth = 0; }
|
|
|
|
PARAM_INT_OPT (ptr_target) { ptr_target = AAPTR_DEFAULT; }
|
2016-03-03 01:02:20 +00:00
|
|
|
PARAM_FIXED_OPT (offsetforward) { offsetforward = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
target = COPY_AAPTR(self, ptr_target == AAPTR_DEFAULT ? AAPTR_TARGET|AAPTR_PLAYER_GETTARGET|AAPTR_NULL : ptr_target); // no player-support by default
|
|
|
|
|
|
|
|
if (flags & CLOFF_MUL_HEIGHT)
|
|
|
|
{
|
|
|
|
if (self->player != NULL)
|
|
|
|
{
|
|
|
|
// Synced with hitscan: self->player->mo->height is strangely conscientious about getting the right actor for player
|
|
|
|
offsetheight = FixedMul(offsetheight, FixedMul (self->player->mo->height, self->player->crouchfactor));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
offsetheight = FixedMul(offsetheight, self->height);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (flags & CLOFF_MUL_WIDTH)
|
|
|
|
{
|
2016-03-03 01:02:20 +00:00
|
|
|
offsetforward = FixedMul(self->radius, offsetforward);
|
2015-01-30 01:30:26 +00:00
|
|
|
offsetwidth = FixedMul(self->radius, offsetwidth);
|
|
|
|
}
|
|
|
|
|
2016-01-19 19:15:45 +00:00
|
|
|
pos = self->PosPlusZ(offsetheight - self->floorclip);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (!(flags & CLOFF_FROMBASE))
|
|
|
|
{ // default to hitscan origin
|
|
|
|
|
|
|
|
// Synced with hitscan: self->height is strangely NON-conscientious about getting the right actor for player
|
2016-01-19 19:15:45 +00:00
|
|
|
pos.z += (self->height >> 1);
|
2015-01-30 01:30:26 +00:00
|
|
|
if (self->player != NULL)
|
|
|
|
{
|
2016-01-19 19:15:45 +00:00
|
|
|
pos.z += FixedMul (self->player->mo->AttackZOffset, self->player->crouchfactor);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-01-19 19:15:45 +00:00
|
|
|
pos.z += 8*FRACUNIT;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (target)
|
|
|
|
{
|
2016-01-10 16:52:41 +00:00
|
|
|
fixed_t xydist = self->Distance2D(target);
|
2016-01-19 19:15:45 +00:00
|
|
|
fixed_t distance = P_AproxDistance(xydist, target->Z() - pos.z);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (range && !(flags & CLOFF_CHECKPARTIAL))
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
if (distance > range)
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
angle_t ang;
|
|
|
|
|
|
|
|
if (flags & CLOFF_NOAIM_HORZ)
|
|
|
|
{
|
|
|
|
ang = self->angle;
|
|
|
|
}
|
2016-01-10 19:46:26 +00:00
|
|
|
else ang = self->AngleTo (target);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
angle += ang;
|
|
|
|
|
|
|
|
ang >>= ANGLETOFINESHIFT;
|
2016-01-19 19:15:45 +00:00
|
|
|
|
|
|
|
fixedvec2 xy = self->Vec2Offset(
|
2016-03-03 01:02:20 +00:00
|
|
|
FixedMul(offsetforward, finecosine[ang]) + FixedMul(offsetwidth, finesine[ang]),
|
|
|
|
FixedMul(offsetforward, finesine[ang]) - FixedMul(offsetwidth, finecosine[ang]));
|
2016-01-19 19:15:45 +00:00
|
|
|
|
|
|
|
pos.x = xy.x;
|
|
|
|
pos.y = xy.y;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (flags & CLOFF_NOAIM_VERT)
|
|
|
|
{
|
|
|
|
pitch += self->pitch;
|
|
|
|
}
|
|
|
|
else if (flags & CLOFF_AIM_VERT_NOOFFSET)
|
|
|
|
{
|
2016-01-28 18:24:40 +00:00
|
|
|
pitch -= R_PointToAngle2 (0,0, xydist, target->Z() - pos.z + offsetheight + target->height / 2);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-01-28 18:24:40 +00:00
|
|
|
pitch -= R_PointToAngle2 (0,0, xydist, target->Z() - pos.z + target->height / 2);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (flags & CLOFF_ALLOWNULL)
|
|
|
|
{
|
|
|
|
angle += self->angle;
|
|
|
|
pitch += self->pitch;
|
|
|
|
|
|
|
|
angle_t ang = self->angle >> ANGLETOFINESHIFT;
|
2016-01-19 19:15:45 +00:00
|
|
|
|
|
|
|
fixedvec2 xy = self->Vec2Offset(
|
2016-03-03 01:02:20 +00:00
|
|
|
FixedMul(offsetforward, finecosine[ang]) + FixedMul(offsetwidth, finesine[ang]),
|
|
|
|
FixedMul(offsetforward, finesine[ang]) - FixedMul(offsetwidth, finecosine[ang]));
|
2016-01-19 19:15:45 +00:00
|
|
|
|
|
|
|
pos.x = xy.x;
|
|
|
|
pos.y = xy.y;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
else
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
angle >>= ANGLETOFINESHIFT;
|
2016-01-26 13:20:55 +00:00
|
|
|
pitch >>= ANGLETOFINESHIFT;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
vx = FixedMul (finecosine[pitch], finecosine[angle]);
|
|
|
|
vy = FixedMul (finecosine[pitch], finesine[angle]);
|
|
|
|
vz = -finesine[pitch];
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Variable set:
|
|
|
|
|
|
|
|
jump, flags, target
|
|
|
|
x1,y1,z1 (trace point of origin)
|
|
|
|
vx,vy,vz (trace unit vector)
|
|
|
|
range
|
|
|
|
*/
|
|
|
|
|
2016-01-19 19:15:45 +00:00
|
|
|
sector_t *sec = P_PointInSector(pos.x, pos.y);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2016-03-03 15:33:42 +00:00
|
|
|
Trace(pos.x, pos.y, pos.z, sec, vx, vy, vz, range, ActorFlags::FromInt(0xFFFFFFFF), ML_BLOCKEVERYTHING, self, trace, TRACE_PortalRestrict,
|
2015-01-30 01:30:26 +00:00
|
|
|
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)
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// 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
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
JLOSF_PROJECTILE = 1 << 0,
|
2015-01-30 01:30:26 +00:00
|
|
|
JLOSF_NOSIGHT = 1 << 1,
|
2016-02-04 21:15:29 +00:00
|
|
|
JLOSF_CLOSENOFOV = 1 << 2,
|
2015-01-30 01:30:26 +00:00
|
|
|
JLOSF_CLOSENOSIGHT = 1 << 3,
|
|
|
|
JLOSF_CLOSENOJUMP = 1 << 4,
|
|
|
|
JLOSF_DEADNOJUMP = 1 << 5,
|
|
|
|
JLOSF_CHECKMASTER = 1 << 6,
|
|
|
|
JLOSF_TARGETLOS = 1 << 7,
|
|
|
|
JLOSF_FLIPFOV = 1 << 8,
|
|
|
|
JLOSF_ALLYNOJUMP = 1 << 9,
|
|
|
|
JLOSF_COMBATANTONLY = 1 << 10,
|
|
|
|
JLOSF_NOAUTOAIM = 1 << 11,
|
|
|
|
JLOSF_CHECKTRACER = 1 << 12,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_STATE (jump);
|
|
|
|
PARAM_ANGLE_OPT (fov) { fov = 0; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_FIXED_OPT (dist_max) { dist_max = 0; }
|
|
|
|
PARAM_FIXED_OPT (dist_close) { dist_close = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
angle_t an;
|
|
|
|
AActor *target, *viewport;
|
2016-03-01 15:38:45 +00:00
|
|
|
FTranslatedLineTarget t;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (target == NULL)
|
2016-02-19 03:39:13 +00:00
|
|
|
{ // [KS] Let's not call P_CheckSight unnecessarily in this case.
|
|
|
|
ACTION_RETURN_STATE(NULL);
|
|
|
|
}
|
2016-01-19 15:09:44 +00:00
|
|
|
if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0))
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-01-19 15:09:44 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
doCheckSight = !(flags & JLOSF_NOSIGHT);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Does the player aim at something that can be shot?
|
2016-03-01 15:38:45 +00:00
|
|
|
P_AimLineAttack(self, self->angle, MISSILERANGE, &t, (flags & JLOSF_NOAUTOAIM) ? ANGLE_1/2 : 0, ALF_PORTALRESTRICT);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-03-01 15:38:45 +00:00
|
|
|
if (!t.linetarget)
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
|
|
|
ACTION_RETURN_STATE(NULL);
|
|
|
|
}
|
2016-03-01 15:38:45 +00:00
|
|
|
target = t.linetarget;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
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
|
2016-02-04 21:15:29 +00:00
|
|
|
if ( (flags & JLOSF_COMBATANTONLY) && (!target->player) && !(target->flags3 & MF3_ISMONSTER))
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
// [FDARI] If actors share team, don't jump
|
2016-02-04 21:15:29 +00:00
|
|
|
if ((flags & JLOSF_ALLYNOJUMP) && self->IsFriend(target))
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2016-01-10 16:52:41 +00:00
|
|
|
fixed_t distance = self->AproxDistance3D(target);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (dist_max && (distance > dist_max))
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
if (dist_close && (distance < dist_close))
|
|
|
|
{
|
|
|
|
if (flags & JLOSF_CLOSENOJUMP)
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
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))
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (flags & JLOSF_FLIPFOV)
|
|
|
|
{
|
|
|
|
if (viewport == self) { viewport = target; target = self; }
|
|
|
|
else { target = viewport; viewport = self; }
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fov && (fov < ANGLE_MAX))
|
|
|
|
{
|
2016-01-10 19:46:26 +00:00
|
|
|
an = viewport->AngleTo(target) - viewport->angle;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2)))
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL); // [KS] Outside of FOV - return
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_JumpIfInTargetLOS (state label, optional fixed fov, optional int flags
|
|
|
|
// optional fixed dist_max, optional fixed dist_close)
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetLOS)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_STATE (jump);
|
|
|
|
PARAM_ANGLE_OPT (fov) { fov = 0; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_FIXED_OPT (dist_max) { dist_max = 0; }
|
|
|
|
PARAM_FIXED_OPT (dist_close) { dist_close = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
angle_t an;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (target == NULL)
|
|
|
|
{ // [KS] Let's not call P_CheckSight unnecessarily in this case.
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0))
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-10 16:52:41 +00:00
|
|
|
fixed_t distance = self->AproxDistance3D(target);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (dist_max && (distance > dist_max))
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
bool doCheckSight = !(flags & JLOSF_NOSIGHT);
|
|
|
|
|
|
|
|
if (dist_close && (distance < dist_close))
|
|
|
|
{
|
|
|
|
if (flags & JLOSF_CLOSENOJUMP)
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
if (flags & JLOSF_CLOSENOFOV)
|
|
|
|
fov = 0;
|
|
|
|
|
|
|
|
if (flags & JLOSF_CLOSENOSIGHT)
|
|
|
|
doCheckSight = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fov && (fov < ANGLE_MAX))
|
|
|
|
{
|
2016-01-10 19:46:26 +00:00
|
|
|
an = target->AngleTo(self) - target->angle;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2)))
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL); // [KS] Outside of FOV - return
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (doCheckSight && !P_CheckSight (target, self, SF_IGNOREVISIBILITY))
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// Modified code pointer from Skulltag
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckForReload)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
if ( self->player == NULL || self->player->ReadyWeapon == NULL )
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_INT (count);
|
|
|
|
PARAM_STATE (jump);
|
|
|
|
PARAM_BOOL_OPT (dontincrement) { dontincrement = false; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-19 02:39:40 +00:00
|
|
|
if (numret > 0)
|
|
|
|
{
|
|
|
|
ret->SetPointer(NULL, ATAG_STATE);
|
|
|
|
numret = 1;
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
AWeapon *weapon = self->player->ReadyWeapon;
|
|
|
|
|
|
|
|
int ReloadCounter = weapon->ReloadCounter;
|
2016-02-04 21:15:29 +00:00
|
|
|
if (!dontincrement || ReloadCounter != 0)
|
2015-01-30 01:30:26 +00:00
|
|
|
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.
|
2016-02-19 02:39:40 +00:00
|
|
|
if (numret != 0)
|
|
|
|
{
|
|
|
|
ret->SetPointer(jump, ATAG_STATE);
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// We need to reload. However, don't reload if we're out of ammo.
|
2016-01-19 15:09:44 +00:00
|
|
|
weapon->CheckAmmo(false, false);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!dontincrement)
|
2016-02-19 02:39:40 +00:00
|
|
|
{
|
2015-01-30 01:30:26 +00:00
|
|
|
weapon->ReloadCounter = ReloadCounter;
|
2016-02-19 02:39:40 +00:00
|
|
|
}
|
|
|
|
return numret;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// Resets the counter for the above function
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_ResetReloadCounter)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
|
|
|
|
if (self->player == NULL || self->player->ReadyWeapon == NULL)
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
AWeapon *weapon = self->player->ReadyWeapon;
|
|
|
|
weapon->ReloadCounter = 0;
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_ChangeFlag
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeFlag)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_STRING (flagname);
|
|
|
|
PARAM_BOOL (value);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
const char *dot = strchr(flagname, '.');
|
2015-01-30 01:30:26 +00:00
|
|
|
FFlagDef *fd;
|
2016-02-04 21:15:29 +00:00
|
|
|
PClassActor *cls = self->GetClass();
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (dot != NULL)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
FString part1(flagname.GetChars(), dot - flagname);
|
|
|
|
fd = FindFlag(cls, part1, dot + 1);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
fd = FindFlag(cls, flagname, NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (fd != NULL)
|
|
|
|
{
|
|
|
|
bool kill_before, kill_after;
|
|
|
|
INTBOOL item_before, item_after;
|
|
|
|
INTBOOL secret_before, secret_after;
|
|
|
|
|
|
|
|
kill_before = self->CountsAsKill();
|
|
|
|
item_before = self->flags & MF_COUNTITEM;
|
|
|
|
secret_before = self->flags5 & MF5_COUNTSECRET;
|
|
|
|
|
|
|
|
if (fd->structoffset == -1)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
HandleDeprecatedFlags(self, cls, value, fd->flagbit);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-04-04 16:40:43 +00:00
|
|
|
ActorFlags *flagp = (ActorFlags*) (((char*)self) + fd->structoffset);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
// If these 2 flags get changed we need to update the blockmap and sector links.
|
|
|
|
bool linkchange = flagp == &self->flags && (fd->flagbit == MF_NOBLOCKMAP || fd->flagbit == MF_NOSECTOR);
|
|
|
|
|
|
|
|
if (linkchange) self->UnlinkFromWorld();
|
2016-02-04 21:15:29 +00:00
|
|
|
ModActorFlag(self, fd, value);
|
2015-01-30 01:30:26 +00:00
|
|
|
if (linkchange) self->LinkToWorld();
|
|
|
|
}
|
|
|
|
kill_after = self->CountsAsKill();
|
|
|
|
item_after = self->flags & MF_COUNTITEM;
|
|
|
|
secret_after = self->flags5 & MF5_COUNTSECRET;
|
|
|
|
// Was this monster previously worth a kill but no longer is?
|
|
|
|
// Or vice versa?
|
|
|
|
if (kill_before != kill_after)
|
|
|
|
{
|
|
|
|
if (kill_after)
|
|
|
|
{ // It counts as a kill now.
|
|
|
|
level.total_monsters++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{ // It no longer counts as a kill.
|
|
|
|
level.total_monsters--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// same for items
|
|
|
|
if (item_before != item_after)
|
|
|
|
{
|
|
|
|
if (item_after)
|
|
|
|
{ // It counts as an item now.
|
|
|
|
level.total_items++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{ // It no longer counts as an item
|
|
|
|
level.total_items--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// and secretd
|
|
|
|
if (secret_before != secret_after)
|
|
|
|
{
|
|
|
|
if (secret_after)
|
|
|
|
{ // It counts as an secret now.
|
|
|
|
level.total_secrets++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{ // It no longer counts as an secret
|
|
|
|
level.total_secrets--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
Printf("Unknown flag '%s' in '%s'\n", flagname.GetChars(), cls->TypeName.GetChars());
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_CheckFlag
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFlag)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_STRING (flagname);
|
|
|
|
PARAM_STATE (jumpto);
|
|
|
|
PARAM_INT_OPT (checkpointer) { checkpointer = AAPTR_DEFAULT; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
AActor *owner = COPY_AAPTR(self, checkpointer);
|
|
|
|
if (owner == NULL)
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (CheckActorFlag(owner, flagname))
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jumpto);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_RaiseMaster
|
|
|
|
//
|
|
|
|
//===========================================================================
|
2016-02-04 21:15:29 +00:00
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_RaiseMaster)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_BOOL_OPT(copy) { copy = false; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (self->master != NULL)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
P_Thing_Raise(self->master, copy ? self : NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_RaiseChildren
|
|
|
|
//
|
|
|
|
//===========================================================================
|
2016-02-04 21:15:29 +00:00
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_RaiseChildren)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_BOOL_OPT(copy) { copy = false; }
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
TThinkerIterator<AActor> it;
|
|
|
|
AActor *mo;
|
|
|
|
|
|
|
|
while ((mo = it.Next()) != NULL)
|
|
|
|
{
|
|
|
|
if (mo->master == self)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
P_Thing_Raise(mo, copy ? self : NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_RaiseSiblings
|
|
|
|
//
|
|
|
|
//===========================================================================
|
2016-02-04 21:15:29 +00:00
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_RaiseSiblings)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_BOOL_OPT(copy) { copy = false; }
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
TThinkerIterator<AActor> it;
|
|
|
|
AActor *mo;
|
|
|
|
|
|
|
|
if (self->master != NULL)
|
|
|
|
{
|
|
|
|
while ((mo = it.Next()) != NULL)
|
|
|
|
{
|
|
|
|
if (mo->master == self->master && mo != self)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
P_Thing_Raise(mo, copy ? self : NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
2015-01-30 01:30:26 +00:00
|
|
|
// [TP] A_FaceConsolePlayer
|
|
|
|
//
|
|
|
|
//===========================================================================
|
2016-02-04 21:15:29 +00:00
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS (AActor, A_FaceConsolePlayer)
|
|
|
|
{
|
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_ANGLE_OPT(max_turn_angle) { max_turn_angle = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
// NOTE: It does nothing for zdoom.
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_MonsterRefire
|
|
|
|
//
|
|
|
|
// Keep firing unless target got out of sight
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_MonsterRefire)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_INT (prob);
|
|
|
|
PARAM_STATE (jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
A_FaceTarget(self);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (pr_monsterrefire() < prob)
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2016-01-19 15:09:44 +00:00
|
|
|
if (self->target == NULL
|
2015-01-30 01:30:26 +00:00
|
|
|
|| P_HitFriend (self)
|
|
|
|
|| self->target->health <= 0
|
|
|
|
|| !P_CheckSight (self, self->target, SF_SEEPASTBLOCKEVERYTHING|SF_SEEPASTSHOOTABLELINES) )
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jump);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetAngle
|
|
|
|
//
|
|
|
|
// Set actor's angle (in degrees).
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
SPF_FORCECLAMP = 1, // players always clamp
|
|
|
|
SPF_INTERPOLATE = 2,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetAngle)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_ANGLE_OPT(angle) { angle = 0; }
|
|
|
|
PARAM_INT_OPT(flags) { flags = 0; }
|
|
|
|
PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
2016-02-04 21:15:29 +00:00
|
|
|
if (ref != NULL)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
ref->SetAngle(angle, !!(flags & SPF_INTERPOLATE));
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetPitch
|
|
|
|
//
|
|
|
|
// Set actor's pitch (in degrees).
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetPitch)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_ANGLE(pitch);
|
|
|
|
PARAM_INT_OPT(flags) { flags = 0; }
|
|
|
|
PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (ref == NULL)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ref->player != NULL || (flags & SPF_FORCECLAMP))
|
|
|
|
{ // clamp the pitch we set
|
|
|
|
int min, max;
|
|
|
|
|
|
|
|
if (ref->player != NULL)
|
|
|
|
{
|
|
|
|
min = ref->player->MinPitch;
|
|
|
|
max = ref->player->MaxPitch;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
min = -ANGLE_90 + (1 << ANGLETOFINESHIFT);
|
|
|
|
max = ANGLE_90 - (1 << ANGLETOFINESHIFT);
|
|
|
|
}
|
|
|
|
pitch = clamp<int>(pitch, min, max);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2015-05-27 22:41:07 +00:00
|
|
|
ref->SetPitch(pitch, !!(flags & SPF_INTERPOLATE), !!(flags & SPF_FORCECLAMP));
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2015-04-04 16:40:43 +00:00
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// [Nash] A_SetRoll
|
|
|
|
//
|
|
|
|
// Set actor's roll (in degrees).
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRoll)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_ANGLE (roll);
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_INT_OPT (ptr) { ptr = AAPTR_DEFAULT; }
|
2015-01-30 01:30:26 +00:00
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (ref != NULL)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
ref->SetRoll(roll, !!(flags & SPF_INTERPOLATE));
|
2015-04-04 16:40:43 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-04-04 16:40:43 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_ScaleVelocity
|
|
|
|
//
|
|
|
|
// Scale actor's velocity.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ScaleVelocity)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_FIXED(scale);
|
|
|
|
PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (ref == NULL)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2015-07-27 17:20:32 +00:00
|
|
|
INTBOOL was_moving = ref->velx | ref->vely | ref->velz;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
ref->velx = FixedMul(ref->velx, scale);
|
|
|
|
ref->vely = FixedMul(ref->vely, scale);
|
|
|
|
ref->velz = FixedMul(ref->velz, scale);
|
|
|
|
|
|
|
|
// If the actor was previously moving but now is not, and is a player,
|
|
|
|
// update its player variables. (See A_Stop.)
|
|
|
|
if (was_moving)
|
|
|
|
{
|
|
|
|
CheckStopped(ref);
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_ChangeVelocity
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeVelocity)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_FIXED_OPT (x) { x = 0; }
|
|
|
|
PARAM_FIXED_OPT (y) { y = 0; }
|
|
|
|
PARAM_FIXED_OPT (z) { z = 0; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_INT_OPT (ptr) { ptr = AAPTR_DEFAULT; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (ref == NULL)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
INTBOOL was_moving = ref->velx | ref->vely | ref->velz;
|
|
|
|
|
|
|
|
fixed_t vx = x, vy = y, vz = z;
|
|
|
|
fixed_t sina = finesine[ref->angle >> ANGLETOFINESHIFT];
|
|
|
|
fixed_t cosa = finecosine[ref->angle >> ANGLETOFINESHIFT];
|
|
|
|
|
|
|
|
if (flags & 1) // relative axes - make x, y relative to actor's current angle
|
|
|
|
{
|
|
|
|
vx = DMulScale16(x, cosa, -y, sina);
|
|
|
|
vy = DMulScale16(x, sina, y, cosa);
|
|
|
|
}
|
|
|
|
if (flags & 2) // discard old velocity - replace old velocity with new velocity
|
|
|
|
{
|
|
|
|
ref->velx = vx;
|
|
|
|
ref->vely = vy;
|
|
|
|
ref->velz = vz;
|
|
|
|
}
|
|
|
|
else // add new velocity to old velocity
|
|
|
|
{
|
|
|
|
ref->velx += vx;
|
|
|
|
ref->vely += vy;
|
|
|
|
ref->velz += vz;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (was_moving)
|
|
|
|
{
|
2015-07-27 17:20:32 +00:00
|
|
|
CheckStopped(ref);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetArg
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetArg)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT(pos);
|
|
|
|
PARAM_INT(value);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
// Set the value of the specified arg
|
|
|
|
if ((size_t)pos < countof(self->args))
|
|
|
|
{
|
|
|
|
self->args[pos] = value;
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetSpecial
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpecial)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (spec);
|
|
|
|
PARAM_INT_OPT (arg0) { arg0 = 0; }
|
|
|
|
PARAM_INT_OPT (arg1) { arg1 = 0; }
|
|
|
|
PARAM_INT_OPT (arg2) { arg2 = 0; }
|
|
|
|
PARAM_INT_OPT (arg3) { arg3 = 0; }
|
|
|
|
PARAM_INT_OPT (arg4) { arg4 = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
self->special = spec;
|
|
|
|
self->args[0] = arg0;
|
|
|
|
self->args[1] = arg1;
|
|
|
|
self->args[2] = arg2;
|
|
|
|
self->args[3] = arg3;
|
|
|
|
self->args[4] = arg4;
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetUserVar
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserVar)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_NAME (varname);
|
|
|
|
PARAM_INT (value);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
PField *var = dyn_cast<PField>(self->GetClass()->Symbols.FindSymbol(varname, true));
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (var == NULL || (var->Flags & VARF_Native) || !var->Type->IsKindOf(RUNTIME_CLASS(PBasicType)))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
Printf("%s is not a user variable in class %s\n", varname.GetChars(),
|
|
|
|
self->GetClass()->TypeName.GetChars());
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
// Set the value of the specified user variable.
|
2016-02-04 21:15:29 +00:00
|
|
|
var->Type->SetValue(reinterpret_cast<BYTE *>(self) + var->Offset, value);
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetUserArray
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserArray)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_NAME (varname);
|
|
|
|
PARAM_INT (pos);
|
|
|
|
PARAM_INT (value);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
PField *var = dyn_cast<PField>(self->GetClass()->Symbols.FindSymbol(varname, true));
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (var == NULL || (var->Flags & VARF_Native) ||
|
|
|
|
!var->Type->IsKindOf(RUNTIME_CLASS(PArray)) ||
|
|
|
|
!static_cast<PArray *>(var->Type)->ElementType->IsKindOf(RUNTIME_CLASS(PBasicType)))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
Printf("%s is not a user array in class %s\n", varname.GetChars(),
|
|
|
|
self->GetClass()->TypeName.GetChars());
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
PArray *arraytype = static_cast<PArray *>(var->Type);
|
|
|
|
if ((unsigned)pos >= arraytype->ElementCount)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
Printf("%d is out of bounds in array %s in class %s\n", pos, varname.GetChars(),
|
|
|
|
self->GetClass()->TypeName.GetChars());
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
// Set the value of the specified user array at index pos.
|
2016-02-04 21:15:29 +00:00
|
|
|
arraytype->ElementType->SetValue(reinterpret_cast<BYTE *>(self) + var->Offset + arraytype->ElementSize * pos, value);
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
2015-04-07 16:14:02 +00:00
|
|
|
// A_Teleport([state teleportstate, [class targettype,
|
|
|
|
// [class fogtype, [int flags, [fixed mindist,
|
|
|
|
// [fixed maxdist]]]]]])
|
2015-01-30 01:30:26 +00:00
|
|
|
//
|
|
|
|
// 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.
|
2015-05-01 13:32:07 +00:00
|
|
|
TF_OVERRIDE = 0x00000400, // Ignore NOTELEPORT.
|
2015-12-17 16:34:38 +00:00
|
|
|
TF_SENSITIVEZ = 0x00000800, // Fail if the actor wouldn't fit in the position (for Z).
|
2015-01-30 01:30:26 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_STATE_OPT (teleport_state) { teleport_state = NULL; }
|
|
|
|
PARAM_CLASS_OPT (target_type, ASpecialSpot) { target_type = PClass::FindActor("BossSpot"); }
|
|
|
|
PARAM_CLASS_OPT (fog_type, AActor) { fog_type = PClass::FindActor("TeleportFog"); }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_FIXED_OPT (mindist) { mindist = 128 << FRACBITS; }
|
|
|
|
PARAM_FIXED_OPT (maxdist) { maxdist = 128 << FRACBITS; }
|
|
|
|
PARAM_INT_OPT (ptr) { ptr = AAPTR_DEFAULT; }
|
2015-04-07 16:14:02 +00:00
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
2016-02-19 02:39:40 +00:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2015-04-07 16:14:02 +00:00
|
|
|
if (!ref)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
return numret;
|
2015-04-07 16:14:02 +00:00
|
|
|
}
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if ((ref->flags2 & MF2_NOTELEPORT) && !(flags & TF_OVERRIDE))
|
2015-05-01 13:32:07 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
return numret;
|
2015-05-01 13:32:07 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
// Randomly choose not to teleport like A_Srcr2Decide.
|
2016-01-19 15:09:44 +00:00
|
|
|
if (flags & TF_RANDOMDECIDE)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
static const int chance[] =
|
|
|
|
{
|
|
|
|
192, 120, 120, 120, 64, 64, 32, 16, 0
|
|
|
|
};
|
|
|
|
|
2015-04-07 16:14:02 +00:00
|
|
|
unsigned int chanceindex = ref->health / ((ref->SpawnHealth()/8 == 0) ? 1 : ref->SpawnHealth()/8);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (chanceindex >= countof(chance))
|
|
|
|
{
|
|
|
|
chanceindex = countof(chance) - 1;
|
|
|
|
}
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (pr_teleport() >= chance[chanceindex])
|
|
|
|
{
|
|
|
|
return numret;
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DSpotState *state = DSpotState::GetSpotState();
|
2015-05-01 13:32:07 +00:00
|
|
|
if (state == NULL)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
return numret;
|
2015-05-01 13:32:07 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (target_type == NULL)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
target_type = PClass::FindActor("BossSpot");
|
2016-01-19 15:09:44 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-19 02:39:40 +00:00
|
|
|
AActor *spot = state->GetSpotWithMinMaxDistance(target_type, ref->X(), ref->Y(), mindist, maxdist);
|
2016-02-04 21:15:29 +00:00
|
|
|
if (spot == NULL)
|
2015-05-01 13:32:07 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
return numret;
|
2015-05-01 13:32:07 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2015-12-17 16:34:38 +00:00
|
|
|
// [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.
|
2016-01-21 00:18:00 +00:00
|
|
|
if (flags & TF_SENSITIVEZ)
|
2015-12-17 16:34:38 +00:00
|
|
|
{
|
2016-01-21 00:18:00 +00:00
|
|
|
fixed_t posz = (flags & TF_USESPOTZ) ? spot->Z() : spot->floorz;
|
2015-12-17 16:34:38 +00:00
|
|
|
if ((posz + ref->height > spot->ceilingz) || (posz < spot->floorz))
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
return numret;
|
2015-12-17 16:34:38 +00:00
|
|
|
}
|
|
|
|
}
|
2016-01-19 19:15:45 +00:00
|
|
|
fixedvec3 prev = ref->Pos();
|
|
|
|
fixed_t aboveFloor = spot->Z() - spot->floorz;
|
2015-01-30 01:30:26 +00:00
|
|
|
fixed_t finalz = spot->floorz + aboveFloor;
|
|
|
|
|
2016-01-19 19:15:45 +00:00
|
|
|
if (spot->Z() + ref->height > spot->ceilingz)
|
2015-04-07 16:14:02 +00:00
|
|
|
finalz = spot->ceilingz - ref->height;
|
2016-01-19 19:15:45 +00:00
|
|
|
else if (spot->Z() < spot->floorz)
|
2015-01-30 01:30:26 +00:00
|
|
|
finalz = spot->floorz;
|
|
|
|
|
|
|
|
//Take precedence and cooperate with telefragging first.
|
2016-01-21 00:18:00 +00:00
|
|
|
bool tele_result = P_TeleportMove(ref, spot->X(), spot->Y(), finalz, !!(flags & TF_TELEFRAG));
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!tele_result && (flags & TF_FORCED))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
//If for some reason the original move didn't work, regardless of telefrag, force it to move.
|
2016-01-19 19:15:45 +00:00
|
|
|
ref->SetOrigin(spot->X(), spot->Y(), finalz, false);
|
2016-01-19 15:09:44 +00:00
|
|
|
tele_result = true;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2015-04-07 16:14:02 +00:00
|
|
|
AActor *fog1 = NULL, *fog2 = NULL;
|
2016-01-19 15:09:44 +00:00
|
|
|
if (tele_result)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
//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.
|
2016-01-19 15:09:44 +00:00
|
|
|
if (fog_type || (flags & TF_USEACTORFOG))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!(flags & TF_NOSRCFOG))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
if (flags & TF_USEACTORFOG)
|
2016-01-19 19:15:45 +00:00
|
|
|
P_SpawnTeleportFog(ref, prev, true, true);
|
2015-01-30 01:30:26 +00:00
|
|
|
else
|
2015-04-07 16:14:02 +00:00
|
|
|
{
|
2016-01-19 19:15:45 +00:00
|
|
|
fog1 = Spawn(fog_type, prev, ALLOW_REPLACE);
|
2015-04-07 16:14:02 +00:00
|
|
|
if (fog1 != NULL)
|
|
|
|
fog1->target = ref;
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!(flags & TF_NODESTFOG))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
if (flags & TF_USEACTORFOG)
|
2016-01-19 19:15:45 +00:00
|
|
|
P_SpawnTeleportFog(ref, ref->Pos(), false, true);
|
2015-01-30 01:30:26 +00:00
|
|
|
else
|
2015-04-07 16:14:02 +00:00
|
|
|
{
|
2016-01-19 19:15:45 +00:00
|
|
|
fog2 = Spawn(fog_type, ref->Pos(), ALLOW_REPLACE);
|
2015-04-07 16:14:02 +00:00
|
|
|
if (fog2 != NULL)
|
|
|
|
fog2->target = ref;
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-19 19:15:45 +00:00
|
|
|
ref->SetZ((flags & TF_USESPOTZ) ? spot->Z() : ref->floorz, false);
|
2016-01-19 15:09:44 +00:00
|
|
|
|
|
|
|
if (!(flags & TF_KEEPANGLE))
|
2015-04-07 16:14:02 +00:00
|
|
|
ref->angle = spot->angle;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!(flags & TF_KEEPVELOCITY))
|
2015-04-07 16:14:02 +00:00
|
|
|
ref->velx = ref->vely = ref->velz = 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (!(flags & TF_NOJUMP)) //The state jump should only happen with the calling actor.
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
if (teleport_state == NULL)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
// Default to Teleport.
|
2016-01-19 15:09:44 +00:00
|
|
|
teleport_state = self->FindState("Teleport");
|
2015-01-30 01:30:26 +00:00
|
|
|
// If still nothing, then return.
|
2016-01-19 15:09:44 +00:00
|
|
|
if (teleport_state == NULL)
|
2016-02-04 21:15:29 +00:00
|
|
|
{
|
|
|
|
return numret;
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
if (numret > 0)
|
|
|
|
{
|
|
|
|
ret[0].SetPointer(teleport_state, ATAG_STATE);
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return numret;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
if (numret > 1)
|
|
|
|
{
|
|
|
|
ret[1].SetInt(tele_result);
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return numret;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Turn
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Turn)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_ANGLE_OPT(angle) { angle = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
self->angle += angle;
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Quake
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Quake)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (intensity);
|
|
|
|
PARAM_INT (duration);
|
|
|
|
PARAM_INT (damrad);
|
|
|
|
PARAM_INT (tremrad);
|
|
|
|
PARAM_SOUND_OPT (sound) { sound = "world/quake"; }
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
P_StartQuake(self, 0, intensity, duration, damrad, tremrad, sound);
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2015-02-14 21:58:39 +00:00
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_QuakeEx
|
|
|
|
//
|
|
|
|
// Extended version of A_Quake. Takes individual axis into account and can
|
|
|
|
// take a flag.
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_QuakeEx)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT(intensityX);
|
|
|
|
PARAM_INT(intensityY);
|
|
|
|
PARAM_INT(intensityZ);
|
|
|
|
PARAM_INT(duration);
|
|
|
|
PARAM_INT(damrad);
|
|
|
|
PARAM_INT(tremrad);
|
|
|
|
PARAM_SOUND_OPT (sound) { sound = "world/quake"; }
|
|
|
|
PARAM_INT_OPT(flags) { flags = 0; }
|
|
|
|
PARAM_FLOAT_OPT(mulWaveX) { mulWaveX = 1.; }
|
|
|
|
PARAM_FLOAT_OPT(mulWaveY) { mulWaveY = 1.; }
|
|
|
|
PARAM_FLOAT_OPT(mulWaveZ) { mulWaveZ = 1.; }
|
Added QF_SINE
- Squashed commit of the following:
commit bc45fe3263d34ef5f746f524687999c19bf7b779
Author: Randy Heit <rheit@users.noreply.github.com>
Date: Sun Mar 1 18:51:05 2015 -0600
wave scale -> wave speed
commit ff96388b128c724c1198757bfa52f1935a263356
Author: Randy Heit <rheit@users.noreply.github.com>
Date: Sun Mar 1 18:45:32 2015 -0600
More sine quake fixes
commit 2a89749a6fe6d271b9fbdc218779f680afcf4cb6
Merge: 719dfbe 5456074
Author: MajorCooke <paul.growney22@gmail.com>
Date: Sat Feb 28 20:37:22 2015 -0600
Added QF_WAVE to A_QuakeEx.
- Changes the random quakes into a sine wave (see Shadow Warrior/Rise of the Triad reboots, Hard Reset, etc.)
- Added 3 properties to control waves per second along each individual axis. Only works with QF_WAVE.
- Intensity X/Y/Z property becomes the amplitude of the wave.
- Stacks with regular quakes, allowing shaking along the camera which must be called using A_QuakeEx WITHOUT the flag, or the other quaking functions.
- Uses the youngest quake's time for positioning.
commit 54560741581e8d15cc7060e8e068cf85e9a4b432
Author: MajorCooke <paul.growney22@gmail.com>
Date: Sat Feb 28 20:21:19 2015 -0600
Recommitted recommended changes by Randi, with some modifications. Now, we should be finished!
commit 6f4473013411686d88fc185bdc1cc58b1035b0f1
Author: MajorCooke <paul.growney22@gmail.com>
Date: Sat Feb 28 12:52:57 2015 -0600
Finish this revert.
commit 467e53f9400f588a2ada9b32e7634cb1f4ad5066
Author: MajorCooke <paul.growney22@gmail.com>
Date: Sat Feb 28 12:46:02 2015 -0600
Reverted back to what was working.
commit da9de56a67efda08036e481fd5fccd5392ce6810
Author: MajorCooke <paul.growney22@gmail.com>
Date: Thu Feb 26 18:53:20 2015 -0600
Forgot this bit, for testing.
commit c5093d9bb97caf8478cefc32abc56a036feeea58
Author: MajorCooke <paul.growney22@gmail.com>
Date: Thu Feb 26 18:52:46 2015 -0600
Some more progress, but...
- This did not solve anything. In fact, it did the opposite -- completely broke wave quakes. Now they only happen whenever a random quake is in progress.
- Left in the commented code on purpose so Randi can test it.
commit 7e526405d2127cbb279f66008c8f8e55a5d497f3
Author: MajorCooke <paul.growney22@gmail.com>
Date: Wed Feb 25 17:50:42 2015 -0600
- Use newest waveform timer, not oldest.
commit 1356443609dbc6c7f46e081d0846816dc0836124
Author: MajorCooke <paul.growney22@gmail.com>
Date: Wed Feb 25 17:32:09 2015 -0600
- Got regular quakes to multiply onto sine quakes, but the vice versa needs fixing too.
commit d95796c94c70cd0229d4a6d30f69e3a7568b9588
Author: MajorCooke <paul.growney22@gmail.com>
Date: Wed Feb 25 16:46:21 2015 -0600
- Last hurdle. Now just need to figure out how to properly scale up and down.
commit 4bc3458e689155ce72c09776604d9eb4fa73d8be
Author: MajorCooke <paul.growney22@gmail.com>
Date: Tue Feb 24 23:18:03 2015 -0600
- Fixed the quakes being unstackable.
commit b51012d6d4ea065bf7f6fc9c1a0472966491f7af
Author: MajorCooke <paul.growney22@gmail.com>
Date: Mon Feb 23 23:48:34 2015 -0600
QF_WAVE renamed from SINE.
- Lots of ground covered, but still more to go.
- Still need to figure out how to make the camera properly shudder.
commit 427e4893193470bbf45415ffec70a0b69b8cccfd
Author: MajorCooke <paul.growney22@gmail.com>
Date: Sun Feb 22 16:52:30 2015 -0600
- Begin the groundworks for QF_SINE.
- Need to figure out how to rework and manipulate the sine wave to move faster, and to allow going below 0 without breaking it too much.
2015-03-02 00:53:34 +00:00
|
|
|
P_StartQuakeXYZ(self, 0, intensityX, intensityY, intensityZ, duration, damrad, tremrad, sound, flags, mulWaveX, mulWaveY, mulWaveZ);
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-02-14 21:58:39 +00:00
|
|
|
}
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Weave
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
void A_Weave(AActor *self, int xyspeed, int zspeed, fixed_t xydist, fixed_t zdist)
|
|
|
|
{
|
|
|
|
fixed_t newX, newY;
|
|
|
|
int weaveXY, weaveZ;
|
|
|
|
int angle;
|
|
|
|
fixed_t dist;
|
|
|
|
|
|
|
|
weaveXY = self->WeaveIndexXY & 63;
|
|
|
|
weaveZ = self->WeaveIndexZ & 63;
|
|
|
|
angle = (self->angle + ANG90) >> ANGLETOFINESHIFT;
|
|
|
|
|
|
|
|
if (xydist != 0 && xyspeed != 0)
|
|
|
|
{
|
|
|
|
dist = MulScale13(finesine[weaveXY << BOBTOFINESHIFT], xydist);
|
2016-01-19 19:15:45 +00:00
|
|
|
newX = self->X() - FixedMul (finecosine[angle], dist);
|
|
|
|
newY = self->Y() - FixedMul (finesine[angle], dist);
|
2015-01-30 01:30:26 +00:00
|
|
|
weaveXY = (weaveXY + xyspeed) & 63;
|
|
|
|
dist = MulScale13(finesine[weaveXY << BOBTOFINESHIFT], xydist);
|
|
|
|
newX += FixedMul (finecosine[angle], dist);
|
|
|
|
newY += FixedMul (finesine[angle], dist);
|
|
|
|
if (!(self->flags5 & MF5_NOINTERACTION))
|
|
|
|
{
|
|
|
|
P_TryMove (self, newX, newY, true);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
self->UnlinkFromWorld ();
|
|
|
|
self->flags |= MF_NOBLOCKMAP;
|
2016-03-08 13:41:37 +00:00
|
|
|
// We need to do portal offsetting here explicitly, because SetXY cannot do that.
|
2016-01-19 19:15:45 +00:00
|
|
|
newX -= self->X();
|
|
|
|
newY -= self->Y();
|
|
|
|
self->SetXY(self->Vec2Offset(newX, newY));
|
2015-01-30 01:30:26 +00:00
|
|
|
self->LinkToWorld ();
|
|
|
|
}
|
|
|
|
self->WeaveIndexXY = weaveXY;
|
|
|
|
}
|
|
|
|
if (zdist != 0 && zspeed != 0)
|
|
|
|
{
|
2016-01-19 19:15:45 +00:00
|
|
|
self->AddZ(-MulScale13(finesine[weaveZ << BOBTOFINESHIFT], zdist));
|
2015-01-30 01:30:26 +00:00
|
|
|
weaveZ = (weaveZ + zspeed) & 63;
|
2016-01-19 19:15:45 +00:00
|
|
|
self->AddZ(MulScale13(finesine[weaveZ << BOBTOFINESHIFT], zdist));
|
2015-01-30 01:30:26 +00:00
|
|
|
self->WeaveIndexZ = weaveZ;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Weave)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (xspeed);
|
|
|
|
PARAM_INT (yspeed);
|
|
|
|
PARAM_FIXED (xdist);
|
|
|
|
PARAM_FIXED (ydist);
|
2015-01-30 01:30:26 +00:00
|
|
|
A_Weave(self, xspeed, yspeed, xdist, ydist);
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_LineEffect
|
|
|
|
//
|
|
|
|
// This allows linedef effects to be activated inside deh frames.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LineEffect)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT_OPT(special) { special = 0; }
|
|
|
|
PARAM_INT_OPT(tag) { tag = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
line_t junk;
|
|
|
|
maplinedef_t oldjunk;
|
2015-01-30 01:30:26 +00:00
|
|
|
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
|
2015-04-19 15:51:34 +00:00
|
|
|
P_TranslateLineDef(&junk, &oldjunk); // Turn into native type
|
2015-01-30 01:30:26 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(res);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A Wolf3D-style attack codepointer
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
enum WolfAttackFlags
|
|
|
|
{
|
|
|
|
WAF_NORANDOM = 1,
|
|
|
|
WAF_USEPUFF = 2,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_WolfAttack)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_SOUND_OPT (sound) { sound = "weapons/pistol"; }
|
|
|
|
PARAM_FIXED_OPT (snipe) { snipe = FRACUNIT; }
|
|
|
|
PARAM_INT_OPT (maxdamage) { maxdamage = 64; }
|
|
|
|
PARAM_INT_OPT (blocksize) { blocksize = 128; }
|
|
|
|
PARAM_INT_OPT (pointblank) { pointblank = 2; }
|
|
|
|
PARAM_INT_OPT (longrange) { longrange = 4; }
|
|
|
|
PARAM_FIXED_OPT (runspeed) { runspeed = 160*FRACUNIT; }
|
|
|
|
PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor(NAME_BulletPuff); }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (!self->target)
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
// Enemy can't see target
|
|
|
|
if (!P_CheckSight(self, self->target))
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
A_FaceTarget (self);
|
|
|
|
|
|
|
|
// Target can dodge if it can see enemy
|
2016-01-10 19:46:26 +00:00
|
|
|
angle_t angle = self->target->AngleTo(self) - self->target->angle;
|
2015-01-30 01:30:26 +00:00
|
|
|
angle >>= 24;
|
|
|
|
bool dodge = (P_CheckSight(self->target, self) && (angle>226 || angle<30));
|
|
|
|
|
|
|
|
// Distance check is simplistic
|
2016-01-19 19:15:45 +00:00
|
|
|
fixedvec2 vec = self->Vec2To(self->target);
|
|
|
|
fixed_t dx = abs (vec.x);
|
|
|
|
fixed_t dy = abs (vec.y);
|
2015-01-30 01:30:26 +00:00
|
|
|
fixed_t dist = dx > dy ? dx : dy;
|
|
|
|
|
|
|
|
// Some enemies are more precise
|
|
|
|
dist = FixedMul(dist, snipe);
|
|
|
|
|
|
|
|
// Convert distance into integer number of blocks
|
|
|
|
dist >>= FRACBITS;
|
|
|
|
dist /= blocksize;
|
|
|
|
|
|
|
|
// Now for the speed accuracy thingie
|
|
|
|
fixed_t speed = FixedMul(self->target->velx, self->target->velx)
|
|
|
|
+ FixedMul(self->target->vely, self->target->vely)
|
|
|
|
+ FixedMul(self->target->velz, self->target->velz);
|
|
|
|
int hitchance = speed < runspeed ? 256 : 160;
|
|
|
|
|
|
|
|
// Distance accuracy (factoring dodge)
|
|
|
|
hitchance -= dist * (dodge ? 16 : 8);
|
|
|
|
|
|
|
|
// While we're here, we may as well do something for this:
|
|
|
|
if (self->target->flags & MF_SHADOW)
|
|
|
|
{
|
|
|
|
hitchance >>= 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The attack itself
|
|
|
|
if (pr_cabullet() < hitchance)
|
|
|
|
{
|
|
|
|
// Compute position for spawning blood/puff
|
2016-01-10 19:46:26 +00:00
|
|
|
angle = self->target->AngleTo(self);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 19:15:45 +00:00
|
|
|
fixedvec3 bloodpos = self->target->Vec3Angle(self->target->radius, angle, self->target->height >> 1);
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
AActor *dpuff = GetDefaultByType(pufftype->GetReplacement());
|
2015-01-30 01:30:26 +00:00
|
|
|
mod = dpuff->DamageType;
|
|
|
|
|
|
|
|
if (dpuff->flags2 & MF2_THRUGHOST && self->target->flags3 & MF3_GHOST)
|
|
|
|
damage = 0;
|
|
|
|
|
|
|
|
if ((0 && dpuff->flags3 & MF3_PUFFONACTORS) || !spawnblood)
|
|
|
|
{
|
|
|
|
spawnblood = false;
|
2016-03-06 20:58:36 +00:00
|
|
|
P_SpawnPuff(self, pufftype, bloodpos, angle, angle, 0);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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)
|
|
|
|
{
|
2016-01-19 19:15:45 +00:00
|
|
|
P_SpawnBlood(bloodpos, angle, newdam > 0 ? newdam : damage, self->target);
|
2016-02-25 15:50:03 +00:00
|
|
|
P_TraceBleed(newdam > 0 ? newdam : damage, self->target, self);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// And finally, let's play the sound
|
|
|
|
S_Sound (self, CHAN_WEAPON, sound, 1, ATTN_NORM);
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_Warp
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Warp)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT(destination_selector);
|
|
|
|
PARAM_FIXED_OPT(xofs) { xofs = 0; }
|
|
|
|
PARAM_FIXED_OPT(yofs) { yofs = 0; }
|
|
|
|
PARAM_FIXED_OPT(zofs) { zofs = 0; }
|
|
|
|
PARAM_ANGLE_OPT(angle) { angle = 0; }
|
|
|
|
PARAM_INT_OPT(flags) { flags = 0; }
|
|
|
|
PARAM_STATE_OPT(success_state) { success_state = NULL; }
|
|
|
|
PARAM_FIXED_OPT(heightoffset) { heightoffset = 0; }
|
|
|
|
PARAM_FIXED_OPT(radiusoffset) { radiusoffset = 0; }
|
|
|
|
PARAM_ANGLE_OPT(pitch) { pitch = 0; }
|
2015-07-31 12:54:01 +00:00
|
|
|
|
|
|
|
AActor *reference;
|
2016-02-04 21:15:29 +00:00
|
|
|
|
2016-02-19 02:39:40 +00:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if ((flags & WARPF_USETID))
|
2015-07-31 12:54:01 +00:00
|
|
|
{
|
|
|
|
reference = SingleActorFromTID(destination_selector, self);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
reference = COPY_AAPTR(self, destination_selector);
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
//If there is no actor to warp to, fail.
|
|
|
|
if (!reference)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
return numret;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2015-10-03 22:28:54 +00:00
|
|
|
if (P_Thing_Warp(self, reference, xofs, yofs, zofs, angle, flags, heightoffset, radiusoffset, pitch))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-22 01:21:39 +00:00
|
|
|
if (success_state)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
// Jumps should never set the result for inventory state chains!
|
2015-01-30 01:30:26 +00:00
|
|
|
// in this case, you have the statejump to help you handle all the success anyway.
|
2016-02-19 02:39:40 +00:00
|
|
|
if (numret > 0)
|
|
|
|
{
|
|
|
|
ret[0].SetPointer(success_state, ATAG_STATE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (numret > 1)
|
|
|
|
{
|
|
|
|
ret[1].SetInt(true);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return numret;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// ACS_Named* stuff
|
|
|
|
|
|
|
|
//
|
|
|
|
// These are exactly like their un-named line special equivalents, except
|
|
|
|
// they take strings instead of integers to indicate which script to run.
|
|
|
|
// Some of these probably aren't very useful, but they are included for
|
|
|
|
// the sake of completeness.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteWithResult)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_NAME (scriptname);
|
|
|
|
PARAM_INT_OPT (arg1) { arg1 = 0; }
|
|
|
|
PARAM_INT_OPT (arg2) { arg2 = 0; }
|
|
|
|
PARAM_INT_OPT (arg3) { arg3 = 0; }
|
|
|
|
PARAM_INT_OPT (arg4) { arg4 = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
int res = P_ExecuteSpecial(ACS_ExecuteWithResult, NULL, self, false, -scriptname, arg1, arg2, arg3, arg4);
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_INT(res);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecute)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_NAME (scriptname);
|
|
|
|
PARAM_INT_OPT (mapnum) { mapnum = 0; }
|
|
|
|
PARAM_INT_OPT (arg1) { arg1 = 0; }
|
|
|
|
PARAM_INT_OPT (arg2) { arg2 = 0; }
|
|
|
|
PARAM_INT_OPT (arg3) { arg3 = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
int res = P_ExecuteSpecial(ACS_Execute, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3);
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_INT(res);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteAlways)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_NAME (scriptname);
|
|
|
|
PARAM_INT_OPT (mapnum) { mapnum = 0; }
|
|
|
|
PARAM_INT_OPT (arg1) { arg1 = 0; }
|
|
|
|
PARAM_INT_OPT (arg2) { arg2 = 0; }
|
|
|
|
PARAM_INT_OPT (arg3) { arg3 = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
int res = P_ExecuteSpecial(ACS_ExecuteAlways, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3);
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_INT(res);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecute)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_NAME (scriptname);
|
|
|
|
PARAM_INT_OPT (mapnum) { mapnum = 0; }
|
|
|
|
PARAM_INT_OPT (arg1) { arg1 = 0; }
|
|
|
|
PARAM_INT_OPT (arg2) { arg2 = 0; }
|
|
|
|
PARAM_INT_OPT (lock) { lock = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
int res = P_ExecuteSpecial(ACS_LockedExecute, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock);
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_INT(res);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecuteDoor)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_NAME (scriptname);
|
|
|
|
PARAM_INT_OPT (mapnum) { mapnum = 0; }
|
|
|
|
PARAM_INT_OPT (arg1) { arg1 = 0; }
|
|
|
|
PARAM_INT_OPT (arg2) { arg2 = 0; }
|
|
|
|
PARAM_INT_OPT (lock) { lock = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
int res = P_ExecuteSpecial(ACS_LockedExecuteDoor, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock);
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_INT(res);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedSuspend)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_NAME (scriptname);
|
|
|
|
PARAM_INT_OPT (mapnum) { mapnum = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
int res = P_ExecuteSpecial(ACS_Suspend, NULL, self, false, -scriptname, mapnum, 0, 0, 0);
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_INT(res);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedTerminate)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_NAME (scriptname);
|
|
|
|
PARAM_INT_OPT (mapnum) { mapnum = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
int res = P_ExecuteSpecial(ACS_Terminate, NULL, self, false, -scriptname, mapnum, 0, 0, 0);
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_INT(res);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
- Significant A_RadiusGive update.
- Added filter and species parameter.
- Added new flags: RGF_INCLUSIVE, RGF_ITEMS, RGF_KILLED, RGF_EXFILTER, RGF_EXSPECIES, and RGF_EITHER.
- RGF_ITEMS: Items can receive inventory.
- RGF_KILLED: Actors who are truly dead might not be corpses, and vice versa.
- RGF_EXFILTER: Blacklists the specified actor filter. All but the filtered actor can receive the item.
- RGF_EXSPECIES: Blacklists the specified species. All but the filtered species can receive the item.
- RGF_EITHER: The actor can receive the item if it satisfies either the filter or the species. Only useful when both are used.
- RGF_INCLUSIVE: An actor marked as more than one pointer to the calling actor can ignore the exclusion pointers, but only if at least one is missing. I.e. an actor who is a target and tracer of the calling actor can still receive the item, if the calling actor doesn't pass RGF_NOTARGET and NOTRACER at the same time. RGF_INCLUSIVE only works with the pointer filtering flags. By default, if not specified, the actor will not be loopholed the item if they are under any one of the three filters.
- Fixed discrepancies and dependencies upon several flags and actor conditions which caused the function to fail.
2015-07-22 21:46:14 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
static bool DoCheckClass(AActor *mo, PClassActor *filterClass, bool exclude)
|
- Significant A_RadiusGive update.
- Added filter and species parameter.
- Added new flags: RGF_INCLUSIVE, RGF_ITEMS, RGF_KILLED, RGF_EXFILTER, RGF_EXSPECIES, and RGF_EITHER.
- RGF_ITEMS: Items can receive inventory.
- RGF_KILLED: Actors who are truly dead might not be corpses, and vice versa.
- RGF_EXFILTER: Blacklists the specified actor filter. All but the filtered actor can receive the item.
- RGF_EXSPECIES: Blacklists the specified species. All but the filtered species can receive the item.
- RGF_EITHER: The actor can receive the item if it satisfies either the filter or the species. Only useful when both are used.
- RGF_INCLUSIVE: An actor marked as more than one pointer to the calling actor can ignore the exclusion pointers, but only if at least one is missing. I.e. an actor who is a target and tracer of the calling actor can still receive the item, if the calling actor doesn't pass RGF_NOTARGET and NOTRACER at the same time. RGF_INCLUSIVE only works with the pointer filtering flags. By default, if not specified, the actor will not be loopholed the item if they are under any one of the three filters.
- Fixed discrepancies and dependencies upon several flags and actor conditions which caused the function to fail.
2015-07-22 21:46:14 +00:00
|
|
|
{
|
|
|
|
const PClass *actorClass = mo->GetClass();
|
|
|
|
if (filterClass == NULL) return true;
|
|
|
|
return exclude ? (actorClass != filterClass) : (actorClass == filterClass);
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
- Significant A_RadiusGive update.
- Added filter and species parameter.
- Added new flags: RGF_INCLUSIVE, RGF_ITEMS, RGF_KILLED, RGF_EXFILTER, RGF_EXSPECIES, and RGF_EITHER.
- RGF_ITEMS: Items can receive inventory.
- RGF_KILLED: Actors who are truly dead might not be corpses, and vice versa.
- RGF_EXFILTER: Blacklists the specified actor filter. All but the filtered actor can receive the item.
- RGF_EXSPECIES: Blacklists the specified species. All but the filtered species can receive the item.
- RGF_EITHER: The actor can receive the item if it satisfies either the filter or the species. Only useful when both are used.
- RGF_INCLUSIVE: An actor marked as more than one pointer to the calling actor can ignore the exclusion pointers, but only if at least one is missing. I.e. an actor who is a target and tracer of the calling actor can still receive the item, if the calling actor doesn't pass RGF_NOTARGET and NOTRACER at the same time. RGF_INCLUSIVE only works with the pointer filtering flags. By default, if not specified, the actor will not be loopholed the item if they are under any one of the three filters.
- Fixed discrepancies and dependencies upon several flags and actor conditions which caused the function to fail.
2015-07-22 21:46:14 +00:00
|
|
|
// A_RadiusGive(item, distance, flags, amount, filter, species)
|
2015-01-30 01:30:26 +00:00
|
|
|
//
|
|
|
|
// Uses code roughly similar to A_Explode (but without all the compatibility
|
- Significant A_RadiusGive update.
- Added filter and species parameter.
- Added new flags: RGF_INCLUSIVE, RGF_ITEMS, RGF_KILLED, RGF_EXFILTER, RGF_EXSPECIES, and RGF_EITHER.
- RGF_ITEMS: Items can receive inventory.
- RGF_KILLED: Actors who are truly dead might not be corpses, and vice versa.
- RGF_EXFILTER: Blacklists the specified actor filter. All but the filtered actor can receive the item.
- RGF_EXSPECIES: Blacklists the specified species. All but the filtered species can receive the item.
- RGF_EITHER: The actor can receive the item if it satisfies either the filter or the species. Only useful when both are used.
- RGF_INCLUSIVE: An actor marked as more than one pointer to the calling actor can ignore the exclusion pointers, but only if at least one is missing. I.e. an actor who is a target and tracer of the calling actor can still receive the item, if the calling actor doesn't pass RGF_NOTARGET and NOTRACER at the same time. RGF_INCLUSIVE only works with the pointer filtering flags. By default, if not specified, the actor will not be loopholed the item if they are under any one of the three filters.
- Fixed discrepancies and dependencies upon several flags and actor conditions which caused the function to fail.
2015-07-22 21:46:14 +00:00
|
|
|
// baggage and damage computation code) to give an item to all eligible mobjs
|
2015-01-30 01:30:26 +00:00
|
|
|
// 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,
|
- Significant A_RadiusGive update.
- Added filter and species parameter.
- Added new flags: RGF_INCLUSIVE, RGF_ITEMS, RGF_KILLED, RGF_EXFILTER, RGF_EXSPECIES, and RGF_EITHER.
- RGF_ITEMS: Items can receive inventory.
- RGF_KILLED: Actors who are truly dead might not be corpses, and vice versa.
- RGF_EXFILTER: Blacklists the specified actor filter. All but the filtered actor can receive the item.
- RGF_EXSPECIES: Blacklists the specified species. All but the filtered species can receive the item.
- RGF_EITHER: The actor can receive the item if it satisfies either the filter or the species. Only useful when both are used.
- RGF_INCLUSIVE: An actor marked as more than one pointer to the calling actor can ignore the exclusion pointers, but only if at least one is missing. I.e. an actor who is a target and tracer of the calling actor can still receive the item, if the calling actor doesn't pass RGF_NOTARGET and NOTRACER at the same time. RGF_INCLUSIVE only works with the pointer filtering flags. By default, if not specified, the actor will not be loopholed the item if they are under any one of the three filters.
- Fixed discrepancies and dependencies upon several flags and actor conditions which caused the function to fail.
2015-07-22 21:46:14 +00:00
|
|
|
RGF_INCLUSIVE = 1 << 12,
|
|
|
|
RGF_ITEMS = 1 << 13,
|
|
|
|
RGF_KILLED = 1 << 14,
|
|
|
|
RGF_EXFILTER = 1 << 15,
|
|
|
|
RGF_EXSPECIES = 1 << 16,
|
|
|
|
RGF_EITHER = 1 << 17,
|
2016-02-19 02:39:40 +00:00
|
|
|
|
|
|
|
RGF_MASK = /*2111*/
|
|
|
|
RGF_GIVESELF |
|
|
|
|
RGF_PLAYERS |
|
|
|
|
RGF_MONSTERS |
|
|
|
|
RGF_OBJECTS |
|
|
|
|
RGF_VOODOO |
|
|
|
|
RGF_CORPSES | RGF_MISSILES,
|
2015-01-30 01:30:26 +00:00
|
|
|
};
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
static bool DoRadiusGive(AActor *self, AActor *thing, PClassActor *item, int amount, fixed_t distance, int flags, PClassActor *filter, FName species, fixed_t mindist)
|
2016-01-27 20:54:46 +00:00
|
|
|
{
|
2016-01-27 21:22:05 +00:00
|
|
|
// [MC] We only want to make an exception for missiles here. Nothing else.
|
2016-02-22 17:30:44 +00:00
|
|
|
bool missilePass = !!((flags & RGF_MISSILES) && thing->flags & MF_MISSILE);
|
2016-01-27 21:22:05 +00:00
|
|
|
if (thing == self)
|
|
|
|
{
|
|
|
|
if (!(flags & RGF_GIVESELF))
|
|
|
|
return false;
|
|
|
|
}
|
2016-02-22 17:30:44 +00:00
|
|
|
else if (thing->flags & MF_MISSILE)
|
2016-01-27 21:22:05 +00:00
|
|
|
{
|
|
|
|
if (!missilePass)
|
|
|
|
return false;
|
|
|
|
}
|
2016-01-27 20:54:46 +00:00
|
|
|
//[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)))
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
if (thing != self) //Don't let filter and species obstruct RGF_GIVESELF.
|
2016-01-27 20:54:46 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Check for target, master, and tracer flagging.
|
|
|
|
bool targetPass = true;
|
|
|
|
bool masterPass = true;
|
|
|
|
bool tracerPass = true;
|
|
|
|
bool ptrPass = false;
|
|
|
|
if ((thing != self) && (flags & (RGF_NOTARGET | RGF_NOMASTER | RGF_NOTRACER)))
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
//Next, actor flag checking.
|
2016-01-27 20:54:46 +00:00
|
|
|
bool selfPass = !!((flags & RGF_GIVESELF) && thing == self);
|
|
|
|
bool corpsePass = !!((flags & RGF_CORPSES) && thing->flags & MF_CORPSE);
|
|
|
|
bool killedPass = !!((flags & RGF_KILLED) && thing->flags6 & MF6_KILLED);
|
|
|
|
bool monsterPass = !!((flags & RGF_MONSTERS) && thing->flags3 & MF3_ISMONSTER);
|
|
|
|
bool objectPass = !!((flags & RGF_OBJECTS) && (thing->player == NULL) && (!(thing->flags3 & MF3_ISMONSTER))
|
|
|
|
&& ((thing->flags & MF_SHOOTABLE) || (thing->flags6 & MF6_VULNERABLE)));
|
|
|
|
bool playerPass = !!((flags & RGF_PLAYERS) && (thing->player != NULL) && (thing->player->mo == thing));
|
|
|
|
bool voodooPass = !!((flags & RGF_VOODOO) && (thing->player != NULL) && (thing->player->mo != thing));
|
|
|
|
//Self calls priority over the rest of this.
|
|
|
|
if (!selfPass)
|
|
|
|
{
|
|
|
|
//If it's specifically a monster/object/player/voodoo... Can be either or...
|
|
|
|
if (monsterPass || objectPass || playerPass || voodooPass)
|
|
|
|
{
|
|
|
|
//...and is dead, without desire to give to the dead...
|
|
|
|
if (((thing->health <= 0) && !(corpsePass || killedPass)))
|
|
|
|
{
|
|
|
|
//Skip!
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool itemPass = !!((flags & RGF_ITEMS) && thing->IsKindOf(RUNTIME_CLASS(AInventory)));
|
|
|
|
|
|
|
|
if (selfPass || monsterPass || corpsePass || killedPass || itemPass || objectPass || missilePass || playerPass || voodooPass)
|
|
|
|
{
|
|
|
|
|
|
|
|
fixedvec3 diff = self->Vec3To(thing);
|
|
|
|
diff.z += (thing->height - self->height) / 2;
|
|
|
|
if (flags & RGF_CUBE)
|
|
|
|
{ // check if inside a cube
|
|
|
|
double dx = fabs((double)(diff.x));
|
|
|
|
double dy = fabs((double)(diff.y));
|
|
|
|
double dz = fabs((double)(diff.z));
|
|
|
|
double dist = (double)distance;
|
|
|
|
double min = (double)mindist;
|
|
|
|
if ((dx > dist || dy > dist || dz > dist) || (min && (dx < min && dy < min && dz < min)))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{ // check if inside a sphere
|
|
|
|
double distsquared = double(distance) * double(distance);
|
|
|
|
double minsquared = double(mindist) * double(mindist);
|
2016-03-10 19:44:53 +00:00
|
|
|
double lengthsquared = DVector3(diff.x, diff.y, diff.z).LengthSquared();
|
2016-01-27 20:54:46 +00:00
|
|
|
if (lengthsquared > distsquared || (minsquared && (lengthsquared < minsquared)))
|
|
|
|
{
|
|
|
|
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, 0, 0, 0, NO_REPLACE));
|
|
|
|
if (gift->IsKindOf(RUNTIME_CLASS(AHealth)))
|
|
|
|
{
|
|
|
|
gift->Amount *= amount;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gift->Amount = amount;
|
|
|
|
}
|
|
|
|
gift->flags |= MF_DROPPED;
|
|
|
|
gift->ClearCounters();
|
|
|
|
if (!gift->CallTryPickup(thing))
|
|
|
|
{
|
|
|
|
gift->Destroy();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusGive)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_CLASS (item, AInventory);
|
|
|
|
PARAM_FIXED (distance);
|
|
|
|
PARAM_INT (flags);
|
|
|
|
PARAM_INT_OPT (amount) { amount = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor) { filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
|
|
|
PARAM_FIXED_OPT (mindist) { mindist = 0; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
// We need a valid item, valid targets, and a valid range
|
2015-09-29 16:40:44 +00:00
|
|
|
if (item == NULL || (flags & RGF_MASK) == 0 || !flags || distance <= 0 || mindist >= distance)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_INT(0);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
- Significant A_RadiusGive update.
- Added filter and species parameter.
- Added new flags: RGF_INCLUSIVE, RGF_ITEMS, RGF_KILLED, RGF_EXFILTER, RGF_EXSPECIES, and RGF_EITHER.
- RGF_ITEMS: Items can receive inventory.
- RGF_KILLED: Actors who are truly dead might not be corpses, and vice versa.
- RGF_EXFILTER: Blacklists the specified actor filter. All but the filtered actor can receive the item.
- RGF_EXSPECIES: Blacklists the specified species. All but the filtered species can receive the item.
- RGF_EITHER: The actor can receive the item if it satisfies either the filter or the species. Only useful when both are used.
- RGF_INCLUSIVE: An actor marked as more than one pointer to the calling actor can ignore the exclusion pointers, but only if at least one is missing. I.e. an actor who is a target and tracer of the calling actor can still receive the item, if the calling actor doesn't pass RGF_NOTARGET and NOTRACER at the same time. RGF_INCLUSIVE only works with the pointer filtering flags. By default, if not specified, the actor will not be loopholed the item if they are under any one of the three filters.
- Fixed discrepancies and dependencies upon several flags and actor conditions which caused the function to fail.
2015-07-22 21:46:14 +00:00
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
if (amount == 0)
|
|
|
|
{
|
|
|
|
amount = 1;
|
|
|
|
}
|
|
|
|
AActor *thing;
|
2016-02-19 02:39:40 +00:00
|
|
|
int given = 0;
|
2016-01-27 21:22:05 +00:00
|
|
|
if (flags & RGF_MISSILES)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-27 21:22:05 +00:00
|
|
|
TThinkerIterator<AActor> it;
|
|
|
|
while ((thing = it.Next()))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
given += DoRadiusGive(self, thing, item, amount, distance, flags, filter, species, mindist);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-01-27 21:22:05 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-02-28 22:08:32 +00:00
|
|
|
FPortalGroupArray check(FPortalGroupArray::PGA_Full3d);
|
|
|
|
fixed_t mid = self->Z() + self->height / 2;
|
2016-03-10 13:22:18 +00:00
|
|
|
FMultiBlockThingsIterator it(check, self->X(), self->Y(), mid-distance, mid+distance, distance, false, self->Sector);
|
2016-02-28 22:08:32 +00:00
|
|
|
FMultiBlockThingsIterator::CheckResult cres;
|
|
|
|
|
|
|
|
while ((it.Next(&cres)))
|
- Significant A_RadiusGive update.
- Added filter and species parameter.
- Added new flags: RGF_INCLUSIVE, RGF_ITEMS, RGF_KILLED, RGF_EXFILTER, RGF_EXSPECIES, and RGF_EITHER.
- RGF_ITEMS: Items can receive inventory.
- RGF_KILLED: Actors who are truly dead might not be corpses, and vice versa.
- RGF_EXFILTER: Blacklists the specified actor filter. All but the filtered actor can receive the item.
- RGF_EXSPECIES: Blacklists the specified species. All but the filtered species can receive the item.
- RGF_EITHER: The actor can receive the item if it satisfies either the filter or the species. Only useful when both are used.
- RGF_INCLUSIVE: An actor marked as more than one pointer to the calling actor can ignore the exclusion pointers, but only if at least one is missing. I.e. an actor who is a target and tracer of the calling actor can still receive the item, if the calling actor doesn't pass RGF_NOTARGET and NOTRACER at the same time. RGF_INCLUSIVE only works with the pointer filtering flags. By default, if not specified, the actor will not be loopholed the item if they are under any one of the three filters.
- Fixed discrepancies and dependencies upon several flags and actor conditions which caused the function to fail.
2015-07-22 21:46:14 +00:00
|
|
|
{
|
2016-02-28 22:08:32 +00:00
|
|
|
given += DoRadiusGive(self, cres.thing, item, amount, distance, flags, filter, species, mindist);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_INT(given);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2015-11-26 07:33:02 +00:00
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_CheckSpecies
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSpecies)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_STATE(jump);
|
|
|
|
PARAM_NAME_OPT(species) { species = NAME_None; }
|
|
|
|
PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; }
|
2015-11-26 07:33:02 +00:00
|
|
|
|
|
|
|
AActor *mobj = COPY_AAPTR(self, ptr);
|
|
|
|
|
2016-02-19 02:39:40 +00:00
|
|
|
if (mobj != NULL && jump && mobj->GetSpecies() == species)
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jump);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-11-26 07:33:02 +00:00
|
|
|
}
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_SetTics
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTics)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT(tics_to_set);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (stateowner != self && self->player != NULL && stateowner->IsKindOf(RUNTIME_CLASS(AWeapon)))
|
|
|
|
{ // Is this a weapon? Need to check psp states for a match, then. Blah.
|
|
|
|
for (int i = 0; i < NUMPSPRITES; ++i)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
if (self->player->psprites[i].state == callingstate)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
self->player->psprites[i].tics = tics_to_set;
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Just set tics for self.
|
|
|
|
self->tics = tics_to_set;
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_SetDamageType
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetDamageType)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_NAME(damagetype);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
self->DamageType = damagetype;
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_DropItem
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropItem)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_CLASS (spawntype, AActor);
|
|
|
|
PARAM_INT_OPT (amount) { amount = -1; }
|
|
|
|
PARAM_INT_OPT (chance) { chance = 256; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
P_DropItem(self, spawntype, amount, chance);
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_SetSpeed
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpeed)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-06 04:24:12 +00:00
|
|
|
PARAM_FIXED(speed);
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (ref != NULL)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
ref->Speed = speed;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2015-09-07 00:57:43 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_SetFloatSpeed
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetFloatSpeed)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-06 04:24:12 +00:00
|
|
|
PARAM_FIXED(speed);
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; }
|
2015-09-07 00:57:43 +00:00
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
if (!ref)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-09-07 00:57:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ref->FloatSpeed = speed;
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-09-07 00:57:43 +00:00
|
|
|
}
|
|
|
|
|
2015-09-17 14:07:13 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_SetPainThreshold
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetPainThreshold)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT(threshold);
|
|
|
|
PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; }
|
2015-09-17 14:07:13 +00:00
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
if (!ref)
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
return 0;
|
2015-09-17 14:07:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ref->PainThreshold = threshold;
|
2016-02-19 02:39:40 +00:00
|
|
|
return 0;
|
2015-09-17 14:07:13 +00:00
|
|
|
}
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
};
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
static void DoDamage(AActor *dmgtarget, AActor *self, int amount, FName DamageType, int flags, PClassActor *filter, FName species)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2015-03-27 08:57:33 +00:00
|
|
|
bool filterpass = DoCheckClass(dmgtarget, filter, !!(flags & DMSS_EXFILTER)),
|
|
|
|
speciespass = DoCheckSpecies(dmgtarget, species, !!(flags & DMSS_EXSPECIES));
|
2015-01-30 01:30:26 +00:00
|
|
|
if ((flags & DMSS_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass))
|
|
|
|
{
|
|
|
|
int dmgFlags = 0;
|
|
|
|
if (flags & DMSS_FOILINVUL)
|
2015-03-27 08:40:47 +00:00
|
|
|
dmgFlags |= DMG_FOILINVUL;
|
2015-01-30 01:30:26 +00:00
|
|
|
if (flags & DMSS_FOILBUDDHA)
|
2015-03-27 08:40:47 +00:00
|
|
|
dmgFlags |= DMG_FOILBUDDHA;
|
|
|
|
if (flags & (DMSS_KILL | DMSS_NOFACTOR)) //Kill implies NoFactor
|
|
|
|
dmgFlags |= DMG_NO_FACTOR;
|
2015-01-30 01:30:26 +00:00
|
|
|
if (!(flags & DMSS_AFFECTARMOR) || (flags & DMSS_KILL)) //Kill overrides AffectArmor
|
2015-03-27 08:40:47 +00:00
|
|
|
dmgFlags |= DMG_NO_ARMOR;
|
2015-01-30 01:30:26 +00:00
|
|
|
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.
|
2015-03-27 08:40:47 +00:00
|
|
|
dmgFlags |= DMG_NO_PROTECT;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (amount > 0)
|
2016-02-04 21:15:29 +00:00
|
|
|
{ //Should wind up passing them through just fine.
|
|
|
|
P_DamageMobj(dmgtarget, self, self, amount, DamageType, dmgFlags);
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
else if (amount < 0)
|
|
|
|
{
|
|
|
|
amount = -amount;
|
|
|
|
P_GiveBody(dmgtarget, amount);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSelf)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (amount);
|
|
|
|
PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor){ filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
DoDamage(self, self, amount, damagetype, flags, filter, species);
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTarget)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (amount);
|
|
|
|
PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor){ filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (self->target != NULL)
|
2016-02-04 21:15:29 +00:00
|
|
|
DoDamage(self->target, self, amount, damagetype, flags, filter, species);
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTracer)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (amount);
|
|
|
|
PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor){ filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (self->tracer != NULL)
|
2016-02-04 21:15:29 +00:00
|
|
|
DoDamage(self->tracer, self, amount, damagetype, flags, filter, species);
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageMaster)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (amount);
|
|
|
|
PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor){ filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (self->master != NULL)
|
2016-02-04 21:15:29 +00:00
|
|
|
DoDamage(self->master, self, amount, damagetype, flags, filter, species);
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageChildren)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (amount);
|
|
|
|
PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor){ filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
TThinkerIterator<AActor> it;
|
2016-02-04 21:15:29 +00:00
|
|
|
AActor *mo;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
while ( (mo = it.Next()) )
|
|
|
|
{
|
|
|
|
if (mo->master == self)
|
2016-02-04 21:15:29 +00:00
|
|
|
DoDamage(mo, self, amount, damagetype, flags, filter, species);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSiblings)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (amount);
|
|
|
|
PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor){ filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
TThinkerIterator<AActor> it;
|
2016-02-04 21:15:29 +00:00
|
|
|
AActor *mo;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (self->master != NULL)
|
|
|
|
{
|
|
|
|
while ((mo = it.Next()))
|
|
|
|
{
|
|
|
|
if (mo->master == self->master && mo != self)
|
2016-02-04 21:15:29 +00:00
|
|
|
DoDamage(mo, self, amount, damagetype, flags, filter, species);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// 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,
|
|
|
|
};
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
static void DoKill(AActor *killtarget, AActor *self, FName damagetype, int flags, PClassActor *filter, FName species)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2015-03-27 08:57:33 +00:00
|
|
|
bool filterpass = DoCheckClass(killtarget, filter, !!(flags & KILS_EXFILTER)),
|
|
|
|
speciespass = DoCheckSpecies(killtarget, species, !!(flags & KILS_EXSPECIES));
|
2015-01-30 01:30:26 +00:00
|
|
|
if ((flags & KILS_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) //Check this first. I think it'll save the engine a lot more time this way.
|
|
|
|
{
|
2015-03-27 08:40:47 +00:00
|
|
|
int dmgFlags = DMG_NO_ARMOR | DMG_NO_FACTOR;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (KILS_FOILINVUL)
|
2015-03-27 08:40:47 +00:00
|
|
|
dmgFlags |= DMG_FOILINVUL;
|
2015-01-30 01:30:26 +00:00
|
|
|
if (KILS_FOILBUDDHA)
|
2015-03-27 08:40:47 +00:00
|
|
|
dmgFlags |= DMG_FOILBUDDHA;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
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)) &&
|
2015-03-27 15:58:21 +00:00
|
|
|
(!(killtarget->flags7 & MF7_BUDDHA) || (flags & KILS_FOILBUDDHA)) &&
|
2015-03-27 08:40:47 +00:00
|
|
|
!(killtarget->flags5 & MF5_NODAMAGE))
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
P_ExplodeMissile(killtarget, NULL, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!(flags & KILS_NOMONSTERS))
|
|
|
|
{
|
|
|
|
P_DamageMobj(killtarget, self, self, killtarget->health, damagetype, dmgFlags);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_KillTarget(damagetype, int flags)
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillTarget)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor){ filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (self->target != NULL)
|
|
|
|
DoKill(self->target, self, damagetype, flags, filter, species);
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_KillTracer(damagetype, int flags)
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillTracer)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor){ filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (self->tracer != NULL)
|
|
|
|
DoKill(self->tracer, self, damagetype, flags, filter, species);
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_KillMaster(damagetype, int flags)
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillMaster)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor){ filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (self->master != NULL)
|
|
|
|
DoKill(self->master, self, damagetype, flags, filter, species);
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_KillChildren(damagetype, int flags)
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillChildren)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor){ filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
TThinkerIterator<AActor> it;
|
|
|
|
AActor *mo;
|
|
|
|
|
|
|
|
while ( (mo = it.Next()) )
|
|
|
|
{
|
|
|
|
if (mo->master == self)
|
|
|
|
{
|
|
|
|
DoKill(mo, self, damagetype, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_KillSiblings(damagetype, int flags)
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillSiblings)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor){ filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
TThinkerIterator<AActor> it;
|
|
|
|
AActor *mo;
|
|
|
|
|
|
|
|
if (self->master != NULL)
|
|
|
|
{
|
|
|
|
while ( (mo = it.Next()) )
|
|
|
|
{
|
|
|
|
if (mo->master == self->master && mo != self)
|
|
|
|
{
|
|
|
|
DoKill(mo, self, damagetype, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// 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,
|
|
|
|
};
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
static void DoRemove(AActor *removetarget, int flags, PClassActor *filter, FName species)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2015-03-27 08:57:33 +00:00
|
|
|
bool filterpass = DoCheckClass(removetarget, filter, !!(flags & RMVF_EXFILTER)),
|
|
|
|
speciespass = DoCheckSpecies(removetarget, species, !!(flags & RMVF_EXSPECIES));
|
2015-01-30 01:30:26 +00:00
|
|
|
if ((flags & RMVF_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass))
|
|
|
|
{
|
|
|
|
if ((flags & RMVF_EVERYTHING))
|
|
|
|
{
|
|
|
|
P_RemoveThing(removetarget);
|
|
|
|
}
|
|
|
|
if ((flags & RMVF_MISC) && !((removetarget->flags3 & MF3_ISMONSTER) && (removetarget->flags & MF_MISSILE)))
|
|
|
|
{
|
|
|
|
P_RemoveThing(removetarget);
|
|
|
|
}
|
|
|
|
if ((removetarget->flags3 & MF3_ISMONSTER) && !(flags & RMVF_NOMONSTERS))
|
|
|
|
{
|
|
|
|
P_RemoveThing(removetarget);
|
|
|
|
}
|
|
|
|
if ((removetarget->flags & MF_MISSILE) && (flags & RMVF_MISSILES))
|
|
|
|
{
|
|
|
|
P_RemoveThing(removetarget);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_RemoveTarget
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveTarget)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT_OPT(flags) { flags = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor){ filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
if (self->target != NULL)
|
|
|
|
{
|
|
|
|
DoRemove(self->target, flags, filter, species);
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_RemoveTracer
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveTracer)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT_OPT(flags) { flags = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor){ filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
if (self->tracer != NULL)
|
|
|
|
{
|
|
|
|
DoRemove(self->tracer, flags, filter, species);
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_RemoveMaster
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveMaster)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor){ filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
if (self->master != NULL)
|
|
|
|
{
|
|
|
|
DoRemove(self->master, flags, filter, species);
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_RemoveChildren
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveChildren)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_BOOL_OPT (removeall) { removeall = false; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor){ filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
TThinkerIterator<AActor> it;
|
|
|
|
AActor *mo;
|
|
|
|
|
|
|
|
while ((mo = it.Next()) != NULL)
|
|
|
|
{
|
|
|
|
if (mo->master == self && (mo->health <= 0 || removeall))
|
|
|
|
{
|
|
|
|
DoRemove(mo, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_RemoveSiblings
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveSiblings)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_BOOL_OPT (removeall) { removeall = false; }
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor){ filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Remove
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Remove)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (removee);
|
|
|
|
PARAM_INT_OPT (flags) { flags = 0; }
|
|
|
|
PARAM_CLASS_OPT (filter, AActor){ filter = NULL; }
|
|
|
|
PARAM_NAME_OPT (species) { species = NAME_None; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
AActor *reference = COPY_AAPTR(self, removee);
|
|
|
|
if (reference != NULL)
|
|
|
|
{
|
|
|
|
DoRemove(reference, flags, filter, species);
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetTeleFog
|
|
|
|
//
|
|
|
|
// Sets the teleport fog(s) for the calling actor.
|
2015-03-31 07:24:16 +00:00
|
|
|
// Takes a name of the classes for the source and destination.
|
2015-01-30 01:30:26 +00:00
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTeleFog)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_CLASS(oldpos, AActor);
|
|
|
|
PARAM_CLASS(newpos, AActor);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2015-03-31 07:24:16 +00:00
|
|
|
self->TeleFogSourceType = oldpos;
|
|
|
|
self->TeleFogDestType = newpos;
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SwapTeleFog
|
|
|
|
//
|
|
|
|
// Switches the source and dest telefogs around.
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_SwapTeleFog)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2015-01-30 01:30:26 +00:00
|
|
|
if ((self->TeleFogSourceType != self->TeleFogDestType)) //Does nothing if they're the same.
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PClassActor *temp = self->TeleFogSourceType;
|
2015-01-30 01:30:26 +00:00
|
|
|
self->TeleFogSourceType = self->TeleFogDestType;
|
|
|
|
self->TeleFogDestType = temp;
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetFloatBobPhase
|
|
|
|
//
|
|
|
|
// Changes the FloatBobPhase of the actor.
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetFloatBobPhase)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT(bob);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
//Respect float bob phase limits.
|
|
|
|
if (self && (bob >= 0 && bob <= 63))
|
2016-02-04 21:15:29 +00:00
|
|
|
{
|
2015-01-30 01:30:26 +00:00
|
|
|
self->FloatBobPhase = bob;
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
// A_SetHealth
|
|
|
|
//
|
|
|
|
// Changes the health of the actor.
|
|
|
|
// Takes a pointer as well.
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetHealth)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT (health);
|
|
|
|
PARAM_INT_OPT (ptr) { ptr = AAPTR_DEFAULT; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
AActor *mobj = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
if (!mobj)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
// A_ResetHealth
|
|
|
|
//
|
|
|
|
// Resets the health of the actor to default, except if their dead.
|
|
|
|
// Takes a pointer.
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ResetHealth)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; }
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
AActor *mobj = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
if (!mobj)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2015-04-30 13:15:48 +00:00
|
|
|
//===========================================================================
|
|
|
|
// A_JumpIfHigherOrLower
|
|
|
|
//
|
|
|
|
// Jumps if a target, master, or tracer is higher or lower than the calling
|
|
|
|
// actor. Can also specify how much higher/lower the actor needs to be than
|
|
|
|
// itself. Can also take into account the height of the actor in question,
|
|
|
|
// depending on which it's checking. This means adding height of the
|
|
|
|
// calling actor's self if the pointer is higher, or height of the pointer
|
|
|
|
// if its lower.
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfHigherOrLower)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_STATE(high);
|
|
|
|
PARAM_STATE(low);
|
|
|
|
PARAM_FIXED_OPT(offsethigh) { offsethigh = 0; }
|
|
|
|
PARAM_FIXED_OPT(offsetlow) { offsetlow = 0; }
|
|
|
|
PARAM_BOOL_OPT(includeHeight) { includeHeight = true; }
|
|
|
|
PARAM_INT_OPT(ptr) { ptr = AAPTR_TARGET; }
|
2015-04-30 13:15:48 +00:00
|
|
|
|
|
|
|
AActor *mobj = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (mobj != NULL && mobj != self) //AAPTR_DEFAULT is completely useless in this regard.
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
if ((high) && (mobj->Z() > ((includeHeight ? self->height : 0) + self->Z() + offsethigh)))
|
|
|
|
{
|
|
|
|
ACTION_RETURN_STATE(high);
|
|
|
|
}
|
|
|
|
else if ((low) && (mobj->Z() + (includeHeight ? mobj->height : 0)) < (self->Z() + offsetlow))
|
|
|
|
{
|
|
|
|
ACTION_RETURN_STATE(low);
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-04-30 13:15:48 +00:00
|
|
|
}
|
|
|
|
|
2015-08-24 17:45:10 +00:00
|
|
|
//===========================================================================
|
|
|
|
// A_SetSpecies(str species, ptr)
|
|
|
|
//
|
|
|
|
// Sets the species of the calling actor('s pointer).
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpecies)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_NAME(species);
|
|
|
|
PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; }
|
|
|
|
|
2015-08-24 17:45:10 +00:00
|
|
|
AActor *mobj = COPY_AAPTR(self, ptr);
|
|
|
|
if (!mobj)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-08-24 17:45:10 +00:00
|
|
|
}
|
|
|
|
|
2015-08-25 13:15:23 +00:00
|
|
|
mobj->Species = species;
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-08-24 17:45:10 +00:00
|
|
|
}
|
2015-04-30 13:15:48 +00:00
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetRipperLevel(int level)
|
|
|
|
//
|
2015-05-03 22:55:01 +00:00
|
|
|
// Sets the ripper level of the calling actor.
|
2015-01-30 01:30:26 +00:00
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipperLevel)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT(level);
|
2015-01-30 01:30:26 +00:00
|
|
|
self->RipperLevel = level;
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetRipMin(int min)
|
|
|
|
//
|
2015-05-03 22:55:01 +00:00
|
|
|
// Sets the minimum level a ripper must be in order to rip through this actor.
|
2015-01-30 01:30:26 +00:00
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipMin)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT(min);
|
|
|
|
self->RipLevelMin = min;
|
|
|
|
return 0;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
2015-05-03 22:55:01 +00:00
|
|
|
// A_SetRipMax(int max)
|
2015-01-30 01:30:26 +00:00
|
|
|
//
|
2015-05-03 22:55:01 +00:00
|
|
|
// Sets the minimum level a ripper must be in order to rip through this actor.
|
2015-01-30 01:30:26 +00:00
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipMax)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT(max);
|
2015-01-30 01:30:26 +00:00
|
|
|
self->RipLevelMax = max;
|
2016-02-04 21:15:29 +00:00
|
|
|
return 0;
|
2015-03-10 15:58:10 +00:00
|
|
|
}
|
2015-09-07 00:57:43 +00:00
|
|
|
|
2015-10-12 16:06:55 +00:00
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// 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_PARAMS(AActor, A_SetChaseThreshold)
|
|
|
|
{
|
2016-02-05 04:41:02 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_INT(threshold);
|
|
|
|
PARAM_BOOL_OPT(def) { def = false; }
|
|
|
|
PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; }
|
|
|
|
|
2015-10-12 16:06:55 +00:00
|
|
|
|
|
|
|
AActor *mobj = COPY_AAPTR(self, ptr);
|
|
|
|
if (!mobj)
|
|
|
|
{
|
2016-02-05 04:41:02 +00:00
|
|
|
return 0;
|
2015-10-12 16:06:55 +00:00
|
|
|
}
|
|
|
|
if (def)
|
|
|
|
mobj->DefThreshold = (threshold >= 0) ? threshold : 0;
|
|
|
|
else
|
|
|
|
mobj->threshold = (threshold >= 0) ? threshold : 0;
|
2016-02-05 04:41:02 +00:00
|
|
|
return 0;
|
2015-10-12 16:06:55 +00:00
|
|
|
}
|
|
|
|
|
2015-11-28 16:53:34 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
//==========================================================================
|
|
|
|
enum CPXFflags
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
CPXF_ANCESTOR = 1 << 0,
|
2015-11-28 16:53:34 +00:00
|
|
|
CPXF_LESSOREQUAL = 1 << 1,
|
|
|
|
CPXF_NOZ = 1 << 2,
|
|
|
|
CPXF_COUNTDEAD = 1 << 3,
|
|
|
|
CPXF_DEADONLY = 1 << 4,
|
|
|
|
CPXF_EXACT = 1 << 5,
|
2016-01-07 23:09:02 +00:00
|
|
|
CPXF_SETTARGET = 1 << 6,
|
|
|
|
CPXF_SETMASTER = 1 << 7,
|
|
|
|
CPXF_SETTRACER = 1 << 8,
|
|
|
|
CPXF_FARTHEST = 1 << 9,
|
|
|
|
CPXF_CLOSEST = 1 << 10,
|
|
|
|
CPXF_SETONPTR = 1 << 11,
|
2016-01-26 16:00:20 +00:00
|
|
|
CPXF_CHECKSIGHT = 1 << 12,
|
2015-11-28 16:53:34 +00:00
|
|
|
};
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckProximity)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_STATE(jump);
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_CLASS(classname, AActor);
|
|
|
|
PARAM_FIXED(distance);
|
|
|
|
PARAM_INT_OPT(count) { count = 1; }
|
2016-02-08 16:26:18 +00:00
|
|
|
PARAM_INT_OPT(flags) { flags = 0; }
|
|
|
|
PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; }
|
2015-11-28 16:53:34 +00:00
|
|
|
|
2016-01-07 23:09:02 +00:00
|
|
|
if (!jump)
|
|
|
|
{
|
|
|
|
if (!(flags & (CPXF_SETTARGET | CPXF_SETMASTER | CPXF_SETTRACER)))
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2016-01-07 23:09:02 +00:00
|
|
|
}
|
2015-11-28 16:53:34 +00:00
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
2016-02-19 02:39:40 +00:00
|
|
|
// We need these to check out.
|
2016-02-04 21:15:29 +00:00
|
|
|
if (!ref || !classname || distance <= 0)
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2015-11-28 16:53:34 +00:00
|
|
|
int counter = 0;
|
|
|
|
bool result = false;
|
2016-01-22 03:15:50 +00:00
|
|
|
fixed_t closer = distance, farther = 0, current = distance;
|
2016-01-07 23:09:02 +00:00
|
|
|
const bool ptrWillChange = !!(flags & (CPXF_SETTARGET | CPXF_SETMASTER | CPXF_SETTRACER));
|
|
|
|
const bool ptrDistPref = !!(flags & (CPXF_CLOSEST | CPXF_FARTHEST));
|
2015-11-28 16:53:34 +00:00
|
|
|
|
|
|
|
TThinkerIterator<AActor> it;
|
2016-01-07 23:09:02 +00:00
|
|
|
AActor *mo, *dist = NULL;
|
2015-11-28 16:53:34 +00:00
|
|
|
|
|
|
|
//[MC] Process of elimination, I think, will get through this as quickly and
|
|
|
|
//efficiently as possible.
|
|
|
|
while ((mo = it.Next()))
|
|
|
|
{
|
|
|
|
if (mo == ref) //Don't count self.
|
|
|
|
continue;
|
|
|
|
|
2016-02-10 10:58:54 +00:00
|
|
|
// no unmorphed versions of currently morphed players.
|
|
|
|
if (mo->flags & MF_UNMORPHED)
|
|
|
|
continue;
|
|
|
|
|
2015-11-28 16:53:34 +00:00
|
|
|
//Check inheritance for the classname. Taken partly from CheckClass DECORATE function.
|
|
|
|
if (flags & CPXF_ANCESTOR)
|
|
|
|
{
|
2016-02-08 16:26:18 +00:00
|
|
|
if (!(mo->IsKindOf(classname)))
|
2015-11-28 16:53:34 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
//Otherwise, just check for the regular class name.
|
|
|
|
else if (classname != mo->GetClass())
|
|
|
|
continue;
|
|
|
|
|
2016-01-07 23:09:02 +00:00
|
|
|
//[MC]Make sure it's in range and respect the desire for Z or not. The function forces it to use
|
|
|
|
//Z later for ensuring CLOSEST and FARTHEST flags are respected perfectly.
|
|
|
|
//Ripped from sphere checking in A_RadiusGive (along with a number of things).
|
2016-01-26 16:00:20 +00:00
|
|
|
if ((ref->AproxDistance(mo) < distance &&
|
2015-11-28 16:53:34 +00:00
|
|
|
((flags & CPXF_NOZ) ||
|
2016-01-22 03:15:50 +00:00
|
|
|
((ref->Z() > mo->Z() && ref->Z() - mo->Top() < distance) ||
|
|
|
|
(ref->Z() <= mo->Z() && mo->Z() - ref->Top() < distance)))))
|
2015-11-28 16:53:34 +00:00
|
|
|
{
|
2016-01-07 23:09:02 +00:00
|
|
|
if ((flags & CPXF_CHECKSIGHT) && !(P_CheckSight(mo, ref, SF_IGNOREVISIBILITY | SF_IGNOREWATERBOUNDARY)))
|
|
|
|
continue;
|
|
|
|
|
2016-01-22 03:15:50 +00:00
|
|
|
if (ptrWillChange)
|
2016-01-07 23:09:02 +00:00
|
|
|
{
|
2016-01-22 03:15:50 +00:00
|
|
|
current = ref->AproxDistance(mo);
|
2016-01-07 23:09:02 +00:00
|
|
|
|
2016-01-25 16:07:00 +00:00
|
|
|
if ((flags & CPXF_CLOSEST) && (current < closer))
|
2016-01-07 23:09:02 +00:00
|
|
|
{
|
|
|
|
dist = mo;
|
2016-02-04 21:15:29 +00:00
|
|
|
closer = current; //This actor's closer. Set the new standard.
|
2016-01-07 23:09:02 +00:00
|
|
|
}
|
|
|
|
else if ((flags & CPXF_FARTHEST) && (current > farther))
|
|
|
|
{
|
|
|
|
dist = mo;
|
|
|
|
farther = current;
|
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
else if (!dist)
|
2016-01-21 17:39:13 +00:00
|
|
|
dist = mo; //Just get the first one and call it quits if there's nothing selected.
|
2016-01-07 23:09:02 +00:00
|
|
|
}
|
2015-11-28 16:53:34 +00:00
|
|
|
if (mo->flags6 & MF6_KILLED)
|
|
|
|
{
|
|
|
|
if (!(flags & (CPXF_COUNTDEAD | CPXF_DEADONLY)))
|
|
|
|
continue;
|
|
|
|
counter++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (flags & CPXF_DEADONLY)
|
|
|
|
continue;
|
|
|
|
counter++;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Abort if the number of matching classes nearby is greater, we have obviously succeeded in our goal.
|
|
|
|
if (counter > count)
|
|
|
|
{
|
|
|
|
result = (flags & (CPXF_LESSOREQUAL | CPXF_EXACT)) ? false : true;
|
2016-01-07 23:09:02 +00:00
|
|
|
|
2016-01-21 17:39:13 +00:00
|
|
|
//However, if we have one SET* flag and either the closest or farthest flags, keep the function going.
|
2016-01-07 23:09:02 +00:00
|
|
|
if (ptrWillChange && ptrDistPref)
|
|
|
|
continue;
|
|
|
|
else
|
|
|
|
break;
|
2015-11-28 16:53:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-07 23:09:02 +00:00
|
|
|
if (ptrWillChange && dist != NULL)
|
|
|
|
{
|
|
|
|
if (flags & CPXF_SETONPTR)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
if (flags & CPXF_SETTARGET) ref->target = dist;
|
|
|
|
if (flags & CPXF_SETMASTER) ref->master = dist;
|
|
|
|
if (flags & CPXF_SETTRACER) ref->tracer = dist;
|
2016-01-07 23:09:02 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
if (flags & CPXF_SETTARGET) self->target = dist;
|
|
|
|
if (flags & CPXF_SETMASTER) self->master = dist;
|
|
|
|
if (flags & CPXF_SETTRACER) self->tracer = dist;
|
2016-01-07 23:09:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-28 16:53:34 +00:00
|
|
|
if (counter == count)
|
|
|
|
result = true;
|
|
|
|
else if (counter < count)
|
|
|
|
result = !!((flags & CPXF_LESSOREQUAL) && !(flags & CPXF_EXACT));
|
|
|
|
|
2016-02-04 21:15:29 +00:00
|
|
|
if (result && jump)
|
2015-11-28 16:53:34 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(jump);
|
2015-11-28 16:53:34 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-11-28 16:53:34 +00:00
|
|
|
}
|
|
|
|
|
2015-10-04 21:00:40 +00:00
|
|
|
/*===========================================================================
|
|
|
|
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
|
|
|
|
{
|
2016-03-03 02:35:54 +00:00
|
|
|
CBF_NOLINES = 1 << 0, //Don't check lines.
|
2015-10-04 21:00:40 +00:00
|
|
|
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.
|
2016-01-31 15:45:41 +00:00
|
|
|
CBF_DROPOFF = 1 << 5, //Check for dropoffs.
|
2016-03-03 02:35:54 +00:00
|
|
|
CBF_NOACTORS = 1 << 6, //Don't check actors.
|
2015-10-04 21:00:40 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckBlock)
|
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
2016-02-22 01:21:39 +00:00
|
|
|
PARAM_STATE(block)
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_INT_OPT(flags) { flags = 0; }
|
|
|
|
PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; }
|
2015-10-04 21:00:40 +00:00
|
|
|
|
|
|
|
AActor *mobj = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
//Needs at least one state jump to work.
|
|
|
|
if (!mobj)
|
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-10-04 21:00:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//Nothing to block it so skip the rest.
|
2016-01-31 15:45:41 +00:00
|
|
|
bool checker = (flags & CBF_DROPOFF) ? P_CheckMove(mobj, mobj->X(), mobj->Y()) : P_TestMobjLocation(mobj);
|
2016-02-19 03:39:13 +00:00
|
|
|
if (checker)
|
|
|
|
{
|
|
|
|
ACTION_RETURN_STATE(NULL);
|
|
|
|
}
|
2015-10-04 21:00:40 +00:00
|
|
|
|
|
|
|
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] If modders don't want jumping, but just getting the pointer, only abort at
|
|
|
|
//this point. I.e. A_CheckBlock("",CBF_SETTRACER) is like having CBF_NOLINES.
|
|
|
|
//It gets the mobj blocking, if any, and doesn't jump at all.
|
|
|
|
if (!block)
|
2016-02-19 03:39:13 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2016-02-19 03:39:13 +00:00
|
|
|
}
|
2016-03-03 02:35:54 +00:00
|
|
|
//[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.
|
|
|
|
|
|
|
|
if ((!(flags & CBF_NOACTORS) && (mobj->BlockingMobj)) || (!(flags & CBF_NOLINES) && mobj->BlockingLine != NULL))
|
2015-10-04 21:00:40 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(block);
|
2015-10-04 21:00:40 +00:00
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_STATE(NULL);
|
2015-11-28 16:53:34 +00:00
|
|
|
}
|
2015-11-25 07:44:19 +00:00
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
2016-01-27 16:12:13 +00:00
|
|
|
// A_FaceMovementDirection(angle offset, bool pitch, ptr)
|
2015-11-25 07:44:19 +00:00
|
|
|
//
|
|
|
|
// Sets the actor('s pointer) to face the direction of travel.
|
|
|
|
//===========================================================================
|
2016-01-27 16:12:13 +00:00
|
|
|
enum FMDFlags
|
2015-11-25 07:44:19 +00:00
|
|
|
{
|
2016-01-27 16:12:13 +00:00
|
|
|
FMDF_NOPITCH = 1 << 0,
|
|
|
|
FMDF_INTERPOLATE = 1 << 1,
|
|
|
|
FMDF_NOANGLE = 1 << 2,
|
2015-11-25 07:44:19 +00:00
|
|
|
};
|
2016-01-27 16:12:13 +00:00
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FaceMovementDirection)
|
2015-11-25 07:44:19 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
PARAM_ANGLE_OPT(offset) { offset = 0; }
|
|
|
|
PARAM_ANGLE_OPT(anglelimit) { anglelimit = 0; }
|
|
|
|
PARAM_ANGLE_OPT(pitchlimit) { pitchlimit = 0; }
|
|
|
|
PARAM_INT_OPT(flags) { flags = 0; }
|
|
|
|
PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; }
|
2015-11-25 07:44:19 +00:00
|
|
|
|
|
|
|
AActor *mobj = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
//Need an actor.
|
2016-01-27 16:12:13 +00:00
|
|
|
if (!mobj || ((flags & FMDF_NOPITCH) && (flags & FMDF_NOANGLE)))
|
2015-11-25 07:44:19 +00:00
|
|
|
{
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(false);
|
2015-11-25 07:44:19 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
|
2015-11-25 07:44:19 +00:00
|
|
|
//Don't bother calculating this if we don't have any horizontal movement.
|
2016-01-27 16:12:13 +00:00
|
|
|
if (!(flags & FMDF_NOANGLE) && (mobj->velx != 0 || mobj->vely != 0))
|
2015-11-25 07:44:19 +00:00
|
|
|
{
|
2016-02-04 21:15:29 +00:00
|
|
|
angle_t current = mobj->angle;
|
2015-11-26 05:15:59 +00:00
|
|
|
const angle_t angle = R_PointToAngle2(0, 0, mobj->velx, mobj->vely);
|
2015-11-26 03:49:25 +00:00
|
|
|
//Done because using anglelimit directly causes a signed/unsigned mismatch.
|
2016-02-04 21:15:29 +00:00
|
|
|
const angle_t limit = anglelimit;
|
2015-11-26 03:49:25 +00:00
|
|
|
|
|
|
|
//Code borrowed from A_Face*.
|
|
|
|
if (limit > 0 && (absangle(current - angle) > limit))
|
|
|
|
{
|
|
|
|
if (current < angle)
|
|
|
|
{
|
|
|
|
// [MC] This may appear backwards, but I assure any who
|
|
|
|
// reads this, it works.
|
|
|
|
if (current - angle > ANGLE_180)
|
|
|
|
current += limit + offset;
|
|
|
|
else
|
|
|
|
current -= limit + offset;
|
2016-01-27 16:12:13 +00:00
|
|
|
mobj->SetAngle(current, !!(flags & FMDF_INTERPOLATE));
|
2015-11-26 03:49:25 +00:00
|
|
|
}
|
|
|
|
else if (current > angle)
|
|
|
|
{
|
|
|
|
if (angle - current > ANGLE_180)
|
|
|
|
current -= limit + offset;
|
|
|
|
else
|
|
|
|
current += limit + offset;
|
2016-01-27 16:12:13 +00:00
|
|
|
mobj->SetAngle(current, !!(flags & FMDF_INTERPOLATE));
|
2015-11-26 03:49:25 +00:00
|
|
|
}
|
|
|
|
else
|
2016-01-27 16:12:13 +00:00
|
|
|
mobj->SetAngle(angle + ANGLE_180 + offset, !!(flags & FMDF_INTERPOLATE));
|
2015-11-26 03:49:25 +00:00
|
|
|
}
|
|
|
|
else
|
2016-01-27 16:12:13 +00:00
|
|
|
mobj->SetAngle(angle + offset, !!(flags & FMDF_INTERPOLATE));
|
2015-11-25 07:44:19 +00:00
|
|
|
}
|
|
|
|
|
2016-01-27 16:12:13 +00:00
|
|
|
if (!(flags & FMDF_NOPITCH))
|
2015-11-25 07:44:19 +00:00
|
|
|
{
|
2015-11-26 03:49:25 +00:00
|
|
|
fixed_t current = mobj->pitch;
|
2016-03-10 19:44:53 +00:00
|
|
|
const DVector2 velocity(mobj->velx, mobj->vely);
|
2016-02-11 19:01:12 +00:00
|
|
|
const fixed_t pitch = R_PointToAngle2(0, 0, xs_CRoundToInt(velocity.Length()), -mobj->velz);
|
2015-11-26 03:49:25 +00:00
|
|
|
if (pitchlimit > 0)
|
|
|
|
{
|
|
|
|
// [MC] angle_t for pitchlimit was required because otherwise
|
|
|
|
// we would wind up with less than desirable turn rates that didn't
|
|
|
|
// match that of A_SetPitch. We want consistency. Also, I didn't know
|
|
|
|
// of a better way to convert from angle_t to fixed_t properly so I
|
|
|
|
// used this instead.
|
|
|
|
fixed_t plimit = fixed_t(pitchlimit);
|
2016-02-04 21:15:29 +00:00
|
|
|
|
2015-11-26 03:49:25 +00:00
|
|
|
if (abs(current - pitch) > plimit)
|
|
|
|
{
|
|
|
|
fixed_t max = 0;
|
2016-02-04 21:15:29 +00:00
|
|
|
|
2015-11-26 03:49:25 +00:00
|
|
|
if (current > pitch)
|
|
|
|
{
|
|
|
|
max = MIN(plimit, (current - pitch));
|
|
|
|
current -= max;
|
|
|
|
}
|
|
|
|
else //if (current > pitch)
|
|
|
|
{
|
|
|
|
max = MIN(plimit, (pitch - current));
|
|
|
|
current += max;
|
|
|
|
}
|
2016-01-27 16:12:13 +00:00
|
|
|
mobj->SetPitch(current, !!(flags & FMDF_INTERPOLATE));
|
2015-11-26 03:49:25 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-01-27 16:12:13 +00:00
|
|
|
mobj->SetPitch(pitch, !!(flags & FMDF_INTERPOLATE));
|
2015-11-26 03:49:25 +00:00
|
|
|
}
|
2016-02-04 21:15:29 +00:00
|
|
|
|
2015-11-26 03:49:25 +00:00
|
|
|
}
|
2015-11-25 07:44:19 +00:00
|
|
|
else
|
|
|
|
{
|
2016-01-27 16:12:13 +00:00
|
|
|
mobj->SetPitch(pitch, !!(flags & FMDF_INTERPOLATE));
|
2015-11-25 07:44:19 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-19 02:39:40 +00:00
|
|
|
ACTION_RETURN_BOOL(true);
|
2015-11-26 05:17:13 +00:00
|
|
|
}
|