qzdoom/src/p_things.cpp
Randy Heit 490742cf46 - 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

424 lines
11 KiB
C++

/*
** p_things.cpp
** ACS-accessible thing utilities
**
**---------------------------------------------------------------------------
** Copyright 1998-2007 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.
**
** 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 "p_effect.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"
// List of spawnable things for the Thing_Spawn and Thing_Projectile specials.
const PClass *SpawnableThings[MAX_SPAWNABLES];
static FRandom pr_leadtarget ("LeadTarget");
bool P_Thing_Spawn (int tid, AActor *source, int type, angle_t angle, bool fog, int newtid)
{
int rtn = 0;
const PClass *kind;
AActor *spot, *mobj;
FActorIterator iterator (tid);
if (type >= MAX_SPAWNABLES)
return false;
if ( (kind = SpawnableThings[type]) == NULL)
return false;
// Handle decorate replacements.
kind = kind->ActorInfo->GetReplacement()->Class;
if ((GetDefaultByType (kind)->flags3 & MF3_ISMONSTER) && (dmflags & DF_NO_MONSTERS))
return false;
if (tid == 0)
{
spot = source;
}
else
{
spot = iterator.Next();
}
while (spot != NULL)
{
mobj = Spawn (kind, spot->x, spot->y, spot->z, ALLOW_REPLACE);
if (mobj != NULL)
{
DWORD oldFlags2 = mobj->flags2;
mobj->flags2 |= MF2_PASSMOBJ;
if (P_TestMobjLocation (mobj))
{
rtn++;
mobj->angle = (angle != ANGLE_MAX ? angle : spot->angle);
if (fog)
{
Spawn<ATeleportFog> (spot->x, spot->y, spot->z + TELEFOGHEIGHT, ALLOW_REPLACE);
}
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.
if (mobj->CountsAsKill())
{
level.total_monsters--;
}
// Same, for items
if (mobj->flags & MF_COUNTITEM)
{
level.total_items--;
}
mobj->Destroy ();
}
}
spot = iterator.Next();
}
return rtn != 0;
}
// [BC] Added
// [RH] Fixed
bool P_MoveThing(AActor *source, fixed_t x, fixed_t y, fixed_t z, bool fog)
{
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)
{
Spawn<ATeleportFog> (x, y, z + TELEFOGHEIGHT, ALLOW_REPLACE);
Spawn<ATeleportFog> (oldx, oldy, oldz + TELEFOGHEIGHT, ALLOW_REPLACE);
}
source->PrevX=x;
source->PrevY=y;
source->PrevZ=z;
return true;
}
else
{
source->SetOrigin (oldx, oldy, oldz);
return false;
}
}
bool P_Thing_Move (int tid, AActor *source, int mapspot, bool fog)
{
AActor *target;
if (tid != 0)
{
FActorIterator iterator1(tid);
source = iterator1.Next();
}
FActorIterator iterator2 (mapspot);
target = iterator2.Next ();
if (source != NULL && target != NULL)
{
return P_MoveThing(source, target->x, target->y, target->z, fog);
}
return false;
}
bool P_Thing_Projectile (int tid, AActor *source, int type, const char * type_name, angle_t angle,
fixed_t speed, fixed_t vspeed, int dest, AActor *forcedest, int gravity, int newtid,
bool leadTarget)
{
int rtn = 0;
const PClass *kind;
AActor *spot, *mobj, *targ = forcedest;
FActorIterator iterator (tid);
double fspeed = speed;
int defflags3;
if (type_name == NULL)
{
if (type >= MAX_SPAWNABLES)
return false;
if ((kind = SpawnableThings[type]) == NULL)
return false;
}
else
{
if ((kind = PClass::FindClass(type_name)) == NULL || kind->ActorInfo == NULL)
return false;
}
// Handle decorate replacements.
kind = kind->ActorInfo->GetReplacement()->Class;
defflags3 = GetDefaultByType (kind)->flags3;
if ((defflags3 & MF3_ISMONSTER) && (dmflags & DF_NO_MONSTERS))
return false;
if (tid == 0)
{
spot = source;
}
else
{
spot = iterator.Next();
}
while (spot != NULL)
{
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;
}
mobj = Spawn (kind, spot->x, spot->y, z, ALLOW_REPLACE);
if (mobj)
{
mobj->tid = newtid;
mobj->AddToHash ();
P_PlaySpawnSound(mobj, spot);
if (gravity)
{
mobj->flags &= ~MF_NOGRAVITY;
if (!(mobj->flags3 & MF3_ISMONSTER) && gravity == 1)
{
mobj->gravity = FRACUNIT/8;
}
}
else
{
mobj->flags |= MF_NOGRAVITY;
}
mobj->target = spot;
if (targ != NULL)
{
fixed_t spot[3] = { targ->x, targ->y, targ->z+targ->height/2 };
FVector3 aim(float(spot[0] - mobj->x), float(spot[1] - mobj->y), float(spot[2] - mobj->z));
if (leadTarget && speed > 0 && (targ->momx | targ->momy | targ->momz))
{
// 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.
FVector3 tvel(targ->momx, targ->momy, targ->momz);
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.
tvel.Z = 0.0;
if ((targ->momx | targ->momy) == 0)
{
goto nolead;
}
}
double dist = aim.Length();
double targspeed = tvel.Length();
double ydotx = -aim | tvel;
double a = acos (clamp (ydotx / targspeed / dist, -1.0, 1.0));
double multiplier = double(pr_leadtarget.Random2())*0.1/255+1.1;
double sinb = clamp (targspeed*multiplier * sin(a) / fspeed, -1.0, 1.0);
// Use the cross product of two of the triangle's sides to get a
// rotation vector.
FVector3 rv(tvel ^ aim);
// The vector must be normalized.
rv.MakeUnit();
// Now combine the rotation vector with angle b to get a rotation matrix.
FMatrix3x3 rm(rv, cos(asin(sinb)), sinb);
// And multiply the original aim vector with the matrix to get a
// new aim vector that leads the target.
FVector3 aimvec = rm * aim;
// And make the projectile follow that vector at the desired speed.
double aimscale = fspeed / dist;
mobj->momx = fixed_t (aimvec[0] * aimscale);
mobj->momy = fixed_t (aimvec[1] * aimscale);
mobj->momz = fixed_t (aimvec[2] * aimscale);
mobj->angle = R_PointToAngle2 (0, 0, mobj->momx, mobj->momy);
}
else
{
nolead: mobj->angle = R_PointToAngle2 (mobj->x, mobj->y, targ->x, targ->y);
aim.Resize (fspeed);
mobj->momx = fixed_t(aim[0]);
mobj->momy = fixed_t(aim[1]);
mobj->momz = fixed_t(aim[2]);
}
if (mobj->flags2 & MF2_SEEKERMISSILE)
{
mobj->tracer = targ;
}
}
else
{
mobj->angle = angle;
mobj->momx = FixedMul (speed, finecosine[angle>>ANGLETOFINESHIFT]);
mobj->momy = FixedMul (speed, finesine[angle>>ANGLETOFINESHIFT]);
mobj->momz = vspeed;
}
// Set the missile's speed to reflect the speed it was spawned at.
if (mobj->flags & MF_MISSILE)
{
mobj->Speed = fixed_t (sqrt (double(mobj->momx)*mobj->momx + double(mobj->momy)*mobj->momy + double(mobj->momz)*mobj->momz));
}
// Hugger missiles don't have any vertical velocity
if (mobj->flags3 & (MF3_FLOORHUGGER|MF3_CEILINGHUGGER))
{
mobj->momz = 0;
}
if (mobj->flags & MF_SPECIAL)
{
mobj->flags |= MF_DROPPED;
}
if (mobj->flags & MF_MISSILE)
{
if (P_CheckMissileSpawn (mobj))
{
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.
if (mobj->CountsAsKill())
{
level.total_monsters--;
}
// Same, for items
if (mobj->flags & MF_COUNTITEM)
{
level.total_items--;
}
mobj->Destroy ();
}
else
{
// It spawned fine.
rtn = 1;
}
}
} while (dest != 0 && (targ = tit.Next()));
}
spot = iterator.Next();
}
return rtn != 0;
}
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);
}
else if (actor->health < actor->GetDefault()->health)
{
actor->health -= amount;
if (actor->health > actor->GetDefault()->health)
{
actor->health = actor->GetDefault()->health;
}
if (actor->player != NULL)
{
actor->player->health = actor->health;
}
}
count++;
}
actor = next;
}
return count;
}
CCMD (dumpspawnables)
{
int i;
for (i = 0; i < MAX_SPAWNABLES; i++)
{
if (SpawnableThings[i] != NULL)
{
Printf ("%d %s\n", i, SpawnableThings[i]->TypeName.GetChars());
}
}
}