qzdoom-gpl/src/p_things.cpp

441 lines
12 KiB
C++
Raw Normal View History

/*
** 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 "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"
#include "g_level.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) || (level.flags2 & LEVEL2_NOMONSTERS)))
return false;
if (tid == 0)
{
spot = source;
}
else
{
spot = iterator.Next();
}
while (spot != NULL)
{
2006-07-16 09:10:45 +00:00
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)
{
2006-07-16 09:10:45 +00:00
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.
2006-04-16 13:29:50 +00:00
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)
{
2006-07-16 09:10:45 +00:00
Spawn<ATeleportFog> (x, y, z + TELEFOGHEIGHT, ALLOW_REPLACE);
Spawn<ATeleportFog> (oldx, oldy, oldz + TELEFOGHEIGHT, ALLOW_REPLACE);
}
- Converted the StrifePlayer to DECORATE. Even though it requires exporting 3 new code pointers without general use it was necessary to handle GiveDefaultInventory consistently for all players without the need to subclass this function. - Added a Player.RunHealth property to expose the StrifePlayer's behavior of not being able to run when its health is below 10. - Changed APlayerPawn::GiveDefaultInventory so that it always adds a HexenArmor and a BasicArmor item to the inventory. If these items are not the first ones added to the inventory anything else that might absorb damage is not guaranteed to work consistently because their function depends on the order in the inventory. - Changed handling of APowerup's DoEffect so that it is called from the owner's Tick function, not the item's. This is so that the order of execution is determined by the order in the inventory. When done in the item's Tick function order depends on the global thinker table which can cause problems with the order in which conflicting powerups apply their effect. Now it is guaranteed that the item that was added to the inventory first applies its effect last. - Fixed: Added checks for Speed==0 to A_Tracer and A_Tracer2 because this could cause a divide by zero. - Fixed: P_MoveThing must also set the moved actor's previous position to prevent interpolation of the move. - Fixed: APowerInvisibility and its subclasses need to constantly update the owner's translucency information in case of interference between different subclasses. Also changed Hexen's Cleric's invulnerability mode to disable the translucency effect if an invisibility powerup is active. SVN r448 (trunk)
2007-01-12 15:24:10 +00:00
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) || (level.flags2 & LEVEL2_NOMONSTERS)))
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;
}
2006-07-16 09:10:45 +00:00
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->velx | targ->vely | targ->velz))
{
// 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->velx, targ->vely, targ->velz);
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->velx | targ->vely) == 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->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);
}
else
{
nolead: mobj->angle = R_PointToAngle2 (mobj->x, mobj->y, targ->x, targ->y);
aim.Resize (fspeed);
mobj->velx = fixed_t(aim[0]);
mobj->vely = fixed_t(aim[1]);
mobj->velz = fixed_t(aim[2]);
}
if (mobj->flags2 & MF2_SEEKERMISSILE)
{
mobj->tracer = targ;
}
}
else
{
mobj->angle = angle;
mobj->velx = FixedMul (speed, finecosine[angle>>ANGLETOFINESHIFT]);
mobj->vely = FixedMul (speed, finesine[angle>>ANGLETOFINESHIFT]);
mobj->velz = 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->velx)*mobj->velx + double(mobj->vely)*mobj->vely + double(mobj->velz)*mobj->velz));
}
// Hugger missiles don't have any vertical velocity
if (mobj->flags3 & (MF3_FLOORHUGGER|MF3_CEILINGHUGGER))
{
mobj->velz = 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.
2006-04-16 13:29:50 +00:00
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;
}
void P_RemoveThing(AActor * actor)
{
// Don't remove live players.
if (actor->player == NULL || actor != actor->player->mo)
{
// be friendly to the level statistics. ;)
if (actor->CountsAsKill() && actor->health > 0) level.total_monsters--;
if (actor->flags&MF_COUNTITEM) level.total_items--;
actor->Destroy ();
}
}
CCMD (dumpspawnables)
{
int i;
for (i = 0; i < MAX_SPAWNABLES; i++)
{
if (SpawnableThings[i] != NULL)
{
Printf ("%d %s\n", i, SpawnableThings[i]->TypeName.GetChars());
}
}
}