2015-01-30 01:30:26 +00:00
|
|
|
/*
|
|
|
|
** thingdef.cpp
|
|
|
|
**
|
|
|
|
** Code pointers for Actor definitions
|
|
|
|
**
|
|
|
|
**---------------------------------------------------------------------------
|
|
|
|
** Copyright 2002-2006 Christoph Oelckers
|
|
|
|
** Copyright 2004-2006 Randy Heit
|
|
|
|
** All rights reserved.
|
|
|
|
**
|
|
|
|
** Redistribution and use in source and binary forms, with or without
|
|
|
|
** modification, are permitted provided that the following conditions
|
|
|
|
** are met:
|
|
|
|
**
|
|
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer.
|
|
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
|
|
** documentation and/or other materials provided with the distribution.
|
|
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
|
|
** derived from this software without specific prior written permission.
|
|
|
|
** 4. When not used as part of ZDoom or a ZDoom derivative, this code will be
|
|
|
|
** covered by the terms of the GNU General Public License as published by
|
|
|
|
** the Free Software Foundation; either version 2 of the License, or (at
|
|
|
|
** your option) any later version.
|
|
|
|
**
|
|
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
**---------------------------------------------------------------------------
|
|
|
|
**
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "gi.h"
|
|
|
|
#include "g_level.h"
|
|
|
|
#include "actor.h"
|
|
|
|
#include "info.h"
|
|
|
|
#include "sc_man.h"
|
|
|
|
#include "tarray.h"
|
|
|
|
#include "w_wad.h"
|
|
|
|
#include "templates.h"
|
|
|
|
#include "r_defs.h"
|
|
|
|
#include "a_pickups.h"
|
|
|
|
#include "s_sound.h"
|
|
|
|
#include "cmdlib.h"
|
|
|
|
#include "p_lnspec.h"
|
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"
|
|
|
|
|
2015-08-01 21:17:06 +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
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
bool ACustomInventory::CallStateChain (AActor *actor, FState * State)
|
|
|
|
{
|
|
|
|
StateCallData StateCall;
|
|
|
|
bool result = false;
|
|
|
|
int counter = 0;
|
|
|
|
|
|
|
|
while (State != NULL)
|
|
|
|
{
|
|
|
|
// Assume success. The code pointer will set this to false if necessary
|
|
|
|
StateCall.State = State;
|
|
|
|
StateCall.Result = true;
|
|
|
|
if (State->CallAction(actor, this, &StateCall))
|
|
|
|
{
|
|
|
|
// collect all the results. Even one successful call signifies overall success.
|
|
|
|
result |= StateCall.Result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Since there are no delays it is a good idea to check for infinite loops here!
|
|
|
|
counter++;
|
|
|
|
if (counter >= 10000) break;
|
|
|
|
|
|
|
|
if (StateCall.State == State)
|
|
|
|
{
|
|
|
|
// Abort immediately if the state jumps to itself!
|
|
|
|
if (State == State->GetNextState())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If both variables are still the same there was no jump
|
|
|
|
// so we must advance to the next state.
|
|
|
|
State = State->GetNextState();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
State = StateCall.State;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_RearrangePointers
|
|
|
|
//
|
|
|
|
// Allow an actor to change its relationship to other actors by
|
|
|
|
// copying pointers freely between TARGET MASTER and TRACER.
|
|
|
|
// Can also assign null value, but does not duplicate A_ClearTarget.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RearrangePointers)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(4);
|
|
|
|
ACTION_PARAM_INT(ptr_target, 0);
|
|
|
|
ACTION_PARAM_INT(ptr_master, 1);
|
|
|
|
ACTION_PARAM_INT(ptr_tracer, 2);
|
|
|
|
ACTION_PARAM_INT(flags, 3);
|
|
|
|
|
|
|
|
// Rearrange pointers internally
|
|
|
|
|
|
|
|
// Fetch all values before modification, so that all fields can get original values
|
|
|
|
AActor
|
|
|
|
*gettarget = self->target,
|
|
|
|
*getmaster = self->master,
|
|
|
|
*gettracer = self->tracer;
|
|
|
|
|
|
|
|
switch (ptr_target) // pick the new target
|
|
|
|
{
|
|
|
|
case AAPTR_MASTER:
|
|
|
|
self->target = getmaster;
|
|
|
|
if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self);
|
|
|
|
break;
|
|
|
|
case AAPTR_TRACER:
|
|
|
|
self->target = gettracer;
|
|
|
|
if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self);
|
|
|
|
break;
|
|
|
|
case AAPTR_NULL:
|
|
|
|
self->target = NULL;
|
|
|
|
// THIS IS NOT "A_ClearTarget", so no other targeting info is removed
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// presently permitting non-monsters to set master
|
|
|
|
switch (ptr_master) // pick the new master
|
|
|
|
{
|
|
|
|
case AAPTR_TARGET:
|
|
|
|
self->master = gettarget;
|
|
|
|
if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self);
|
|
|
|
break;
|
|
|
|
case AAPTR_TRACER:
|
|
|
|
self->master = gettracer;
|
|
|
|
if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self);
|
|
|
|
break;
|
|
|
|
case AAPTR_NULL:
|
|
|
|
self->master = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (ptr_tracer) // pick the new tracer
|
|
|
|
{
|
|
|
|
case AAPTR_TARGET:
|
|
|
|
self->tracer = gettarget;
|
|
|
|
break; // no verification deemed necessary; the engine never follows a tracer chain(?)
|
|
|
|
case AAPTR_MASTER:
|
|
|
|
self->tracer = getmaster;
|
|
|
|
break; // no verification deemed necessary; the engine never follows a tracer chain(?)
|
|
|
|
case AAPTR_NULL:
|
|
|
|
self->tracer = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_TransferPointer
|
|
|
|
//
|
|
|
|
// Copy one pointer (MASTER, TARGET or TRACER) from this actor (SELF),
|
|
|
|
// or from this actor's MASTER, TARGET or TRACER.
|
|
|
|
//
|
|
|
|
// You can copy any one of that actor's pointers
|
|
|
|
//
|
|
|
|
// Assign the copied pointer to any one pointer in SELF,
|
|
|
|
// MASTER, TARGET or TRACER.
|
|
|
|
//
|
|
|
|
// Any attempt to make an actor point to itself will replace the pointer
|
|
|
|
// with a null value.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TransferPointer)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
ACTION_PARAM_INT(ptr_source, 0);
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_PARAM_INT(ptr_recipient, 1);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_INT(ptr_sourcefield, 2);
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_PARAM_INT(ptr_recipientfield, 3);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_INT(flags, 4);
|
|
|
|
|
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-01-19 15:09:44 +00:00
|
|
|
COPY_AAPTR_NOT_NULL(self, recipient, ptr_recipient); // pick an actor to store the provided pointer value
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
// convert source from dataprovider to data
|
|
|
|
|
|
|
|
source = COPY_AAPTR(source, ptr_sourcefield);
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (source == recipient) source = NULL; // The recipient should not acquire a pointer to itself; will write NULL
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (ptr_recipientfield == AAPTR_DEFAULT) ptr_recipientfield = ptr_sourcefield; // If default: Write to same field as data was read from
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
ASSIGN_AAPTR(recipient, ptr_recipientfield, source, flags);
|
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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_INT(ptr_source, 0);
|
|
|
|
|
|
|
|
if (self->player) return;
|
|
|
|
|
|
|
|
AActor *source;
|
|
|
|
COPY_AAPTR_NOT_NULL(self, source, ptr_source);
|
|
|
|
self->CopyFriendliness(source, false, false); // No change in current target or health
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Simple flag changers
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_SetSolid)
|
|
|
|
{
|
|
|
|
self->flags |= MF_SOLID;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_UnsetSolid)
|
|
|
|
{
|
|
|
|
self->flags &= ~MF_SOLID;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_SetFloat)
|
|
|
|
{
|
|
|
|
self->flags |= MF_FLOAT;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_UnsetFloat)
|
|
|
|
{
|
|
|
|
self->flags &= ~(MF_FLOAT|MF_INFLOAT);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Customizable attack functions which use actor parameters.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
static void DoAttack (AActor *self, bool domelee, bool domissile,
|
|
|
|
int MeleeDamage, FSoundID MeleeSound, const PClass *MissileType,fixed_t MissileHeight)
|
|
|
|
{
|
|
|
|
if (self->target == NULL) return;
|
|
|
|
|
|
|
|
A_FaceTarget (self);
|
|
|
|
if (domelee && MeleeDamage>0 && self->CheckMeleeRange ())
|
|
|
|
{
|
|
|
|
int damage = pr_camelee.HitDice(MeleeDamage);
|
|
|
|
if (MeleeSound) S_Sound (self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM);
|
|
|
|
int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee);
|
|
|
|
P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self);
|
|
|
|
}
|
|
|
|
else if (domissile && MissileType != NULL)
|
|
|
|
{
|
|
|
|
// This seemingly senseless code is needed for proper aiming.
|
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)
|
|
|
|
{
|
|
|
|
int MeleeDamage = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeDamage, 0);
|
|
|
|
FSoundID MeleeSound = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeSound, 0);
|
|
|
|
DoAttack(self, true, false, MeleeDamage, MeleeSound, NULL, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_MissileAttack)
|
|
|
|
{
|
|
|
|
const PClass *MissileType=PClass::FindClass((ENamedName) self->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None));
|
|
|
|
fixed_t MissileHeight= self->GetClass()->Meta.GetMetaFixed (ACMETA_MissileHeight, 32*FRACUNIT);
|
|
|
|
DoAttack(self, false, true, 0, 0, MissileType, MissileHeight);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_ComboAttack)
|
|
|
|
{
|
|
|
|
int MeleeDamage = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeDamage, 0);
|
|
|
|
FSoundID MeleeSound = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeSound, 0);
|
|
|
|
const PClass *MissileType=PClass::FindClass((ENamedName) self->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None));
|
|
|
|
fixed_t MissileHeight= self->GetClass()->Meta.GetMetaFixed (ACMETA_MissileHeight, 32*FRACUNIT);
|
|
|
|
DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BasicAttack)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(4);
|
|
|
|
ACTION_PARAM_INT(MeleeDamage, 0);
|
|
|
|
ACTION_PARAM_SOUND(MeleeSound, 1);
|
|
|
|
ACTION_PARAM_CLASS(MissileType, 2);
|
|
|
|
ACTION_PARAM_FIXED(MissileHeight, 3);
|
|
|
|
|
|
|
|
if (MissileType == NULL) return;
|
|
|
|
DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Custom sound functions.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySound)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
ACTION_PARAM_SOUND(soundid, 0);
|
|
|
|
ACTION_PARAM_INT(channel, 1);
|
|
|
|
ACTION_PARAM_FLOAT(volume, 2);
|
|
|
|
ACTION_PARAM_BOOL(looping, 3);
|
|
|
|
ACTION_PARAM_FLOAT(attenuation, 4);
|
|
|
|
|
|
|
|
if (!looping)
|
|
|
|
{
|
|
|
|
S_Sound (self, channel, soundid, volume, attenuation);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!S_IsActorPlayingSomething (self, channel&7, soundid))
|
|
|
|
{
|
|
|
|
S_Sound (self, channel | CHAN_LOOP, soundid, volume, attenuation);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSound)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_INT(slot, 0);
|
|
|
|
|
|
|
|
S_StopSound(self, slot);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// These come from a time when DECORATE constants did not exist yet and
|
|
|
|
// the sound interface was less flexible. As a result the parameters are
|
|
|
|
// not optimal and these functions have been deprecated in favor of extending
|
|
|
|
// A_PlaySound and A_StopSound.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayWeaponSound)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_SOUND(soundid, 0);
|
|
|
|
|
|
|
|
S_Sound (self, CHAN_WEAPON, soundid, 1, ATTN_NORM);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySoundEx)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(4);
|
|
|
|
ACTION_PARAM_SOUND(soundid, 0);
|
|
|
|
ACTION_PARAM_NAME(channel, 1);
|
|
|
|
ACTION_PARAM_BOOL(looping, 2);
|
|
|
|
ACTION_PARAM_INT(attenuation_raw, 3);
|
|
|
|
|
|
|
|
float attenuation;
|
|
|
|
switch (attenuation_raw)
|
|
|
|
{
|
|
|
|
case -1: attenuation = ATTN_STATIC; break; // drop off rapidly
|
|
|
|
default:
|
|
|
|
case 0: attenuation = ATTN_NORM; break; // normal
|
|
|
|
case 1:
|
|
|
|
case 2: attenuation = ATTN_NONE; break; // full volume
|
|
|
|
}
|
|
|
|
|
|
|
|
if (channel < NAME_Auto || channel > NAME_SoundSlot7)
|
|
|
|
{
|
|
|
|
channel = NAME_Auto;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!looping)
|
|
|
|
{
|
|
|
|
S_Sound (self, int(channel) - NAME_Auto, soundid, 1, attenuation);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!S_IsActorPlayingSomething (self, int(channel) - NAME_Auto, soundid))
|
|
|
|
{
|
|
|
|
S_Sound (self, (int(channel) - NAME_Auto) | CHAN_LOOP, soundid, 1, attenuation);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSoundEx)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_NAME(channel, 0);
|
|
|
|
|
|
|
|
if (channel > NAME_Auto && channel <= NAME_SoundSlot7)
|
|
|
|
{
|
|
|
|
S_StopSound (self, int(channel) - NAME_Auto);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Generic seeker missile function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
static FRandom pr_seekermissile ("SeekerMissile");
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
SMF_LOOK = 1,
|
|
|
|
SMF_PRECISE = 2,
|
|
|
|
SMF_CURSPEED = 4,
|
|
|
|
};
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SeekerMissile)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
ACTION_PARAM_INT(ang1, 0);
|
|
|
|
ACTION_PARAM_INT(ang2, 1);
|
|
|
|
ACTION_PARAM_INT(flags, 2);
|
|
|
|
ACTION_PARAM_INT(chance, 3);
|
|
|
|
ACTION_PARAM_INT(distance, 4);
|
|
|
|
|
|
|
|
if ((flags & SMF_LOOK) && (self->tracer == 0) && (pr_seekermissile()<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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Hitscan attack with a customizable amount of bullets (specified in damage)
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_BulletAttack)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int bangle;
|
|
|
|
int slope;
|
|
|
|
|
|
|
|
if (!self->target) return;
|
|
|
|
|
|
|
|
A_FaceTarget (self);
|
|
|
|
bangle = self->angle;
|
|
|
|
|
|
|
|
slope = P_AimLineAttack (self, bangle, MISSILERANGE);
|
|
|
|
|
|
|
|
S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM);
|
|
|
|
for (i = self->GetMissileDamage (0, 1); i > 0; --i)
|
|
|
|
{
|
|
|
|
int angle = bangle + (pr_cabullet.Random2() << 20);
|
|
|
|
int damage = ((pr_cabullet()%5)+1)*3;
|
|
|
|
P_LineAttack(self, angle, MISSILERANGE, slope, damage,
|
|
|
|
NAME_Hitscan, NAME_BulletPuff);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Do the state jump
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
static void DoJump(AActor * self, FState * CallingState, FState *jumpto, StateCallData *statecall)
|
|
|
|
{
|
|
|
|
if (jumpto == NULL) return;
|
|
|
|
|
|
|
|
if (statecall != NULL)
|
|
|
|
{
|
|
|
|
statecall->State = jumpto;
|
|
|
|
}
|
|
|
|
else if (self->player != NULL && CallingState == self->player->psprites[ps_weapon].state)
|
|
|
|
{
|
|
|
|
P_SetPsprite(self->player, ps_weapon, jumpto);
|
|
|
|
}
|
|
|
|
else if (self->player != NULL && CallingState == self->player->psprites[ps_flash].state)
|
|
|
|
{
|
|
|
|
P_SetPsprite(self->player, ps_flash, jumpto);
|
|
|
|
}
|
|
|
|
else if (CallingState == self->state)
|
|
|
|
{
|
|
|
|
self->SetState (jumpto);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// something went very wrong. This should never happen.
|
|
|
|
assert(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is just to avoid having to directly reference the internally defined
|
|
|
|
// CallingState and statecall parameters in the code below.
|
|
|
|
#define ACTION_JUMP(offset) DoJump(self, CallingState, offset, statecall)
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// State jump function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Jump)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
ACTION_PARAM_INT(count, 0);
|
|
|
|
ACTION_PARAM_INT(maxchance, 1);
|
|
|
|
|
|
|
|
if (count >= 2 && (maxchance >= 256 || pr_cajump() < maxchance))
|
|
|
|
{
|
|
|
|
int jumps = 2 + (count == 2? 0 : (pr_cajump() % (count - 1)));
|
|
|
|
ACTION_PARAM_STATE(jumpto, jumps);
|
|
|
|
ACTION_JUMP(jumpto);
|
|
|
|
}
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// State jump function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfHealthLower)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
ACTION_PARAM_INT(health, 0);
|
|
|
|
ACTION_PARAM_STATE(jump, 1);
|
|
|
|
ACTION_PARAM_INT(ptr_selector, 2);
|
|
|
|
|
|
|
|
AActor *measured;
|
|
|
|
|
|
|
|
measured = COPY_AAPTR(self, ptr_selector);
|
|
|
|
|
|
|
|
if (measured && measured->health < health)
|
|
|
|
{
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// State jump function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetOutsideMeleeRange)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_STATE(jump, 0);
|
|
|
|
|
|
|
|
if (!self->CheckMeleeRange())
|
|
|
|
{
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// State jump function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInsideMeleeRange)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_STATE(jump, 0);
|
|
|
|
|
|
|
|
if (self->CheckMeleeRange())
|
|
|
|
{
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
}
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// State jump function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
void DoJumpIfCloser(AActor *target, DECLARE_PARAMINFO)
|
|
|
|
{
|
2015-10-10 20:30:18 +00:00
|
|
|
ACTION_PARAM_START(3);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_FIXED(dist, 0);
|
|
|
|
ACTION_PARAM_STATE(jump, 1);
|
2015-10-10 15:11:59 +00:00
|
|
|
ACTION_PARAM_BOOL(noz, 2);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
|
|
|
|
// No target - no jump
|
2015-10-10 15:11:59 +00:00
|
|
|
if (!target)
|
|
|
|
return;
|
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
|
|
|
{
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfCloser)
|
|
|
|
{
|
|
|
|
AActor *target;
|
|
|
|
|
|
|
|
if (!self->player)
|
|
|
|
{
|
|
|
|
target = self->target;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Does the player aim at something that can be shot?
|
|
|
|
P_BulletSlope(self, &target);
|
|
|
|
}
|
|
|
|
DoJumpIfCloser(target, PUSH_PARAMINFO);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTracerCloser)
|
|
|
|
{
|
|
|
|
DoJumpIfCloser(self->tracer, PUSH_PARAMINFO);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfMasterCloser)
|
|
|
|
{
|
|
|
|
DoJumpIfCloser(self->master, PUSH_PARAMINFO);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// State jump function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
void DoJumpIfInventory(AActor * owner, DECLARE_PARAMINFO)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(4);
|
|
|
|
ACTION_PARAM_CLASS(Type, 0);
|
|
|
|
ACTION_PARAM_INT(ItemAmount, 1);
|
|
|
|
ACTION_PARAM_STATE(JumpOffset, 2);
|
|
|
|
ACTION_PARAM_INT(setowner, 3);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
|
|
|
|
if (!Type) return;
|
|
|
|
COPY_AAPTR_NOT_NULL(owner, owner, setowner); // returns if owner ends up being NULL
|
|
|
|
|
|
|
|
AInventory *Item = owner->FindInventory(Type);
|
|
|
|
|
|
|
|
if (Item)
|
|
|
|
{
|
|
|
|
if (ItemAmount > 0)
|
|
|
|
{
|
|
|
|
if (Item->Amount >= ItemAmount)
|
|
|
|
ACTION_JUMP(JumpOffset);
|
|
|
|
}
|
|
|
|
else if (Item->Amount >= Item->MaxAmount)
|
|
|
|
{
|
|
|
|
ACTION_JUMP(JumpOffset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInventory)
|
|
|
|
{
|
|
|
|
DoJumpIfInventory(self, PUSH_PARAMINFO);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetInventory)
|
|
|
|
{
|
|
|
|
DoJumpIfInventory(self->target, PUSH_PARAMINFO);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// State jump function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfArmorType)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
ACTION_PARAM_NAME(Type, 0);
|
|
|
|
ACTION_PARAM_STATE(JumpOffset, 1);
|
|
|
|
ACTION_PARAM_INT(amount, 2);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
|
|
|
|
ABasicArmor * armor = (ABasicArmor *) self->FindInventory(NAME_BasicArmor);
|
|
|
|
|
|
|
|
if (armor && armor->ArmorType == Type && armor->Amount >= amount)
|
|
|
|
ACTION_JUMP(JumpOffset);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Parameterized version of A_Explode
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
XF_HURTSOURCE = 1,
|
|
|
|
XF_NOTMISSILE = 4,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Explode)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(8);
|
|
|
|
ACTION_PARAM_INT(damage, 0);
|
|
|
|
ACTION_PARAM_INT(distance, 1);
|
|
|
|
ACTION_PARAM_INT(flags, 2);
|
|
|
|
ACTION_PARAM_BOOL(alert, 3);
|
|
|
|
ACTION_PARAM_INT(fulldmgdistance, 4);
|
|
|
|
ACTION_PARAM_INT(nails, 5);
|
|
|
|
ACTION_PARAM_INT(naildamage, 6);
|
|
|
|
ACTION_PARAM_CLASS(pufftype, 7);
|
|
|
|
|
|
|
|
if (damage < 0) // get parameters from metadata
|
|
|
|
{
|
|
|
|
damage = self->GetClass()->Meta.GetMetaInt (ACMETA_ExplosionDamage, 128);
|
|
|
|
distance = self->GetClass()->Meta.GetMetaInt (ACMETA_ExplosionRadius, damage);
|
|
|
|
flags = !self->GetClass()->Meta.GetMetaInt (ACMETA_DontHurtShooter);
|
|
|
|
alert = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (distance <= 0) distance = damage;
|
|
|
|
}
|
|
|
|
// NailBomb effect, from SMMU but not from its source code: instead it was implemented and
|
|
|
|
// generalized from the documentation at http://www.doomworld.com/eternity/engine/codeptrs.html
|
|
|
|
|
|
|
|
if (nails)
|
|
|
|
{
|
|
|
|
angle_t ang;
|
|
|
|
for (int i = 0; i < nails; i++)
|
|
|
|
{
|
|
|
|
ang = i*(ANGLE_MAX/nails);
|
|
|
|
// Comparing the results of a test wad with Eternity, it seems A_NailBomb does not aim
|
|
|
|
P_LineAttack (self, ang, MISSILERANGE, 0,
|
|
|
|
//P_AimLineAttack (self, ang, MISSILERANGE),
|
|
|
|
naildamage, NAME_Hitscan, pufftype);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
P_RadiusAttack (self, self->target, damage, distance, self->DamageType, flags, fulldmgdistance);
|
|
|
|
P_CheckSplash(self, distance<<FRACBITS);
|
|
|
|
if (alert && self->target != NULL && self->target->player != NULL)
|
|
|
|
{
|
|
|
|
validcount++;
|
|
|
|
P_RecursiveSound (self->Sector, self->target, false, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_RadiusThrust
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
RTF_AFFECTSOURCE = 1,
|
|
|
|
RTF_NOIMPACTDAMAGE = 2,
|
|
|
|
RTF_NOTMISSILE = 4,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusThrust)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
ACTION_PARAM_INT(force, 0);
|
|
|
|
ACTION_PARAM_INT(distance, 1);
|
|
|
|
ACTION_PARAM_INT(flags, 2);
|
|
|
|
ACTION_PARAM_INT(fullthrustdistance, 3);
|
|
|
|
|
|
|
|
bool sourcenothrust = false;
|
|
|
|
|
|
|
|
if (force == 0) force = 128;
|
|
|
|
if (distance <= 0) distance = abs(force);
|
|
|
|
|
|
|
|
// Temporarily negate MF2_NODMGTHRUST on the shooter, since it renders this function useless.
|
|
|
|
if (!(flags & RTF_NOTMISSILE) && self->target != NULL && self->target->flags2 & MF2_NODMGTHRUST)
|
|
|
|
{
|
|
|
|
sourcenothrust = true;
|
|
|
|
self->target->flags2 &= ~MF2_NODMGTHRUST;
|
|
|
|
}
|
|
|
|
|
|
|
|
P_RadiusAttack (self, self->target, force, distance, self->DamageType, flags | RADF_NODAMAGE, fullthrustdistance);
|
|
|
|
P_CheckSplash(self, distance << FRACBITS);
|
|
|
|
|
|
|
|
if (sourcenothrust)
|
|
|
|
{
|
|
|
|
self->target->flags2 |= MF2_NODMGTHRUST;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Execute a line special / script
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CallSpecial)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(6);
|
|
|
|
ACTION_PARAM_INT(special, 0);
|
|
|
|
ACTION_PARAM_INT(arg1, 1);
|
|
|
|
ACTION_PARAM_INT(arg2, 2);
|
|
|
|
ACTION_PARAM_INT(arg3, 3);
|
|
|
|
ACTION_PARAM_INT(arg4, 4);
|
|
|
|
ACTION_PARAM_INT(arg5, 5);
|
|
|
|
|
|
|
|
bool res = !!P_ExecuteSpecial(special, NULL, self, false, arg1, arg2, arg3, arg4, arg5);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// The ultimate code pointer: Fully customizable missiles!
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
enum CM_Flags
|
|
|
|
{
|
|
|
|
CMF_AIMMODE = 3,
|
|
|
|
CMF_TRACKOWNER = 4,
|
|
|
|
CMF_CHECKTARGETDEAD = 8,
|
|
|
|
|
|
|
|
CMF_ABSOLUTEPITCH = 16,
|
|
|
|
CMF_OFFSETPITCH = 32,
|
|
|
|
CMF_SAVEPITCH = 64,
|
|
|
|
|
|
|
|
CMF_ABSOLUTEANGLE = 128
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomMissile)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(7);
|
|
|
|
ACTION_PARAM_CLASS(ti, 0);
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_PARAM_FIXED(spawnheight, 1);
|
|
|
|
ACTION_PARAM_INT(spawnofs_xy, 2);
|
|
|
|
ACTION_PARAM_ANGLE(angle, 3);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_INT(flags, 4);
|
|
|
|
ACTION_PARAM_ANGLE(pitch, 5);
|
|
|
|
ACTION_PARAM_INT(ptr, 6);
|
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
int aimmode = flags & CMF_AIMMODE;
|
|
|
|
|
|
|
|
AActor * targ;
|
|
|
|
AActor * missile;
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
FVector2 velocity (missile->velx, missile->vely);
|
|
|
|
pitch += R_PointToAngle2(0,0, (fixed_t)velocity.Length(), missile->velz);
|
|
|
|
}
|
|
|
|
ang = pitch >> ANGLETOFINESHIFT;
|
|
|
|
missilespeed = abs(FixedMul(finecosine[ang], missile->Speed));
|
|
|
|
missile->velz = FixedMul(finesine[ang], missile->Speed);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
FVector2 velocity (missile->velx, missile->vely);
|
|
|
|
missilespeed = (fixed_t)velocity.Length();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (CMF_SAVEPITCH & flags)
|
|
|
|
{
|
|
|
|
missile->pitch = pitch;
|
|
|
|
// In aimmode 0 and 1 without absolutepitch or offsetpitch, the pitch parameter
|
|
|
|
// contains the unapplied parameter. In that case, it is set as pitch without
|
|
|
|
// otherwise affecting the spawned actor.
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// An even more customizable hitscan attack
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
enum CBA_Flags
|
|
|
|
{
|
|
|
|
CBAF_AIMFACING = 1,
|
|
|
|
CBAF_NORANDOM = 2,
|
|
|
|
CBAF_EXPLICITANGLE = 4,
|
|
|
|
CBAF_NOPITCH = 8,
|
|
|
|
CBAF_NORANDOMPUFFZ = 16,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomBulletAttack)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(8);
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_PARAM_ANGLE(spread_xy, 0);
|
|
|
|
ACTION_PARAM_ANGLE(spread_z, 1);
|
|
|
|
ACTION_PARAM_INT(numbullets, 2);
|
|
|
|
ACTION_PARAM_INT(damageperbullet, 3);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_CLASS(pufftype, 4);
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_PARAM_FIXED(range, 5);
|
|
|
|
ACTION_PARAM_INT(flags, 6);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_INT(ptr, 7);
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
if (!pufftype) pufftype = PClass::FindClass(NAME_BulletPuff);
|
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A fully customizable melee attack
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomMeleeAttack)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
ACTION_PARAM_INT(damage, 0);
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_PARAM_SOUND(meleesound, 1);
|
|
|
|
ACTION_PARAM_SOUND(misssound, 2);
|
|
|
|
ACTION_PARAM_NAME(damagetype, 3);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_BOOL(bleed, 4);
|
|
|
|
|
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)
|
|
|
|
return;
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A fully customizable combo attack
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomComboAttack)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(6);
|
|
|
|
ACTION_PARAM_CLASS(ti, 0);
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_PARAM_FIXED(spawnheight, 1);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_INT(damage, 2);
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_PARAM_SOUND(meleesound, 3);
|
|
|
|
ACTION_PARAM_NAME(damagetype, 4);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_BOOL(bleed, 5);
|
|
|
|
|
|
|
|
if (!self->target)
|
|
|
|
return;
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// State jump function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfNoAmmo)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_STATE(jump, 0);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
if (!ACTION_CALL_FROM_WEAPON()) return;
|
|
|
|
|
|
|
|
if (!self->player->ReadyWeapon->CheckAmmo(self->player->ReadyWeapon->bAltFire, false, true))
|
|
|
|
{
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// An even more customizable hitscan attack
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
enum FB_Flags
|
|
|
|
{
|
|
|
|
FBF_USEAMMO = 1,
|
|
|
|
FBF_NORANDOM = 2,
|
|
|
|
FBF_EXPLICITANGLE = 4,
|
|
|
|
FBF_NOPITCH = 8,
|
|
|
|
FBF_NOFLASH = 16,
|
|
|
|
FBF_NORANDOMPUFFZ = 32,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(7);
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_PARAM_ANGLE(spread_xy, 0);
|
|
|
|
ACTION_PARAM_ANGLE(spread_z, 1);
|
|
|
|
ACTION_PARAM_INT(numbullets, 2);
|
|
|
|
ACTION_PARAM_INT(damageperbullet, 3);
|
|
|
|
ACTION_PARAM_CLASS(pufftype, 4);
|
|
|
|
ACTION_PARAM_INT(flags, 5);
|
|
|
|
ACTION_PARAM_FIXED(range, 6);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (!self->player) return;
|
|
|
|
|
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))
|
|
|
|
return; // 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)
|
|
|
|
pufftype = PClass::FindClass(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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// 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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(7);
|
|
|
|
ACTION_PARAM_CLASS(ti, 0);
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_PARAM_ANGLE(angle, 1);
|
|
|
|
ACTION_PARAM_BOOL(useammo, 2);
|
|
|
|
ACTION_PARAM_INT(spawnofs_xy, 3);
|
|
|
|
ACTION_PARAM_FIXED(spawnheight, 4);
|
|
|
|
ACTION_PARAM_INT(flags, 5);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_ANGLE(pitch, 6);
|
|
|
|
|
|
|
|
if (!self->player) return;
|
|
|
|
|
|
|
|
|
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
|
|
|
AActor *linetarget;
|
|
|
|
|
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))
|
|
|
|
return; // 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-01-19 15:09:44 +00:00
|
|
|
AActor * misl=P_SpawnPlayerMissile (self, x, y, z, ti, shootangle, &linetarget, NULL, false, (flags & FPF_NOAUTOAIM) != 0);
|
|
|
|
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;
|
|
|
|
if (linetarget && (misl->flags2 & MF2_SEEKERMISSILE))
|
|
|
|
misl->tracer = linetarget;
|
|
|
|
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.
|
|
|
|
FVector3 velocity(misl->velx, misl->vely, 0);
|
|
|
|
fixed_t missilespeed = (fixed_t)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]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_CustomPunch
|
|
|
|
//
|
|
|
|
// Berserk is not handled here. That can be done with A_CheckIfInventory
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
CPF_USEAMMO = 1,
|
|
|
|
CPF_DAGGER = 2,
|
|
|
|
CPF_PULLIN = 4,
|
|
|
|
CPF_NORANDOMPUFFZ = 8,
|
|
|
|
CPF_NOTURN = 16,
|
|
|
|
CPF_STEALARMOR = 32,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomPunch)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(8);
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_PARAM_INT(damage, 0);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_BOOL(norandom, 1);
|
|
|
|
ACTION_PARAM_INT(flags, 2);
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_PARAM_CLASS(pufftype, 3);
|
|
|
|
ACTION_PARAM_FIXED(range, 4);
|
|
|
|
ACTION_PARAM_FIXED(lifesteal, 5);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_INT(lifestealmax, 6);
|
|
|
|
ACTION_PARAM_CLASS(armorbonustype, 7);
|
2015-11-30 17:42:08 +00:00
|
|
|
ACTION_PARAM_SOUND(MeleeSound, 8);
|
|
|
|
ACTION_PARAM_SOUND(MissSound, 9);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (!self->player) return;
|
|
|
|
|
|
|
|
player_t *player=self->player;
|
|
|
|
AWeapon * weapon=player->ReadyWeapon;
|
|
|
|
|
|
|
|
|
|
|
|
angle_t angle;
|
|
|
|
int pitch;
|
|
|
|
AActor * linetarget;
|
|
|
|
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;
|
|
|
|
pitch = P_AimLineAttack (self, angle, range, &linetarget);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
// only use ammo when actually hitting something!
|
2016-01-26 08:43:47 +00:00
|
|
|
if ((flags & CPF_USEAMMO) && 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))
|
|
|
|
return; // out of ammo
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (pufftype == NULL)
|
|
|
|
pufftype = PClass::FindClass(NAME_BulletPuff);
|
2015-01-30 01:30:26 +00:00
|
|
|
int puffFlags = LAF_ISMELEEATTACK | ((flags & CPF_NORANDOMPUFFZ) ? LAF_NORANDOMPUFFZ : 0);
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
P_LineAttack (self, angle, range, pitch, damage, NAME_Melee, pufftype, puffFlags, &linetarget, &actualdamage);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2015-11-30 17:42:08 +00:00
|
|
|
if (!linetarget)
|
|
|
|
{
|
|
|
|
if (MissSound) S_Sound(self, CHAN_WEAPON, MissSound, 1, ATTN_NORM);
|
|
|
|
}
|
|
|
|
else
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2016-01-19 15:09:44 +00:00
|
|
|
if (lifesteal && !(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-01-19 15:09:44 +00:00
|
|
|
armorbonustype = PClass::FindClass("ArmorBonus");
|
|
|
|
}
|
|
|
|
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-01-10 19:46:26 +00:00
|
|
|
self->angle = self->AngleTo(linetarget);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (flags & CPF_PULLIN) self->flags |= MF_JUSTATTACKED;
|
|
|
|
if (flags & CPF_DAGGER) P_DaggerAlert (self, linetarget);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// customizable railgun attack function
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack)
|
|
|
|
{
|
2015-04-23 18:11:54 +00:00
|
|
|
ACTION_PARAM_START(17);
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_PARAM_INT(damage, 0);
|
|
|
|
ACTION_PARAM_INT(spawnofs_xy, 1);
|
|
|
|
ACTION_PARAM_BOOL(useammo, 2);
|
|
|
|
ACTION_PARAM_COLOR(color1, 3);
|
|
|
|
ACTION_PARAM_COLOR(color2, 4);
|
|
|
|
ACTION_PARAM_INT(flags, 5);
|
|
|
|
ACTION_PARAM_DOUBLE(maxdiff, 6);
|
|
|
|
ACTION_PARAM_CLASS(pufftype, 7);
|
|
|
|
ACTION_PARAM_ANGLE(spread_xy, 8);
|
|
|
|
ACTION_PARAM_ANGLE(spread_z, 9);
|
|
|
|
ACTION_PARAM_FIXED(range, 10);
|
|
|
|
ACTION_PARAM_INT(duration, 11);
|
|
|
|
ACTION_PARAM_DOUBLE(sparsity, 12);
|
|
|
|
ACTION_PARAM_DOUBLE(driftspeed, 13);
|
|
|
|
ACTION_PARAM_CLASS(spawnclass, 14);
|
|
|
|
ACTION_PARAM_FIXED(spawnofs_z, 15);
|
2015-04-23 18:11:54 +00:00
|
|
|
ACTION_PARAM_INT(SpiralOffset, 16);
|
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
|
|
|
|
|
|
|
if (!self->player) return;
|
|
|
|
|
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))
|
|
|
|
return; // 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);
|
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)
|
|
|
|
{
|
2015-04-23 18:11:54 +00:00
|
|
|
ACTION_PARAM_START(17);
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_PARAM_INT(damage, 0);
|
|
|
|
ACTION_PARAM_INT(spawnofs_xy, 1);
|
|
|
|
ACTION_PARAM_COLOR(color1, 2);
|
|
|
|
ACTION_PARAM_COLOR(color2, 3);
|
|
|
|
ACTION_PARAM_INT(flags, 4);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_INT(aim, 5);
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_PARAM_DOUBLE(maxdiff, 6);
|
|
|
|
ACTION_PARAM_CLASS(pufftype, 7);
|
|
|
|
ACTION_PARAM_ANGLE(spread_xy, 8);
|
|
|
|
ACTION_PARAM_ANGLE(spread_z, 9);
|
|
|
|
ACTION_PARAM_FIXED(range, 10);
|
|
|
|
ACTION_PARAM_INT(duration, 11);
|
|
|
|
ACTION_PARAM_DOUBLE(sparsity, 12);
|
|
|
|
ACTION_PARAM_DOUBLE(driftspeed, 13);
|
|
|
|
ACTION_PARAM_CLASS(spawnclass, 14);
|
|
|
|
ACTION_PARAM_FIXED(spawnofs_z, 15);
|
2015-04-23 18:11:54 +00:00
|
|
|
ACTION_PARAM_INT(SpiralOffset, 16);
|
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
|
|
|
|
|
|
|
AActor *linetarget;
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// [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
|
|
|
}
|
|
|
|
self->pitch = P_AimLineAttack (self, self->angle, MISSILERANGE, &linetarget, ANGLE_1*60, 0, aim ? self->target : NULL);
|
|
|
|
if (linetarget == NULL && aim)
|
|
|
|
{
|
|
|
|
// We probably won't hit the target, but aim at it anyway so we don't look stupid.
|
2016-01-19 19:15:45 +00:00
|
|
|
fixedvec2 pos = self->Vec2To(self->target);
|
|
|
|
TVector2<double> xydiff(pos.x, pos.y);
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// DoGiveInventory
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
2015-01-30 01:38:16 +00:00
|
|
|
static void DoGiveInventory(AActor * receiver, bool use_aaptr, DECLARE_PARAMINFO)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2015-01-30 01:38:16 +00:00
|
|
|
ACTION_PARAM_START(2+use_aaptr);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_CLASS(mi, 0);
|
|
|
|
ACTION_PARAM_INT(amount, 1);
|
|
|
|
|
2015-01-30 01:38:16 +00:00
|
|
|
if (use_aaptr)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_INT(setreceiver, 2);
|
|
|
|
COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver);
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
bool res=true;
|
|
|
|
|
|
|
|
if (amount==0) amount=1;
|
|
|
|
if (mi)
|
|
|
|
{
|
|
|
|
AInventory *item = static_cast<AInventory *>(Spawn (mi, 0, 0, 0, NO_REPLACE));
|
2015-01-30 01:38:16 +00:00
|
|
|
if (!item)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
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();
|
|
|
|
if (!item->CallTryPickup (receiver))
|
|
|
|
{
|
|
|
|
item->Destroy ();
|
|
|
|
res = false;
|
|
|
|
}
|
|
|
|
else res = true;
|
|
|
|
}
|
|
|
|
else res = false;
|
|
|
|
ACTION_SET_RESULT(res);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveInventory)
|
|
|
|
{
|
2015-01-30 01:38:16 +00:00
|
|
|
DoGiveInventory(self, true, PUSH_PARAMINFO);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToTarget)
|
|
|
|
{
|
2015-01-30 01:38:16 +00:00
|
|
|
DoGiveInventory(self->target, true, PUSH_PARAMINFO);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToChildren)
|
|
|
|
{
|
|
|
|
TThinkerIterator<AActor> it;
|
|
|
|
AActor * mo;
|
|
|
|
|
|
|
|
while ((mo = it.Next()))
|
|
|
|
{
|
2015-01-30 01:38:16 +00:00
|
|
|
if (mo->master == self) DoGiveInventory(mo, false, PUSH_PARAMINFO);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToSiblings)
|
|
|
|
{
|
|
|
|
TThinkerIterator<AActor> it;
|
|
|
|
AActor * mo;
|
|
|
|
|
|
|
|
if (self->master != NULL)
|
|
|
|
{
|
|
|
|
while ((mo = it.Next()))
|
|
|
|
{
|
2015-01-30 01:38:16 +00:00
|
|
|
if (mo->master == self->master && mo != self) DoGiveInventory(mo, false, PUSH_PARAMINFO);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_TakeInventory
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
TIF_NOTAKEINFINITE = 1,
|
|
|
|
};
|
|
|
|
|
2015-01-30 01:38:16 +00:00
|
|
|
void DoTakeInventory(AActor * receiver, bool use_aaptr, DECLARE_PARAMINFO)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
2015-01-30 01:38:16 +00:00
|
|
|
ACTION_PARAM_START(3+use_aaptr);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_CLASS(item, 0);
|
|
|
|
ACTION_PARAM_INT(amount, 1);
|
|
|
|
ACTION_PARAM_INT(flags, 2);
|
|
|
|
|
2015-01-30 01:38:16 +00:00
|
|
|
if (!item)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (use_aaptr)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_INT(setreceiver, 3);
|
|
|
|
COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver);
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2015-04-28 13:34:13 +00:00
|
|
|
bool res = receiver->TakeInventory(item, amount, true, (flags & TIF_NOTAKEINFINITE) != 0);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_SET_RESULT(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeInventory)
|
|
|
|
{
|
2015-01-30 01:38:16 +00:00
|
|
|
DoTakeInventory(self, true, PUSH_PARAMINFO);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromTarget)
|
|
|
|
{
|
2015-01-30 01:38:16 +00:00
|
|
|
DoTakeInventory(self->target, true, PUSH_PARAMINFO);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromChildren)
|
|
|
|
{
|
|
|
|
TThinkerIterator<AActor> it;
|
|
|
|
AActor * mo;
|
|
|
|
|
|
|
|
while ((mo = it.Next()))
|
|
|
|
{
|
2015-01-30 01:38:16 +00:00
|
|
|
if (mo->master == self) DoTakeInventory(mo, false, PUSH_PARAMINFO);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromSiblings)
|
|
|
|
{
|
|
|
|
TThinkerIterator<AActor> it;
|
|
|
|
AActor * mo;
|
|
|
|
|
|
|
|
if (self->master != NULL)
|
|
|
|
{
|
|
|
|
while ((mo = it.Next()))
|
|
|
|
{
|
2015-01-30 01:38:16 +00:00
|
|
|
if (mo->master == self->master && mo != self) DoTakeInventory(mo, false, PUSH_PARAMINFO);
|
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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
ACTION_PARAM_CLASS(missile, 0);
|
|
|
|
ACTION_PARAM_FIXED(distance, 1);
|
|
|
|
ACTION_PARAM_FIXED(zheight, 2);
|
|
|
|
ACTION_PARAM_BOOL(useammo, 3);
|
|
|
|
ACTION_PARAM_BOOL(transfer_translation, 4);
|
|
|
|
|
|
|
|
if (!missile)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't spawn monsters if this actor has been massacred
|
|
|
|
if (self->DamageType == NAME_Massacre && GetDefaultByType(missile)->flags3&MF3_ISMONSTER) return;
|
|
|
|
|
|
|
|
if (distance==0)
|
|
|
|
{
|
|
|
|
// use the minimum distance that does not result in an overlap
|
|
|
|
distance=(self->radius+GetDefaultByType(missile)->radius)>>FRACBITS;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ACTION_CALL_FROM_WEAPON())
|
|
|
|
{
|
|
|
|
// Used from a weapon so use some ammo
|
|
|
|
AWeapon * weapon=self->player->ReadyWeapon;
|
|
|
|
|
|
|
|
if (!weapon) return;
|
|
|
|
if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) return;
|
|
|
|
}
|
|
|
|
|
2016-01-19 19:15:45 +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);
|
|
|
|
bool res = InitSpawnedItem(self, mo, flags);
|
|
|
|
ACTION_SET_RESULT(res); // for an inventory item's use state
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SpawnItemEx
|
|
|
|
//
|
|
|
|
// Enhanced spawning function
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItemEx)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(11);
|
|
|
|
ACTION_PARAM_CLASS(missile, 0);
|
|
|
|
ACTION_PARAM_FIXED(xofs, 1);
|
|
|
|
ACTION_PARAM_FIXED(yofs, 2);
|
|
|
|
ACTION_PARAM_FIXED(zofs, 3);
|
|
|
|
ACTION_PARAM_FIXED(xvel, 4);
|
|
|
|
ACTION_PARAM_FIXED(yvel, 5);
|
|
|
|
ACTION_PARAM_FIXED(zvel, 6);
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_PARAM_ANGLE(angle, 7);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_INT(flags, 8);
|
|
|
|
ACTION_PARAM_INT(chance, 9);
|
|
|
|
ACTION_PARAM_INT(tid, 10);
|
|
|
|
|
|
|
|
if (!missile)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (chance > 0 && pr_spawnitemex()<chance) return;
|
|
|
|
|
|
|
|
// Don't spawn monsters if this actor has been massacred
|
|
|
|
if (self->DamageType == NAME_Massacre && GetDefaultByType(missile)->flags3&MF3_ISMONSTER) return;
|
|
|
|
|
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);
|
|
|
|
ACTION_SET_RESULT(res); // for an inventory item's use state
|
|
|
|
if (res)
|
|
|
|
{
|
|
|
|
if (tid != 0)
|
|
|
|
{
|
|
|
|
assert(mo->tid == 0);
|
|
|
|
mo->tid = tid;
|
|
|
|
mo->AddToHash();
|
|
|
|
}
|
|
|
|
if (flags & SIXF_MULTIPLYSPEED)
|
|
|
|
{
|
|
|
|
mo->velx = FixedMul(xvel, mo->Speed);
|
|
|
|
mo->vely = FixedMul(yvel, mo->Speed);
|
|
|
|
mo->velz = FixedMul(zvel, mo->Speed);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mo->velx = xvel;
|
|
|
|
mo->vely = yvel;
|
|
|
|
mo->velz = zvel;
|
|
|
|
}
|
2016-01-19 15:09:44 +00:00
|
|
|
mo->angle = angle;
|
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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
ACTION_PARAM_CLASS(missile, 0);
|
|
|
|
ACTION_PARAM_FIXED(zheight, 1);
|
|
|
|
ACTION_PARAM_FIXED(xyvel, 2);
|
|
|
|
ACTION_PARAM_FIXED(zvel, 3);
|
|
|
|
ACTION_PARAM_BOOL(useammo, 4);
|
|
|
|
|
|
|
|
if (missile == NULL) return;
|
|
|
|
|
|
|
|
if (ACTION_CALL_FROM_WEAPON())
|
|
|
|
{
|
|
|
|
// Used from a weapon, so use some ammo
|
|
|
|
AWeapon *weapon = self->player->ReadyWeapon;
|
|
|
|
|
|
|
|
if (!weapon) return;
|
|
|
|
if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
AActor * bo;
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
else ACTION_SET_RESULT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Recoil
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Recoil)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_FIXED(xyvel, 0);
|
|
|
|
|
|
|
|
angle_t angle = self->angle + ANG180;
|
|
|
|
angle >>= ANGLETOFINESHIFT;
|
2016-01-19 15:09:44 +00:00
|
|
|
self->velx += FixedMul(xyvel, finecosine[angle]);
|
|
|
|
self->vely += FixedMul(xyvel, finesine[angle]);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SelectWeapon
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SelectWeapon)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_CLASS(cls, 0);
|
|
|
|
|
|
|
|
if (cls == NULL || self->player == NULL)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
AWeapon * weaponitem = static_cast<AWeapon*>(self->FindInventory(cls));
|
|
|
|
|
|
|
|
if (weaponitem != NULL && weaponitem->IsKindOf(RUNTIME_CLASS(AWeapon)))
|
|
|
|
{
|
|
|
|
if (self->player->ReadyWeapon != weaponitem)
|
|
|
|
{
|
|
|
|
self->player->PendingWeapon = weaponitem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else ACTION_SET_RESULT(false);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Print
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
EXTERN_CVAR(Float, con_midtime)
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Print)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
ACTION_PARAM_STRING(text, 0);
|
|
|
|
ACTION_PARAM_FLOAT(time, 1);
|
|
|
|
ACTION_PARAM_NAME(fontname, 2);
|
|
|
|
|
|
|
|
if (text[0] == '$') text = GStrings(text+1);
|
|
|
|
if (self->CheckLocalView (consoleplayer) ||
|
|
|
|
(self->target!=NULL && self->target->CheckLocalView (consoleplayer)))
|
|
|
|
{
|
|
|
|
float saved = con_midtime;
|
|
|
|
FFont *font = NULL;
|
|
|
|
|
|
|
|
if (fontname != NAME_None)
|
|
|
|
{
|
|
|
|
font = V_GetFont(fontname);
|
|
|
|
}
|
|
|
|
if (time > 0)
|
|
|
|
{
|
|
|
|
con_midtime = time;
|
|
|
|
}
|
|
|
|
|
|
|
|
FString formatted = strbin1(text);
|
|
|
|
C_MidPrint(font != NULL ? font : SmallFont, formatted.GetChars());
|
|
|
|
con_midtime = saved;
|
|
|
|
}
|
|
|
|
ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains!
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_PrintBold
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PrintBold)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
ACTION_PARAM_STRING(text, 0);
|
|
|
|
ACTION_PARAM_FLOAT(time, 1);
|
|
|
|
ACTION_PARAM_NAME(fontname, 2);
|
|
|
|
|
|
|
|
float saved = con_midtime;
|
|
|
|
FFont *font = NULL;
|
|
|
|
|
|
|
|
if (text[0] == '$') text = GStrings(text+1);
|
|
|
|
if (fontname != NAME_None)
|
|
|
|
{
|
|
|
|
font = V_GetFont(fontname);
|
|
|
|
}
|
|
|
|
if (time > 0)
|
|
|
|
{
|
|
|
|
con_midtime = time;
|
|
|
|
}
|
|
|
|
|
|
|
|
FString formatted = strbin1(text);
|
|
|
|
C_MidPrintBold(font != NULL ? font : SmallFont, formatted.GetChars());
|
|
|
|
con_midtime = saved;
|
|
|
|
ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains!
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Log
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Log)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_STRING(text, 0);
|
|
|
|
|
|
|
|
if (text[0] == '$') text = GStrings(text+1);
|
|
|
|
FString formatted = strbin1(text);
|
2015-02-26 14:46:01 +00:00
|
|
|
Printf("%s\n", formatted.GetChars());
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains!
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================================
|
|
|
|
//
|
|
|
|
// A_LogInt
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LogInt)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_INT(num, 0);
|
|
|
|
Printf("%d\n", num);
|
|
|
|
ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains!
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetTranslucent
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTranslucent)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_FIXED(alpha, 0);
|
|
|
|
ACTION_PARAM_INT(mode, 1);
|
|
|
|
|
|
|
|
mode = mode == 0 ? STYLE_Translucent : mode == 2 ? STYLE_Fuzzy : STYLE_Add;
|
|
|
|
|
|
|
|
self->RenderStyle.Flags &= ~STYLEF_Alpha1;
|
|
|
|
self->alpha = clamp<fixed_t>(alpha, 0, FRACUNIT);
|
|
|
|
self->RenderStyle = ERenderStyle(mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_FadeIn
|
|
|
|
//
|
|
|
|
// Fades the actor in
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
enum FadeFlags
|
|
|
|
{
|
|
|
|
FTF_REMOVE = 1 << 0,
|
|
|
|
FTF_CLAMP = 1 << 1,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeIn)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_FIXED(reduce, 0);
|
|
|
|
ACTION_PARAM_INT(flags, 1);
|
|
|
|
|
|
|
|
if (reduce == 0)
|
|
|
|
{
|
|
|
|
reduce = FRACUNIT / 10;
|
|
|
|
}
|
|
|
|
self->RenderStyle.Flags &= ~STYLEF_Alpha1;
|
|
|
|
self->alpha += reduce;
|
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_FadeOut
|
|
|
|
//
|
|
|
|
// fades the actor out and destroys it when done
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeOut)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_FIXED(reduce, 0);
|
|
|
|
ACTION_PARAM_INT(flags, 1);
|
|
|
|
|
|
|
|
if (reduce == 0)
|
|
|
|
{
|
|
|
|
reduce = FRACUNIT/10;
|
|
|
|
}
|
|
|
|
self->RenderStyle.Flags &= ~STYLEF_Alpha1;
|
|
|
|
self->alpha -= reduce;
|
|
|
|
if (self->alpha <= 0)
|
|
|
|
{
|
|
|
|
if (flags & FTF_CLAMP)
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_FadeTo
|
|
|
|
//
|
|
|
|
// fades the actor to a specified transparency by a specified amount and
|
|
|
|
// destroys it if so desired
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeTo)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
ACTION_PARAM_FIXED(target, 0);
|
|
|
|
ACTION_PARAM_FIXED(amount, 1);
|
|
|
|
ACTION_PARAM_INT(flags, 2);
|
|
|
|
|
|
|
|
self->RenderStyle.Flags &= ~STYLEF_Alpha1;
|
|
|
|
|
|
|
|
if (self->alpha > target)
|
|
|
|
{
|
|
|
|
self->alpha -= amount;
|
|
|
|
|
|
|
|
if (self->alpha < target)
|
|
|
|
{
|
|
|
|
self->alpha = target;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (self->alpha < target)
|
|
|
|
{
|
|
|
|
self->alpha += amount;
|
|
|
|
|
|
|
|
if (self->alpha > target)
|
|
|
|
{
|
|
|
|
self->alpha = target;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (flags & FTF_CLAMP)
|
|
|
|
{
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Scale(float scalex, optional float scaley)
|
|
|
|
//
|
|
|
|
// Scales the actor's graphics. If scaley is 0, use scalex.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetScale)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
ACTION_PARAM_FIXED(scalex, 0);
|
|
|
|
ACTION_PARAM_FIXED(scaley, 1);
|
|
|
|
ACTION_PARAM_INT(ptr, 2);
|
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
if (!ref)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ref->scaleX = scalex;
|
|
|
|
ref->scaleY = scaley ? scaley : scalex;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetMass(int mass)
|
|
|
|
//
|
|
|
|
// Sets the actor's mass.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetMass)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_INT(mass, 0);
|
|
|
|
|
|
|
|
self->Mass = mass;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SpawnDebris
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnDebris)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
AActor * mo;
|
|
|
|
|
|
|
|
ACTION_PARAM_START(4);
|
|
|
|
ACTION_PARAM_CLASS(debris, 0);
|
|
|
|
ACTION_PARAM_BOOL(transfer_translation, 1);
|
|
|
|
ACTION_PARAM_FIXED(mult_h, 2);
|
|
|
|
ACTION_PARAM_FIXED(mult_v, 3);
|
|
|
|
|
|
|
|
if (debris == NULL) return;
|
|
|
|
|
|
|
|
// only positive values make sense here
|
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;
|
|
|
|
}
|
|
|
|
if (i < mo->GetClass()->ActorInfo->NumOwnedStates)
|
|
|
|
{
|
|
|
|
mo->SetState(mo->GetClass()->ActorInfo->OwnedStates + i);
|
|
|
|
}
|
|
|
|
mo->velz = FixedMul(mult_v, ((pr_spawndebris()&7)+5)*FRACUNIT);
|
|
|
|
mo->velx = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6));
|
|
|
|
mo->vely = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-08 09:58:24 +00:00
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SpawnParticle
|
|
|
|
//
|
|
|
|
//===========================================================================
|
2016-01-21 22:36:58 +00:00
|
|
|
enum SPFflag
|
|
|
|
{
|
|
|
|
SPF_FULLBRIGHT = 1,
|
|
|
|
SPF_RELPOS = 1 << 1,
|
|
|
|
SPF_RELVEL = 1 << 2,
|
|
|
|
SPF_RELACCEL = 1 << 3,
|
|
|
|
SPF_RELANG = 1 << 4,
|
|
|
|
};
|
2016-01-21 23:58:42 +00:00
|
|
|
|
2015-12-08 09:58:24 +00:00
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnParticle)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(15);
|
- Reorganized A_SpawnParticle parameters.
- Decorate order is now color, x/y/zoff, velx/y/z, lifetime, angle, flags, size, startalphaf, fadestepf, accelx/y/z.
- ACS order is now color, xyz offset, xyz velocity, lifetime, fullbright, size, startalpha, fadestep, xyz accel
2016-01-22 00:34:39 +00:00
|
|
|
ACTION_PARAM_COLOR(color, 0);
|
2016-01-22 01:59:33 +00:00
|
|
|
ACTION_PARAM_INT(flags, 1);
|
|
|
|
ACTION_PARAM_INT(lifetime, 2);
|
|
|
|
ACTION_PARAM_INT(size, 3);
|
|
|
|
ACTION_PARAM_ANGLE(angle, 4);
|
|
|
|
ACTION_PARAM_FIXED(xoff, 5);
|
|
|
|
ACTION_PARAM_FIXED(yoff, 6);
|
|
|
|
ACTION_PARAM_FIXED(zoff, 7);
|
|
|
|
ACTION_PARAM_FIXED(xvel, 8);
|
|
|
|
ACTION_PARAM_FIXED(yvel, 9);
|
|
|
|
ACTION_PARAM_FIXED(zvel, 10);
|
|
|
|
ACTION_PARAM_FIXED(accelx, 11);
|
|
|
|
ACTION_PARAM_FIXED(accely, 12);
|
|
|
|
ACTION_PARAM_FIXED(accelz, 13);
|
|
|
|
ACTION_PARAM_FIXED(startalphaf, 14);
|
|
|
|
ACTION_PARAM_FIXED(fadestepf, 15);
|
- Reorganized A_SpawnParticle parameters.
- Decorate order is now color, x/y/zoff, velx/y/z, lifetime, angle, flags, size, startalphaf, fadestepf, accelx/y/z.
- ACS order is now color, xyz offset, xyz velocity, lifetime, fullbright, size, startalpha, fadestep, xyz accel
2016-01-22 00:34:39 +00:00
|
|
|
|
2016-01-21 19:13:55 +00:00
|
|
|
BYTE startalpha = (BYTE)Scale(clamp(startalphaf, 0, FRACUNIT), 255, FRACUNIT);
|
2016-01-21 23:54:09 +00:00
|
|
|
int fadestep = fadestepf < 0? -1 : Scale(clamp(fadestepf, 0, FRACUNIT), 255, FRACUNIT);
|
2016-01-25 21:44:11 +00:00
|
|
|
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
|
|
|
}
|
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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_STATE(jump, 0);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
|
|
|
|
for (int i = 0; i < MAXPLAYERS; i++)
|
|
|
|
{
|
|
|
|
if (playeringame[i])
|
|
|
|
{
|
|
|
|
// Always check sight from each player.
|
|
|
|
if (P_CheckSight(players[i].mo, self, SF_IGNOREVISIBILITY))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// If a player is viewing from a non-player, then check that too.
|
|
|
|
if (players[i].camera != NULL && players[i].camera->player == NULL &&
|
|
|
|
P_CheckSight(players[i].camera, self, SF_IGNOREVISIBILITY))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_CheckSightOrRange
|
|
|
|
// Jumps if this actor is out of range of all players *and* out of sight.
|
|
|
|
// Useful for maps with many multi-actor special effects.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
static bool DoCheckSightOrRange(AActor *self, AActor *camera, double range, bool twodi)
|
|
|
|
{
|
|
|
|
if (camera == NULL)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Check distance first, since it's cheaper than checking sight.
|
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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
double range = EvalExpressionF(ParameterIndex+0, self);
|
|
|
|
ACTION_PARAM_STATE(jump, 1);
|
|
|
|
ACTION_PARAM_BOOL(twodi, 2);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
|
|
|
|
range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots
|
|
|
|
for (int i = 0; i < MAXPLAYERS; ++i)
|
|
|
|
{
|
|
|
|
if (playeringame[i])
|
|
|
|
{
|
|
|
|
// Always check from each player.
|
|
|
|
if (DoCheckSightOrRange(self, players[i].mo, range, twodi))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// If a player is viewing from a non-player, check that too.
|
|
|
|
if (players[i].camera != NULL && players[i].camera->player == NULL &&
|
|
|
|
DoCheckSightOrRange(self, players[i].camera, range, twodi))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_CheckRange
|
|
|
|
// Jumps if this actor is out of range of all players.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
static bool DoCheckRange(AActor *self, AActor *camera, double range, bool twodi)
|
|
|
|
{
|
|
|
|
if (camera == NULL)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Check distance first, since it's cheaper than checking sight.
|
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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
double range = EvalExpressionF(ParameterIndex+0, self);
|
|
|
|
ACTION_PARAM_STATE(jump, 1);
|
|
|
|
ACTION_PARAM_BOOL(twodi, 2);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
|
|
|
|
range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots
|
|
|
|
for (int i = 0; i < MAXPLAYERS; ++i)
|
|
|
|
{
|
|
|
|
if (playeringame[i])
|
|
|
|
{
|
|
|
|
// Always check from each player.
|
|
|
|
if (DoCheckRange(self, players[i].mo, range, twodi))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// If a player is viewing from a non-player, check that too.
|
|
|
|
if (players[i].camera != NULL && players[i].camera->player == NULL &&
|
|
|
|
DoCheckRange(self, players[i].camera, range, twodi))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// Inventory drop
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropInventory)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_CLASS(drop, 0);
|
|
|
|
|
|
|
|
if (drop)
|
|
|
|
{
|
|
|
|
AInventory * inv = self->FindInventory(drop);
|
|
|
|
if (inv)
|
|
|
|
{
|
|
|
|
self->DropInventory(inv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetBlend
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetBlend)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(4);
|
|
|
|
ACTION_PARAM_COLOR(color, 0);
|
|
|
|
ACTION_PARAM_FLOAT(alpha, 1);
|
|
|
|
ACTION_PARAM_INT(tics, 2);
|
|
|
|
ACTION_PARAM_COLOR(color2, 3);
|
|
|
|
|
|
|
|
if (color == MAKEARGB(255,255,255,255)) color=0;
|
|
|
|
if (color2 == MAKEARGB(255,255,255,255)) color2=0;
|
|
|
|
if (!color2.a)
|
|
|
|
color2 = color;
|
|
|
|
|
|
|
|
new DFlashFader(color.r/255.0f, color.g/255.0f, color.b/255.0f, alpha,
|
|
|
|
color2.r/255.0f, color2.g/255.0f, color2.b/255.0f, 0,
|
|
|
|
(float)tics/TICRATE, self);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_JumpIf
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIf)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_BOOL(expression, 0);
|
|
|
|
ACTION_PARAM_STATE(jump, 1);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
if (expression) ACTION_JUMP(jump);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_CountdownArg
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CountdownArg)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_INT(cnt, 0);
|
|
|
|
ACTION_PARAM_STATE(state, 1);
|
|
|
|
|
|
|
|
if (cnt<0 || cnt>=5) return;
|
|
|
|
if (!self->args[cnt]--)
|
|
|
|
{
|
|
|
|
if (self->flags&MF_MISSILE)
|
|
|
|
{
|
|
|
|
P_ExplodeMissile(self, NULL, NULL);
|
|
|
|
}
|
|
|
|
else if (self->flags&MF_SHOOTABLE)
|
|
|
|
{
|
|
|
|
P_DamageMobj (self, NULL, NULL, self->health, NAME_None, DMG_FORCED);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// can't use "Death" as default parameter with current DECORATE parser.
|
|
|
|
if (state == NULL) state = self->FindState(NAME_Death);
|
|
|
|
self->SetState(state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//============================================================================
|
|
|
|
//
|
|
|
|
// A_Burst
|
|
|
|
//
|
|
|
|
//============================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Burst)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_CLASS(chunk, 0);
|
|
|
|
|
|
|
|
int i, numChunks;
|
|
|
|
AActor * mo;
|
|
|
|
|
|
|
|
if (chunk == NULL) return;
|
|
|
|
|
|
|
|
self->velx = self->vely = self->velz = 0;
|
|
|
|
self->height = self->GetDefault()->height;
|
|
|
|
|
|
|
|
// [RH] In Hexen, this creates a random number of shards (range [24,56])
|
|
|
|
// with no relation to the size of the self shattering. I think it should
|
|
|
|
// base the number of shards on the size of the dead thing, so bigger
|
|
|
|
// things break up into more shards than smaller things.
|
|
|
|
// An self with radius 20 and height 64 creates ~40 chunks.
|
|
|
|
numChunks = MAX<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)
|
|
|
|
{
|
|
|
|
CALL_ACTION(A_BossDeath, self);
|
|
|
|
}
|
|
|
|
A_Unblock(self, true);
|
|
|
|
|
|
|
|
self->Destroy ();
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_CheckFloor
|
|
|
|
// [GRB] Jumps if actor is standing on floor
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFloor)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_STATE(jump, 0);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
2016-01-19 19:15:45 +00:00
|
|
|
if (self->Z() <= self->floorz)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_CheckCeiling
|
|
|
|
// [GZ] Totally copied on A_CheckFloor, jumps if actor touches ceiling
|
|
|
|
//
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckCeiling)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_STATE(jump, 0);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false);
|
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
|
|
|
{
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Stop
|
|
|
|
// resets all velocity of the actor to 0
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_Stop)
|
|
|
|
{
|
|
|
|
self->velx = self->vely = self->velz = 0;
|
|
|
|
if (self->player && self->player->mo == self && !(self->player->cheats & CF_PREDICTING))
|
|
|
|
{
|
|
|
|
self->player->mo->PlayIdle();
|
|
|
|
self->player->velx = self->player->vely = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void CheckStopped(AActor *self)
|
|
|
|
{
|
|
|
|
if (self->player != NULL &&
|
|
|
|
self->player->mo == self &&
|
|
|
|
!(self->player->cheats & CF_PREDICTING) &&
|
|
|
|
!(self->velx | self->vely | self->velz))
|
|
|
|
{
|
|
|
|
self->player->mo->PlayIdle();
|
|
|
|
self->player->velx = self->player->vely = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Respawn
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
extern void AF_A_RestoreSpecialPosition(DECLARE_PARAMINFO);
|
|
|
|
|
|
|
|
enum RS_Flags
|
|
|
|
{
|
|
|
|
RSF_FOG=1,
|
|
|
|
RSF_KEEPTARGET=2,
|
|
|
|
RSF_TELEFRAG=4,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Respawn)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_INT(flags, 0);
|
|
|
|
bool oktorespawn = false;
|
2016-01-19 19:15:45 +00:00
|
|
|
fixedvec3 pos = self->Pos();
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_PlayerSkinCheck
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayerSkinCheck)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_STATE(jump, 0);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
if (self->player != NULL &&
|
|
|
|
skins[self->player->userinfo.GetSkin()].othergame)
|
|
|
|
{
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetGravity
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetGravity)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_FIXED(val, 0);
|
|
|
|
|
|
|
|
self->gravity = clamp<fixed_t> (val, 0, FRACUNIT*10);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// [KS] *** Start of my modifications ***
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_ClearTarget
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_ClearTarget)
|
|
|
|
{
|
|
|
|
self->target = NULL;
|
|
|
|
self->LastHeard = NULL;
|
|
|
|
self->lastenemy = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_CheckLOF (state jump, int flags = CRF_AIM_VERT|CRF_AIM_HOR,
|
|
|
|
// fixed range = 0, angle angle = 0, angle pitch = 0,
|
|
|
|
// fixed offsetheight = 32, fixed offsetwidth = 0,
|
|
|
|
// int ptr_target = AAPTR_DEFAULT (target) )
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
enum CLOF_flags
|
|
|
|
{
|
|
|
|
CLOFF_NOAIM_VERT = 0x00000001,
|
|
|
|
CLOFF_NOAIM_HORZ = 0x00000002,
|
|
|
|
|
|
|
|
CLOFF_JUMPENEMY = 0x00000004,
|
|
|
|
CLOFF_JUMPFRIEND = 0x00000008,
|
|
|
|
CLOFF_JUMPOBJECT = 0x00000010,
|
|
|
|
CLOFF_JUMPNONHOSTILE = 0x00000020,
|
|
|
|
|
|
|
|
CLOFF_SKIPENEMY = 0x00000040,
|
|
|
|
CLOFF_SKIPFRIEND = 0x00000080,
|
|
|
|
CLOFF_SKIPOBJECT = 0x00000100,
|
|
|
|
CLOFF_SKIPNONHOSTILE = 0x00000200,
|
|
|
|
|
|
|
|
CLOFF_MUSTBESHOOTABLE = 0x00000400,
|
|
|
|
|
|
|
|
CLOFF_SKIPTARGET = 0x00000800,
|
|
|
|
CLOFF_ALLOWNULL = 0x00001000,
|
|
|
|
CLOFF_CHECKPARTIAL = 0x00002000,
|
|
|
|
|
|
|
|
CLOFF_MUSTBEGHOST = 0x00004000,
|
|
|
|
CLOFF_IGNOREGHOST = 0x00008000,
|
|
|
|
|
|
|
|
CLOFF_MUSTBESOLID = 0x00010000,
|
|
|
|
CLOFF_BEYONDTARGET = 0x00020000,
|
|
|
|
|
|
|
|
CLOFF_FROMBASE = 0x00040000,
|
|
|
|
CLOFF_MUL_HEIGHT = 0x00080000,
|
|
|
|
CLOFF_MUL_WIDTH = 0x00100000,
|
|
|
|
|
|
|
|
CLOFF_JUMP_ON_MISS = 0x00200000,
|
|
|
|
CLOFF_AIM_VERT_NOOFFSET = 0x00400000,
|
|
|
|
|
|
|
|
CLOFF_SETTARGET = 0x00800000,
|
|
|
|
CLOFF_SETMASTER = 0x01000000,
|
|
|
|
CLOFF_SETTRACER = 0x02000000,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct LOFData
|
|
|
|
{
|
|
|
|
AActor *Self;
|
|
|
|
AActor *Target;
|
|
|
|
int Flags;
|
|
|
|
bool BadActor;
|
|
|
|
};
|
|
|
|
|
|
|
|
ETraceStatus CheckLOFTraceFunc(FTraceResults &trace, void *userdata)
|
|
|
|
{
|
|
|
|
LOFData *data = (LOFData *)userdata;
|
|
|
|
int flags = data->Flags;
|
|
|
|
|
|
|
|
if (trace.HitType != TRACE_HitActor)
|
|
|
|
{
|
|
|
|
return TRACE_Stop;
|
|
|
|
}
|
|
|
|
if (trace.Actor == data->Target)
|
|
|
|
{
|
|
|
|
if (flags & CLOFF_SKIPTARGET)
|
|
|
|
{
|
|
|
|
if (flags & CLOFF_BEYONDTARGET)
|
|
|
|
{
|
|
|
|
return TRACE_Skip;
|
|
|
|
}
|
|
|
|
return TRACE_Abort;
|
|
|
|
}
|
|
|
|
return TRACE_Stop;
|
|
|
|
}
|
|
|
|
if (flags & CLOFF_MUSTBESHOOTABLE)
|
|
|
|
{ // all shootability checks go here
|
|
|
|
if (!(trace.Actor->flags & MF_SHOOTABLE))
|
|
|
|
{
|
|
|
|
return TRACE_Skip;
|
|
|
|
}
|
|
|
|
if (trace.Actor->flags2 & MF2_NONSHOOTABLE)
|
|
|
|
{
|
|
|
|
return TRACE_Skip;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((flags & CLOFF_MUSTBESOLID) && !(trace.Actor->flags & MF_SOLID))
|
|
|
|
{
|
|
|
|
return TRACE_Skip;
|
|
|
|
}
|
|
|
|
if (flags & CLOFF_MUSTBEGHOST)
|
|
|
|
{
|
|
|
|
if (!(trace.Actor->flags3 & MF3_GHOST))
|
|
|
|
{
|
|
|
|
return TRACE_Skip;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (flags & CLOFF_IGNOREGHOST)
|
|
|
|
{
|
|
|
|
if (trace.Actor->flags3 & MF3_GHOST)
|
|
|
|
{
|
|
|
|
return TRACE_Skip;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
((flags & CLOFF_JUMPENEMY) && data->Self->IsHostile(trace.Actor)) ||
|
|
|
|
((flags & CLOFF_JUMPFRIEND) && data->Self->IsFriend(trace.Actor)) ||
|
|
|
|
((flags & CLOFF_JUMPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) ||
|
|
|
|
((flags & CLOFF_JUMPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor))
|
|
|
|
)
|
|
|
|
{
|
|
|
|
return TRACE_Stop;
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
((flags & CLOFF_SKIPENEMY) && data->Self->IsHostile(trace.Actor)) ||
|
|
|
|
((flags & CLOFF_SKIPFRIEND) && data->Self->IsFriend(trace.Actor)) ||
|
|
|
|
((flags & CLOFF_SKIPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) ||
|
|
|
|
((flags & CLOFF_SKIPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor))
|
|
|
|
)
|
|
|
|
{
|
|
|
|
return TRACE_Skip;
|
|
|
|
}
|
|
|
|
data->BadActor = true;
|
|
|
|
return TRACE_Abort;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckLOF)
|
|
|
|
{
|
|
|
|
// Check line of fire
|
|
|
|
|
|
|
|
/*
|
|
|
|
Not accounted for / I don't know how it works: FLOORCLIP
|
|
|
|
*/
|
|
|
|
|
|
|
|
AActor *target;
|
2016-01-19 19:15:45 +00:00
|
|
|
fixedvec3 pos;
|
|
|
|
fixed_t vx, vy, vz;
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
ACTION_PARAM_START(9);
|
|
|
|
|
|
|
|
ACTION_PARAM_STATE(jump, 0);
|
|
|
|
ACTION_PARAM_INT(flags, 1);
|
|
|
|
ACTION_PARAM_FIXED(range, 2);
|
|
|
|
ACTION_PARAM_FIXED(minrange, 3);
|
|
|
|
{
|
|
|
|
ACTION_PARAM_ANGLE(angle, 4);
|
|
|
|
ACTION_PARAM_ANGLE(pitch, 5);
|
|
|
|
ACTION_PARAM_FIXED(offsetheight, 6);
|
|
|
|
ACTION_PARAM_FIXED(offsetwidth, 7);
|
|
|
|
ACTION_PARAM_INT(ptr_target, 8);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
|
|
|
|
target = COPY_AAPTR(self, ptr_target == AAPTR_DEFAULT ? AAPTR_TARGET|AAPTR_PLAYER_GETTARGET|AAPTR_NULL : ptr_target); // no player-support by default
|
|
|
|
|
|
|
|
if (flags & CLOFF_MUL_HEIGHT)
|
|
|
|
{
|
|
|
|
if (self->player != NULL)
|
|
|
|
{
|
|
|
|
// Synced with hitscan: self->player->mo->height is strangely conscientious about getting the right actor for player
|
|
|
|
offsetheight = FixedMul(offsetheight, FixedMul (self->player->mo->height, self->player->crouchfactor));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
offsetheight = FixedMul(offsetheight, self->height);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (flags & CLOFF_MUL_WIDTH)
|
|
|
|
{
|
|
|
|
offsetwidth = FixedMul(self->radius, offsetwidth);
|
|
|
|
}
|
|
|
|
|
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))
|
|
|
|
{
|
|
|
|
if (distance > range) return;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
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(
|
|
|
|
FixedMul(offsetwidth, finesine[ang]),
|
|
|
|
-FixedMul(offsetwidth, finecosine[ang]));
|
|
|
|
|
|
|
|
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-19 19:15:45 +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-19 19:15:45 +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(
|
|
|
|
FixedMul(offsetwidth, finesine[ang]),
|
|
|
|
-FixedMul(offsetwidth, finecosine[ang]));
|
|
|
|
|
|
|
|
pos.x = xy.x;
|
|
|
|
pos.y = xy.y;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else return;
|
|
|
|
|
|
|
|
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-01-19 19:15:45 +00:00
|
|
|
Trace(pos.x, pos.y, pos.z, sec, vx, vy, vz, range, ActorFlags::FromInt(0xFFFFFFFF), ML_BLOCKEVERYTHING, self, trace, 0,
|
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)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ((trace.HitType == TRACE_HitActor) && (trace.Actor != NULL) && !(lof_data.BadActor))
|
|
|
|
{
|
|
|
|
if (flags & (CLOFF_SETTARGET)) self->target = trace.Actor;
|
|
|
|
if (flags & (CLOFF_SETMASTER)) self->master = trace.Actor;
|
|
|
|
if (flags & (CLOFF_SETTRACER)) self->tracer = trace.Actor;
|
|
|
|
}
|
|
|
|
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_JumpIfTargetInLOS (state label, optional fixed fov, optional int flags,
|
|
|
|
// optional fixed dist_max, optional fixed dist_close)
|
|
|
|
//
|
|
|
|
// Jumps if the actor can see its target, or if the player has a linetarget.
|
|
|
|
// ProjectileTarget affects how projectiles are treated. If set, it will use
|
|
|
|
// the target of the projectile for seekers, and ignore the target for
|
|
|
|
// normal projectiles. If not set, it will use the missile's owner instead
|
|
|
|
// (the default). ProjectileTarget is now flag JLOSF_PROJECTILE. dist_max
|
|
|
|
// sets the maximum distance that actor can see, 0 means forever. dist_close
|
|
|
|
// uses special behavior if certain flags are set, 0 means no checks.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
enum JLOS_flags
|
|
|
|
{
|
|
|
|
JLOSF_PROJECTILE = 1,
|
|
|
|
JLOSF_NOSIGHT = 1 << 1,
|
|
|
|
JLOSF_CLOSENOFOV = 1 << 2,
|
|
|
|
JLOSF_CLOSENOSIGHT = 1 << 3,
|
|
|
|
JLOSF_CLOSENOJUMP = 1 << 4,
|
|
|
|
JLOSF_DEADNOJUMP = 1 << 5,
|
|
|
|
JLOSF_CHECKMASTER = 1 << 6,
|
|
|
|
JLOSF_TARGETLOS = 1 << 7,
|
|
|
|
JLOSF_FLIPFOV = 1 << 8,
|
|
|
|
JLOSF_ALLYNOJUMP = 1 << 9,
|
|
|
|
JLOSF_COMBATANTONLY = 1 << 10,
|
|
|
|
JLOSF_NOAUTOAIM = 1 << 11,
|
|
|
|
JLOSF_CHECKTRACER = 1 << 12,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
ACTION_PARAM_STATE(jump, 0);
|
|
|
|
ACTION_PARAM_ANGLE(fov, 1);
|
|
|
|
ACTION_PARAM_INT(flags, 2);
|
|
|
|
ACTION_PARAM_FIXED(dist_max, 3);
|
|
|
|
ACTION_PARAM_FIXED(dist_close, 4);
|
|
|
|
|
|
|
|
angle_t an;
|
|
|
|
AActor *target, *viewport;
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
|
|
|
|
bool doCheckSight;
|
|
|
|
|
|
|
|
if (!self->player)
|
|
|
|
{
|
|
|
|
if (flags & JLOSF_CHECKMASTER)
|
|
|
|
{
|
|
|
|
target = self->master;
|
|
|
|
}
|
|
|
|
else if ((self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) || (flags & JLOSF_CHECKTRACER))
|
|
|
|
{
|
|
|
|
if ((self->flags2 & MF2_SEEKERMISSILE) || (flags & JLOSF_CHECKTRACER))
|
|
|
|
target = self->tracer;
|
|
|
|
else
|
|
|
|
target = NULL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
target = self->target;
|
|
|
|
}
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if (target == NULL)
|
|
|
|
return; // [KS] Let's not call P_CheckSight unnecessarily in this case.
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
doCheckSight = !(flags & JLOSF_NOSIGHT);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Does the player aim at something that can be shot?
|
|
|
|
P_AimLineAttack(self, self->angle, MISSILERANGE, &target, (flags & JLOSF_NOAUTOAIM) ? ANGLE_1/2 : 0);
|
|
|
|
|
|
|
|
if (!target) return;
|
|
|
|
|
|
|
|
switch (flags & (JLOSF_TARGETLOS|JLOSF_FLIPFOV))
|
|
|
|
{
|
|
|
|
case JLOSF_TARGETLOS|JLOSF_FLIPFOV:
|
|
|
|
// target makes sight check, player makes fov check; player has verified fov
|
|
|
|
fov = 0;
|
|
|
|
// fall-through
|
|
|
|
case JLOSF_TARGETLOS:
|
|
|
|
doCheckSight = !(flags & JLOSF_NOSIGHT); // The target is responsible for sight check and fov
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// player has verified sight and fov
|
|
|
|
fov = 0;
|
|
|
|
// fall-through
|
|
|
|
case JLOSF_FLIPFOV: // Player has verified sight, but target must verify fov
|
|
|
|
doCheckSight = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// [FDARI] If target is not a combatant, don't jump
|
|
|
|
if ( (flags & JLOSF_COMBATANTONLY) && (!target->player) && !(target->flags3 & MF3_ISMONSTER)) return;
|
|
|
|
|
|
|
|
// [FDARI] If actors share team, don't jump
|
|
|
|
if ((flags & JLOSF_ALLYNOJUMP) && self->IsFriend(target)) return;
|
|
|
|
|
2016-01-10 16:52:41 +00:00
|
|
|
fixed_t distance = self->AproxDistance3D(target);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (dist_max && (distance > dist_max)) return;
|
|
|
|
|
|
|
|
if (dist_close && (distance < dist_close))
|
|
|
|
{
|
|
|
|
if (flags & JLOSF_CLOSENOJUMP)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (flags & JLOSF_CLOSENOFOV)
|
|
|
|
fov = 0;
|
|
|
|
|
|
|
|
if (flags & JLOSF_CLOSENOSIGHT)
|
|
|
|
doCheckSight = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flags & JLOSF_TARGETLOS) { viewport = target; target = self; }
|
|
|
|
else { viewport = self; }
|
|
|
|
|
|
|
|
if (doCheckSight && !P_CheckSight (viewport, target, SF_IGNOREVISIBILITY))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (flags & JLOSF_FLIPFOV)
|
|
|
|
{
|
|
|
|
if (viewport == self) { viewport = target; target = self; }
|
|
|
|
else { target = viewport; viewport = self; }
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fov && (fov < ANGLE_MAX))
|
|
|
|
{
|
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)))
|
|
|
|
{
|
|
|
|
return; // [KS] Outside of FOV - return
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_JumpIfInTargetLOS (state label, optional fixed fov, optional int flags
|
|
|
|
// optional fixed dist_max, optional fixed dist_close)
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetLOS)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
ACTION_PARAM_STATE(jump, 0);
|
|
|
|
ACTION_PARAM_ANGLE(fov, 1);
|
|
|
|
ACTION_PARAM_INT(flags, 2);
|
|
|
|
ACTION_PARAM_FIXED(dist_max, 3);
|
|
|
|
ACTION_PARAM_FIXED(dist_close, 4);
|
|
|
|
|
|
|
|
angle_t an;
|
|
|
|
AActor *target;
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
|
|
|
|
if (flags & JLOSF_CHECKMASTER)
|
|
|
|
{
|
|
|
|
target = self->master;
|
|
|
|
}
|
|
|
|
else if (self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE))
|
|
|
|
{
|
|
|
|
if (self->flags2 & MF2_SEEKERMISSILE)
|
|
|
|
target = self->tracer;
|
|
|
|
else
|
|
|
|
target = NULL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
target = self->target;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!target) return; // [KS] Let's not call P_CheckSight unnecessarily in this case.
|
|
|
|
|
|
|
|
if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) return;
|
|
|
|
|
2016-01-10 16:52:41 +00:00
|
|
|
fixed_t distance = self->AproxDistance3D(target);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
if (dist_max && (distance > dist_max)) return;
|
|
|
|
|
|
|
|
bool doCheckSight = !(flags & JLOSF_NOSIGHT);
|
|
|
|
|
|
|
|
if (dist_close && (distance < dist_close))
|
|
|
|
{
|
|
|
|
if (flags & JLOSF_CLOSENOJUMP)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (flags & JLOSF_CLOSENOFOV)
|
|
|
|
fov = 0;
|
|
|
|
|
|
|
|
if (flags & JLOSF_CLOSENOSIGHT)
|
|
|
|
doCheckSight = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fov && (fov < ANGLE_MAX))
|
|
|
|
{
|
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)))
|
|
|
|
{
|
|
|
|
return; // [KS] Outside of FOV - return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (doCheckSight && !P_CheckSight (target, self, SF_IGNOREVISIBILITY))
|
|
|
|
return;
|
|
|
|
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// Modified code pointer from Skulltag
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckForReload)
|
|
|
|
{
|
|
|
|
if ( self->player == NULL || self->player->ReadyWeapon == NULL )
|
|
|
|
return;
|
|
|
|
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_INT(count, 0);
|
|
|
|
ACTION_PARAM_STATE(jump, 1);
|
|
|
|
ACTION_PARAM_BOOL(dontincrement, 2)
|
|
|
|
|
|
|
|
if (count <= 0) return;
|
|
|
|
|
|
|
|
AWeapon *weapon = self->player->ReadyWeapon;
|
|
|
|
|
|
|
|
int ReloadCounter = weapon->ReloadCounter;
|
|
|
|
if(!dontincrement || ReloadCounter != 0)
|
|
|
|
ReloadCounter = (weapon->ReloadCounter+1) % count;
|
|
|
|
else // 0 % 1 = 1? So how do we check if the weapon was never fired? We should only do this when we're not incrementing the counter though.
|
|
|
|
ReloadCounter = 1;
|
|
|
|
|
|
|
|
// If we have not made our last shot...
|
|
|
|
if (ReloadCounter != 0)
|
|
|
|
{
|
|
|
|
// Go back to the refire frames, instead of continuing on to the reload frames.
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// We need to reload. However, don't reload if we're out of ammo.
|
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)
|
2015-01-30 01:30:26 +00:00
|
|
|
weapon->ReloadCounter = ReloadCounter;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// Resets the counter for the above function
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_ResetReloadCounter)
|
|
|
|
{
|
|
|
|
if ( self->player == NULL || self->player->ReadyWeapon == NULL )
|
|
|
|
return;
|
|
|
|
|
|
|
|
AWeapon *weapon = self->player->ReadyWeapon;
|
|
|
|
weapon->ReloadCounter = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_ChangeFlag
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeFlag)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_STRING(flagname, 0);
|
|
|
|
ACTION_PARAM_BOOL(expression, 1);
|
|
|
|
|
|
|
|
const char *dot = strchr (flagname, '.');
|
|
|
|
FFlagDef *fd;
|
|
|
|
const PClass *cls = self->GetClass();
|
|
|
|
|
|
|
|
if (dot != NULL)
|
|
|
|
{
|
|
|
|
FString part1(flagname, dot-flagname);
|
|
|
|
fd = FindFlag (cls, part1, dot+1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fd = FindFlag (cls, flagname, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fd != NULL)
|
|
|
|
{
|
|
|
|
bool kill_before, kill_after;
|
|
|
|
INTBOOL item_before, item_after;
|
|
|
|
INTBOOL secret_before, secret_after;
|
|
|
|
|
|
|
|
kill_before = self->CountsAsKill();
|
|
|
|
item_before = self->flags & MF_COUNTITEM;
|
|
|
|
secret_before = self->flags5 & MF5_COUNTSECRET;
|
|
|
|
|
|
|
|
if (fd->structoffset == -1)
|
|
|
|
{
|
|
|
|
HandleDeprecatedFlags(self, cls->ActorInfo, expression, fd->flagbit);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
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();
|
|
|
|
ModActorFlag(self, fd, expression);
|
|
|
|
if (linkchange) self->LinkToWorld();
|
|
|
|
}
|
|
|
|
kill_after = self->CountsAsKill();
|
|
|
|
item_after = self->flags & MF_COUNTITEM;
|
|
|
|
secret_after = self->flags5 & MF5_COUNTSECRET;
|
|
|
|
// Was this monster previously worth a kill but no longer is?
|
|
|
|
// Or vice versa?
|
|
|
|
if (kill_before != kill_after)
|
|
|
|
{
|
|
|
|
if (kill_after)
|
|
|
|
{ // It counts as a kill now.
|
|
|
|
level.total_monsters++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{ // It no longer counts as a kill.
|
|
|
|
level.total_monsters--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// same for items
|
|
|
|
if (item_before != item_after)
|
|
|
|
{
|
|
|
|
if (item_after)
|
|
|
|
{ // It counts as an item now.
|
|
|
|
level.total_items++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{ // It no longer counts as an item
|
|
|
|
level.total_items--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// and secretd
|
|
|
|
if (secret_before != secret_after)
|
|
|
|
{
|
|
|
|
if (secret_after)
|
|
|
|
{ // It counts as an secret now.
|
|
|
|
level.total_secrets++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{ // It no longer counts as an secret
|
|
|
|
level.total_secrets--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf("Unknown flag '%s' in '%s'\n", flagname, cls->TypeName.GetChars());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_CheckFlag
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFlag)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
ACTION_PARAM_STRING(flagname, 0);
|
|
|
|
ACTION_PARAM_STATE(jumpto, 1);
|
|
|
|
ACTION_PARAM_INT(checkpointer, 2);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
|
|
|
|
AActor *owner;
|
|
|
|
|
|
|
|
COPY_AAPTR_NOT_NULL(self, owner, checkpointer);
|
|
|
|
|
|
|
|
if (CheckActorFlag(owner, flagname))
|
|
|
|
{
|
|
|
|
ACTION_JUMP(jumpto);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_RaiseMaster
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseMaster)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_BOOL(copy, 0);
|
|
|
|
|
|
|
|
if (self->master != NULL)
|
|
|
|
{
|
|
|
|
if (copy)
|
|
|
|
P_Thing_Raise(self->master, self);
|
|
|
|
else
|
|
|
|
P_Thing_Raise(self->master, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_RaiseChildren
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseChildren)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_BOOL(copy, 0);
|
|
|
|
TThinkerIterator<AActor> it;
|
|
|
|
AActor *mo;
|
|
|
|
|
|
|
|
while ((mo = it.Next()) != NULL)
|
|
|
|
{
|
|
|
|
if (mo->master == self)
|
|
|
|
{
|
|
|
|
if (copy)
|
|
|
|
P_Thing_Raise(mo, self);
|
|
|
|
else
|
|
|
|
P_Thing_Raise(mo, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_RaiseSiblings
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseSiblings)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_BOOL(copy, 0);
|
|
|
|
TThinkerIterator<AActor> it;
|
|
|
|
AActor *mo;
|
|
|
|
|
|
|
|
if (self->master != NULL)
|
|
|
|
{
|
|
|
|
while ((mo = it.Next()) != NULL)
|
|
|
|
{
|
|
|
|
if (mo->master == self->master && mo != self)
|
|
|
|
{
|
|
|
|
if (copy)
|
|
|
|
P_Thing_Raise(mo, self);
|
|
|
|
else
|
|
|
|
P_Thing_Raise(mo, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// [TP] A_FaceConsolePlayer
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS (AActor, A_FaceConsolePlayer) {
|
|
|
|
ACTION_PARAM_START (1);
|
|
|
|
ACTION_PARAM_ANGLE (MaxTurnAngle, 0);
|
|
|
|
// NOTE: It does nothing for zdoom.
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_MonsterRefire
|
|
|
|
//
|
|
|
|
// Keep firing unless target got out of sight
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_MonsterRefire)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_INT(prob, 0);
|
|
|
|
ACTION_PARAM_STATE(jump, 1);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
A_FaceTarget (self);
|
|
|
|
|
|
|
|
if (pr_monsterrefire() < prob)
|
|
|
|
return;
|
|
|
|
|
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) )
|
|
|
|
{
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetAngle
|
|
|
|
//
|
|
|
|
// Set actor's angle (in degrees).
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
SPF_FORCECLAMP = 1, // players always clamp
|
|
|
|
SPF_INTERPOLATE = 2,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetAngle)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
ACTION_PARAM_ANGLE(angle, 0);
|
|
|
|
ACTION_PARAM_INT(flags, 1);
|
|
|
|
ACTION_PARAM_INT(ptr, 2);
|
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
2016-01-24 10:50:21 +00:00
|
|
|
if (ref == NULL)
|
2015-01-30 01:30:26 +00:00
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ref->SetAngle(angle, !!(flags & SPF_INTERPOLATE));
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetPitch
|
|
|
|
//
|
|
|
|
// Set actor's pitch (in degrees).
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetPitch)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
ACTION_PARAM_ANGLE(pitch, 0);
|
|
|
|
ACTION_PARAM_INT(flags, 1);
|
|
|
|
ACTION_PARAM_INT(ptr, 2);
|
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
if (!ref)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-05-27 22:41:07 +00:00
|
|
|
ref->SetPitch(pitch, !!(flags & SPF_INTERPOLATE), !!(flags & SPF_FORCECLAMP));
|
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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
ACTION_PARAM_ANGLE(roll, 0);
|
|
|
|
ACTION_PARAM_INT(flags, 1);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_INT(ptr, 2);
|
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
if (!ref)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
2015-04-04 16:40:43 +00:00
|
|
|
}
|
|
|
|
ref->SetRoll(roll, !!(flags & SPF_INTERPOLATE));
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_ScaleVelocity
|
|
|
|
//
|
|
|
|
// Scale actor's velocity.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ScaleVelocity)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_FIXED(scale, 0);
|
|
|
|
ACTION_PARAM_INT(ptr, 1);
|
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
if (!ref)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_ChangeVelocity
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeVelocity)
|
|
|
|
{
|
2015-07-27 17:20:32 +00:00
|
|
|
ACTION_PARAM_START(5);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_FIXED(x, 0);
|
|
|
|
ACTION_PARAM_FIXED(y, 1);
|
|
|
|
ACTION_PARAM_FIXED(z, 2);
|
|
|
|
ACTION_PARAM_INT(flags, 3);
|
|
|
|
ACTION_PARAM_INT(ptr, 4);
|
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
if (!ref)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
INTBOOL was_moving = ref->velx | ref->vely | ref->velz;
|
|
|
|
|
|
|
|
fixed_t vx = x, vy = y, vz = z;
|
|
|
|
fixed_t sina = finesine[ref->angle >> ANGLETOFINESHIFT];
|
|
|
|
fixed_t cosa = finecosine[ref->angle >> ANGLETOFINESHIFT];
|
|
|
|
|
|
|
|
if (flags & 1) // relative axes - make x, y relative to actor's current angle
|
|
|
|
{
|
|
|
|
vx = DMulScale16(x, cosa, -y, sina);
|
|
|
|
vy = DMulScale16(x, sina, y, cosa);
|
|
|
|
}
|
|
|
|
if (flags & 2) // discard old velocity - replace old velocity with new velocity
|
|
|
|
{
|
|
|
|
ref->velx = vx;
|
|
|
|
ref->vely = vy;
|
|
|
|
ref->velz = vz;
|
|
|
|
}
|
|
|
|
else // add new velocity to old velocity
|
|
|
|
{
|
|
|
|
ref->velx += vx;
|
|
|
|
ref->vely += vy;
|
|
|
|
ref->velz += vz;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (was_moving)
|
|
|
|
{
|
2015-07-27 17:20:32 +00:00
|
|
|
CheckStopped(ref);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetArg
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetArg)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_INT(pos, 0);
|
|
|
|
ACTION_PARAM_INT(value, 1);
|
|
|
|
|
|
|
|
// Set the value of the specified arg
|
|
|
|
if ((size_t)pos < countof(self->args))
|
|
|
|
{
|
|
|
|
self->args[pos] = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetSpecial
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpecial)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(6);
|
|
|
|
ACTION_PARAM_INT(spec, 0);
|
|
|
|
ACTION_PARAM_INT(arg0, 1);
|
|
|
|
ACTION_PARAM_INT(arg1, 2);
|
|
|
|
ACTION_PARAM_INT(arg2, 3);
|
|
|
|
ACTION_PARAM_INT(arg3, 4);
|
|
|
|
ACTION_PARAM_INT(arg4, 5);
|
|
|
|
|
|
|
|
self->special = spec;
|
|
|
|
self->args[0] = arg0;
|
|
|
|
self->args[1] = arg1;
|
|
|
|
self->args[2] = arg2;
|
|
|
|
self->args[3] = arg3;
|
|
|
|
self->args[4] = arg4;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetUserVar
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserVar)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_NAME(varname, 0);
|
|
|
|
ACTION_PARAM_INT(value, 1);
|
|
|
|
|
|
|
|
PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true);
|
|
|
|
PSymbolVariable *var;
|
|
|
|
|
|
|
|
if (sym == NULL || sym->SymbolType != SYM_Variable ||
|
|
|
|
!(var = static_cast<PSymbolVariable *>(sym))->bUserVar ||
|
|
|
|
var->ValueType.Type != VAL_Int)
|
|
|
|
{
|
|
|
|
Printf("%s is not a user variable in class %s\n", varname.GetChars(),
|
|
|
|
self->GetClass()->TypeName.GetChars());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Set the value of the specified user variable.
|
|
|
|
*(int *)(reinterpret_cast<BYTE *>(self) + var->offset) = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetUserArray
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserArray)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
ACTION_PARAM_NAME(varname, 0);
|
|
|
|
ACTION_PARAM_INT(pos, 1);
|
|
|
|
ACTION_PARAM_INT(value, 2);
|
|
|
|
|
|
|
|
PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true);
|
|
|
|
PSymbolVariable *var;
|
|
|
|
|
|
|
|
if (sym == NULL || sym->SymbolType != SYM_Variable ||
|
|
|
|
!(var = static_cast<PSymbolVariable *>(sym))->bUserVar ||
|
|
|
|
var->ValueType.Type != VAL_Array || var->ValueType.BaseType != VAL_Int)
|
|
|
|
{
|
|
|
|
Printf("%s is not a user array in class %s\n", varname.GetChars(),
|
|
|
|
self->GetClass()->TypeName.GetChars());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (pos < 0 || pos >= var->ValueType.size)
|
|
|
|
{
|
|
|
|
Printf("%d is out of bounds in array %s in class %s\n", pos, varname.GetChars(),
|
|
|
|
self->GetClass()->TypeName.GetChars());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Set the value of the specified user array at index pos.
|
|
|
|
((int *)(reinterpret_cast<BYTE *>(self) + var->offset))[pos] = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
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)
|
|
|
|
{
|
2015-04-07 16:14:02 +00:00
|
|
|
ACTION_PARAM_START(7);
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_PARAM_STATE(teleport_state, 0);
|
|
|
|
ACTION_PARAM_CLASS(target_type, 1);
|
|
|
|
ACTION_PARAM_CLASS(fog_type, 2);
|
|
|
|
ACTION_PARAM_INT(flags, 3);
|
|
|
|
ACTION_PARAM_FIXED(mindist, 4);
|
|
|
|
ACTION_PARAM_FIXED(maxdist, 5);
|
2015-04-07 16:14:02 +00:00
|
|
|
ACTION_PARAM_INT(ptr, 6);
|
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
if (!ref)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-01-19 15:09:44 +00:00
|
|
|
if ((ref->flags2 & MF2_NOTELEPORT) && !(flags & TF_OVERRIDE))
|
2015-05-01 13:32:07 +00:00
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
2015-04-07 16:14:02 +00:00
|
|
|
return;
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pr_teleport() >= chance[chanceindex]) return;
|
|
|
|
}
|
|
|
|
|
|
|
|
DSpotState *state = DSpotState::GetSpotState();
|
2015-05-01 13:32:07 +00:00
|
|
|
if (state == NULL)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
2015-04-07 16:14:02 +00:00
|
|
|
return;
|
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)
|
|
|
|
{
|
|
|
|
target_type = PClass::FindClass("BossSpot");
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-19 19:15:45 +00:00
|
|
|
AActor * spot = state->GetSpotWithMinMaxDistance(target_type, ref->X(), ref->Y(), mindist, maxdist);
|
2015-04-07 16:14:02 +00:00
|
|
|
if (spot == NULL)
|
2015-05-01 13:32:07 +00:00
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
2015-04-07 16:14:02 +00:00
|
|
|
return;
|
2015-05-01 13:32:07 +00:00
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2016-01-21 00:18:00 +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))
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
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
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
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)
|
|
|
|
return;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_JUMP(teleport_state);
|
2015-01-30 01:30:26 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2016-01-19 15:09:44 +00:00
|
|
|
ACTION_SET_RESULT(tele_result);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Turn
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Turn)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_ANGLE(angle, 0);
|
|
|
|
self->angle += angle;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Quake
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Quake)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
ACTION_PARAM_INT(intensity, 0);
|
|
|
|
ACTION_PARAM_INT(duration, 1);
|
|
|
|
ACTION_PARAM_INT(damrad, 2);
|
|
|
|
ACTION_PARAM_INT(tremrad, 3);
|
|
|
|
ACTION_PARAM_SOUND(sound, 4);
|
|
|
|
P_StartQuake(self, 0, intensity, duration, damrad, tremrad, sound);
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
{
|
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
|
|
|
ACTION_PARAM_START(11);
|
2015-02-14 21:58:39 +00:00
|
|
|
ACTION_PARAM_INT(intensityX, 0);
|
|
|
|
ACTION_PARAM_INT(intensityY, 1);
|
|
|
|
ACTION_PARAM_INT(intensityZ, 2);
|
|
|
|
ACTION_PARAM_INT(duration, 3);
|
|
|
|
ACTION_PARAM_INT(damrad, 4);
|
|
|
|
ACTION_PARAM_INT(tremrad, 5);
|
|
|
|
ACTION_PARAM_SOUND(sound, 6);
|
|
|
|
ACTION_PARAM_INT(flags, 7);
|
2015-03-03 01:17:24 +00:00
|
|
|
ACTION_PARAM_DOUBLE(mulWaveX, 8);
|
|
|
|
ACTION_PARAM_DOUBLE(mulWaveY, 9);
|
|
|
|
ACTION_PARAM_DOUBLE(mulWaveZ, 10);
|
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);
|
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-01-19 19:15:45 +00:00
|
|
|
// the following 4 lines are for future-proofing this for both interpolation overhaul and line portals.
|
|
|
|
// For portals we need to calculate the destination including the portal offset
|
|
|
|
// and for interpolation we need to set the performed movement explicitly, because SetXY cannot do that.
|
|
|
|
newX -= self->X();
|
|
|
|
newY -= self->Y();
|
|
|
|
self->SetXY(self->Vec2Offset(newX, newY));
|
|
|
|
self->SetMovement(newX, newY, 0);
|
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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(4);
|
|
|
|
ACTION_PARAM_INT(xspeed, 0);
|
|
|
|
ACTION_PARAM_INT(yspeed, 1);
|
|
|
|
ACTION_PARAM_FIXED(xdist, 2);
|
|
|
|
ACTION_PARAM_FIXED(ydist, 3);
|
|
|
|
A_Weave(self, xspeed, yspeed, xdist, ydist);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_LineEffect
|
|
|
|
//
|
|
|
|
// This allows linedef effects to be activated inside deh frames.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LineEffect)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_INT(special, 0);
|
|
|
|
ACTION_PARAM_INT(tag, 1);
|
|
|
|
|
|
|
|
line_t junk; maplinedef_t oldjunk;
|
|
|
|
bool res = false;
|
|
|
|
if (!(self->flags6 & MF6_LINEDONE)) // Unless already used up
|
|
|
|
{
|
|
|
|
if ((oldjunk.special = special)) // Linedef type
|
|
|
|
{
|
|
|
|
oldjunk.tag = tag; // Sector tag for linedef
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ACTION_SET_RESULT(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A Wolf3D-style attack codepointer
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
enum WolfAttackFlags
|
|
|
|
{
|
|
|
|
WAF_NORANDOM = 1,
|
|
|
|
WAF_USEPUFF = 2,
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_WolfAttack)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(9);
|
|
|
|
ACTION_PARAM_INT(flags, 0);
|
|
|
|
ACTION_PARAM_SOUND(sound, 1);
|
|
|
|
ACTION_PARAM_FIXED(snipe, 2);
|
|
|
|
ACTION_PARAM_INT(maxdamage, 3);
|
|
|
|
ACTION_PARAM_INT(blocksize, 4);
|
|
|
|
ACTION_PARAM_INT(pointblank, 5);
|
|
|
|
ACTION_PARAM_INT(longrange, 6);
|
|
|
|
ACTION_PARAM_FIXED(runspeed, 7);
|
|
|
|
ACTION_PARAM_CLASS(pufftype, 8);
|
|
|
|
|
|
|
|
if (!self->target)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Enemy can't see target
|
|
|
|
if (!P_CheckSight(self, self->target))
|
|
|
|
return;
|
|
|
|
|
|
|
|
A_FaceTarget (self);
|
|
|
|
|
|
|
|
|
|
|
|
// Target can dodge if it can see enemy
|
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)
|
|
|
|
{
|
|
|
|
AActor * dpuff = GetDefaultByType(pufftype->GetReplacement());
|
|
|
|
mod = dpuff->DamageType;
|
|
|
|
|
|
|
|
if (dpuff->flags2 & MF2_THRUGHOST && self->target->flags3 & MF3_GHOST)
|
|
|
|
damage = 0;
|
|
|
|
|
|
|
|
if ((0 && dpuff->flags3 & MF3_PUFFONACTORS) || !spawnblood)
|
|
|
|
{
|
|
|
|
spawnblood = false;
|
2016-01-19 19:15:45 +00:00
|
|
|
P_SpawnPuff(self, pufftype, bloodpos, 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-01-10 19:46:26 +00:00
|
|
|
P_TraceBleed(newdam > 0 ? newdam : damage, self->target, self->AngleTo(dx, dy, self->target), 0);
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// And finally, let's play the sound
|
|
|
|
S_Sound (self, CHAN_WEAPON, sound, 1, ATTN_NORM);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_Warp
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Warp)
|
|
|
|
{
|
2015-10-03 22:28:54 +00:00
|
|
|
ACTION_PARAM_START(10);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
ACTION_PARAM_INT(destination_selector, 0);
|
|
|
|
ACTION_PARAM_FIXED(xofs, 1);
|
|
|
|
ACTION_PARAM_FIXED(yofs, 2);
|
|
|
|
ACTION_PARAM_FIXED(zofs, 3);
|
|
|
|
ACTION_PARAM_ANGLE(angle, 4);
|
|
|
|
ACTION_PARAM_INT(flags, 5);
|
|
|
|
ACTION_PARAM_STATE(success_state, 6);
|
2015-10-03 22:28:54 +00:00
|
|
|
ACTION_PARAM_FIXED(heightoffset, 7);
|
|
|
|
ACTION_PARAM_FIXED(radiusoffset, 8);
|
|
|
|
ACTION_PARAM_ANGLE(pitch, 9);
|
2015-07-31 12:54:01 +00:00
|
|
|
|
|
|
|
AActor *reference;
|
|
|
|
|
|
|
|
if((flags & WARPF_USETID))
|
|
|
|
{
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
{
|
|
|
|
if (success_state)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
|
|
|
// in this case, you have the statejump to help you handle all the success anyway.
|
|
|
|
ACTION_JUMP(success_state);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(true);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// ACS_Named* stuff
|
|
|
|
|
|
|
|
//
|
|
|
|
// These are exactly like their un-named line special equivalents, except
|
|
|
|
// they take strings instead of integers to indicate which script to run.
|
|
|
|
// Some of these probably aren't very useful, but they are included for
|
|
|
|
// the sake of completeness.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteWithResult)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
|
|
|
|
ACTION_PARAM_NAME(scriptname, 0);
|
|
|
|
ACTION_PARAM_INT(arg1, 1);
|
|
|
|
ACTION_PARAM_INT(arg2, 2);
|
|
|
|
ACTION_PARAM_INT(arg3, 3);
|
|
|
|
ACTION_PARAM_INT(arg4, 4);
|
|
|
|
|
|
|
|
bool res = !!P_ExecuteSpecial(ACS_ExecuteWithResult, NULL, self, false, -scriptname, arg1, arg2, arg3, arg4);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecute)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
|
|
|
|
ACTION_PARAM_NAME(scriptname, 0);
|
|
|
|
ACTION_PARAM_INT(mapnum, 1);
|
|
|
|
ACTION_PARAM_INT(arg1, 2);
|
|
|
|
ACTION_PARAM_INT(arg2, 3);
|
|
|
|
ACTION_PARAM_INT(arg3, 4);
|
|
|
|
|
|
|
|
bool res = !!P_ExecuteSpecial(ACS_Execute, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteAlways)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
|
|
|
|
ACTION_PARAM_NAME(scriptname, 0);
|
|
|
|
ACTION_PARAM_INT(mapnum, 1);
|
|
|
|
ACTION_PARAM_INT(arg1, 2);
|
|
|
|
ACTION_PARAM_INT(arg2, 3);
|
|
|
|
ACTION_PARAM_INT(arg3, 4);
|
|
|
|
|
|
|
|
bool res = !!P_ExecuteSpecial(ACS_ExecuteAlways, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecute)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
|
|
|
|
ACTION_PARAM_NAME(scriptname, 0);
|
|
|
|
ACTION_PARAM_INT(mapnum, 1);
|
|
|
|
ACTION_PARAM_INT(arg1, 2);
|
|
|
|
ACTION_PARAM_INT(arg2, 3);
|
|
|
|
ACTION_PARAM_INT(lock, 4);
|
|
|
|
|
|
|
|
bool res = !!P_ExecuteSpecial(ACS_LockedExecute, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecuteDoor)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
|
|
|
|
ACTION_PARAM_NAME(scriptname, 0);
|
|
|
|
ACTION_PARAM_INT(mapnum, 1);
|
|
|
|
ACTION_PARAM_INT(arg1, 2);
|
|
|
|
ACTION_PARAM_INT(arg2, 3);
|
|
|
|
ACTION_PARAM_INT(lock, 4);
|
|
|
|
|
|
|
|
bool res = !!P_ExecuteSpecial(ACS_LockedExecuteDoor, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedSuspend)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
|
|
|
|
ACTION_PARAM_NAME(scriptname, 0);
|
|
|
|
ACTION_PARAM_INT(mapnum, 1);
|
|
|
|
|
|
|
|
bool res = !!P_ExecuteSpecial(ACS_Suspend, NULL, self, false, -scriptname, mapnum, 0, 0, 0);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedTerminate)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
|
|
|
|
ACTION_PARAM_NAME(scriptname, 0);
|
|
|
|
ACTION_PARAM_INT(mapnum, 1);
|
|
|
|
|
|
|
|
bool res = !!P_ExecuteSpecial(ACS_Terminate, NULL, self, false, -scriptname, mapnum, 0, 0, 0);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
- 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool DoCheckClass(AActor *mo, const PClass *filterClass, bool exclude)
|
|
|
|
{
|
|
|
|
const PClass *actorClass = mo->GetClass();
|
|
|
|
if (filterClass == NULL) return true;
|
|
|
|
return exclude ? (actorClass != filterClass) : (actorClass == filterClass);
|
|
|
|
}
|
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_MASK = 2111,
|
|
|
|
RGF_NOTARGET = 1 << 6,
|
|
|
|
RGF_NOTRACER = 1 << 7,
|
|
|
|
RGF_NOMASTER = 1 << 8,
|
|
|
|
RGF_CUBE = 1 << 9,
|
|
|
|
RGF_NOSIGHT = 1 << 10,
|
|
|
|
RGF_MISSILES = 1 << 11,
|
- 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,
|
2015-01-30 01:30:26 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusGive)
|
|
|
|
{
|
2015-09-29 16:40:44 +00:00
|
|
|
ACTION_PARAM_START(7);
|
2015-01-30 01:30:26 +00:00
|
|
|
ACTION_PARAM_CLASS(item, 0);
|
|
|
|
ACTION_PARAM_FIXED(distance, 1);
|
|
|
|
ACTION_PARAM_INT(flags, 2);
|
|
|
|
ACTION_PARAM_INT(amount, 3);
|
- 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
|
|
|
ACTION_PARAM_CLASS(filter, 4);
|
|
|
|
ACTION_PARAM_NAME(species, 5);
|
2015-09-29 16:40:44 +00:00
|
|
|
ACTION_PARAM_FIXED(mindist, 6);
|
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
|
|
|
{
|
2015-09-29 16:40:44 +00:00
|
|
|
ACTION_SET_RESULT(false);
|
2015-01-30 01:30:26 +00:00
|
|
|
return;
|
|
|
|
}
|
- 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;
|
|
|
|
}
|
2016-01-19 19:15:45 +00:00
|
|
|
FBlockThingsIterator it(FBoundingBox(self->X(), self->Y(), distance));
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
AActor *thing;
|
2015-09-29 16:40:44 +00:00
|
|
|
bool given = false;
|
2015-01-30 01:30:26 +00:00
|
|
|
while ((thing = it.Next()))
|
|
|
|
{
|
- 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
|
|
|
//[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)))
|
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
|
|
|
if (thing != self) //Don't let filter and species obstruct RGF_GIVESELF.
|
2015-01-30 01:30:26 +00:00
|
|
|
continue;
|
|
|
|
}
|
- 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
|
|
|
|
|
|
|
if (thing == self)
|
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
|
|
|
if (!(flags & RGF_GIVESELF))
|
2015-01-30 01:30:26 +00:00
|
|
|
continue;
|
|
|
|
}
|
- 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
|
|
|
|
|
|
|
//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)))
|
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
|
|
|
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)
|
2015-01-30 01:30:26 +00:00
|
|
|
continue;
|
|
|
|
}
|
- 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
|
|
|
|
|
|
|
//Next, actor flag checking.
|
|
|
|
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);
|
2015-09-29 16:40:44 +00:00
|
|
|
bool objectPass = !!((flags & RGF_OBJECTS) && (thing->player == NULL) && (!(thing->flags3 & MF3_ISMONSTER))
|
|
|
|
&& ((thing->flags & MF_SHOOTABLE) || (thing->flags6 & MF6_VULNERABLE)));
|
2015-07-24 18:48:46 +00:00
|
|
|
bool playerPass = !!((flags & RGF_PLAYERS) && (thing->player != NULL) && (thing->player->mo == thing));
|
|
|
|
bool voodooPass = !!((flags & RGF_VOODOO) && (thing->player != NULL) && (thing->player->mo != thing));
|
- 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
|
|
|
//Self calls priority over the rest of this.
|
|
|
|
if (!selfPass)
|
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
|
|
|
//If it's specifically a monster/object/player/voodoo... Can be either or...
|
|
|
|
if (monsterPass || objectPass || playerPass || voodooPass)
|
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
|
|
|
//...and is dead, without desire to give to the dead...
|
|
|
|
if (((thing->health <= 0) && !(corpsePass || killedPass)))
|
|
|
|
{
|
|
|
|
//Skip!
|
|
|
|
continue;
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-22 22:12:42 +00:00
|
|
|
bool itemPass = !!((flags & RGF_ITEMS) && thing->IsKindOf(RUNTIME_CLASS(AInventory)));
|
- 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
|
|
|
bool missilePass = !!((flags & RGF_MISSILES) && thing->flags & MF_MISSILE);
|
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
|
|
|
if (selfPass || monsterPass || corpsePass || killedPass || itemPass || objectPass || missilePass || playerPass || voodooPass)
|
|
|
|
{
|
2015-09-29 16:40:44 +00:00
|
|
|
|
2016-01-19 19:15:45 +00:00
|
|
|
fixedvec3 diff = self->Vec3To(thing);
|
|
|
|
diff.z += (thing->height - self->height) / 2;
|
- 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
|
|
|
if (flags & RGF_CUBE)
|
|
|
|
{ // check if inside a cube
|
2016-01-19 19:15:45 +00:00
|
|
|
double dx = fabs((double)(diff.x));
|
|
|
|
double dy = fabs((double)(diff.y));
|
|
|
|
double dz = fabs((double)(diff.z));
|
2015-09-29 16:40:44 +00:00
|
|
|
double dist = (double)distance;
|
|
|
|
double min = (double)mindist;
|
|
|
|
if ((dx > dist || dy > dist || dz > dist) || (min && (dx < min && dy < min && dz < min)))
|
- 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
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
else
|
- 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
|
|
|
{ // check if inside a sphere
|
2015-09-29 16:40:44 +00:00
|
|
|
double distsquared = double(distance) * double(distance);
|
|
|
|
double minsquared = double(mindist) * double(mindist);
|
2016-01-19 19:15:45 +00:00
|
|
|
double lengthsquared = TVector3<double>(diff.x, diff.y, diff.z).LengthSquared();
|
|
|
|
if (lengthsquared > distsquared || (minsquared && (lengthsquared < minsquared)))
|
- 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
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
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
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
2015-09-29 16:40:44 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
given = true;
|
|
|
|
}
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-09-29 16:40:44 +00:00
|
|
|
ACTION_SET_RESULT(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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
ACTION_PARAM_STATE(jump, 0);
|
|
|
|
ACTION_PARAM_NAME(species, 1);
|
|
|
|
ACTION_PARAM_INT(ptr, 2);
|
|
|
|
|
|
|
|
AActor *mobj = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
//Needs at least one state jump to work.
|
|
|
|
if (!mobj)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (jump && mobj->GetSpecies() == species)
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
|
2015-01-30 01:30:26 +00:00
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_SetTics
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTics)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_INT(tics_to_set, 0);
|
|
|
|
|
|
|
|
if (stateowner != self && self->player != NULL && stateowner->IsKindOf(RUNTIME_CLASS(AWeapon)))
|
|
|
|
{ // Is this a weapon? Need to check psp states for a match, then. Blah.
|
|
|
|
for (int i = 0; i < NUMPSPRITES; ++i)
|
|
|
|
{
|
|
|
|
if (self->player->psprites[i].state == CallingState)
|
|
|
|
{
|
|
|
|
self->player->psprites[i].tics = tics_to_set;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Just set tics for self.
|
|
|
|
self->tics = tics_to_set;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_SetDamageType
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetDamageType)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_NAME(damagetype, 0);
|
|
|
|
|
|
|
|
self->DamageType = damagetype;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_DropItem
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropItem)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
ACTION_PARAM_CLASS(spawntype, 0);
|
|
|
|
ACTION_PARAM_INT(amount, 1);
|
|
|
|
ACTION_PARAM_INT(chance, 2);
|
|
|
|
|
|
|
|
P_DropItem(self, spawntype, amount, chance);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_SetSpeed
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpeed)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_FIXED(speed, 0);
|
|
|
|
ACTION_PARAM_INT(ptr, 1);
|
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
if (!ref)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ref->Speed = speed;
|
|
|
|
}
|
|
|
|
|
2015-09-07 00:57:43 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_SetFloatSpeed
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetFloatSpeed)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_FIXED(speed, 0);
|
|
|
|
ACTION_PARAM_INT(ptr, 1);
|
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
if (!ref)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ref->FloatSpeed = speed;
|
|
|
|
}
|
|
|
|
|
2015-09-17 14:07:13 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// A_SetPainThreshold
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetPainThreshold)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_INT(threshold, 0);
|
|
|
|
ACTION_PARAM_INT(ptr, 1);
|
|
|
|
|
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
if (!ref)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ref->PainThreshold = threshold;
|
|
|
|
}
|
|
|
|
|
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.
|
|
|
|
};
|
|
|
|
|
|
|
|
static void DoDamage(AActor *dmgtarget, AActor *self, int amount, FName DamageType, int flags, const PClass *filter, FName species)
|
|
|
|
{
|
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)
|
|
|
|
P_DamageMobj(dmgtarget, self, self, amount, DamageType, dmgFlags); //Should wind up passing them through just fine.
|
|
|
|
|
|
|
|
else if (amount < 0)
|
|
|
|
{
|
|
|
|
amount = -amount;
|
|
|
|
P_GiveBody(dmgtarget, amount);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSelf)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
ACTION_PARAM_INT(amount, 0);
|
|
|
|
ACTION_PARAM_NAME(DamageType, 1);
|
|
|
|
ACTION_PARAM_INT(flags, 2);
|
|
|
|
ACTION_PARAM_CLASS(filter, 3);
|
|
|
|
ACTION_PARAM_NAME(species, 4);
|
|
|
|
|
|
|
|
DoDamage(self, self, amount, DamageType, flags, filter, species);
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTarget)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
ACTION_PARAM_INT(amount, 0);
|
|
|
|
ACTION_PARAM_NAME(DamageType, 1);
|
|
|
|
ACTION_PARAM_INT(flags, 2);
|
|
|
|
ACTION_PARAM_CLASS(filter, 3);
|
|
|
|
ACTION_PARAM_NAME(species, 4);
|
|
|
|
|
|
|
|
if (self->target != NULL)
|
|
|
|
{
|
|
|
|
DoDamage(self->target, self, amount, DamageType, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTracer)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
ACTION_PARAM_INT(amount, 0);
|
|
|
|
ACTION_PARAM_NAME(DamageType, 1);
|
|
|
|
ACTION_PARAM_INT(flags, 2);
|
|
|
|
ACTION_PARAM_CLASS(filter, 3);
|
|
|
|
ACTION_PARAM_NAME(species, 4);
|
|
|
|
|
|
|
|
if (self->tracer != NULL)
|
|
|
|
{
|
|
|
|
DoDamage(self->tracer, self, amount, DamageType, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageMaster)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
ACTION_PARAM_INT(amount, 0);
|
|
|
|
ACTION_PARAM_NAME(DamageType, 1);
|
|
|
|
ACTION_PARAM_INT(flags, 2);
|
|
|
|
ACTION_PARAM_CLASS(filter, 3);
|
|
|
|
ACTION_PARAM_NAME(species, 4);
|
|
|
|
|
|
|
|
if (self->master != NULL)
|
|
|
|
{
|
|
|
|
DoDamage(self->master, self, amount, DamageType, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageChildren)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
ACTION_PARAM_INT(amount, 0);
|
|
|
|
ACTION_PARAM_NAME(DamageType, 1);
|
|
|
|
ACTION_PARAM_INT(flags, 2);
|
|
|
|
ACTION_PARAM_CLASS(filter, 3);
|
|
|
|
ACTION_PARAM_NAME(species, 4);
|
|
|
|
|
|
|
|
TThinkerIterator<AActor> it;
|
|
|
|
AActor * mo;
|
|
|
|
|
|
|
|
while ( (mo = it.Next()) )
|
|
|
|
{
|
|
|
|
if (mo->master == self)
|
|
|
|
{
|
|
|
|
DoDamage(mo, self, amount, DamageType, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSiblings)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
ACTION_PARAM_INT(amount, 0);
|
|
|
|
ACTION_PARAM_NAME(DamageType, 1);
|
|
|
|
ACTION_PARAM_INT(flags, 2);
|
|
|
|
ACTION_PARAM_CLASS(filter, 3);
|
|
|
|
ACTION_PARAM_NAME(species, 4);
|
|
|
|
|
|
|
|
TThinkerIterator<AActor> it;
|
|
|
|
AActor * mo;
|
|
|
|
|
|
|
|
if (self->master != NULL)
|
|
|
|
{
|
|
|
|
while ((mo = it.Next()))
|
|
|
|
{
|
|
|
|
if (mo->master == self->master && mo != self)
|
|
|
|
{
|
|
|
|
DoDamage(mo, self, amount, DamageType, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Kill*(damagetype, int flags)
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
enum KILS
|
|
|
|
{
|
|
|
|
KILS_FOILINVUL = 1 << 0,
|
|
|
|
KILS_KILLMISSILES = 1 << 1,
|
|
|
|
KILS_NOMONSTERS = 1 << 2,
|
|
|
|
KILS_FOILBUDDHA = 1 << 3,
|
|
|
|
KILS_EXFILTER = 1 << 4,
|
|
|
|
KILS_EXSPECIES = 1 << 5,
|
|
|
|
KILS_EITHER = 1 << 6,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void DoKill(AActor *killtarget, AActor *self, FName damagetype, int flags, const PClass *filter, FName species)
|
|
|
|
{
|
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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(4);
|
|
|
|
ACTION_PARAM_NAME(damagetype, 0);
|
|
|
|
ACTION_PARAM_INT(flags, 1);
|
|
|
|
ACTION_PARAM_CLASS(filter, 2);
|
|
|
|
ACTION_PARAM_NAME(species, 3);
|
|
|
|
|
|
|
|
if (self->target != NULL)
|
|
|
|
{
|
|
|
|
DoKill(self->target, self, damagetype, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_KillTracer(damagetype, int flags)
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillTracer)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(4);
|
|
|
|
ACTION_PARAM_NAME(damagetype, 0);
|
|
|
|
ACTION_PARAM_INT(flags, 1);
|
|
|
|
ACTION_PARAM_CLASS(filter, 2);
|
|
|
|
ACTION_PARAM_NAME(species, 3);
|
|
|
|
|
|
|
|
if (self->tracer != NULL)
|
|
|
|
{
|
|
|
|
DoKill(self->tracer, self, damagetype, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_KillMaster(damagetype, int flags)
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillMaster)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(4);
|
|
|
|
ACTION_PARAM_NAME(damagetype, 0);
|
|
|
|
ACTION_PARAM_INT(flags, 1);
|
|
|
|
ACTION_PARAM_CLASS(filter, 2);
|
|
|
|
ACTION_PARAM_NAME(species, 3);
|
|
|
|
|
|
|
|
if (self->master != NULL)
|
|
|
|
{
|
|
|
|
DoKill(self->master, self, damagetype, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_KillChildren(damagetype, int flags)
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillChildren)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(4);
|
|
|
|
ACTION_PARAM_NAME(damagetype, 0);
|
|
|
|
ACTION_PARAM_INT(flags, 1);
|
|
|
|
ACTION_PARAM_CLASS(filter, 2);
|
|
|
|
ACTION_PARAM_NAME(species, 3);
|
|
|
|
|
|
|
|
TThinkerIterator<AActor> it;
|
|
|
|
AActor *mo;
|
|
|
|
|
|
|
|
while ( (mo = it.Next()) )
|
|
|
|
{
|
|
|
|
if (mo->master == self)
|
|
|
|
{
|
|
|
|
DoKill(mo, self, damagetype, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_KillSiblings(damagetype, int flags)
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillSiblings)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(4);
|
|
|
|
ACTION_PARAM_NAME(damagetype, 0);
|
|
|
|
ACTION_PARAM_INT(flags, 1);
|
|
|
|
ACTION_PARAM_CLASS(filter, 2);
|
|
|
|
ACTION_PARAM_NAME(species, 3);
|
|
|
|
|
|
|
|
TThinkerIterator<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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// DoRemove
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
enum RMVF_flags
|
|
|
|
{
|
|
|
|
RMVF_MISSILES = 1 << 0,
|
|
|
|
RMVF_NOMONSTERS = 1 << 1,
|
|
|
|
RMVF_MISC = 1 << 2,
|
|
|
|
RMVF_EVERYTHING = 1 << 3,
|
|
|
|
RMVF_EXFILTER = 1 << 4,
|
|
|
|
RMVF_EXSPECIES = 1 << 5,
|
|
|
|
RMVF_EITHER = 1 << 6,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void DoRemove(AActor *removetarget, int flags, const PClass *filter, FName species)
|
|
|
|
{
|
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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_INT(flags, 0);
|
|
|
|
ACTION_PARAM_CLASS(filter, 1);
|
|
|
|
ACTION_PARAM_NAME(species, 2);
|
|
|
|
|
|
|
|
if (self->target != NULL)
|
|
|
|
{
|
|
|
|
DoRemove(self->target, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_RemoveTracer
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveTracer)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_INT(flags, 0);
|
|
|
|
ACTION_PARAM_CLASS(filter, 1);
|
|
|
|
ACTION_PARAM_NAME(species, 2);
|
|
|
|
|
|
|
|
if (self->tracer != NULL)
|
|
|
|
{
|
|
|
|
DoRemove(self->tracer, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_RemoveMaster
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveMaster)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_INT(flags, 0);
|
|
|
|
ACTION_PARAM_CLASS(filter, 1);
|
|
|
|
ACTION_PARAM_NAME(species, 2);
|
|
|
|
|
|
|
|
if (self->master != NULL)
|
|
|
|
{
|
|
|
|
DoRemove(self->master, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_RemoveChildren
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveChildren)
|
|
|
|
{
|
|
|
|
TThinkerIterator<AActor> it;
|
|
|
|
AActor *mo;
|
|
|
|
ACTION_PARAM_START(4);
|
|
|
|
ACTION_PARAM_BOOL(removeall, 0);
|
|
|
|
ACTION_PARAM_INT(flags, 1);
|
|
|
|
ACTION_PARAM_CLASS(filter, 2);
|
|
|
|
ACTION_PARAM_NAME(species, 3);
|
|
|
|
|
|
|
|
|
|
|
|
while ((mo = it.Next()) != NULL)
|
|
|
|
{
|
|
|
|
if (mo->master == self && (mo->health <= 0 || removeall))
|
|
|
|
{
|
|
|
|
DoRemove(mo, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_RemoveSiblings
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveSiblings)
|
|
|
|
{
|
|
|
|
TThinkerIterator<AActor> it;
|
|
|
|
AActor *mo;
|
|
|
|
ACTION_PARAM_START(4);
|
|
|
|
ACTION_PARAM_BOOL(removeall, 0);
|
|
|
|
ACTION_PARAM_INT(flags, 1);
|
|
|
|
ACTION_PARAM_CLASS(filter, 2);
|
|
|
|
ACTION_PARAM_NAME(species, 3);
|
|
|
|
|
|
|
|
if (self->master != NULL)
|
|
|
|
{
|
|
|
|
while ((mo = it.Next()) != NULL)
|
|
|
|
{
|
|
|
|
if (mo->master == self->master && mo != self && (mo->health <= 0 || removeall))
|
|
|
|
{
|
|
|
|
DoRemove(mo, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_Remove
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Remove)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(4);
|
|
|
|
ACTION_PARAM_INT(removee, 0);
|
|
|
|
ACTION_PARAM_INT(flags, 1);
|
|
|
|
ACTION_PARAM_CLASS(filter, 2);
|
|
|
|
ACTION_PARAM_NAME(species, 3);
|
|
|
|
|
|
|
|
AActor *reference = COPY_AAPTR(self, removee);
|
|
|
|
if (reference != NULL)
|
|
|
|
{
|
|
|
|
DoRemove(reference, flags, filter, species);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetTeleFog
|
|
|
|
//
|
|
|
|
// Sets the teleport fog(s) for the calling actor.
|
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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
2015-03-31 07:24:16 +00:00
|
|
|
ACTION_PARAM_CLASS(oldpos, 0);
|
|
|
|
ACTION_PARAM_CLASS(newpos, 1);
|
2015-01-30 01:30:26 +00:00
|
|
|
|
2015-03-31 07:24:16 +00:00
|
|
|
self->TeleFogSourceType = oldpos;
|
|
|
|
self->TeleFogDestType = newpos;
|
2015-01-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SwapTeleFog
|
|
|
|
//
|
|
|
|
// Switches the source and dest telefogs around.
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_SwapTeleFog)
|
|
|
|
{
|
|
|
|
if ((self->TeleFogSourceType != self->TeleFogDestType)) //Does nothing if they're the same.
|
|
|
|
{
|
|
|
|
const PClass *temp = self->TeleFogSourceType;
|
|
|
|
self->TeleFogSourceType = self->TeleFogDestType;
|
|
|
|
self->TeleFogDestType = temp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// A_SetFloatBobPhase
|
|
|
|
//
|
|
|
|
// Changes the FloatBobPhase of the actor.
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetFloatBobPhase)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_INT(bob, 0);
|
|
|
|
|
|
|
|
//Respect float bob phase limits.
|
|
|
|
if (self && (bob >= 0 && bob <= 63))
|
|
|
|
self->FloatBobPhase = bob;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
// A_SetHealth
|
|
|
|
//
|
|
|
|
// Changes the health of the actor.
|
|
|
|
// Takes a pointer as well.
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetHealth)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_INT(health, 0);
|
|
|
|
ACTION_PARAM_INT(ptr, 1);
|
|
|
|
|
|
|
|
AActor *mobj = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
if (!mobj)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
player_t *player = mobj->player;
|
|
|
|
if (player)
|
|
|
|
{
|
|
|
|
if (health <= 0)
|
|
|
|
player->mo->health = mobj->health = player->health = 1; //Copied from the buddha cheat.
|
|
|
|
else
|
|
|
|
player->mo->health = mobj->health = player->health = health;
|
|
|
|
}
|
|
|
|
else if (mobj)
|
|
|
|
{
|
|
|
|
if (health <= 0)
|
|
|
|
mobj->health = 1;
|
|
|
|
else
|
|
|
|
mobj->health = health;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
// A_ResetHealth
|
|
|
|
//
|
|
|
|
// Resets the health of the actor to default, except if their dead.
|
|
|
|
// Takes a pointer.
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ResetHealth)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_INT(ptr, 0);
|
|
|
|
|
|
|
|
AActor *mobj = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
if (!mobj)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
player_t *player = mobj->player;
|
|
|
|
if (player && (player->mo->health > 0))
|
|
|
|
{
|
|
|
|
player->health = player->mo->health = player->mo->GetDefault()->health; //Copied from the resurrect cheat.
|
|
|
|
}
|
|
|
|
else if (mobj && (mobj->health > 0))
|
|
|
|
{
|
|
|
|
mobj->health = mobj->SpawnHealth();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(6);
|
2015-04-30 13:28:41 +00:00
|
|
|
ACTION_PARAM_STATE(high, 0);
|
|
|
|
ACTION_PARAM_STATE(low, 1);
|
|
|
|
ACTION_PARAM_FIXED(offsethigh, 2);
|
|
|
|
ACTION_PARAM_FIXED(offsetlow, 3);
|
|
|
|
ACTION_PARAM_BOOL(includeHeight, 4);
|
|
|
|
ACTION_PARAM_INT(ptr, 5);
|
2015-04-30 13:15:48 +00:00
|
|
|
|
|
|
|
AActor *mobj = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
|
|
|
|
if (!mobj || (mobj == self)) //AAPTR_DEFAULT is completely useless in this regard.
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ACTION_SET_RESULT(false); //No inventory jump chains please.
|
|
|
|
|
2016-01-19 19:15:45 +00:00
|
|
|
if ((high) && (mobj->Z() > ((includeHeight ? self->height : 0) + self->Z() + offsethigh)))
|
2015-04-30 13:15:48 +00:00
|
|
|
ACTION_JUMP(high);
|
2016-01-19 19:15:45 +00:00
|
|
|
else if ((low) && (mobj->Z() + (includeHeight ? mobj->height : 0)) < (self->Z() + offsetlow))
|
2015-04-30 13:15:48 +00:00
|
|
|
ACTION_JUMP(low);
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(2);
|
|
|
|
ACTION_PARAM_NAME(species, 0);
|
|
|
|
ACTION_PARAM_INT(ptr, 1);
|
|
|
|
AActor *mobj = COPY_AAPTR(self, ptr);
|
|
|
|
if (!mobj)
|
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-08-25 13:15:23 +00:00
|
|
|
mobj->Species = species;
|
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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
|
|
|
ACTION_PARAM_INT(level, 0);
|
|
|
|
self->RipperLevel = level;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// 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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
2015-05-03 22:55:01 +00:00
|
|
|
ACTION_PARAM_INT(min, 0);
|
2015-01-30 01:30:26 +00:00
|
|
|
self->RipLevelMin = min;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(1);
|
2015-05-03 22:55:01 +00:00
|
|
|
ACTION_PARAM_INT(max, 0);
|
2015-01-30 01:30:26 +00:00
|
|
|
self->RipLevelMax = max;
|
2015-03-10 15:58:10 +00:00
|
|
|
}
|
2015-09-07 00:57:43 +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
|
|
|
|
{
|
|
|
|
CPXF_ANCESTOR = 1,
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(6);
|
|
|
|
ACTION_PARAM_STATE(jump, 0);
|
|
|
|
ACTION_PARAM_CLASS(classname, 1);
|
|
|
|
ACTION_PARAM_FIXED(distance, 2);
|
|
|
|
ACTION_PARAM_INT(count, 3);
|
|
|
|
ACTION_PARAM_INT(flags, 4);
|
|
|
|
ACTION_PARAM_INT(ptr, 5);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false); //No inventory chain results please.
|
2016-01-07 23:09:02 +00:00
|
|
|
|
|
|
|
if (!jump)
|
|
|
|
{
|
|
|
|
if (!(flags & (CPXF_SETTARGET | CPXF_SETMASTER | CPXF_SETTRACER)))
|
|
|
|
return;
|
|
|
|
}
|
2015-11-28 16:53:34 +00:00
|
|
|
AActor *ref = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
//We need these to check out.
|
2016-01-26 16:00:20 +00:00
|
|
|
if (!ref || !classname || (distance <= 0))
|
2015-11-28 16:53:34 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
//Check inheritance for the classname. Taken partly from CheckClass DECORATE function.
|
|
|
|
if (flags & CPXF_ANCESTOR)
|
|
|
|
{
|
|
|
|
if (!(mo->GetClass()->IsAncestorOf(classname)))
|
|
|
|
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;
|
|
|
|
closer = current; //This actor's closer. Set the new standard.
|
|
|
|
}
|
|
|
|
else if ((flags & CPXF_FARTHEST) && (current > farther))
|
|
|
|
{
|
|
|
|
dist = mo;
|
|
|
|
farther = current;
|
|
|
|
}
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
if (flags & CPXF_SETTARGET) ref->target = dist;
|
|
|
|
if (flags & CPXF_SETMASTER) ref->master = dist;
|
|
|
|
if (flags & CPXF_SETTRACER) ref->tracer = dist;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (flags & CPXF_SETTARGET) self->target = dist;
|
|
|
|
if (flags & CPXF_SETMASTER) self->master = dist;
|
|
|
|
if (flags & CPXF_SETTRACER) self->tracer = dist;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-01-07 23:09:02 +00:00
|
|
|
if (!jump) return;
|
2015-11-28 16:53:34 +00:00
|
|
|
|
|
|
|
if (result)
|
|
|
|
{
|
|
|
|
ACTION_JUMP(jump);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
{
|
|
|
|
CBF_NOLINES = 1 << 0, //Don't check actors.
|
|
|
|
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.
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckBlock)
|
|
|
|
{
|
|
|
|
ACTION_PARAM_START(3);
|
|
|
|
ACTION_PARAM_STATE(block, 0);
|
|
|
|
ACTION_PARAM_INT(flags, 1);
|
|
|
|
ACTION_PARAM_INT(ptr, 2);
|
|
|
|
|
|
|
|
AActor *mobj = COPY_AAPTR(self, ptr);
|
|
|
|
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
//Needs at least one state jump to work.
|
|
|
|
if (!mobj)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Nothing to block it so skip the rest.
|
2015-10-04 21:12:35 +00:00
|
|
|
if (P_TestMobjLocation(mobj)) return;
|
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)
|
|
|
|
return;
|
|
|
|
|
|
|
|
//[MC] Easiest way to tell if an actor is blocking it, use the pointers.
|
2015-10-04 21:12:35 +00:00
|
|
|
if (mobj->BlockingMobj || (!(flags & CBF_NOLINES) && mobj->BlockingLine != NULL))
|
2015-10-04 21:00:40 +00:00
|
|
|
{
|
|
|
|
ACTION_JUMP(block);
|
|
|
|
}
|
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
|
|
|
{
|
2015-11-26 03:49:25 +00:00
|
|
|
ACTION_PARAM_START(5);
|
|
|
|
ACTION_PARAM_ANGLE(offset, 0);
|
|
|
|
ACTION_PARAM_ANGLE(anglelimit, 1);
|
|
|
|
ACTION_PARAM_ANGLE(pitchlimit, 2);
|
|
|
|
ACTION_PARAM_INT(flags, 3);
|
|
|
|
ACTION_PARAM_INT(ptr, 4);
|
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
|
|
|
{
|
|
|
|
ACTION_SET_RESULT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//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
|
|
|
{
|
2015-11-26 03:49:25 +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.
|
|
|
|
const angle_t limit = anglelimit;
|
|
|
|
|
|
|
|
//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;
|
|
|
|
const FVector2 velocity(mobj->velx, mobj->vely);
|
|
|
|
const fixed_t pitch = R_PointToAngle2(0, 0, (fixed_t)velocity.Length(), -mobj->velz);
|
|
|
|
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);
|
|
|
|
|
|
|
|
if (abs(current - pitch) > plimit)
|
|
|
|
{
|
|
|
|
fixed_t max = 0;
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
}
|
2015-11-26 05:17:13 +00:00
|
|
|
}
|
2016-01-20 21:51:11 +00:00
|
|
|
|