2006-02-24 04:48:15 +00:00
|
|
|
/*
|
|
|
|
** p_things.cpp
|
|
|
|
** ACS-accessible thing utilities
|
|
|
|
**
|
|
|
|
**---------------------------------------------------------------------------
|
- Fixed: EV_Teleport() did not set players to their idle state, so if they
were running when the teleported, they would still be running afterward
even though they weren't moving anywhere. Normally, P_XYMovement() does
this when they stop due to friction.
- Fixed: AActor::TakeSpecialDamage() completely bypassed the standard rules
for target switching on actors with MF5_NODAMAGE set.
- Changed the return values of the ACS spawn, spawnspot, and spawnspotfacing
commands to be the total count of things spawned, rather than a pretty
much useless reference to the actor spawned at the last map spot.
- Fixed: DLevelScript::DoSpawn() takes a byte angle, but DoSpawnSpotFacing()
passed it a full-length angle.
- Fixed: When MF_SKULLFLY is removed because an actor slams into something,
it was set to a see or spawn state, resetting its tic count and bypassing
the effectiveness of the MF2_DORMANT flag. While I was at it, I decided
dormant skulls shouldn't do slamming damage, either.
- Fixed: P_Thing_Spawn() returned success only if all thing instances were
successfully spawned. As long as at least one thing was spawned, it should
be considered a success.
- Fixed: Flipped single rotation sprites were only flipped every other 22.5
degree interval.
SVN r484 (trunk)
2007-02-14 22:47:01 +00:00
|
|
|
** Copyright 1998-2007 Randy Heit
|
2006-02-24 04:48:15 +00:00
|
|
|
** 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.
|
|
|
|
**
|
|
|
|
** 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 "doomtype.h"
|
|
|
|
#include "p_local.h"
|
|
|
|
#include "info.h"
|
|
|
|
#include "s_sound.h"
|
|
|
|
#include "tables.h"
|
|
|
|
#include "doomstat.h"
|
|
|
|
#include "m_random.h"
|
|
|
|
#include "c_console.h"
|
|
|
|
#include "c_dispatch.h"
|
|
|
|
#include "a_sharedglobal.h"
|
|
|
|
#include "gi.h"
|
|
|
|
#include "templates.h"
|
2008-09-14 23:54:38 +00:00
|
|
|
#include "g_level.h"
|
2015-04-03 22:39:09 +00:00
|
|
|
#include "v_text.h"
|
|
|
|
#include "i_system.h"
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2012-11-09 23:53:58 +00:00
|
|
|
// Set of spawnable things for the Thing_Spawn and Thing_Projectile specials.
|
2015-04-04 22:31:15 +00:00
|
|
|
FClassMap SpawnableThings;
|
2015-04-03 22:39:09 +00:00
|
|
|
|
2006-02-24 04:48:15 +00:00
|
|
|
static FRandom pr_leadtarget ("LeadTarget");
|
|
|
|
|
2006-10-27 03:03:34 +00:00
|
|
|
bool P_Thing_Spawn (int tid, AActor *source, int type, angle_t angle, bool fog, int newtid)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
int rtn = 0;
|
2010-03-24 02:49:37 +00:00
|
|
|
PClassActor *kind;
|
2006-02-24 04:48:15 +00:00
|
|
|
AActor *spot, *mobj;
|
|
|
|
FActorIterator iterator (tid);
|
|
|
|
|
2012-11-09 23:25:56 +00:00
|
|
|
kind = P_GetSpawnableType(type);
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2012-11-09 23:25:56 +00:00
|
|
|
if (kind == NULL)
|
2006-02-24 04:48:15 +00:00
|
|
|
return false;
|
|
|
|
|
2006-07-13 03:34:50 +00:00
|
|
|
// Handle decorate replacements.
|
2010-03-24 02:49:37 +00:00
|
|
|
kind = kind->GetReplacement();
|
2006-07-13 03:34:50 +00:00
|
|
|
|
2010-03-24 02:49:37 +00:00
|
|
|
if ((GetDefaultByType(kind)->flags3 & MF3_ISMONSTER) &&
|
2009-02-03 19:11:43 +00:00
|
|
|
((dmflags & DF_NO_MONSTERS) || (level.flags2 & LEVEL2_NOMONSTERS)))
|
2006-02-24 04:48:15 +00:00
|
|
|
return false;
|
|
|
|
|
2006-10-27 03:03:34 +00:00
|
|
|
if (tid == 0)
|
|
|
|
{
|
|
|
|
spot = source;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
spot = iterator.Next();
|
|
|
|
}
|
|
|
|
while (spot != NULL)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2006-07-16 09:10:45 +00:00
|
|
|
mobj = Spawn (kind, spot->x, spot->y, spot->z, ALLOW_REPLACE);
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
if (mobj != NULL)
|
|
|
|
{
|
2015-04-04 16:40:43 +00:00
|
|
|
ActorFlags2 oldFlags2 = mobj->flags2;
|
2006-02-24 04:48:15 +00:00
|
|
|
mobj->flags2 |= MF2_PASSMOBJ;
|
|
|
|
if (P_TestMobjLocation (mobj))
|
|
|
|
{
|
|
|
|
rtn++;
|
|
|
|
mobj->angle = (angle != ANGLE_MAX ? angle : spot->angle);
|
|
|
|
if (fog)
|
|
|
|
{
|
2015-04-07 16:14:02 +00:00
|
|
|
P_SpawnTeleportFog(mobj, spot->x, spot->y, spot->z + TELEFOGHEIGHT, false, true);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
if (mobj->flags & MF_SPECIAL)
|
|
|
|
mobj->flags |= MF_DROPPED; // Don't respawn
|
|
|
|
mobj->tid = newtid;
|
|
|
|
mobj->AddToHash ();
|
|
|
|
mobj->flags2 = oldFlags2;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// If this is a monster, subtract it from the total monster
|
|
|
|
// count, because it already added to it during spawning.
|
2010-09-19 00:06:45 +00:00
|
|
|
mobj->ClearCounters();
|
2006-02-24 04:48:15 +00:00
|
|
|
mobj->Destroy ();
|
|
|
|
}
|
|
|
|
}
|
2006-10-27 03:03:34 +00:00
|
|
|
spot = iterator.Next();
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return rtn != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// [BC] Added
|
|
|
|
// [RH] Fixed
|
|
|
|
|
2006-10-27 03:03:34 +00:00
|
|
|
bool P_MoveThing(AActor *source, fixed_t x, fixed_t y, fixed_t z, bool fog)
|
2006-06-18 15:49:00 +00:00
|
|
|
{
|
|
|
|
fixed_t oldx, oldy, oldz;
|
|
|
|
|
|
|
|
oldx = source->x;
|
|
|
|
oldy = source->y;
|
|
|
|
oldz = source->z;
|
|
|
|
|
|
|
|
source->SetOrigin (x, y, z);
|
|
|
|
if (P_TestMobjLocation (source))
|
|
|
|
{
|
|
|
|
if (fog)
|
|
|
|
{
|
2015-04-07 16:14:02 +00:00
|
|
|
P_SpawnTeleportFog(source, x, y, z, false, true);
|
|
|
|
P_SpawnTeleportFog(source, oldx, oldy, oldz, true, true);
|
2006-06-18 15:49:00 +00:00
|
|
|
}
|
2009-12-30 18:53:14 +00:00
|
|
|
source->PrevX = x;
|
|
|
|
source->PrevY = y;
|
|
|
|
source->PrevZ = z;
|
2012-09-16 04:59:01 +00:00
|
|
|
if (source == players[consoleplayer].camera)
|
|
|
|
{
|
|
|
|
R_ResetViewInterpolation();
|
|
|
|
}
|
2006-06-18 15:49:00 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
source->SetOrigin (oldx, oldy, oldz);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-10-27 03:03:34 +00:00
|
|
|
bool P_Thing_Move (int tid, AActor *source, int mapspot, bool fog)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2006-10-27 03:03:34 +00:00
|
|
|
AActor *target;
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2006-10-27 03:03:34 +00:00
|
|
|
if (tid != 0)
|
|
|
|
{
|
|
|
|
FActorIterator iterator1(tid);
|
|
|
|
source = iterator1.Next();
|
|
|
|
}
|
|
|
|
FActorIterator iterator2 (mapspot);
|
2006-02-24 04:48:15 +00:00
|
|
|
target = iterator2.Next ();
|
|
|
|
|
|
|
|
if (source != NULL && target != NULL)
|
|
|
|
{
|
2006-06-18 15:49:00 +00:00
|
|
|
return P_MoveThing(source, target->x, target->y, target->z, fog);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2008-12-18 06:22:18 +00:00
|
|
|
bool P_Thing_Projectile (int tid, AActor *source, int type, const char *type_name, angle_t angle,
|
2006-02-24 04:48:15 +00:00
|
|
|
fixed_t speed, fixed_t vspeed, int dest, AActor *forcedest, int gravity, int newtid,
|
|
|
|
bool leadTarget)
|
|
|
|
{
|
|
|
|
int rtn = 0;
|
2010-03-24 02:49:37 +00:00
|
|
|
PClassActor *kind;
|
2006-02-24 04:48:15 +00:00
|
|
|
AActor *spot, *mobj, *targ = forcedest;
|
|
|
|
FActorIterator iterator (tid);
|
2007-02-28 16:49:19 +00:00
|
|
|
double fspeed = speed;
|
2006-02-24 04:48:15 +00:00
|
|
|
int defflags3;
|
|
|
|
|
2006-06-03 12:30:11 +00:00
|
|
|
if (type_name == NULL)
|
|
|
|
{
|
2012-11-09 23:25:56 +00:00
|
|
|
kind = P_GetSpawnableType(type);
|
2006-06-03 12:30:11 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2013-06-07 03:31:30 +00:00
|
|
|
kind = PClass::FindActor(type_name);
|
2012-11-09 23:25:56 +00:00
|
|
|
}
|
2013-06-07 03:31:30 +00:00
|
|
|
if (kind == NULL)
|
2012-11-09 23:25:56 +00:00
|
|
|
{
|
|
|
|
return false;
|
2006-06-03 12:30:11 +00:00
|
|
|
}
|
2006-07-13 03:34:50 +00:00
|
|
|
|
|
|
|
// Handle decorate replacements.
|
2010-03-24 02:49:37 +00:00
|
|
|
kind = kind->GetReplacement();
|
2006-07-13 03:34:50 +00:00
|
|
|
|
2010-03-24 02:49:37 +00:00
|
|
|
defflags3 = GetDefaultByType(kind)->flags3;
|
2007-11-08 09:22:06 +00:00
|
|
|
if ((defflags3 & MF3_ISMONSTER) &&
|
2009-02-03 19:11:43 +00:00
|
|
|
((dmflags & DF_NO_MONSTERS) || (level.flags2 & LEVEL2_NOMONSTERS)))
|
2006-02-24 04:48:15 +00:00
|
|
|
return false;
|
|
|
|
|
2006-10-27 03:03:34 +00:00
|
|
|
if (tid == 0)
|
|
|
|
{
|
|
|
|
spot = source;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
spot = iterator.Next();
|
|
|
|
}
|
|
|
|
while (spot != NULL)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
FActorIterator tit (dest);
|
|
|
|
|
|
|
|
if (dest == 0 || (targ = tit.Next()))
|
|
|
|
{
|
|
|
|
do
|
|
|
|
{
|
|
|
|
fixed_t z = spot->z;
|
|
|
|
if (defflags3 & MF3_FLOORHUGGER)
|
|
|
|
{
|
|
|
|
z = ONFLOORZ;
|
|
|
|
}
|
|
|
|
else if (defflags3 & MF3_CEILINGHUGGER)
|
|
|
|
{
|
|
|
|
z = ONCEILINGZ;
|
|
|
|
}
|
|
|
|
else if (z != ONFLOORZ)
|
|
|
|
{
|
|
|
|
z -= spot->floorclip;
|
|
|
|
}
|
2006-07-16 09:10:45 +00:00
|
|
|
mobj = Spawn (kind, spot->x, spot->y, z, ALLOW_REPLACE);
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
if (mobj)
|
|
|
|
{
|
|
|
|
mobj->tid = newtid;
|
|
|
|
mobj->AddToHash ();
|
2006-11-25 12:25:05 +00:00
|
|
|
P_PlaySpawnSound(mobj, spot);
|
2006-02-24 04:48:15 +00:00
|
|
|
if (gravity)
|
|
|
|
{
|
|
|
|
mobj->flags &= ~MF_NOGRAVITY;
|
|
|
|
if (!(mobj->flags3 & MF3_ISMONSTER) && gravity == 1)
|
|
|
|
{
|
2007-01-20 14:27:44 +00:00
|
|
|
mobj->gravity = FRACUNIT/8;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mobj->flags |= MF_NOGRAVITY;
|
|
|
|
}
|
|
|
|
mobj->target = spot;
|
|
|
|
|
|
|
|
if (targ != NULL)
|
|
|
|
{
|
|
|
|
fixed_t spot[3] = { targ->x, targ->y, targ->z+targ->height/2 };
|
2007-01-19 02:00:39 +00:00
|
|
|
FVector3 aim(float(spot[0] - mobj->x), float(spot[1] - mobj->y), float(spot[2] - mobj->z));
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2009-06-30 20:57:51 +00:00
|
|
|
if (leadTarget && speed > 0 && (targ->velx | targ->vely | targ->velz))
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
// Aiming at the target's position some time in the future
|
|
|
|
// is basically just an application of the law of sines:
|
|
|
|
// a/sin(A) = b/sin(B)
|
|
|
|
// Thanks to all those on the notgod phorum for helping me
|
|
|
|
// with the math. I don't think I would have thought of using
|
|
|
|
// trig alone had I been left to solve it by myself.
|
|
|
|
|
2009-06-30 20:57:51 +00:00
|
|
|
FVector3 tvel(targ->velx, targ->vely, targ->velz);
|
2006-02-24 04:48:15 +00:00
|
|
|
if (!(targ->flags & MF_NOGRAVITY) && targ->waterlevel < 3)
|
|
|
|
{ // If the target is subject to gravity and not underwater,
|
|
|
|
// assume that it isn't moving vertically. Thanks to gravity,
|
|
|
|
// even if we did consider the vertical component of the target's
|
|
|
|
// velocity, we would still miss more often than not.
|
2007-01-19 02:00:39 +00:00
|
|
|
tvel.Z = 0.0;
|
2009-06-30 20:57:51 +00:00
|
|
|
if ((targ->velx | targ->vely) == 0)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
goto nolead;
|
|
|
|
}
|
|
|
|
}
|
2007-02-28 17:38:56 +00:00
|
|
|
double dist = aim.Length();
|
2007-01-19 02:00:39 +00:00
|
|
|
double targspeed = tvel.Length();
|
|
|
|
double ydotx = -aim | tvel;
|
2006-02-24 04:48:15 +00:00
|
|
|
double a = acos (clamp (ydotx / targspeed / dist, -1.0, 1.0));
|
|
|
|
double multiplier = double(pr_leadtarget.Random2())*0.1/255+1.1;
|
2008-12-18 06:22:18 +00:00
|
|
|
double sinb = -clamp (targspeed*multiplier * sin(a) / fspeed, -1.0, 1.0);
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
// Use the cross product of two of the triangle's sides to get a
|
|
|
|
// rotation vector.
|
2007-01-19 02:00:39 +00:00
|
|
|
FVector3 rv(tvel ^ aim);
|
2006-02-24 04:48:15 +00:00
|
|
|
// The vector must be normalized.
|
2007-01-19 02:00:39 +00:00
|
|
|
rv.MakeUnit();
|
2006-02-24 04:48:15 +00:00
|
|
|
// Now combine the rotation vector with angle b to get a rotation matrix.
|
2007-01-19 02:00:39 +00:00
|
|
|
FMatrix3x3 rm(rv, cos(asin(sinb)), sinb);
|
2006-02-24 04:48:15 +00:00
|
|
|
// And multiply the original aim vector with the matrix to get a
|
|
|
|
// new aim vector that leads the target.
|
2007-01-19 02:00:39 +00:00
|
|
|
FVector3 aimvec = rm * aim;
|
2006-02-24 04:48:15 +00:00
|
|
|
// And make the projectile follow that vector at the desired speed.
|
|
|
|
double aimscale = fspeed / dist;
|
2009-06-30 20:57:51 +00:00
|
|
|
mobj->velx = fixed_t (aimvec[0] * aimscale);
|
|
|
|
mobj->vely = fixed_t (aimvec[1] * aimscale);
|
|
|
|
mobj->velz = fixed_t (aimvec[2] * aimscale);
|
|
|
|
mobj->angle = R_PointToAngle2 (0, 0, mobj->velx, mobj->vely);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2007-02-28 16:49:19 +00:00
|
|
|
nolead: mobj->angle = R_PointToAngle2 (mobj->x, mobj->y, targ->x, targ->y);
|
2007-01-19 02:00:39 +00:00
|
|
|
aim.Resize (fspeed);
|
2009-06-30 20:57:51 +00:00
|
|
|
mobj->velx = fixed_t(aim[0]);
|
|
|
|
mobj->vely = fixed_t(aim[1]);
|
|
|
|
mobj->velz = fixed_t(aim[2]);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
if (mobj->flags2 & MF2_SEEKERMISSILE)
|
|
|
|
{
|
|
|
|
mobj->tracer = targ;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mobj->angle = angle;
|
2009-06-30 20:57:51 +00:00
|
|
|
mobj->velx = FixedMul (speed, finecosine[angle>>ANGLETOFINESHIFT]);
|
|
|
|
mobj->vely = FixedMul (speed, finesine[angle>>ANGLETOFINESHIFT]);
|
|
|
|
mobj->velz = vspeed;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
// Set the missile's speed to reflect the speed it was spawned at.
|
|
|
|
if (mobj->flags & MF_MISSILE)
|
|
|
|
{
|
2009-06-30 20:57:51 +00:00
|
|
|
mobj->Speed = fixed_t (sqrt (double(mobj->velx)*mobj->velx + double(mobj->vely)*mobj->vely + double(mobj->velz)*mobj->velz));
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
// Hugger missiles don't have any vertical velocity
|
|
|
|
if (mobj->flags3 & (MF3_FLOORHUGGER|MF3_CEILINGHUGGER))
|
|
|
|
{
|
2009-06-30 20:57:51 +00:00
|
|
|
mobj->velz = 0;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
if (mobj->flags & MF_SPECIAL)
|
|
|
|
{
|
|
|
|
mobj->flags |= MF_DROPPED;
|
|
|
|
}
|
|
|
|
if (mobj->flags & MF_MISSILE)
|
|
|
|
{
|
2013-03-21 22:21:54 +00:00
|
|
|
if (P_CheckMissileSpawn (mobj, spot->radius))
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
rtn = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (!P_TestMobjLocation (mobj))
|
|
|
|
{
|
|
|
|
// If this is a monster, subtract it from the total monster
|
|
|
|
// count, because it already added to it during spawning.
|
2010-09-19 00:06:45 +00:00
|
|
|
mobj->ClearCounters();
|
2006-02-24 04:48:15 +00:00
|
|
|
mobj->Destroy ();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// It spawned fine.
|
|
|
|
rtn = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (dest != 0 && (targ = tit.Next()));
|
2006-10-27 03:03:34 +00:00
|
|
|
}
|
|
|
|
spot = iterator.Next();
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return rtn != 0;
|
|
|
|
}
|
|
|
|
|
- Moved the implementation for the Thing_Damage special into another function
so that I can create the ACS function Thing_Damage2. It's exactly the same as
Thing_Damage, except the damage type is specified by name. When I did this,
I noticed that it didn't do anything useful for a TID of 0, so I made it
affect the activator in that case.
- Added a new SetActorState ACS function:
int SetActorState (int tid, str statename, optional bool exact);
If tid is 0, it affects the script activator, otherwise it affects all the
matching actors. Statename is the name of the state you want to put the
actor in. The final parameter, exact, specifies whether or not partial
state name matches are accepted. If you don't specify it or set it to
false, if you try to do something like:
SetActorState (0, "Foo.Bar");
And the actor has a Foo state but no Foo.Bar state, it will enter the Foo
state. If you set exact to true:
SetActorState (0, "Foo.Bar", true);
Then the actor must have a Foo.Bar state, or it will not change state at
all, even if it has a Foo state.
The return value for this function is the number of actors that successfully
changed state. Note that you should refrain from using this function to
enter special states such as Death, or unpredictable results could occur.
SVN r505 (trunk)
2007-03-23 22:26:14 +00:00
|
|
|
int P_Thing_Damage (int tid, AActor *whofor0, int amount, FName type)
|
|
|
|
{
|
|
|
|
FActorIterator iterator (tid);
|
|
|
|
int count = 0;
|
|
|
|
AActor *actor;
|
|
|
|
|
|
|
|
actor = (tid == 0 ? whofor0 : iterator.Next());
|
|
|
|
while (actor)
|
|
|
|
{
|
|
|
|
AActor *next = tid == 0 ? NULL : iterator.Next ();
|
|
|
|
if (actor->flags & MF_SHOOTABLE)
|
|
|
|
{
|
|
|
|
if (amount > 0)
|
|
|
|
{
|
|
|
|
P_DamageMobj (actor, NULL, whofor0, amount, type);
|
|
|
|
}
|
2009-07-04 18:17:44 +00:00
|
|
|
else if (actor->health < actor->SpawnHealth())
|
- Moved the implementation for the Thing_Damage special into another function
so that I can create the ACS function Thing_Damage2. It's exactly the same as
Thing_Damage, except the damage type is specified by name. When I did this,
I noticed that it didn't do anything useful for a TID of 0, so I made it
affect the activator in that case.
- Added a new SetActorState ACS function:
int SetActorState (int tid, str statename, optional bool exact);
If tid is 0, it affects the script activator, otherwise it affects all the
matching actors. Statename is the name of the state you want to put the
actor in. The final parameter, exact, specifies whether or not partial
state name matches are accepted. If you don't specify it or set it to
false, if you try to do something like:
SetActorState (0, "Foo.Bar");
And the actor has a Foo state but no Foo.Bar state, it will enter the Foo
state. If you set exact to true:
SetActorState (0, "Foo.Bar", true);
Then the actor must have a Foo.Bar state, or it will not change state at
all, even if it has a Foo state.
The return value for this function is the number of actors that successfully
changed state. Note that you should refrain from using this function to
enter special states such as Death, or unpredictable results could occur.
SVN r505 (trunk)
2007-03-23 22:26:14 +00:00
|
|
|
{
|
|
|
|
actor->health -= amount;
|
2009-07-04 18:17:44 +00:00
|
|
|
if (actor->health > actor->SpawnHealth())
|
- Moved the implementation for the Thing_Damage special into another function
so that I can create the ACS function Thing_Damage2. It's exactly the same as
Thing_Damage, except the damage type is specified by name. When I did this,
I noticed that it didn't do anything useful for a TID of 0, so I made it
affect the activator in that case.
- Added a new SetActorState ACS function:
int SetActorState (int tid, str statename, optional bool exact);
If tid is 0, it affects the script activator, otherwise it affects all the
matching actors. Statename is the name of the state you want to put the
actor in. The final parameter, exact, specifies whether or not partial
state name matches are accepted. If you don't specify it or set it to
false, if you try to do something like:
SetActorState (0, "Foo.Bar");
And the actor has a Foo state but no Foo.Bar state, it will enter the Foo
state. If you set exact to true:
SetActorState (0, "Foo.Bar", true);
Then the actor must have a Foo.Bar state, or it will not change state at
all, even if it has a Foo state.
The return value for this function is the number of actors that successfully
changed state. Note that you should refrain from using this function to
enter special states such as Death, or unpredictable results could occur.
SVN r505 (trunk)
2007-03-23 22:26:14 +00:00
|
|
|
{
|
2009-07-04 18:17:44 +00:00
|
|
|
actor->health = actor->SpawnHealth();
|
- Moved the implementation for the Thing_Damage special into another function
so that I can create the ACS function Thing_Damage2. It's exactly the same as
Thing_Damage, except the damage type is specified by name. When I did this,
I noticed that it didn't do anything useful for a TID of 0, so I made it
affect the activator in that case.
- Added a new SetActorState ACS function:
int SetActorState (int tid, str statename, optional bool exact);
If tid is 0, it affects the script activator, otherwise it affects all the
matching actors. Statename is the name of the state you want to put the
actor in. The final parameter, exact, specifies whether or not partial
state name matches are accepted. If you don't specify it or set it to
false, if you try to do something like:
SetActorState (0, "Foo.Bar");
And the actor has a Foo state but no Foo.Bar state, it will enter the Foo
state. If you set exact to true:
SetActorState (0, "Foo.Bar", true);
Then the actor must have a Foo.Bar state, or it will not change state at
all, even if it has a Foo state.
The return value for this function is the number of actors that successfully
changed state. Note that you should refrain from using this function to
enter special states such as Death, or unpredictable results could occur.
SVN r505 (trunk)
2007-03-23 22:26:14 +00:00
|
|
|
}
|
|
|
|
if (actor->player != NULL)
|
|
|
|
{
|
|
|
|
actor->player->health = actor->health;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
actor = next;
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2008-12-06 10:22:37 +00:00
|
|
|
void P_RemoveThing(AActor * actor)
|
|
|
|
{
|
|
|
|
// Don't remove live players.
|
|
|
|
if (actor->player == NULL || actor != actor->player->mo)
|
|
|
|
{
|
2014-10-29 07:54:14 +00:00
|
|
|
// Don't also remove owned inventory items
|
2014-10-29 09:40:08 +00:00
|
|
|
if (actor->IsKindOf(RUNTIME_CLASS(AInventory)) && static_cast<AInventory*>(actor)->Owner != NULL) return;
|
2014-10-29 07:54:14 +00:00
|
|
|
|
2008-12-06 10:22:37 +00:00
|
|
|
// be friendly to the level statistics. ;)
|
2010-09-19 00:06:45 +00:00
|
|
|
actor->ClearCounters();
|
2008-12-06 10:22:37 +00:00
|
|
|
actor->Destroy ();
|
|
|
|
}
|
2014-10-29 07:54:14 +00:00
|
|
|
|
2008-12-06 10:22:37 +00:00
|
|
|
}
|
|
|
|
|
2014-11-27 21:12:33 +00:00
|
|
|
bool P_Thing_Raise(AActor *thing, AActor *raiser)
|
2009-07-13 22:07:18 +00:00
|
|
|
{
|
2014-05-05 09:24:20 +00:00
|
|
|
FState * RaiseState = thing->GetRaiseState();
|
2009-07-13 22:07:18 +00:00
|
|
|
if (RaiseState == NULL)
|
2014-05-05 09:24:20 +00:00
|
|
|
{
|
2009-07-13 22:07:18 +00:00
|
|
|
return true; // monster doesn't have a raise state
|
2014-05-05 09:24:20 +00:00
|
|
|
}
|
2009-07-13 22:07:18 +00:00
|
|
|
|
|
|
|
AActor *info = thing->GetDefault ();
|
|
|
|
|
|
|
|
thing->velx = thing->vely = 0;
|
|
|
|
|
|
|
|
// [RH] Check against real height and radius
|
|
|
|
fixed_t oldheight = thing->height;
|
|
|
|
fixed_t oldradius = thing->radius;
|
2015-04-04 16:40:43 +00:00
|
|
|
ActorFlags oldflags = thing->flags;
|
2009-07-13 22:07:18 +00:00
|
|
|
|
|
|
|
thing->flags |= MF_SOLID;
|
|
|
|
thing->height = info->height; // [RH] Use real height
|
|
|
|
thing->radius = info->radius; // [RH] Use real radius
|
|
|
|
if (!P_CheckPosition (thing, thing->x, thing->y))
|
|
|
|
{
|
|
|
|
thing->flags = oldflags;
|
|
|
|
thing->radius = oldradius;
|
|
|
|
thing->height = oldheight;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-05-05 09:24:20 +00:00
|
|
|
|
2009-07-13 22:07:18 +00:00
|
|
|
S_Sound (thing, CHAN_BODY, "vile/raise", 1, ATTN_IDLE);
|
2014-05-05 09:24:20 +00:00
|
|
|
|
|
|
|
thing->Revive();
|
|
|
|
|
2014-11-27 21:12:33 +00:00
|
|
|
if (raiser != NULL)
|
|
|
|
{
|
|
|
|
// Let's copy the friendliness of the one who raised it.
|
|
|
|
thing->CopyFriendliness(raiser, false);
|
|
|
|
}
|
|
|
|
|
2009-07-13 22:07:18 +00:00
|
|
|
thing->SetState (RaiseState);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-09-23 10:10:39 +00:00
|
|
|
bool P_Thing_CanRaise(AActor *thing)
|
|
|
|
{
|
|
|
|
FState * RaiseState = thing->GetRaiseState();
|
|
|
|
if (RaiseState == NULL)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
AActor *info = thing->GetDefault();
|
|
|
|
|
|
|
|
// Check against real height and radius
|
2015-04-04 16:40:43 +00:00
|
|
|
ActorFlags oldflags = thing->flags;
|
2014-09-23 10:10:39 +00:00
|
|
|
fixed_t oldheight = thing->height;
|
|
|
|
fixed_t oldradius = thing->radius;
|
|
|
|
|
|
|
|
thing->flags |= MF_SOLID;
|
|
|
|
thing->height = info->height;
|
|
|
|
thing->radius = info->radius;
|
|
|
|
|
|
|
|
bool check = P_CheckPosition (thing, thing->x, thing->y);
|
|
|
|
|
|
|
|
// Restore checked properties
|
|
|
|
thing->flags = oldflags;
|
|
|
|
thing->radius = oldradius;
|
|
|
|
thing->height = oldheight;
|
|
|
|
|
|
|
|
if (!check)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2009-09-08 03:19:57 +00:00
|
|
|
void P_Thing_SetVelocity(AActor *actor, fixed_t vx, fixed_t vy, fixed_t vz, bool add, bool setbob)
|
2009-09-06 08:27:18 +00:00
|
|
|
{
|
|
|
|
if (actor != NULL)
|
|
|
|
{
|
|
|
|
if (!add)
|
|
|
|
{
|
|
|
|
actor->velx = actor->vely = actor->velz = 0;
|
|
|
|
if (actor->player != NULL) actor->player->velx = actor->player->vely = 0;
|
|
|
|
}
|
|
|
|
actor->velx += vx;
|
|
|
|
actor->vely += vy;
|
|
|
|
actor->velz += vz;
|
2009-09-08 03:19:57 +00:00
|
|
|
if (setbob && actor->player != NULL)
|
2009-09-06 08:27:18 +00:00
|
|
|
{
|
|
|
|
actor->player->velx += vx;
|
|
|
|
actor->player->vely += vy;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-07 03:31:30 +00:00
|
|
|
PClassActor *P_GetSpawnableType(int spawnnum)
|
2012-11-09 23:25:56 +00:00
|
|
|
{
|
|
|
|
if (spawnnum < 0)
|
|
|
|
{ // A named arg from a UDMF map
|
|
|
|
FName spawnname = FName(ENamedName(-spawnnum));
|
|
|
|
if (spawnname.IsValidName())
|
|
|
|
{
|
2013-06-07 03:31:30 +00:00
|
|
|
return PClass::FindActor(spawnname);
|
2012-11-09 23:25:56 +00:00
|
|
|
}
|
|
|
|
}
|
2012-11-09 23:53:58 +00:00
|
|
|
else
|
2012-11-09 23:25:56 +00:00
|
|
|
{ // A numbered arg from a Hexen or UDMF map
|
2013-06-07 03:31:30 +00:00
|
|
|
PClassActor **type = SpawnableThings.CheckKey(spawnnum);
|
2012-11-09 23:53:58 +00:00
|
|
|
if (type != NULL)
|
|
|
|
{
|
|
|
|
return *type;
|
|
|
|
}
|
2012-11-09 23:25:56 +00:00
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
2008-12-06 10:22:37 +00:00
|
|
|
|
2015-04-04 22:31:15 +00:00
|
|
|
struct MapinfoSpawnItem
|
|
|
|
{
|
|
|
|
FName classname; // DECORATE is read after MAPINFO so we do not have the actual classes available here yet.
|
|
|
|
// These are for error reporting. We must store the file information because it's no longer available when these items get resolved.
|
|
|
|
FString filename;
|
|
|
|
int linenum;
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef TMap<int, MapinfoSpawnItem> SpawnMap;
|
|
|
|
static SpawnMap SpawnablesFromMapinfo;
|
|
|
|
static SpawnMap ConversationIDsFromMapinfo;
|
2008-12-06 10:22:37 +00:00
|
|
|
|
2012-11-09 23:53:58 +00:00
|
|
|
static int STACK_ARGS SpawnableSort(const void *a, const void *b)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2015-04-04 22:31:15 +00:00
|
|
|
return (*((FClassMap::Pair **)a))->Key - (*((FClassMap::Pair **)b))->Key;
|
2012-11-09 23:53:58 +00:00
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2015-04-04 22:31:15 +00:00
|
|
|
static void DumpClassMap(FClassMap &themap)
|
2012-11-09 23:53:58 +00:00
|
|
|
{
|
2015-04-04 22:31:15 +00:00
|
|
|
FClassMap::Iterator it(themap);
|
|
|
|
FClassMap::Pair *pair, **allpairs;
|
2012-11-09 23:53:58 +00:00
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
// Sort into numerical order, since their arrangement in the map can
|
|
|
|
// be in an unspecified order.
|
2015-04-04 22:31:15 +00:00
|
|
|
allpairs = new FClassMap::Pair *[themap.CountUsed()];
|
2012-11-09 23:53:58 +00:00
|
|
|
while (it.NextPair(pair))
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2012-11-09 23:53:58 +00:00
|
|
|
allpairs[i++] = pair;
|
|
|
|
}
|
|
|
|
qsort(allpairs, i, sizeof(*allpairs), SpawnableSort);
|
|
|
|
for (int j = 0; j < i; ++j)
|
|
|
|
{
|
|
|
|
pair = allpairs[j];
|
|
|
|
Printf ("%d %s\n", pair->Key, pair->Value->TypeName.GetChars());
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2012-11-09 23:53:58 +00:00
|
|
|
delete[] allpairs;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2008-12-06 10:22:37 +00:00
|
|
|
|
2015-04-04 22:31:15 +00:00
|
|
|
CCMD(dumpspawnables)
|
|
|
|
{
|
|
|
|
DumpClassMap(SpawnableThings);
|
|
|
|
}
|
|
|
|
|
|
|
|
CCMD (dumpconversationids)
|
|
|
|
{
|
|
|
|
DumpClassMap(StrifeTypes);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void ParseSpawnMap(FScanner &sc, SpawnMap & themap, const char *descript)
|
2015-04-03 22:39:09 +00:00
|
|
|
{
|
|
|
|
TMap<int, bool> defined;
|
|
|
|
int error = 0;
|
|
|
|
|
|
|
|
MapinfoSpawnItem editem;
|
|
|
|
|
|
|
|
editem.filename = sc.ScriptName;
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
if (sc.CheckString("}")) return;
|
|
|
|
else if (sc.CheckNumber())
|
|
|
|
{
|
|
|
|
int ednum = sc.Number;
|
|
|
|
sc.MustGetStringName("=");
|
|
|
|
sc.MustGetString();
|
|
|
|
|
|
|
|
bool *def = defined.CheckKey(ednum);
|
|
|
|
if (def != NULL)
|
|
|
|
{
|
2015-04-04 22:31:15 +00:00
|
|
|
sc.ScriptMessage("%s %d defined more than once", descript, ednum);
|
2015-04-03 22:39:09 +00:00
|
|
|
error++;
|
|
|
|
}
|
|
|
|
else if (ednum < 0)
|
|
|
|
{
|
2015-04-04 22:31:15 +00:00
|
|
|
sc.ScriptMessage("%s must be positive, got %d", descript, ednum);
|
2015-04-03 22:39:09 +00:00
|
|
|
error++;
|
|
|
|
}
|
|
|
|
defined[ednum] = true;
|
|
|
|
editem.classname = sc.String;
|
|
|
|
|
2015-04-04 22:31:15 +00:00
|
|
|
themap.Insert(ednum, editem);
|
2015-04-03 22:39:09 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sc.ScriptError("Number expected");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (error > 0)
|
|
|
|
{
|
2015-04-04 22:31:15 +00:00
|
|
|
sc.ScriptError("%d errors encountered in %s definition", error, descript);
|
2015-04-03 22:39:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-04 22:31:15 +00:00
|
|
|
void FMapInfoParser::ParseSpawnNums()
|
|
|
|
{
|
|
|
|
ParseOpenBrace();
|
|
|
|
ParseSpawnMap(sc, SpawnablesFromMapinfo, "Spawn number");
|
|
|
|
}
|
|
|
|
|
|
|
|
void FMapInfoParser::ParseConversationIDs()
|
2015-04-03 22:39:09 +00:00
|
|
|
{
|
2015-04-04 22:31:15 +00:00
|
|
|
ParseOpenBrace();
|
|
|
|
ParseSpawnMap(sc, ConversationIDsFromMapinfo, "Conversation ID");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void InitClassMap(FClassMap &themap, SpawnMap &thedata)
|
|
|
|
{
|
|
|
|
themap.Clear();
|
|
|
|
SpawnMap::Iterator it(thedata);
|
2015-04-03 22:39:09 +00:00
|
|
|
SpawnMap::Pair *pair;
|
|
|
|
int error = 0;
|
|
|
|
|
|
|
|
while (it.NextPair(pair))
|
|
|
|
{
|
2015-04-28 10:54:01 +00:00
|
|
|
PClassActor *cls = NULL;
|
2015-04-03 22:39:09 +00:00
|
|
|
if (pair->Value.classname != NAME_None)
|
|
|
|
{
|
2015-04-28 10:54:01 +00:00
|
|
|
cls = PClass::FindActor(pair->Value.classname);
|
2015-04-03 22:39:09 +00:00
|
|
|
if (cls == NULL)
|
|
|
|
{
|
|
|
|
Printf(TEXTCOLOR_RED "Script error, \"%s\" line %d:\nUnknown actor class %s\n",
|
|
|
|
pair->Value.filename.GetChars(), pair->Value.linenum, pair->Value.classname.GetChars());
|
|
|
|
error++;
|
|
|
|
}
|
2015-04-23 18:09:12 +00:00
|
|
|
themap.Insert(pair->Key, cls);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
themap.Remove(pair->Key);
|
2015-04-03 22:39:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (error > 0)
|
|
|
|
{
|
|
|
|
I_Error("%d unknown actor classes found", error);
|
|
|
|
}
|
2015-04-04 22:31:15 +00:00
|
|
|
thedata.Clear(); // we do not need this any longer
|
|
|
|
}
|
|
|
|
|
|
|
|
void InitSpawnablesFromMapinfo()
|
|
|
|
{
|
|
|
|
InitClassMap(SpawnableThings, SpawnablesFromMapinfo);
|
|
|
|
InitClassMap(StrifeTypes, ConversationIDsFromMapinfo);
|
2015-04-03 22:39:09 +00:00
|
|
|
}
|