mirror of
https://github.com/ZDoom/qzdoom-gpl.git
synced 2024-11-17 09:41:21 +00:00
e8e7cebe18
function in an easy-to-understand manner. - Fixed: D_AddWildFile() blindly assumed that all matches were files. - Added back the dead player check to CheckIfExitIsGood(), but now it applies only if the next map is part of the same hub. Otherwise, you can still exit the map while dead. - Removed the SpawnedPuff global variable and made it a return value from P_LineAttack(). - Fixed: P_SpawnPuff() played sounds for temporary puffs. SVN r739 (trunk)
4778 lines
128 KiB
C++
4778 lines
128 KiB
C++
// Emacs style mode select -*- C++ -*-
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Id:$
|
|
//
|
|
// Copyright (C) 1993-1996 by id Software, Inc.
|
|
//
|
|
// This source is available for distribution and/or modification
|
|
// only under the terms of the DOOM Source Code License as
|
|
// published by id Software. All rights reserved.
|
|
//
|
|
// The source is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
|
|
// for more details.
|
|
//
|
|
// $Log:$
|
|
//
|
|
// DESCRIPTION:
|
|
// Movement, collision handling.
|
|
// Shooting and aiming.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
#include "templates.h"
|
|
#include "vectors.h"
|
|
|
|
#include "m_alloc.h"
|
|
#include "m_bbox.h"
|
|
#include "m_random.h"
|
|
#include "i_system.h"
|
|
|
|
#include "doomdef.h"
|
|
#include "p_local.h"
|
|
#include "p_lnspec.h"
|
|
#include "p_effect.h"
|
|
#include "p_terrain.h"
|
|
#include "p_trace.h"
|
|
|
|
#include "s_sound.h"
|
|
#include "decallib.h"
|
|
|
|
// State.
|
|
#include "doomstat.h"
|
|
#include "r_state.h"
|
|
|
|
#include "gi.h"
|
|
|
|
#include "a_sharedglobal.h"
|
|
#include "a_doomglobal.h"
|
|
#include "p_conversation.h"
|
|
#include "r_translate.h"
|
|
|
|
#define WATER_SINK_FACTOR 3
|
|
#define WATER_SINK_SMALL_FACTOR 4
|
|
#define WATER_SINK_SPEED (FRACUNIT/2)
|
|
#define WATER_JUMP_SPEED (FRACUNIT*7/2)
|
|
|
|
#define USE_PUZZLE_ITEM_SPECIAL 129
|
|
|
|
|
|
CVAR (Bool, cl_bloodsplats, true, CVAR_ARCHIVE)
|
|
CVAR (Int, sv_smartaim, 0, CVAR_ARCHIVE|CVAR_SERVERINFO)
|
|
|
|
static void CheckForPushSpecial (line_t *line, int side, AActor *mobj);
|
|
static void SpawnShootDecal (AActor *t1, const FTraceResults &trace);
|
|
static void SpawnDeepSplash (AActor *t1, const FTraceResults &trace, AActor *puff,
|
|
fixed_t vx, fixed_t vy, fixed_t vz);
|
|
|
|
fixed_t tmbbox[4];
|
|
AActor *tmthing;
|
|
static int tmflags;
|
|
static fixed_t tmx;
|
|
static fixed_t tmy;
|
|
static fixed_t tmz; // [RH] Needed for third dimension of teleporters
|
|
static fixed_t pe_x; // Pain Elemental position for Lost Soul checks // phares
|
|
static fixed_t pe_y; // Pain Elemental position for Lost Soul checks // phares
|
|
static fixed_t ls_x; // Lost Soul position for Lost Soul checks // phares
|
|
static fixed_t ls_y; // Lost Soul position for Lost Soul checks // phares
|
|
|
|
static FRandom pr_tracebleed ("TraceBleed");
|
|
static FRandom pr_checkthing ("CheckThing");
|
|
static FRandom pr_lineattack ("LineAttack");
|
|
static FRandom pr_crunch ("DoCrunch");
|
|
|
|
// If "floatok" true, move would be ok
|
|
// if within "tmfloorz - tmceilingz".
|
|
bool floatok;
|
|
|
|
fixed_t tmfloorz;
|
|
fixed_t tmceilingz;
|
|
fixed_t tmdropoffz;
|
|
int tmfloorpic;
|
|
sector_t *tmfloorsector;
|
|
int tmceilingpic;
|
|
sector_t *tmceilingsector;
|
|
|
|
static fixed_t tmfbbox[4];
|
|
static AActor *tmfthing;
|
|
fixed_t tmffloorz;
|
|
fixed_t tmfceilingz;
|
|
fixed_t tmfdropoffz;
|
|
fixed_t tmffloorpic;
|
|
sector_t *tmffloorsector;
|
|
fixed_t tmfceilingpic;
|
|
sector_t *tmfceilingsector;
|
|
|
|
//Added by MC: So bot will know what kind of sector it's entering.
|
|
sector_t* tmsector;
|
|
|
|
// keep track of the line that lowers the ceiling,
|
|
// so missiles don't explode against sky hack walls
|
|
line_t* ceilingline;
|
|
|
|
line_t *BlockingLine;
|
|
|
|
// keep track of special lines as they are hit,
|
|
// but don't process them until the move is proven valid
|
|
TArray<line_t *> spechit;
|
|
|
|
AActor *onmobj; // generic global onmobj...used for landing on pods/players
|
|
AActor *BlockingMobj;
|
|
|
|
// Temporary holder for thing_sectorlist threads
|
|
msecnode_t* sector_list = NULL; // phares 3/16/98
|
|
|
|
extern sector_t *openbottomsec;
|
|
extern sector_t *opentopsec;
|
|
|
|
bool DoRipping;
|
|
AActor *LastRipped;
|
|
|
|
//==========================================================================
|
|
//
|
|
// PIT_FindFloorCeiling
|
|
//
|
|
//==========================================================================
|
|
|
|
static bool PIT_FindFloorCeiling (line_t *ld)
|
|
{
|
|
if (tmfbbox[BOXRIGHT] <= ld->bbox[BOXLEFT]
|
|
|| tmfbbox[BOXLEFT] >= ld->bbox[BOXRIGHT]
|
|
|| tmfbbox[BOXTOP] <= ld->bbox[BOXBOTTOM]
|
|
|| tmfbbox[BOXBOTTOM] >= ld->bbox[BOXTOP] )
|
|
return true;
|
|
|
|
if (P_BoxOnLineSide (tmfbbox, ld) != -1)
|
|
return true;
|
|
|
|
// A line has been hit
|
|
|
|
if (!ld->backsector)
|
|
{ // One sided line
|
|
return true;
|
|
}
|
|
|
|
fixed_t sx, sy;
|
|
|
|
// set openrange, opentop, openbottom
|
|
if (((ld->frontsector->floorplane.a | ld->frontsector->floorplane.b) |
|
|
(ld->backsector->floorplane.a | ld->backsector->floorplane.b) |
|
|
(ld->frontsector->ceilingplane.a | ld->frontsector->ceilingplane.b) |
|
|
(ld->backsector->ceilingplane.a | ld->backsector->ceilingplane.b)) == 0)
|
|
{
|
|
P_LineOpening (ld, sx=tmx, sy=tmy, tmx, tmy);
|
|
}
|
|
else
|
|
{ // Find the point on the line closest to the actor's center, and use
|
|
// that to calculate openings
|
|
float dx = (float)ld->dx;
|
|
float dy = (float)ld->dy;
|
|
fixed_t r = (fixed_t)(((float)(tmx - ld->v1->x) * dx +
|
|
(float)(tmy - ld->v1->y) * dy) /
|
|
(dx*dx + dy*dy) * 16777216.f);
|
|
if (r <= 0)
|
|
{
|
|
P_LineOpening (ld, sx=ld->v1->x, sy=ld->v1->y, tmx, tmy);
|
|
}
|
|
else if (r >= (1<<24))
|
|
{
|
|
P_LineOpening (ld, sx=ld->v2->x, sy=ld->v2->y, tmfthing->x, tmfthing->y);
|
|
}
|
|
else
|
|
{
|
|
P_LineOpening (ld, sx=ld->v1->x + MulScale24 (r, ld->dx),
|
|
sy=ld->v1->y + MulScale24 (r, ld->dy), tmx, tmy);
|
|
}
|
|
}
|
|
|
|
// adjust floor / ceiling heights
|
|
if (opentop < tmfceilingz)
|
|
{
|
|
tmfceilingz = opentop;
|
|
BlockingLine = ld;
|
|
}
|
|
|
|
if (openbottom > tmffloorz)
|
|
{
|
|
tmffloorz = openbottom;
|
|
tmffloorsector = openbottomsec;
|
|
BlockingLine = ld;
|
|
}
|
|
|
|
if (lowfloor < tmfdropoffz)
|
|
tmfdropoffz = lowfloor;
|
|
|
|
return true;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// P_FindFloorCeiling
|
|
//
|
|
//==========================================================================
|
|
|
|
void P_FindFloorCeiling (AActor *actor)
|
|
{
|
|
int xl,xh,yl,yh,bx,by;
|
|
fixed_t x, y;
|
|
sector_t *sec;
|
|
|
|
x = actor->x;
|
|
y = actor->y;
|
|
|
|
tmfbbox[BOXTOP] = y + actor->radius;
|
|
tmfbbox[BOXBOTTOM] = y - actor->radius;
|
|
tmfbbox[BOXRIGHT] = x + actor->radius;
|
|
tmfbbox[BOXLEFT] = x - actor->radius;
|
|
|
|
sec = P_PointInSector (x, y);
|
|
tmffloorz = tmfdropoffz = sec->floorplane.ZatPoint (x, y);
|
|
tmfceilingz = sec->ceilingplane.ZatPoint (x, y);
|
|
tmffloorpic = sec->floorpic;
|
|
tmffloorsector = sec;
|
|
tmfceilingpic = sec->ceilingpic;
|
|
tmfceilingsector = sec;
|
|
tmfthing = actor;
|
|
validcount++;
|
|
|
|
xl = (tmfbbox[BOXLEFT] - bmaporgx) >> MAPBLOCKSHIFT;
|
|
xh = (tmfbbox[BOXRIGHT] - bmaporgx) >> MAPBLOCKSHIFT;
|
|
yl = (tmfbbox[BOXBOTTOM] - bmaporgy) >> MAPBLOCKSHIFT;
|
|
yh = (tmfbbox[BOXTOP] - bmaporgy) >> MAPBLOCKSHIFT;
|
|
|
|
for (bx = xl; bx <= xh; bx++)
|
|
for (by = yl; by <= yh; by++)
|
|
if (!P_BlockLinesIterator (bx, by, PIT_FindFloorCeiling))
|
|
return;
|
|
}
|
|
|
|
//
|
|
// TELEPORT MOVE
|
|
//
|
|
|
|
//
|
|
// PIT_StompThing
|
|
//
|
|
static bool StompAlwaysFrags;
|
|
|
|
bool PIT_StompThing (AActor *thing)
|
|
{
|
|
fixed_t blockdist;
|
|
|
|
if (!(thing->flags & MF_SHOOTABLE))
|
|
return true;
|
|
|
|
// don't clip against self
|
|
if (thing == tmthing)
|
|
return true;
|
|
|
|
blockdist = thing->radius + tmthing->radius;
|
|
|
|
if (abs(thing->x - tmx) >= blockdist || abs(thing->y - tmy) >= blockdist)
|
|
{
|
|
// didn't hit it
|
|
return true;
|
|
}
|
|
|
|
// [RH] Z-Check
|
|
// But not if not MF2_PASSMOBJ or MF3_DONTOVERLAP are set!
|
|
// Otherwise those things would get stuck inside each other.
|
|
if ((tmthing->flags2 & MF2_PASSMOBJ || thing->flags4 & MF4_ACTLIKEBRIDGE) && !(i_compatflags & COMPATF_NO_PASSMOBJ))
|
|
{
|
|
if (!(thing->flags3 & tmthing->flags3 & MF3_DONTOVERLAP))
|
|
{
|
|
if (tmz > thing->z + thing->height)
|
|
return true; // overhead
|
|
if (tmz+tmthing->height < thing->z)
|
|
return true; // underneath
|
|
}
|
|
}
|
|
|
|
// monsters don't stomp things except on boss level
|
|
// [RH] Some Heretic/Hexen monsters can telestomp
|
|
if (StompAlwaysFrags || (tmthing->flags2 & MF2_TELESTOMP))
|
|
{
|
|
P_DamageMobj (thing, tmthing, tmthing, 1000000, NAME_Telefrag);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// P_TeleportMove
|
|
//
|
|
// [RH] Added telefrag parameter: When true, anything in the spawn spot
|
|
// will always be telefragged, and the move will be successful.
|
|
// Added z parameter. Originally, the thing's z was set *after* the
|
|
// move was made, so the height checking I added for 1.13 could
|
|
// potentially erroneously indicate the move was okay if the thing
|
|
// was being teleported between two non-overlapping height ranges.
|
|
bool P_TeleportMove (AActor *thing, fixed_t x, fixed_t y, fixed_t z, bool telefrag)
|
|
{
|
|
static TArray<AActor *> telebt;
|
|
|
|
int xl;
|
|
int xh;
|
|
int yl;
|
|
int yh;
|
|
int bx;
|
|
int by;
|
|
|
|
sector_t* newsec;
|
|
|
|
// kill anything occupying the position
|
|
tmthing = thing;
|
|
tmflags = thing->flags;
|
|
|
|
tmx = x;
|
|
tmy = y;
|
|
tmz = z;
|
|
|
|
tmfbbox[BOXTOP] = tmbbox[BOXTOP] = y + tmthing->radius;
|
|
tmfbbox[BOXBOTTOM] = tmbbox[BOXBOTTOM] = y - tmthing->radius;
|
|
tmfbbox[BOXRIGHT] = tmbbox[BOXRIGHT] = x + tmthing->radius;
|
|
tmfbbox[BOXLEFT] = tmbbox[BOXLEFT] = x - tmthing->radius;
|
|
|
|
newsec = P_PointInSector (x,y);
|
|
ceilingline = NULL;
|
|
|
|
// The base floor/ceiling is from the subsector that contains the point.
|
|
// Any contacted lines the step closer together will adjust them.
|
|
tmffloorz = tmfdropoffz = newsec->floorplane.ZatPoint (x, y);
|
|
tmfceilingz = newsec->ceilingplane.ZatPoint (x, y);
|
|
tmffloorpic = newsec->floorpic;
|
|
tmffloorsector = newsec;
|
|
tmfceilingpic = newsec->ceilingpic;
|
|
tmfceilingsector = newsec;
|
|
tmfthing = tmthing;
|
|
|
|
validcount++;
|
|
spechit.Clear ();
|
|
telebt.Clear();
|
|
|
|
StompAlwaysFrags = tmthing->player || (level.flags & LEVEL_MONSTERSTELEFRAG) || telefrag;
|
|
|
|
// stomp on any things contacted
|
|
xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
|
|
for (bx=xl ; bx<=xh ; bx++)
|
|
{
|
|
for (by=yl ; by<=yh ; by++)
|
|
{
|
|
if (!P_BlockLinesIterator(bx,by,PIT_FindFloorCeiling))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
fixed_t savefloorz = tmffloorz;
|
|
fixed_t saveceilingz = tmfceilingz;
|
|
sector_t *savesector = tmffloorsector;
|
|
int savepic = tmffloorpic;
|
|
sector_t *savecsector = tmffloorsector;
|
|
int savecpic = tmffloorpic;
|
|
fixed_t savedropoff = tmfdropoffz;
|
|
|
|
for (bx=xl ; bx<=xh ; bx++)
|
|
{
|
|
for (by=yl ; by<=yh ; by++)
|
|
{
|
|
if (!P_BlockThingsIterator(bx,by,PIT_StompThing,telebt))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// the move is ok, so link the thing into its new position
|
|
thing->SetOrigin (x, y, z);
|
|
thing->floorz = savefloorz;
|
|
thing->ceilingz = saveceilingz;
|
|
thing->floorsector = savesector;
|
|
thing->floorpic = savepic;
|
|
thing->ceilingsector = savecsector;
|
|
thing->ceilingpic = savecpic;
|
|
thing->dropoffz = savedropoff; // killough 11/98
|
|
|
|
if (thing->flags2 & MF2_FLOORCLIP)
|
|
{
|
|
thing->AdjustFloorClip ();
|
|
}
|
|
|
|
if (thing == players[consoleplayer].camera)
|
|
{
|
|
R_ResetViewInterpolation ();
|
|
}
|
|
|
|
thing->PrevX = x;
|
|
thing->PrevY = y;
|
|
thing->PrevZ = z;
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// [RH] P_PlayerStartStomp
|
|
//
|
|
// Like P_TeleportMove, but it doesn't move anything, and only monsters and other
|
|
// players get telefragged.
|
|
//
|
|
bool PIT_StompThing2 (AActor *thing)
|
|
{
|
|
fixed_t blockdist;
|
|
|
|
if (!(thing->flags & MF_SHOOTABLE))
|
|
return true;
|
|
|
|
// don't clip against self, and don't kill your own voodoo dolls
|
|
if (thing == tmthing ||
|
|
(thing->player == tmthing->player && thing->player != NULL))
|
|
return true;
|
|
|
|
// only kill monsters and other players
|
|
if (thing->player == NULL && !(thing->flags3 & MF3_ISMONSTER))
|
|
return true;
|
|
|
|
blockdist = thing->radius + tmthing->radius;
|
|
|
|
if (abs(thing->x - tmthing->x) >= blockdist || abs(thing->y - tmthing->y) >= blockdist)
|
|
{
|
|
// didn't hit it
|
|
return true;
|
|
}
|
|
|
|
if (tmthing->z > thing->z + thing->height)
|
|
return true; // overhead
|
|
if (tmthing->z + tmthing->height < thing->z)
|
|
return true; // underneath
|
|
|
|
P_DamageMobj (thing, tmthing, tmthing, 1000000, NAME_Telefrag);
|
|
return true;
|
|
}
|
|
|
|
void P_PlayerStartStomp (AActor *actor)
|
|
{
|
|
static TArray<AActor *> telebt;
|
|
|
|
int xl;
|
|
int xh;
|
|
int yl;
|
|
int yh;
|
|
int bx;
|
|
int by;
|
|
|
|
tmthing = actor;
|
|
tmflags = actor->flags;
|
|
|
|
tmbbox[BOXTOP] = actor->y + actor->radius;
|
|
tmbbox[BOXBOTTOM] = actor->y - actor->radius;
|
|
tmbbox[BOXRIGHT] = actor->x + actor->radius;
|
|
tmbbox[BOXLEFT] = actor->x - actor->radius;
|
|
|
|
telebt.Clear();
|
|
|
|
// stomp on any things contacted
|
|
xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
|
|
for (bx = xl; bx <= xh; bx++)
|
|
{
|
|
for (by = yl; by <= yh; by++)
|
|
{
|
|
P_BlockThingsIterator (bx, by, PIT_StompThing2, telebt);
|
|
}
|
|
}
|
|
}
|
|
|
|
inline fixed_t secfriction (const sector_t *sec)
|
|
{
|
|
fixed_t friction = Terrains[TerrainTypes[sec->floorpic]].Friction;
|
|
return friction != 0 ? friction : sec->friction;
|
|
}
|
|
|
|
inline fixed_t secmovefac (const sector_t *sec)
|
|
{
|
|
fixed_t movefactor = Terrains[TerrainTypes[sec->floorpic]].MoveFactor;
|
|
return movefactor != 0 ? movefactor : sec->movefactor;
|
|
}
|
|
|
|
//
|
|
// killough 8/28/98:
|
|
//
|
|
// P_GetFriction()
|
|
//
|
|
// Returns the friction associated with a particular mobj.
|
|
|
|
int P_GetFriction (const AActor *mo, int *frictionfactor)
|
|
{
|
|
int friction = ORIG_FRICTION;
|
|
int movefactor = ORIG_FRICTION_FACTOR;
|
|
const msecnode_t *m;
|
|
const sector_t *sec;
|
|
|
|
if (mo->flags2 & MF2_FLY && mo->flags & MF_NOGRAVITY)
|
|
{
|
|
friction = FRICTION_FLY;
|
|
}
|
|
else if (!(mo->flags & MF_NOGRAVITY) && mo->waterlevel > 1 ||
|
|
(mo->waterlevel == 1 && mo->z > mo->floorz + 6*FRACUNIT))
|
|
{
|
|
friction = secfriction (mo->Sector);
|
|
movefactor = secmovefac (mo->Sector) >> 1;
|
|
}
|
|
else if (var_friction && !(mo->flags & (MF_NOCLIP|MF_NOGRAVITY)))
|
|
{ // When the object is straddling sectors with the same
|
|
// floor height that have different frictions, use the lowest
|
|
// friction value (muddy has precedence over icy).
|
|
|
|
for (m = mo->touching_sectorlist; m; m = m->m_tnext)
|
|
{
|
|
sec = m->m_sector;
|
|
if (!(sec->special & FRICTION_MASK) &&
|
|
Terrains[TerrainTypes[sec->floorpic]].Friction == 0)
|
|
{
|
|
continue;
|
|
}
|
|
if ((secfriction(sec) < friction || friction == ORIG_FRICTION) &&
|
|
(mo->z <= sec->floorplane.ZatPoint (mo->x, mo->y) ||
|
|
(sec->heightsec && !(sec->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC) &&
|
|
mo->z <= sec->heightsec->floorplane.ZatPoint (mo->x, mo->y))))
|
|
{
|
|
friction = secfriction (sec);
|
|
movefactor = secmovefac (sec);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (frictionfactor)
|
|
*frictionfactor = movefactor;
|
|
|
|
return friction;
|
|
}
|
|
|
|
// phares 3/19/98
|
|
// P_GetMoveFactor() returns the value by which the x,y
|
|
// movements are multiplied to add to player movement.
|
|
//
|
|
// killough 8/28/98: rewritten
|
|
|
|
int P_GetMoveFactor (const AActor *mo, int *frictionp)
|
|
{
|
|
int movefactor, friction;
|
|
|
|
// If the floor is icy or muddy, it's harder to get moving. This is where
|
|
// the different friction factors are applied to 'trying to move'. In
|
|
// p_mobj.c, the friction factors are applied as you coast and slow down.
|
|
|
|
if ((friction = P_GetFriction(mo, &movefactor)) < ORIG_FRICTION)
|
|
{
|
|
// phares 3/11/98: you start off slowly, then increase as
|
|
// you get better footing
|
|
|
|
int momentum = P_AproxDistance(mo->momx,mo->momy);
|
|
|
|
if (momentum > MORE_FRICTION_MOMENTUM<<2)
|
|
movefactor <<= 3;
|
|
else if (momentum > MORE_FRICTION_MOMENTUM<<1)
|
|
movefactor <<= 2;
|
|
else if (momentum > MORE_FRICTION_MOMENTUM)
|
|
movefactor <<= 1;
|
|
}
|
|
|
|
if (frictionp)
|
|
*frictionp = friction;
|
|
|
|
return movefactor;
|
|
}
|
|
|
|
//
|
|
// MOVEMENT ITERATOR FUNCTIONS
|
|
//
|
|
|
|
// // phares
|
|
// PIT_CrossLine // |
|
|
// Checks to see if a PE->LS trajectory line crosses a blocking // V
|
|
// line. Returns false if it does.
|
|
//
|
|
// tmbbox holds the bounding box of the trajectory. If that box
|
|
// does not touch the bounding box of the line in question,
|
|
// then the trajectory is not blocked. If the PE is on one side
|
|
// of the line and the LS is on the other side, then the
|
|
// trajectory is blocked.
|
|
//
|
|
// Currently this assumes an infinite line, which is not quite
|
|
// correct. A more correct solution would be to check for an
|
|
// intersection of the trajectory and the line, but that takes
|
|
// longer and probably really isn't worth the effort.
|
|
//
|
|
|
|
static // killough 3/26/98: make static
|
|
bool PIT_CrossLine (line_t* ld)
|
|
{
|
|
if (!(ld->flags & ML_TWOSIDED) ||
|
|
(ld->flags & (ML_BLOCKING|ML_BLOCKMONSTERS|ML_BLOCKEVERYTHING)))
|
|
if (!(tmbbox[BOXLEFT] > ld->bbox[BOXRIGHT] ||
|
|
tmbbox[BOXRIGHT] < ld->bbox[BOXLEFT] ||
|
|
tmbbox[BOXTOP] < ld->bbox[BOXBOTTOM] ||
|
|
tmbbox[BOXBOTTOM] > ld->bbox[BOXTOP]))
|
|
if (P_PointOnLineSide(pe_x,pe_y,ld) != P_PointOnLineSide(ls_x,ls_y,ld))
|
|
return(false); // line blocks trajectory // ^
|
|
return(true); // line doesn't block trajectory // |
|
|
} // phares
|
|
|
|
//
|
|
// PIT_CheckLine
|
|
// Adjusts tmfloorz and tmceilingz as lines are contacted
|
|
//
|
|
|
|
static // killough 3/26/98: make static
|
|
bool PIT_CheckLine (line_t *ld)
|
|
{
|
|
bool rail = false;
|
|
|
|
if (tmbbox[BOXRIGHT] <= ld->bbox[BOXLEFT]
|
|
|| tmbbox[BOXLEFT] >= ld->bbox[BOXRIGHT]
|
|
|| tmbbox[BOXTOP] <= ld->bbox[BOXBOTTOM]
|
|
|| tmbbox[BOXBOTTOM] >= ld->bbox[BOXTOP] )
|
|
return true;
|
|
|
|
if (P_BoxOnLineSide (tmbbox, ld) != -1)
|
|
return true;
|
|
|
|
// A line has been hit
|
|
/*
|
|
=
|
|
= The moving thing's destination position will cross the given line.
|
|
= If this should not be allowed, return false.
|
|
= If the line is special, keep track of it to process later if the move
|
|
= is proven ok. NOTE: specials are NOT sorted by order, so two special lines
|
|
= that are only 8 pixels apart could be crossed in either order.
|
|
*/
|
|
|
|
if (!ld->backsector)
|
|
{ // One sided line
|
|
if (tmthing->flags2 & MF2_BLASTED)
|
|
{
|
|
P_DamageMobj (tmthing, NULL, NULL, tmthing->Mass >> 5, NAME_Melee);
|
|
}
|
|
BlockingLine = ld;
|
|
CheckForPushSpecial (ld, 0, tmthing);
|
|
return false;
|
|
}
|
|
|
|
if (!(tmthing->flags & MF_MISSILE) || (ld->flags & ML_BLOCKEVERYTHING))
|
|
{
|
|
if (ld->flags & ML_RAILING)
|
|
{
|
|
rail = true;
|
|
}
|
|
else if ((ld->flags & (ML_BLOCKING|ML_BLOCKEVERYTHING)) || // explicitly blocking everything
|
|
(!(tmthing->flags3 & MF3_NOBLOCKMONST) && (ld->flags & ML_BLOCKMONSTERS)) || // block monsters only
|
|
(tmthing->player != NULL && (ld->flags & ML_BLOCK_PLAYERS)) || // block players
|
|
((ld->flags & ML_BLOCK_FLOATERS) && (tmthing->flags & MF_FLOAT))) // block floaters
|
|
{
|
|
if (tmthing->flags2 & MF2_BLASTED)
|
|
{
|
|
P_DamageMobj (tmthing, NULL, NULL, tmthing->Mass >> 5, NAME_Melee);
|
|
}
|
|
BlockingLine = ld;
|
|
CheckForPushSpecial (ld, 0, tmthing);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// [RH] Steep sectors count as dropoffs (unless already in one)
|
|
if (!(tmthing->flags & MF_DROPOFF) &&
|
|
!(tmthing->flags & (MF_NOGRAVITY|MF_NOCLIP)))
|
|
{
|
|
if (ld->frontsector->floorplane.c < STEEPSLOPE ||
|
|
ld->backsector->floorplane.c < STEEPSLOPE)
|
|
{
|
|
const msecnode_t *node = tmthing->touching_sectorlist;
|
|
bool allow = false;
|
|
int count = 0;
|
|
while (node != NULL)
|
|
{
|
|
count++;
|
|
if (node->m_sector->floorplane.c < STEEPSLOPE)
|
|
{
|
|
allow = true;
|
|
break;
|
|
}
|
|
node = node->m_tnext;
|
|
}
|
|
if (!allow)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
fixed_t sx, sy;
|
|
|
|
// set openrange, opentop, openbottom
|
|
if (((ld->frontsector->floorplane.a | ld->frontsector->floorplane.b) |
|
|
(ld->backsector->floorplane.a | ld->backsector->floorplane.b) |
|
|
(ld->frontsector->ceilingplane.a | ld->frontsector->ceilingplane.b) |
|
|
(ld->backsector->ceilingplane.a | ld->backsector->ceilingplane.b)) == 0)
|
|
{
|
|
P_LineOpening (ld, sx=tmx, sy=tmy, tmx, tmy);
|
|
}
|
|
else
|
|
{ // Find the point on the line closest to the actor's center, and use
|
|
// that to calculate openings
|
|
float dx = (float)ld->dx;
|
|
float dy = (float)ld->dy;
|
|
fixed_t r = (fixed_t)(((float)(tmx - ld->v1->x) * dx +
|
|
(float)(tmy - ld->v1->y) * dy) /
|
|
(dx*dx + dy*dy) * 16777216.f);
|
|
/* Printf ("%d:%d: %d (%d %d %d %d) (%d %d %d %d)\n", level.time, ld-lines, r,
|
|
ld->frontsector->floorplane.a,
|
|
ld->frontsector->floorplane.b,
|
|
ld->frontsector->floorplane.c,
|
|
ld->frontsector->floorplane.ic,
|
|
ld->backsector->floorplane.a,
|
|
ld->backsector->floorplane.b,
|
|
ld->backsector->floorplane.c,
|
|
ld->backsector->floorplane.ic);*/
|
|
if (r <= 0)
|
|
{
|
|
P_LineOpening (ld, sx=ld->v1->x, sy=ld->v1->y, tmx, tmy);
|
|
}
|
|
else if (r >= (1<<24))
|
|
{
|
|
P_LineOpening (ld, sx=ld->v2->x, sy=ld->v2->y, tmthing->x, tmthing->y);
|
|
}
|
|
else
|
|
{
|
|
P_LineOpening (ld, sx=ld->v1->x + MulScale24 (r, ld->dx),
|
|
sy=ld->v1->y + MulScale24 (r, ld->dy), tmx, tmy);
|
|
}
|
|
/* Printf (" %d %d %d\n", sx, sy, openbottom);*/
|
|
}
|
|
|
|
if (rail &&
|
|
// Eww! Gross! This check means the rail only exists if you stand on the
|
|
// high side of the rail. So if you're walking on the low side of the rail,
|
|
// it's possible to get stuck in the rail until you jump out. Unfortunately,
|
|
// there is an area on Strife MAP04 that requires this behavior. Still, it's
|
|
// better than Strife's handling of rails, which lets you jump into rails
|
|
// from either side. How long until somebody reports this as a bug and I'm
|
|
// forced to say, "It's not a bug. It's a feature?" Ugh.
|
|
(gameinfo.gametype != GAME_Strife ||
|
|
level.flags & LEVEL_HEXENFORMAT ||
|
|
openbottom == tmthing->Sector->floorplane.ZatPoint (sx, sy)))
|
|
{
|
|
openbottom += 32*FRACUNIT;
|
|
}
|
|
|
|
// adjust floor / ceiling heights
|
|
if (opentop < tmceilingz)
|
|
{
|
|
tmceilingz = opentop;
|
|
tmceilingsector = opentopsec;
|
|
tmceilingpic = opentopsec->ceilingpic;
|
|
ceilingline = ld;
|
|
BlockingLine = ld;
|
|
}
|
|
|
|
if (openbottom > tmfloorz)
|
|
{
|
|
tmfloorz = openbottom;
|
|
tmfloorsector = openbottomsec;
|
|
tmfloorpic = openbottomsec->floorpic;
|
|
BlockingLine = ld;
|
|
}
|
|
|
|
if (lowfloor < tmdropoffz)
|
|
tmdropoffz = lowfloor;
|
|
|
|
// if contacted a special line, add it to the list
|
|
if (ld->special)
|
|
{
|
|
spechit.Push (ld);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PIT_CheckThing
|
|
//
|
|
//==========================================================================
|
|
|
|
static AActor *stepthing;
|
|
|
|
bool PIT_CheckThing (AActor *thing)
|
|
{
|
|
fixed_t topz;
|
|
fixed_t blockdist;
|
|
bool solid;
|
|
int damage;
|
|
|
|
// don't clip against self
|
|
if (thing == tmthing)
|
|
return true;
|
|
|
|
if (!(thing->flags & (MF_SOLID|MF_SPECIAL|MF_SHOOTABLE)) )
|
|
return true; // can't hit thing
|
|
|
|
blockdist = thing->radius + tmthing->radius;
|
|
if (abs(thing->x - tmx) >= blockdist || abs(thing->y - tmy) >= blockdist)
|
|
{
|
|
// didn't hit thing
|
|
return true;
|
|
}
|
|
BlockingMobj = thing;
|
|
topz = thing->z + thing->height;
|
|
if (!(i_compatflags & COMPATF_NO_PASSMOBJ) && !(tmthing->flags & (MF_FLOAT|MF_MISSILE|MF_SKULLFLY|MF_NOGRAVITY)) &&
|
|
(thing->flags & MF_SOLID) && (thing->flags4 & MF4_ACTLIKEBRIDGE))
|
|
{
|
|
// [RH] Let monsters walk on actors as well as floors
|
|
if ((tmthing->flags3 & MF3_ISMONSTER) &&
|
|
topz >= tmfloorz && topz <= tmthing->z + tmthing->MaxStepHeight)
|
|
{
|
|
// The commented-out if is an attempt to prevent monsters from walking off a
|
|
// thing further than they would walk off a ledge. I can't think of an easy
|
|
// way to do this, so I restrict them to only walking on bridges instead.
|
|
// Uncommenting the if here makes it almost impossible for them to walk on
|
|
// anything, bridge or otherwise.
|
|
// if (abs(thing->x - tmx) <= thing->radius &&
|
|
// abs(thing->y - tmy) <= thing->radius)
|
|
{
|
|
stepthing = thing;
|
|
tmfloorz = topz;
|
|
}
|
|
}
|
|
}
|
|
// [RH] If the other thing is a bridge, then treat the moving thing as if it had MF2_PASSMOBJ, so
|
|
// you can use a scrolling floor to move scenery items underneath a bridge.
|
|
if ((tmthing->flags2 & MF2_PASSMOBJ || thing->flags4 & MF4_ACTLIKEBRIDGE) && !(i_compatflags & COMPATF_NO_PASSMOBJ))
|
|
{ // check if a mobj passed over/under another object
|
|
if (tmthing->flags3 & thing->flags3 & MF3_DONTOVERLAP)
|
|
{ // Some things prefer not to overlap each other, if possible
|
|
return false;
|
|
}
|
|
if ((tmthing->z >= topz) || (tmthing->z + tmthing->height <= thing->z))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
// Check for skulls slamming into things
|
|
if (tmthing->flags & MF_SKULLFLY)
|
|
{
|
|
bool res = tmthing->Slam (BlockingMobj);
|
|
BlockingMobj = NULL;
|
|
return res;
|
|
}
|
|
// Check for blasted thing running into another
|
|
if ((tmthing->flags2 & MF2_BLASTED) && (thing->flags & MF_SHOOTABLE))
|
|
{
|
|
if (!(thing->flags2 & MF2_BOSS) && (thing->flags3 & MF3_ISMONSTER))
|
|
{
|
|
thing->momx += tmthing->momx;
|
|
thing->momy += tmthing->momy;
|
|
if ((thing->momx + thing->momy) > 3*FRACUNIT)
|
|
{
|
|
damage = (tmthing->Mass / 100) + 1;
|
|
P_DamageMobj (thing, tmthing, tmthing, damage, tmthing->DamageType);
|
|
P_TraceBleed (damage, thing, tmthing);
|
|
damage = (thing->Mass / 100) + 1;
|
|
P_DamageMobj (tmthing, thing, thing, damage >> 2, tmthing->DamageType);
|
|
P_TraceBleed (damage, tmthing, thing);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
// Check for missile
|
|
if (tmthing->flags & MF_MISSILE)
|
|
{
|
|
// Check for a non-shootable mobj
|
|
if (thing->flags2 & MF2_NONSHOOTABLE)
|
|
{
|
|
return true;
|
|
}
|
|
// Check for passing through a ghost
|
|
if ((thing->flags3 & MF3_GHOST) && (tmthing->flags2 & MF2_THRUGHOST))
|
|
{
|
|
return true;
|
|
}
|
|
// Check if it went over / under
|
|
if (tmthing->z > thing->z + thing->height)
|
|
{ // Over thing
|
|
return true;
|
|
}
|
|
if (tmthing->z+tmthing->height < thing->z)
|
|
{ // Under thing
|
|
return true;
|
|
}
|
|
|
|
if (tmthing->flags2 & MF2_BOUNCE2)
|
|
{
|
|
if (tmthing->Damage == 0)
|
|
{
|
|
return (tmthing->target == thing || !(thing->flags & MF_SOLID));
|
|
}
|
|
}
|
|
|
|
switch (tmthing->SpecialMissileHit (thing))
|
|
{
|
|
case 0: return false;
|
|
case 1: return true;
|
|
default: break;
|
|
}
|
|
|
|
// [RH] Extend DeHacked infighting to allow for monsters
|
|
// to never fight each other
|
|
|
|
// [Graf Zahl] Why do I have the feeling that this didn't really work anymore now
|
|
// that ZDoom supports friendly monsters?
|
|
|
|
|
|
if (tmthing->target != NULL)
|
|
{
|
|
if (thing == tmthing->target)
|
|
{ // Don't missile self
|
|
return true;
|
|
}
|
|
|
|
// players are never subject to infighting settings and are always allowed
|
|
// to harm / be harmed by anything.
|
|
if (!thing->player && !tmthing->target->player)
|
|
{
|
|
int infight;
|
|
if (level.flags & LEVEL_TOTALINFIGHTING) infight=1;
|
|
else if (level.flags & LEVEL_NOINFIGHTING) infight=-1;
|
|
else infight = infighting;
|
|
|
|
if (infight < 0)
|
|
{
|
|
// -1: Monsters cannot hurt each other, but make exceptions for
|
|
// friendliness and hate status.
|
|
if (tmthing->target->flags & MF_SHOOTABLE)
|
|
{
|
|
if (!(thing->flags3 & MF3_ISMONSTER))
|
|
{
|
|
return false; // Question: Should monsters be allowed to shoot barrels in this mode?
|
|
// The old code does not.
|
|
}
|
|
|
|
// Monsters that are clearly hostile can always hurt each other
|
|
if (!thing->IsHostile (tmthing->target))
|
|
{
|
|
// The same if the shooter hates the target
|
|
if (thing->tid == 0 || tmthing->target->TIDtoHate != thing->tid)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (infight == 0)
|
|
{
|
|
// 0: Monsters cannot hurt same species except
|
|
// cases where they are clearly supposed to do that
|
|
if (thing->IsFriend (tmthing->target))
|
|
{
|
|
// Friends never harm each other
|
|
return false;
|
|
}
|
|
if (thing->TIDtoHate != 0 && thing->TIDtoHate == tmthing->target->TIDtoHate)
|
|
{
|
|
// [RH] Don't hurt monsters that hate the same thing as you do
|
|
return false;
|
|
}
|
|
if (thing->GetSpecies() == tmthing->target->GetSpecies())
|
|
{
|
|
// Don't hurt same species or any relative -
|
|
// but only if the target isn't one's hostile.
|
|
if (!thing->IsHostile (tmthing->target))
|
|
{
|
|
// Allow hurting monsters the shooter hates.
|
|
if (thing->tid == 0 || tmthing->target->TIDtoHate != thing->tid)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// else if (infight==1) any shot hurts anything - no further tests
|
|
}
|
|
}
|
|
if (!(thing->flags & MF_SHOOTABLE))
|
|
{ // Didn't do any damage
|
|
return !(thing->flags & MF_SOLID);
|
|
}
|
|
if ((thing->flags4 & MF4_SPECTRAL) && !(tmthing->flags4 & MF4_SPECTRAL))
|
|
{
|
|
return true;
|
|
}
|
|
if (DoRipping)
|
|
{
|
|
if (LastRipped != thing)
|
|
{
|
|
LastRipped = thing;
|
|
if (!(thing->flags & MF_NOBLOOD) &&
|
|
!(thing->flags2 & MF2_REFLECTIVE) &&
|
|
!(tmthing->flags3 & MF3_BLOODLESSIMPACT) &&
|
|
!(thing->flags2 & (MF2_INVULNERABLE|MF2_DORMANT)))
|
|
{ // Ok to spawn blood
|
|
P_RipperBlood (tmthing, thing);
|
|
}
|
|
S_Sound (tmthing, CHAN_BODY, "misc/ripslop", 1, ATTN_IDLE);
|
|
damage = tmthing->GetMissileDamage (3, 2);
|
|
P_DamageMobj (thing, tmthing, tmthing->target, damage, tmthing->DamageType);
|
|
if (!(tmthing->flags3 & MF3_BLOODLESSIMPACT))
|
|
{
|
|
P_TraceBleed (damage, thing, tmthing);
|
|
}
|
|
if (thing->flags2 & MF2_PUSHABLE
|
|
&& !(tmthing->flags2 & MF2_CANNOTPUSH))
|
|
{ // Push thing
|
|
thing->momx += tmthing->momx>>2;
|
|
thing->momy += tmthing->momy>>2;
|
|
}
|
|
}
|
|
spechit.Clear ();
|
|
return true;
|
|
}
|
|
// Do damage
|
|
damage = tmthing->GetMissileDamage ((tmthing->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1);
|
|
if (damage > 0)
|
|
{
|
|
P_DamageMobj (thing, tmthing, tmthing->target, damage, tmthing->DamageType);
|
|
if ((tmthing->flags5 & MF5_BLOODSPLATTER) &&
|
|
!(thing->flags & MF_NOBLOOD) &&
|
|
!(thing->flags2 & MF2_REFLECTIVE) &&
|
|
!(thing->flags2 & (MF2_INVULNERABLE|MF2_DORMANT)) &&
|
|
!(tmthing->flags3 & MF3_BLOODLESSIMPACT) &&
|
|
(pr_checkthing() < 192))
|
|
{
|
|
P_BloodSplatter (tmthing->x, tmthing->y, tmthing->z, thing);
|
|
}
|
|
if (!(tmthing->flags3 & MF3_BLOODLESSIMPACT))
|
|
{
|
|
P_TraceBleed (damage, thing, tmthing);
|
|
}
|
|
}
|
|
else if (damage < 0)
|
|
{
|
|
P_GiveBody (thing, -damage);
|
|
}
|
|
return false; // don't traverse any more
|
|
}
|
|
if (thing->flags2 & MF2_PUSHABLE && !(tmthing->flags2 & MF2_CANNOTPUSH) &&
|
|
(tmthing->player == NULL || !(tmthing->player->cheats & CF_PREDICTING)))
|
|
{ // Push thing
|
|
thing->momx += tmthing->momx >> 2;
|
|
thing->momy += tmthing->momy >> 2;
|
|
}
|
|
solid = (thing->flags & MF_SOLID) &&
|
|
!(thing->flags & MF_NOCLIP) &&
|
|
(tmthing->flags & MF_SOLID);
|
|
// Check for special pickup
|
|
if ((thing->flags & MF_SPECIAL) && (tmflags & MF_PICKUP)
|
|
// [RH] The next condition is to compensate for the extra height
|
|
// that gets added by P_CheckPosition() so that you cannot pick
|
|
// up things that are above your true height.
|
|
&& thing->z < tmthing->z + tmthing->height - tmthing->MaxStepHeight)
|
|
{ // Can be picked up by tmthing
|
|
P_TouchSpecialThing (thing, tmthing); // can remove thing
|
|
}
|
|
|
|
// killough 3/16/98: Allow non-solid moving objects to move through solid
|
|
// ones, by allowing the moving thing (tmthing) to move if it's non-solid,
|
|
// despite another solid thing being in the way.
|
|
// killough 4/11/98: Treat no-clipping things as not blocking
|
|
|
|
return !solid;
|
|
|
|
// return !(thing->flags & MF_SOLID); // old code -- killough
|
|
}
|
|
|
|
// This routine checks for Lost Souls trying to be spawned // phares
|
|
// across 1-sided lines, impassible lines, or "monsters can't // |
|
|
// cross" lines. Draw an imaginary line between the PE // V
|
|
// and the new Lost Soul spawn spot. If that line crosses
|
|
// a 'blocking' line, then disallow the spawn. Only search
|
|
// lines in the blocks of the blockmap where the bounding box
|
|
// of the trajectory line resides. Then check bounding box
|
|
// of the trajectory vs. the bounding box of each blocking
|
|
// line to see if the trajectory and the blocking line cross.
|
|
// Then check the PE and LS to see if they're on different
|
|
// sides of the blocking line. If so, return true, otherwise
|
|
// false.
|
|
|
|
bool Check_Sides(AActor* actor, int x, int y)
|
|
{
|
|
int bx,by,xl,xh,yl,yh;
|
|
|
|
pe_x = actor->x;
|
|
pe_y = actor->y;
|
|
ls_x = x;
|
|
ls_y = y;
|
|
|
|
// Here is the bounding box of the trajectory
|
|
|
|
tmbbox[BOXLEFT] = pe_x < x ? pe_x : x;
|
|
tmbbox[BOXRIGHT] = pe_x > x ? pe_x : x;
|
|
tmbbox[BOXTOP] = pe_y > y ? pe_y : y;
|
|
tmbbox[BOXBOTTOM] = pe_y < y ? pe_y : y;
|
|
|
|
// Determine which blocks to look in for blocking lines
|
|
|
|
xl = (tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT;
|
|
xh = (tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT;
|
|
yl = (tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT;
|
|
yh = (tmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT;
|
|
|
|
// xl->xh, yl->yh determine the mapblock set to search
|
|
|
|
validcount++; // prevents checking same line twice
|
|
for (bx = xl ; bx <= xh ; bx++)
|
|
for (by = yl ; by <= yh ; by++)
|
|
if (!P_BlockLinesIterator(bx,by,PIT_CrossLine))
|
|
return true; // ^
|
|
return(false); // |
|
|
} // phares
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// PIT_CheckOnmobjZ
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool PIT_CheckOnmobjZ (AActor *thing)
|
|
{
|
|
if (!(thing->flags & MF_SOLID))
|
|
{ // Can't hit thing
|
|
return true;
|
|
}
|
|
if (thing->flags & (MF_SPECIAL|MF_NOCLIP|MF_CORPSE))
|
|
{ // [RH] Corpses and specials and noclippers don't block moves
|
|
return true;
|
|
}
|
|
if (!(thing->flags4 & MF4_ACTLIKEBRIDGE) && (tmthing->flags & MF_SPECIAL))
|
|
{ // [RH] Only bridges block pickup items
|
|
return true;
|
|
}
|
|
if (thing == tmthing)
|
|
{ // Don't clip against self
|
|
return true;
|
|
}
|
|
if (tmthing->z > thing->z+thing->height)
|
|
{ // over thing
|
|
return true;
|
|
}
|
|
else if (tmthing->z+tmthing->height <= thing->z)
|
|
{ // under thing
|
|
return true;
|
|
}
|
|
else if (!tmflags && onmobj != NULL && thing->z + thing->height < onmobj->z + onmobj->height)
|
|
{ // something higher is in the way
|
|
return true;
|
|
}
|
|
fixed_t blockdist = thing->radius+tmthing->radius;
|
|
if (abs(thing->x-tmx) >= blockdist || abs(thing->y-tmy) >= blockdist)
|
|
{ // Didn't hit thing
|
|
return true;
|
|
}
|
|
onmobj = thing;
|
|
return !tmflags;
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
MOVEMENT CLIPPING
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// FUNC P_TestMobjLocation
|
|
//
|
|
// Returns true if the mobj is not blocked by anything at its current
|
|
// location, otherwise returns false.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
bool P_TestMobjLocation (AActor *mobj)
|
|
{
|
|
int flags;
|
|
|
|
flags = mobj->flags;
|
|
mobj->flags &= ~MF_PICKUP;
|
|
if (P_CheckPosition(mobj, mobj->x, mobj->y))
|
|
{ // XY is ok, now check Z
|
|
mobj->flags = flags;
|
|
fixed_t z = mobj->z;
|
|
if (mobj->flags2 & MF2_FLOATBOB)
|
|
{
|
|
z -= FloatBobOffsets[(mobj->FloatBobPhase + level.maptime - 1) & 63];
|
|
}
|
|
if ((z < mobj->floorz) || (z + mobj->height > mobj->ceilingz))
|
|
{ // Bad Z
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
mobj->flags = flags;
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// P_CheckPosition
|
|
// This is purely informative, nothing is modified
|
|
// (except things picked up and missile damage applied).
|
|
//
|
|
// in:
|
|
// a AActor (can be valid or invalid)
|
|
// a position to be checked
|
|
// (doesn't need to be related to the AActor->x,y)
|
|
//
|
|
// during:
|
|
// special things are touched if MF_PICKUP
|
|
// early out on solid lines?
|
|
//
|
|
// out:
|
|
// newsubsec
|
|
// floorz
|
|
// ceilingz
|
|
// tmdropoffz = the lowest point contacted (monsters won't move to a dropoff)
|
|
// speciallines[]
|
|
// numspeciallines
|
|
// AActor *BlockingMobj = pointer to thing that blocked position (NULL if not
|
|
// blocked, or blocked by a line).
|
|
bool P_CheckPosition (AActor *thing, fixed_t x, fixed_t y)
|
|
{
|
|
static TArray<AActor *> checkpbt;
|
|
|
|
int xl, xh;
|
|
int yl, yh;
|
|
int bx, by;
|
|
sector_t *newsec;
|
|
AActor *thingblocker;
|
|
AActor *fakedblocker;
|
|
fixed_t realheight = thing->height;
|
|
|
|
tmthing = thing;
|
|
tmflags = thing->flags;
|
|
|
|
tmx = x;
|
|
tmy = y;
|
|
|
|
tmbbox[BOXTOP] = y + thing->radius;
|
|
tmbbox[BOXBOTTOM] = y - thing->radius;
|
|
tmbbox[BOXRIGHT] = x + thing->radius;
|
|
tmbbox[BOXLEFT] = x - thing->radius;
|
|
|
|
newsec = P_PointInSector (x,y);
|
|
ceilingline = BlockingLine = NULL;
|
|
|
|
// The base floor / ceiling is from the subsector that contains the point.
|
|
// Any contacted lines the step closer together will adjust them.
|
|
tmfloorz = tmdropoffz = newsec->floorplane.ZatPoint (x, y);
|
|
tmceilingz = newsec->ceilingplane.ZatPoint (x, y);
|
|
tmfloorpic = newsec->floorpic;
|
|
tmfloorsector = newsec;
|
|
tmceilingpic = newsec->ceilingpic;
|
|
tmceilingsector = newsec;
|
|
|
|
//Added by MC: Fill the tmsector.
|
|
tmsector = newsec;
|
|
|
|
|
|
validcount++;
|
|
spechit.Clear ();
|
|
checkpbt.Clear ();
|
|
|
|
if ((tmflags & MF_NOCLIP) && !(tmflags & MF_SKULLFLY))
|
|
return true;
|
|
|
|
// Check things first, possibly picking things up.
|
|
// The bounding box is extended by MAXRADIUS
|
|
// because DActors are grouped into mapblocks
|
|
// based on their origin point, and can overlap
|
|
// into adjacent blocks by up to MAXRADIUS units.
|
|
xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
|
|
BlockingMobj = NULL;
|
|
thingblocker = NULL;
|
|
fakedblocker = NULL;
|
|
if (thing->player)
|
|
{ // [RH] Fake taller height to catch stepping up into things.
|
|
thing->height = realheight + thing->MaxStepHeight;
|
|
}
|
|
|
|
stepthing = NULL;
|
|
for (bx = xl; bx <= xh; bx++)
|
|
{
|
|
for (by = yl; by <= yh; by++)
|
|
{
|
|
AActor *robin = NULL;
|
|
do
|
|
{
|
|
if (!P_BlockThingsIterator (bx, by, PIT_CheckThing, checkpbt, robin))
|
|
{ // [RH] If a thing can be stepped up on, we need to continue checking
|
|
// other things in the blocks and see if we hit something that is
|
|
// definitely blocking. Otherwise, we need to check the lines, or we
|
|
// could end up stuck inside a wall.
|
|
if (BlockingMobj == NULL || (i_compatflags & COMPATF_NO_PASSMOBJ))
|
|
{ // Thing slammed into something; don't let it move now.
|
|
thing->height = realheight;
|
|
return false;
|
|
}
|
|
else if (!BlockingMobj->player && !(thing->flags & (MF_FLOAT|MF_MISSILE|MF_SKULLFLY)) &&
|
|
BlockingMobj->z+BlockingMobj->height-thing->z <= thing->MaxStepHeight)
|
|
{
|
|
if (thingblocker == NULL ||
|
|
BlockingMobj->z > thingblocker->z)
|
|
{
|
|
thingblocker = BlockingMobj;
|
|
}
|
|
robin = BlockingMobj;
|
|
BlockingMobj = NULL;
|
|
}
|
|
else if (thing->player &&
|
|
thing->z + thing->height - BlockingMobj->z <= thing->MaxStepHeight)
|
|
{
|
|
if (thingblocker)
|
|
{ // There is something to step up on. Return this thing as
|
|
// the blocker so that we don't step up.
|
|
thing->height = realheight;
|
|
return false;
|
|
}
|
|
// Nothing is blocking us, but this actor potentially could
|
|
// if there is something else to step on.
|
|
fakedblocker = BlockingMobj;
|
|
robin = BlockingMobj;
|
|
BlockingMobj = NULL;
|
|
}
|
|
else
|
|
{ // Definitely blocking
|
|
thing->height = realheight;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
robin = NULL;
|
|
}
|
|
} while (robin);
|
|
}
|
|
}
|
|
|
|
// check lines
|
|
|
|
// [RH] We need to increment validcount again, because a function above may
|
|
// have already set some lines to equal the current validcount.
|
|
//
|
|
// Specifically, when DehackedPickup spawns a new item in its TryPickup()
|
|
// function, that new actor will set the lines around it to match validcount
|
|
// when it links itself into the world. If we just leave validcount alone,
|
|
// that will give the player the freedom to walk through walls at will near
|
|
// a pickup they cannot get, because their validcount will prevent them from
|
|
// being considered for collision with the player.
|
|
validcount++;
|
|
|
|
BlockingMobj = NULL;
|
|
thing->height = realheight;
|
|
if (tmflags & MF_NOCLIP)
|
|
return (BlockingMobj = thingblocker) == NULL;
|
|
xl = (tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT;
|
|
xh = (tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT;
|
|
yl = (tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT;
|
|
yh = (tmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT;
|
|
|
|
fixed_t thingdropoffz = tmfloorz;
|
|
//bool onthing = (thingdropoffz != tmdropoffz);
|
|
tmfloorz = tmdropoffz;
|
|
|
|
for (bx=xl ; bx<=xh ; bx++)
|
|
for (by=yl ; by<=yh ; by++)
|
|
if (!P_BlockLinesIterator (bx,by,PIT_CheckLine))
|
|
return false;
|
|
|
|
if (tmceilingz - tmfloorz < thing->height)
|
|
return false;
|
|
|
|
if (stepthing != NULL)
|
|
{
|
|
tmdropoffz = thingdropoffz;
|
|
}
|
|
|
|
return (BlockingMobj = thingblocker) == NULL;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// P_CheckOnmobj(AActor *thing)
|
|
//
|
|
// Checks if the new Z position is legal
|
|
//=============================================================================
|
|
|
|
AActor *P_CheckOnmobj (AActor *thing)
|
|
{
|
|
fixed_t oldz;
|
|
bool good;
|
|
|
|
oldz = thing->z;
|
|
P_FakeZMovement (thing);
|
|
good = P_TestMobjZ (thing, false);
|
|
thing->z = oldz;
|
|
|
|
return good ? NULL : onmobj;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// P_TestMobjZ
|
|
//
|
|
//=============================================================================
|
|
|
|
bool P_TestMobjZ (AActor *actor, bool quick)
|
|
{
|
|
static TArray<AActor *> mobjzbt;
|
|
|
|
int xl,xh,yl,yh,bx,by;
|
|
fixed_t x, y;
|
|
|
|
onmobj = NULL;
|
|
if (actor->flags & MF_NOCLIP)
|
|
return true;
|
|
|
|
tmx = x = actor->x;
|
|
tmy = y = actor->y;
|
|
tmthing = actor;
|
|
tmflags = quick;
|
|
|
|
tmbbox[BOXTOP] = y + actor->radius;
|
|
tmbbox[BOXBOTTOM] = y - actor->radius;
|
|
tmbbox[BOXRIGHT] = x + actor->radius;
|
|
tmbbox[BOXLEFT] = x - actor->radius;
|
|
//
|
|
// the bounding box is extended by MAXRADIUS because actors are grouped
|
|
// into mapblocks based on their origin point, and can overlap into adjacent
|
|
// blocks by up to MAXRADIUS units
|
|
//
|
|
xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
|
|
mobjzbt.Clear();
|
|
|
|
for (bx = xl; bx <= xh; bx++)
|
|
for (by = yl; by <= yh; by++)
|
|
if (!P_BlockThingsIterator (bx, by, PIT_CheckOnmobjZ, mobjzbt))
|
|
return false;
|
|
|
|
return onmobj == NULL;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// P_FakeZMovement
|
|
//
|
|
// Fake the zmovement so that we can check if a move is legal
|
|
//=============================================================================
|
|
|
|
void P_FakeZMovement (AActor *mo)
|
|
{
|
|
//
|
|
// adjust height
|
|
//
|
|
mo->z += mo->momz;
|
|
if ((mo->flags&MF_FLOAT) && mo->target)
|
|
{ // float down towards target if too close
|
|
if (!(mo->flags & MF_SKULLFLY) && !(mo->flags & MF_INFLOAT))
|
|
{
|
|
fixed_t dist = P_AproxDistance (mo->x - mo->target->x, mo->y - mo->target->y);
|
|
fixed_t delta = (mo->target->z + (mo->height>>1)) - mo->z;
|
|
if (delta < 0 && dist < -(delta*3))
|
|
mo->z -= mo->FloatSpeed;
|
|
else if (delta > 0 && dist < (delta*3))
|
|
mo->z += mo->FloatSpeed;
|
|
}
|
|
}
|
|
if (mo->player && mo->flags&MF_NOGRAVITY && (mo->z > mo->floorz))
|
|
{
|
|
mo->z += finesine[(FINEANGLES/80*level.maptime)&FINEMASK]/8;
|
|
}
|
|
|
|
//
|
|
// clip movement
|
|
//
|
|
if (mo->z <= mo->floorz)
|
|
{ // hit the floor
|
|
mo->z = mo->floorz;
|
|
}
|
|
|
|
if (mo->z + mo->height > mo->ceilingz)
|
|
{ // hit the ceiling
|
|
mo->z = mo->ceilingz - mo->height;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// CheckForPushSpecial
|
|
//
|
|
//===========================================================================
|
|
|
|
static void CheckForPushSpecial (line_t *line, int side, AActor *mobj)
|
|
{
|
|
if (line->special)
|
|
{
|
|
if (mobj->flags2 & MF2_PUSHWALL)
|
|
{
|
|
P_ActivateLine (line, mobj, side, SPAC_PUSH);
|
|
}
|
|
else if (mobj->flags2 & MF2_IMPACT)
|
|
{
|
|
if ((level.flags & LEVEL_MISSILESACTIVATEIMPACT) ||
|
|
!(mobj->flags & MF_MISSILE) ||
|
|
(mobj->target == NULL))
|
|
{
|
|
P_ActivateLine (line, mobj, side, SPAC_IMPACT);
|
|
}
|
|
else
|
|
{
|
|
P_ActivateLine (line, mobj->target, side, SPAC_IMPACT);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// P_TryMove
|
|
// Attempt to move to a new position,
|
|
// crossing special lines unless MF_TELEPORT is set.
|
|
//
|
|
bool P_TryMove (AActor *thing, fixed_t x, fixed_t y,
|
|
bool dropoff, // killough 3/15/98: allow dropoff as option
|
|
bool onfloor) // [RH] Let P_TryMove keep the thing on the floor
|
|
{
|
|
fixed_t oldx;
|
|
fixed_t oldy;
|
|
fixed_t oldz;
|
|
int side;
|
|
int oldside;
|
|
line_t* ld;
|
|
sector_t* oldsec = thing->Sector; // [RH] for sector actions
|
|
sector_t* newsec;
|
|
|
|
floatok = false;
|
|
oldz = thing->z;
|
|
if (onfloor)
|
|
{
|
|
thing->z = thing->floorsector->floorplane.ZatPoint (x, y);
|
|
}
|
|
if (!P_CheckPosition (thing, x, y))
|
|
{
|
|
// Solid wall or thing
|
|
if (!BlockingMobj || BlockingMobj->player || !thing->player)
|
|
{
|
|
goto pushline;
|
|
}
|
|
else
|
|
{
|
|
if (BlockingMobj->player || !thing->player)
|
|
{
|
|
goto pushline;
|
|
}
|
|
else if (BlockingMobj->z+BlockingMobj->height-thing->z
|
|
> thing->MaxStepHeight
|
|
|| (BlockingMobj->Sector->ceilingplane.ZatPoint (x, y)
|
|
- (BlockingMobj->z+BlockingMobj->height) < thing->height)
|
|
|| (tmceilingz-(BlockingMobj->z+BlockingMobj->height)
|
|
< thing->height))
|
|
{
|
|
goto pushline;
|
|
}
|
|
}
|
|
if (!(tmthing->flags2 & MF2_PASSMOBJ) || (i_compatflags & COMPATF_NO_PASSMOBJ))
|
|
{
|
|
thing->z = oldz;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (thing->flags3 & MF3_FLOORHUGGER)
|
|
{
|
|
thing->z = tmfloorz;
|
|
}
|
|
else if (thing->flags3 & MF3_CEILINGHUGGER)
|
|
{
|
|
thing->z = tmceilingz - thing->height;
|
|
}
|
|
|
|
if (onfloor && tmfloorsector == thing->floorsector)
|
|
{
|
|
thing->z = tmfloorz;
|
|
}
|
|
if (!(thing->flags & MF_NOCLIP))
|
|
{
|
|
if (tmceilingz - tmfloorz < thing->height)
|
|
{
|
|
goto pushline; // doesn't fit
|
|
}
|
|
|
|
floatok = true;
|
|
|
|
if (!(thing->flags & MF_TELEPORT)
|
|
&& tmceilingz - thing->z < thing->height
|
|
&& !(thing->flags3 & MF3_CEILINGHUGGER)
|
|
&& (!(thing->flags2 & MF2_FLY) || !(thing->flags & MF_NOGRAVITY)))
|
|
{
|
|
goto pushline; // mobj must lower itself to fit
|
|
}
|
|
if (thing->flags2 & MF2_FLY && thing->flags & MF_NOGRAVITY)
|
|
{
|
|
#if 1
|
|
if (thing->z+thing->height > tmceilingz)
|
|
goto pushline;
|
|
#else
|
|
// When flying, slide up or down blocking lines until the actor
|
|
// is not blocked.
|
|
if (thing->z+thing->height > tmceilingz)
|
|
{
|
|
thing->momz = -8*FRACUNIT;
|
|
goto pushline;
|
|
}
|
|
else if (thing->z < tmfloorz && tmfloorz-tmdropoffz > thing->MaxDropOffHeight)
|
|
{
|
|
thing->momz = 8*FRACUNIT;
|
|
goto pushline;
|
|
}
|
|
#endif
|
|
}
|
|
if (!(thing->flags & MF_TELEPORT) && !(thing->flags3 & MF3_FLOORHUGGER))
|
|
{
|
|
if (tmfloorz-thing->z > thing->MaxStepHeight)
|
|
{ // too big a step up
|
|
goto pushline;
|
|
}
|
|
else if ((thing->flags & MF_MISSILE) && tmfloorz > thing->z)
|
|
{ // [RH] Don't let normal missiles climb steps
|
|
goto pushline;
|
|
}
|
|
else if (thing->z < tmfloorz)
|
|
{ // [RH] Check to make sure there's nothing in the way for the step up
|
|
fixed_t savedz = thing->z;
|
|
bool good;
|
|
thing->z = tmfloorz;
|
|
good = P_TestMobjZ (thing);
|
|
thing->z = savedz;
|
|
if (!good)
|
|
{
|
|
goto pushline;
|
|
}
|
|
}
|
|
}
|
|
|
|
// killough 3/15/98: Allow certain objects to drop off
|
|
if ((!dropoff && !(thing->flags & (MF_DROPOFF|MF_FLOAT|MF_MISSILE))) || (thing->flags5&MF5_NODROPOFF))
|
|
{
|
|
if (!(thing->flags5&MF5_AVOIDINGDROPOFF))
|
|
{
|
|
fixed_t floorz = tmfloorz;
|
|
// [RH] If the thing is standing on something, use its current z as the floorz.
|
|
// This is so that it does not walk off of things onto a drop off.
|
|
if (thing->flags2 & MF2_ONMOBJ)
|
|
{
|
|
floorz = MAX(thing->z, tmfloorz);
|
|
}
|
|
|
|
if (floorz - tmdropoffz > thing->MaxDropOffHeight &&
|
|
!(thing->flags2 & MF2_BLASTED))
|
|
{ // Can't move over a dropoff unless it's been blasted
|
|
thing->z = oldz;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// special logic to move a monster off a dropoff
|
|
// this intentionally does not check for standing on things.
|
|
if (thing->floorz - tmfloorz > thing->MaxDropOffHeight ||
|
|
thing->dropoffz - tmdropoffz > thing->MaxDropOffHeight) return false;
|
|
}
|
|
}
|
|
if (thing->flags2 & MF2_CANTLEAVEFLOORPIC
|
|
&& (tmfloorpic != thing->floorpic
|
|
|| tmfloorz - thing->z != 0))
|
|
{ // must stay within a sector of a certain floor type
|
|
thing->z = oldz;
|
|
return false;
|
|
}
|
|
|
|
//Added by MC: To prevent bot from getting into dangerous sectors.
|
|
if (thing->player && thing->player->isbot && thing->flags & MF_SHOOTABLE)
|
|
{
|
|
if (tmsector != thing->Sector
|
|
&& bglobal.IsDangerous (tmsector))
|
|
{
|
|
thing->player->prev = thing->player->dest;
|
|
thing->player->dest = NULL;
|
|
thing->momx = 0;
|
|
thing->momy = 0;
|
|
thing->z = oldz;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// [RH] Check status of eyes against fake floor/ceiling in case
|
|
// it slopes or the player's eyes are bobbing in and out.
|
|
|
|
bool oldAboveFakeFloor, oldAboveFakeCeiling;
|
|
fixed_t viewheight;
|
|
|
|
viewheight = thing->player ? thing->player->viewheight : thing->height / 2;
|
|
oldAboveFakeFloor = oldAboveFakeCeiling = false; // pacify GCC
|
|
|
|
if (oldsec->heightsec)
|
|
{
|
|
fixed_t eyez = oldz + viewheight;
|
|
|
|
oldAboveFakeFloor = eyez > oldsec->heightsec->floorplane.ZatPoint (thing->x, thing->y);
|
|
oldAboveFakeCeiling = eyez > oldsec->heightsec->ceilingplane.ZatPoint (thing->x, thing->y);
|
|
}
|
|
|
|
// the move is ok, so link the thing into its new position
|
|
thing->UnlinkFromWorld ();
|
|
|
|
oldx = thing->x;
|
|
oldy = thing->y;
|
|
thing->floorz = tmfloorz;
|
|
thing->ceilingz = tmceilingz;
|
|
thing->dropoffz = tmdropoffz; // killough 11/98: keep track of dropoffs
|
|
thing->floorpic = tmfloorpic;
|
|
thing->floorsector = tmfloorsector;
|
|
thing->ceilingpic = tmceilingpic;
|
|
thing->ceilingsector = tmceilingsector;
|
|
thing->x = x;
|
|
thing->y = y;
|
|
|
|
thing->LinkToWorld ();
|
|
|
|
if (thing->flags2 & MF2_FLOORCLIP)
|
|
{
|
|
thing->AdjustFloorClip ();
|
|
}
|
|
|
|
// [RH] Don't activate anything if just predicting
|
|
if (thing->player && (thing->player->cheats & CF_PREDICTING))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// if any special lines were hit, do the effect
|
|
if (!(thing->flags & (MF_TELEPORT|MF_NOCLIP)))
|
|
{
|
|
while (spechit.Pop (ld))
|
|
{
|
|
// see if the line was crossed
|
|
side = P_PointOnLineSide (thing->x, thing->y, ld);
|
|
oldside = P_PointOnLineSide (oldx, oldy, ld);
|
|
if (side != oldside && ld->special)
|
|
{
|
|
if (thing->player)
|
|
{
|
|
P_ActivateLine (ld, thing, oldside, SPAC_CROSS);
|
|
}
|
|
else if (thing->flags2 & MF2_MCROSS)
|
|
{
|
|
P_ActivateLine (ld, thing, oldside, SPAC_MCROSS);
|
|
}
|
|
else if (thing->flags2 & MF2_PCROSS)
|
|
{
|
|
P_ActivateLine (ld, thing, oldside, SPAC_PCROSS);
|
|
}
|
|
else if ((ld->special == Teleport ||
|
|
ld->special == Teleport_NoFog ||
|
|
ld->special == Teleport_Line))
|
|
{ // [RH] Just a little hack for BOOM compatibility
|
|
P_ActivateLine (ld, thing, oldside, SPAC_MCROSS);
|
|
}
|
|
else
|
|
{
|
|
// I don't think allowing non-monsters to activate
|
|
// monster-allowed lines will hurt Hexen compatibility.
|
|
P_ActivateLine (ld, thing, oldside, SPAC_OTHERCROSS);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// [RH] Check for crossing fake floor/ceiling
|
|
newsec = thing->Sector;
|
|
if (newsec->heightsec && oldsec->heightsec && newsec->SecActTarget)
|
|
{
|
|
const sector_t *hs = newsec->heightsec;
|
|
fixed_t eyez = thing->z + viewheight;
|
|
fixed_t fakez = hs->floorplane.ZatPoint (x, y);
|
|
|
|
if (!oldAboveFakeFloor && eyez > fakez)
|
|
{ // View went above fake floor
|
|
newsec->SecActTarget->TriggerAction (thing, SECSPAC_EyesSurface);
|
|
}
|
|
else if (oldAboveFakeFloor && eyez <= fakez)
|
|
{ // View went below fake floor
|
|
newsec->SecActTarget->TriggerAction (thing, SECSPAC_EyesDive);
|
|
}
|
|
|
|
if (!(hs->MoreFlags & SECF_FAKEFLOORONLY))
|
|
{
|
|
fakez = hs->ceilingplane.ZatPoint (x, y);
|
|
if (!oldAboveFakeCeiling && eyez > fakez)
|
|
{ // View went above fake ceiling
|
|
newsec->SecActTarget->TriggerAction (thing, SECSPAC_EyesAboveC);
|
|
}
|
|
else if (oldAboveFakeCeiling && eyez <= fakez)
|
|
{ // View went below fake ceiling
|
|
newsec->SecActTarget->TriggerAction (thing, SECSPAC_EyesBelowC);
|
|
}
|
|
}
|
|
}
|
|
|
|
// [RH] If changing sectors, trigger transitions
|
|
if (oldsec != newsec)
|
|
{
|
|
if (oldsec->SecActTarget)
|
|
{
|
|
oldsec->SecActTarget->TriggerAction (thing, SECSPAC_Exit);
|
|
}
|
|
if (newsec->SecActTarget)
|
|
{
|
|
int act = SECSPAC_Enter;
|
|
if (thing->z <= newsec->floorplane.ZatPoint (thing->x, thing->y))
|
|
{
|
|
act |= SECSPAC_HitFloor;
|
|
}
|
|
if (thing->z + thing->height >= newsec->ceilingplane.ZatPoint (thing->x, thing->y))
|
|
{
|
|
act |= SECSPAC_HitCeiling;
|
|
}
|
|
if (newsec->heightsec &&
|
|
thing->z == newsec->heightsec->floorplane.ZatPoint (thing->x, thing->y))
|
|
{
|
|
act |= SECSPAC_HitFakeFloor;
|
|
}
|
|
newsec->SecActTarget->TriggerAction (thing, act);
|
|
}
|
|
}
|
|
return true;
|
|
|
|
pushline:
|
|
// [RH] Don't activate anything if just predicting
|
|
if (thing->player && (thing->player->cheats & CF_PREDICTING))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
thing->z = oldz;
|
|
if (!(thing->flags&(MF_TELEPORT|MF_NOCLIP)))
|
|
{
|
|
int numSpecHitTemp;
|
|
|
|
if (tmthing->flags2 & MF2_BLASTED)
|
|
{
|
|
P_DamageMobj (tmthing, NULL, NULL, tmthing->Mass >> 5, NAME_Melee);
|
|
}
|
|
numSpecHitTemp = (int)spechit.Size ();
|
|
while (numSpecHitTemp > 0)
|
|
{
|
|
// see which lines were pushed
|
|
ld = spechit[--numSpecHitTemp];
|
|
side = P_PointOnLineSide (thing->x, thing->y, ld);
|
|
CheckForPushSpecial (ld, side, thing);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// SLIDE MOVE
|
|
// Allows the player to slide along any angled walls.
|
|
//
|
|
fixed_t bestslidefrac;
|
|
fixed_t secondslidefrac;
|
|
|
|
line_t* bestslideline;
|
|
line_t* secondslideline;
|
|
|
|
AActor* slidemo;
|
|
|
|
fixed_t tmxmove;
|
|
fixed_t tmymove;
|
|
|
|
extern bool onground;
|
|
|
|
|
|
//
|
|
// P_HitSlideLine
|
|
// Adjusts the xmove / ymove
|
|
// so that the next move will slide along the wall.
|
|
// If the floor is icy, then you can bounce off a wall. // phares
|
|
//
|
|
void P_HitSlideLine (line_t* ld)
|
|
{
|
|
int side;
|
|
|
|
angle_t lineangle;
|
|
angle_t moveangle;
|
|
angle_t deltaangle;
|
|
|
|
fixed_t movelen;
|
|
bool icyfloor; // is floor icy? // phares
|
|
// |
|
|
// Under icy conditions, if the angle of approach to the wall // V
|
|
// is more than 45 degrees, then you'll bounce and lose half
|
|
// your momentum. If less than 45 degrees, you'll slide along
|
|
// the wall. 45 is arbitrary and is believable.
|
|
|
|
// Check for the special cases of horz or vert walls.
|
|
|
|
// killough 10/98: only bounce if hit hard (prevents wobbling)
|
|
icyfloor =
|
|
(P_AproxDistance(tmxmove, tmymove) > 4*FRACUNIT) &&
|
|
var_friction && // killough 8/28/98: calc friction on demand
|
|
slidemo->z <= slidemo->floorz &&
|
|
P_GetFriction (slidemo, NULL) > ORIG_FRICTION;
|
|
|
|
if (ld->slopetype == ST_HORIZONTAL)
|
|
{
|
|
if (icyfloor && (abs(tmymove) > abs(tmxmove)))
|
|
{
|
|
tmxmove /= 2; // absorb half the momentum
|
|
tmymove = -tmymove/2;
|
|
if (slidemo->player && slidemo->health > 0 && !(slidemo->player->cheats & CF_PREDICTING))
|
|
{
|
|
S_Sound (slidemo, CHAN_VOICE, "*grunt", 1, ATTN_IDLE); // oooff!
|
|
}
|
|
}
|
|
else
|
|
tmymove = 0; // no more movement in the Y direction
|
|
return;
|
|
}
|
|
|
|
if (ld->slopetype == ST_VERTICAL)
|
|
{
|
|
if (icyfloor && (abs(tmxmove) > abs(tmymove)))
|
|
{
|
|
tmxmove = -tmxmove/2; // absorb half the momentum
|
|
tmymove /= 2;
|
|
if (slidemo->player && slidemo->health > 0 && !(slidemo->player->cheats & CF_PREDICTING))
|
|
{
|
|
S_Sound (slidemo, CHAN_VOICE, "*grunt", 1, ATTN_IDLE); // oooff!// ^
|
|
}
|
|
} // |
|
|
else // phares
|
|
tmxmove = 0; // no more movement in the X direction
|
|
return;
|
|
}
|
|
|
|
// The wall is angled. Bounce if the angle of approach is // phares
|
|
// less than 45 degrees. // phares
|
|
|
|
side = P_PointOnLineSide (slidemo->x, slidemo->y, ld);
|
|
|
|
lineangle = R_PointToAngle2 (0,0, ld->dx, ld->dy);
|
|
|
|
if (side == 1)
|
|
lineangle += ANG180;
|
|
|
|
moveangle = R_PointToAngle2 (0,0, tmxmove, tmymove);
|
|
|
|
moveangle += 10; // prevents sudden path reversal due to // phares
|
|
// rounding error // |
|
|
deltaangle = moveangle-lineangle; // V
|
|
movelen = P_AproxDistance (tmxmove, tmymove);
|
|
if (icyfloor && (deltaangle > ANG45) && (deltaangle < ANG90+ANG45))
|
|
{
|
|
moveangle = lineangle - deltaangle;
|
|
movelen /= 2; // absorb
|
|
if (slidemo->player && slidemo->health > 0 && !(slidemo->player->cheats & CF_PREDICTING))
|
|
{
|
|
S_Sound (slidemo, CHAN_VOICE, "*grunt", 1, ATTN_IDLE); // oooff!
|
|
}
|
|
moveangle >>= ANGLETOFINESHIFT;
|
|
tmxmove = FixedMul (movelen, finecosine[moveangle]);
|
|
tmymove = FixedMul (movelen, finesine[moveangle]);
|
|
} // ^
|
|
else // |
|
|
{ // phares
|
|
#if 0
|
|
fixed_t newlen;
|
|
|
|
if (deltaangle > ANG180)
|
|
deltaangle += ANG180;
|
|
// I_Error ("SlideLine: ang>ANG180");
|
|
|
|
lineangle >>= ANGLETOFINESHIFT;
|
|
deltaangle >>= ANGLETOFINESHIFT;
|
|
|
|
newlen = FixedMul (movelen, finecosine[deltaangle]);
|
|
|
|
tmxmove = FixedMul (newlen, finecosine[lineangle]);
|
|
tmymove = FixedMul (newlen, finesine[lineangle]);
|
|
#else
|
|
divline_t dll, dlv;
|
|
fixed_t inter1, inter2, inter3;
|
|
|
|
P_MakeDivline (ld, &dll);
|
|
|
|
dlv.x = slidemo->x;
|
|
dlv.y = slidemo->y;
|
|
dlv.dx = dll.dy;
|
|
dlv.dy = -dll.dx;
|
|
|
|
inter1 = P_InterceptVector(&dll, &dlv);
|
|
|
|
dlv.dx = tmxmove;
|
|
dlv.dy = tmymove;
|
|
inter2 = P_InterceptVector (&dll, &dlv);
|
|
inter3 = P_InterceptVector (&dlv, &dll);
|
|
|
|
if (inter3 != 0)
|
|
{
|
|
tmxmove = Scale (inter2-inter1, dll.dx, inter3);
|
|
tmymove = Scale (inter2-inter1, dll.dy, inter3);
|
|
}
|
|
else
|
|
{
|
|
tmxmove = tmymove = 0;
|
|
}
|
|
#endif
|
|
} // phares
|
|
}
|
|
|
|
|
|
//
|
|
// PTR_SlideTraverse
|
|
//
|
|
bool PTR_SlideTraverse (intercept_t* in)
|
|
{
|
|
line_t* li;
|
|
|
|
if (!in->isaline)
|
|
I_Error ("PTR_SlideTraverse: not a line?");
|
|
|
|
li = in->d.line;
|
|
|
|
if ( !(li->flags & ML_TWOSIDED) )
|
|
{
|
|
if (P_PointOnLineSide (slidemo->x, slidemo->y, li))
|
|
{
|
|
// don't hit the back side
|
|
return true;
|
|
}
|
|
goto isblocking;
|
|
}
|
|
if (li->flags & (ML_BLOCKING|ML_BLOCKEVERYTHING))
|
|
{
|
|
goto isblocking;
|
|
}
|
|
if (li->flags & ML_BLOCK_PLAYERS && slidemo->player != NULL)
|
|
{
|
|
goto isblocking;
|
|
}
|
|
|
|
// set openrange, opentop, openbottom
|
|
P_LineOpening (li, trace.x + FixedMul (trace.dx, in->frac),
|
|
trace.y + FixedMul (trace.dy, in->frac));
|
|
|
|
if (openrange < slidemo->height)
|
|
goto isblocking; // doesn't fit
|
|
|
|
if (opentop - slidemo->z < slidemo->height)
|
|
goto isblocking; // mobj is too high
|
|
|
|
if (openbottom - slidemo->z > slidemo->MaxStepHeight)
|
|
{
|
|
goto isblocking; // too big a step up
|
|
}
|
|
else if (slidemo->z < openbottom)
|
|
{ // [RH] Check to make sure there's nothing in the way for the step up
|
|
fixed_t savedz = slidemo->z;
|
|
slidemo->z = openbottom;
|
|
bool good = P_TestMobjZ (slidemo);
|
|
slidemo->z = savedz;
|
|
if (!good)
|
|
{
|
|
goto isblocking;
|
|
}
|
|
}
|
|
|
|
// this line doesn't block movement
|
|
return true;
|
|
|
|
// the line does block movement,
|
|
// see if it is closer than best so far
|
|
isblocking:
|
|
if (in->frac < bestslidefrac)
|
|
{
|
|
secondslidefrac = bestslidefrac;
|
|
secondslideline = bestslideline;
|
|
bestslidefrac = in->frac;
|
|
bestslideline = li;
|
|
}
|
|
|
|
return false; // stop
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// P_SlideMove
|
|
//
|
|
// The momx / momy move is bad, so try to slide along a wall.
|
|
//
|
|
// Find the first line hit, move flush to it, and slide along it
|
|
//
|
|
// This is a kludgy mess.
|
|
//
|
|
void P_SlideMove (AActor *mo, fixed_t tryx, fixed_t tryy, int numsteps)
|
|
{
|
|
fixed_t leadx, leady;
|
|
fixed_t trailx, traily;
|
|
fixed_t newx, newy;
|
|
fixed_t xmove, ymove;
|
|
bool walkplane;
|
|
int hitcount;
|
|
|
|
slidemo = mo;
|
|
hitcount = 3;
|
|
|
|
if (mo->player && mo->reactiontime > 0)
|
|
return; // player coming right out of a teleporter.
|
|
|
|
retry:
|
|
if (!--hitcount)
|
|
goto stairstep; // don't loop forever
|
|
|
|
// trace along the three leading corners
|
|
if (tryx > 0)
|
|
{
|
|
leadx = mo->x + mo->radius;
|
|
trailx = mo->x - mo->radius;
|
|
}
|
|
else
|
|
{
|
|
leadx = mo->x - mo->radius;
|
|
trailx = mo->x + mo->radius;
|
|
}
|
|
|
|
if (tryy > 0)
|
|
{
|
|
leady = mo->y + mo->radius;
|
|
traily = mo->y - mo->radius;
|
|
}
|
|
else
|
|
{
|
|
leady = mo->y - mo->radius;
|
|
traily = mo->y + mo->radius;
|
|
}
|
|
|
|
bestslidefrac = FRACUNIT+1;
|
|
|
|
P_PathTraverse (leadx, leady, leadx+tryx, leady+tryy, PT_ADDLINES, PTR_SlideTraverse);
|
|
P_PathTraverse (trailx, leady, trailx+tryx, leady+tryy, PT_ADDLINES, PTR_SlideTraverse);
|
|
P_PathTraverse (leadx, traily, leadx+tryx, traily+tryy, PT_ADDLINES, PTR_SlideTraverse);
|
|
|
|
// move up to the wall
|
|
if (bestslidefrac == FRACUNIT+1)
|
|
{
|
|
// the move must have hit the middle, so stairstep
|
|
stairstep:
|
|
// killough 3/15/98: Allow objects to drop off ledges
|
|
xmove = 0, ymove = tryy;
|
|
walkplane = P_CheckSlopeWalk (mo, xmove, ymove);
|
|
if (!P_TryMove (mo, mo->x + xmove, mo->y + ymove, true, walkplane))
|
|
{
|
|
xmove = tryx, ymove = 0;
|
|
walkplane = P_CheckSlopeWalk (mo, xmove, ymove);
|
|
P_TryMove (mo, mo->x + xmove, mo->y + ymove, true, walkplane);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// fudge a bit to make sure it doesn't hit
|
|
bestslidefrac -= FRACUNIT/32;
|
|
if (bestslidefrac > 0)
|
|
{
|
|
newx = FixedMul (tryx, bestslidefrac);
|
|
newy = FixedMul (tryy, bestslidefrac);
|
|
|
|
// killough 3/15/98: Allow objects to drop off ledges
|
|
if (!P_TryMove (mo, mo->x+newx, mo->y+newy, true))
|
|
goto stairstep;
|
|
}
|
|
|
|
// Now continue along the wall.
|
|
bestslidefrac = FRACUNIT - (bestslidefrac + FRACUNIT/32); // remainder
|
|
if (bestslidefrac > FRACUNIT)
|
|
bestslidefrac = FRACUNIT;
|
|
else if (bestslidefrac <= 0)
|
|
return;
|
|
|
|
tryx = tmxmove = FixedMul (tryx, bestslidefrac);
|
|
tryy = tmymove = FixedMul (tryy, bestslidefrac);
|
|
|
|
P_HitSlideLine (bestslideline); // clip the moves
|
|
|
|
mo->momx = tmxmove * numsteps;
|
|
mo->momy = tmymove * numsteps;
|
|
|
|
// killough 10/98: affect the bobbing the same way (but not voodoo dolls)
|
|
if (mo->player && mo->player->mo == mo)
|
|
{
|
|
if (abs(mo->player->momx) > abs(mo->momx))
|
|
mo->player->momx = mo->momx;
|
|
if (abs(mo->player->momy) > abs(mo->momy))
|
|
mo->player->momy = mo->momy;
|
|
}
|
|
|
|
walkplane = P_CheckSlopeWalk (mo, tmxmove, tmymove);
|
|
|
|
// killough 3/15/98: Allow objects to drop off ledges
|
|
if (!P_TryMove (mo, mo->x+tmxmove, mo->y+tmymove, true, walkplane))
|
|
{
|
|
goto retry;
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// P_CheckSlopeWalk
|
|
//
|
|
//============================================================================
|
|
|
|
bool P_CheckSlopeWalk (AActor *actor, fixed_t &xmove, fixed_t &ymove)
|
|
{
|
|
if (actor->flags & MF_NOGRAVITY)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const secplane_t *plane = &actor->floorsector->floorplane;
|
|
fixed_t planezhere = plane->ZatPoint (actor->x, actor->y);
|
|
|
|
if (actor->floorsector != actor->Sector)
|
|
{
|
|
// this additional check prevents sliding on sloped dropoffs
|
|
if (planezhere>actor->floorz+4*FRACUNIT)
|
|
return false;
|
|
}
|
|
|
|
if (actor->z - planezhere > FRACUNIT)
|
|
{ // not on floor
|
|
return false;
|
|
}
|
|
|
|
if ((plane->a | plane->b) != 0)
|
|
{
|
|
fixed_t destx, desty;
|
|
fixed_t t;
|
|
|
|
destx = actor->x + xmove;
|
|
desty = actor->y + ymove;
|
|
t = TMulScale16 (plane->a, destx, plane->b, desty, plane->c, actor->z) + plane->d;
|
|
if (t < 0)
|
|
{ // Desired location is behind (below) the plane
|
|
// (i.e. Walking up the plane)
|
|
if (plane->c < STEEPSLOPE)
|
|
{ // Can't climb up slopes of ~45 degrees or more
|
|
if (actor->flags & MF_NOCLIP)
|
|
{
|
|
return (actor->floorsector == actor->Sector);
|
|
}
|
|
else
|
|
{
|
|
const msecnode_t *node;
|
|
bool dopush = true;
|
|
|
|
if (plane->c > STEEPSLOPE*2/3)
|
|
{
|
|
for (node = actor->touching_sectorlist; node; node = node->m_tnext)
|
|
{
|
|
const sector_t *sec = node->m_sector;
|
|
if (sec->floorplane.c >= STEEPSLOPE)
|
|
{
|
|
if (sec->floorplane.ZatPoint (destx, desty) >= actor->z - actor->MaxStepHeight)
|
|
{
|
|
dopush = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (dopush)
|
|
{
|
|
xmove = actor->momx = plane->a * 2;
|
|
ymove = actor->momy = plane->b * 2;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
// Slide the desired location along the plane's normal
|
|
// so that it lies on the plane's surface
|
|
destx -= FixedMul (plane->a, t);
|
|
desty -= FixedMul (plane->b, t);
|
|
xmove = destx - actor->x;
|
|
ymove = desty - actor->y;
|
|
return (actor->floorsector == actor->Sector);
|
|
}
|
|
else if (t > 0)
|
|
{ // Desired location is in front of (above) the plane
|
|
if (planezhere == actor->z)
|
|
{ // Actor's current spot is on/in the plane, so walk down it
|
|
// Same principle as walking up, except reversed
|
|
destx += FixedMul (plane->a, t);
|
|
desty += FixedMul (plane->b, t);
|
|
xmove = destx - actor->x;
|
|
ymove = desty - actor->y;
|
|
return (actor->floorsector == actor->Sector);//(plane->c >= STEEPSLOPE);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// PTR_BounceTraverse
|
|
//
|
|
//============================================================================
|
|
|
|
bool PTR_BounceTraverse (intercept_t *in)
|
|
{
|
|
line_t *li;
|
|
|
|
if (!in->isaline)
|
|
I_Error ("PTR_BounceTraverse: not a line?");
|
|
|
|
li = in->d.line;
|
|
if (li->flags & ML_BLOCKEVERYTHING)
|
|
{
|
|
goto bounceblocking;
|
|
}
|
|
if (!(li->flags&ML_TWOSIDED))
|
|
{
|
|
if (P_PointOnLineSide (slidemo->x, slidemo->y, li))
|
|
return true; // don't hit the back side
|
|
goto bounceblocking;
|
|
}
|
|
|
|
P_LineOpening (li, trace.x + FixedMul (trace.dx, in->frac),
|
|
trace.y + FixedMul (trace.dy, in->frac)); // set openrange, opentop, openbottom
|
|
if (openrange < slidemo->height)
|
|
goto bounceblocking; // doesn't fit
|
|
|
|
if (opentop - slidemo->z < slidemo->height)
|
|
goto bounceblocking; // mobj is too high
|
|
return true; // this line doesn't block movement
|
|
|
|
// the line does block movement, see if it is closer than best so far
|
|
bounceblocking:
|
|
if (in->frac < bestslidefrac)
|
|
{
|
|
secondslidefrac = bestslidefrac;
|
|
secondslideline = bestslideline;
|
|
bestslidefrac = in->frac;
|
|
bestslideline = li;
|
|
}
|
|
return false; // stop
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// P_BounceWall
|
|
//
|
|
//============================================================================
|
|
|
|
bool P_BounceWall (AActor *mo)
|
|
{
|
|
fixed_t leadx, leady;
|
|
int side;
|
|
angle_t lineangle, moveangle, deltaangle;
|
|
fixed_t movelen;
|
|
line_t *line;
|
|
|
|
if (!(mo->flags2 & MF2_BOUNCE2))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
slidemo = mo;
|
|
//
|
|
// trace along the three leading corners
|
|
//
|
|
if (mo->momx > 0)
|
|
{
|
|
leadx = mo->x+mo->radius;
|
|
}
|
|
else
|
|
{
|
|
leadx = mo->x-mo->radius;
|
|
}
|
|
if (mo->momy > 0)
|
|
{
|
|
leady = mo->y+mo->radius;
|
|
}
|
|
else
|
|
{
|
|
leady = mo->y-mo->radius;
|
|
}
|
|
bestslidefrac = FRACUNIT+1;
|
|
if (P_PathTraverse(leadx, leady, leadx+mo->momx, leady+mo->momy,
|
|
PT_ADDLINES, PTR_BounceTraverse))
|
|
{ // Could not find a wall, so bounce off the floor/ceiling instead.
|
|
fixed_t floordist = mo->z - mo->floorz;
|
|
fixed_t ceildist = mo->ceilingz - mo->z;
|
|
if (floordist <= ceildist)
|
|
{
|
|
mo->FloorBounceMissile (mo->Sector->floorplane);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
mo->FloorBounceMissile (mo->Sector->ceilingplane);
|
|
return true;
|
|
}
|
|
/*
|
|
else
|
|
{
|
|
return (mo->flags2 & MF2_BOUNCE2) != 0;
|
|
}
|
|
*/
|
|
}
|
|
line = bestslideline;
|
|
|
|
if (line->special == Line_Horizon)
|
|
{
|
|
mo->SeeSound = 0; // it might make a sound otherwise
|
|
mo->Destroy();
|
|
return true;
|
|
}
|
|
|
|
side = P_PointOnLineSide (mo->x, mo->y, line);
|
|
lineangle = R_PointToAngle2 (0, 0, line->dx, line->dy);
|
|
if (side == 1)
|
|
{
|
|
lineangle += ANG180;
|
|
}
|
|
moveangle = R_PointToAngle2 (0, 0, mo->momx, mo->momy);
|
|
deltaangle = (2*lineangle)-moveangle;
|
|
mo->angle = deltaangle;
|
|
|
|
lineangle >>= ANGLETOFINESHIFT;
|
|
deltaangle >>= ANGLETOFINESHIFT;
|
|
|
|
movelen = P_AproxDistance (mo->momx, mo->momy);
|
|
movelen = (movelen * 192) >> 8; // friction
|
|
|
|
fixed_t box[4];
|
|
box[BOXTOP] = mo->y + mo->radius;
|
|
box[BOXBOTTOM] = mo->y - mo->radius;
|
|
box[BOXLEFT] = mo->x - mo->radius;
|
|
box[BOXRIGHT] = mo->x + mo->radius;
|
|
if (P_BoxOnLineSide (box, line) == -1)
|
|
{
|
|
mo->SetOrigin (mo->x + FixedMul(mo->radius,
|
|
finecosine[deltaangle]), mo->y + FixedMul(mo->radius, finesine[deltaangle]), mo->z);;
|
|
}
|
|
if (movelen < FRACUNIT)
|
|
{
|
|
movelen = 2*FRACUNIT;
|
|
}
|
|
mo->momx = FixedMul(movelen, finecosine[deltaangle]);
|
|
mo->momy = FixedMul(movelen, finesine[deltaangle]);
|
|
return true;
|
|
}
|
|
|
|
|
|
//============================================================================
|
|
//
|
|
// Aiming
|
|
//
|
|
//============================================================================
|
|
AActor* linetarget; // who got hit (or NULL)
|
|
AActor* shootthing;
|
|
fixed_t shootz; // Height if not aiming up or down
|
|
fixed_t attackrange;
|
|
fixed_t aimpitch;
|
|
|
|
struct aim_t
|
|
{
|
|
|
|
|
|
fixed_t toppitch, bottompitch;
|
|
AActor * thing_friend, * thing_other;
|
|
angle_t pitch_friend, pitch_other;
|
|
bool notsmart;
|
|
|
|
};
|
|
|
|
aim_t aim;
|
|
|
|
|
|
|
|
//============================================================================
|
|
//
|
|
// PTR_AimTraverse
|
|
// Sets linetaget and aimpitch when a target is aimed at.
|
|
//
|
|
//============================================================================
|
|
|
|
bool PTR_AimTraverse (intercept_t* in)
|
|
{
|
|
fixed_t & toppitch=aim.toppitch;
|
|
fixed_t & bottompitch=aim.bottompitch;
|
|
|
|
line_t* li;
|
|
AActor* th;
|
|
fixed_t pitch;
|
|
fixed_t thingtoppitch;
|
|
fixed_t thingbottompitch;
|
|
fixed_t dist;
|
|
int thingpitch;
|
|
|
|
if (in->isaline)
|
|
{
|
|
li = in->d.line;
|
|
|
|
if ( !(li->flags & ML_TWOSIDED) || (li->flags & ML_BLOCKEVERYTHING) )
|
|
return false; // stop
|
|
|
|
// Crosses a two sided line.
|
|
// A two sided line will restrict the possible target ranges.
|
|
P_LineOpening (li, trace.x + FixedMul (trace.dx, in->frac),
|
|
trace.y + FixedMul (trace.dy, in->frac));
|
|
|
|
if (openbottom >= opentop)
|
|
return false; // stop
|
|
|
|
dist = FixedMul (attackrange, in->frac);
|
|
|
|
pitch = -(int)R_PointToAngle2 (0, shootz, dist, openbottom);
|
|
if (pitch < bottompitch)
|
|
bottompitch = pitch;
|
|
|
|
pitch = -(int)R_PointToAngle2 (0, shootz, dist, opentop);
|
|
if (pitch > toppitch)
|
|
toppitch = pitch;
|
|
|
|
if (toppitch >= bottompitch)
|
|
return false; // stop
|
|
|
|
return true; // shot continues
|
|
}
|
|
|
|
// shoot a thing
|
|
th = in->d.thing;
|
|
if (th == shootthing)
|
|
return true; // can't shoot self
|
|
|
|
if (!(th->flags&MF_SHOOTABLE))
|
|
return true; // corpse or something
|
|
|
|
// check for physical attacks on a ghost
|
|
if ((th->flags3 & MF3_GHOST) &&
|
|
shootthing->player && // [RH] Be sure shootthing is a player
|
|
shootthing->player->ReadyWeapon &&
|
|
(shootthing->player->ReadyWeapon->flags2 & MF2_THRUGHOST))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
dist = FixedMul (attackrange, in->frac);
|
|
// check angles to see if the thing can be aimed at
|
|
|
|
thingtoppitch = -(int)R_PointToAngle2 (0, shootz, dist, th->z + th->height);
|
|
|
|
if (thingtoppitch > bottompitch)
|
|
return true; // shot over the thing
|
|
|
|
thingbottompitch = -(int)R_PointToAngle2 (0, shootz, dist, th->z);
|
|
|
|
if (thingbottompitch < toppitch)
|
|
return true; // shot under the thing
|
|
|
|
// this thing can be hit!
|
|
if (thingtoppitch < toppitch)
|
|
thingtoppitch = toppitch;
|
|
|
|
if (thingbottompitch > bottompitch)
|
|
thingbottompitch = bottompitch;
|
|
|
|
thingpitch = thingtoppitch/2 + thingbottompitch/2;
|
|
|
|
if (sv_smartaim && !aim.notsmart)
|
|
{
|
|
// try to be a little smarter about what to aim at!
|
|
// In particular avoid autoaiming at friends amd barrels.
|
|
if (th->IsFriend(shootthing))
|
|
{
|
|
if (sv_smartaim < 2)
|
|
{
|
|
// friends don't aim at friends (except players), at least not first
|
|
aim.thing_friend=th;
|
|
aim.pitch_friend=thingpitch;
|
|
}
|
|
}
|
|
else if (!(th->flags3&MF3_ISMONSTER) )
|
|
{
|
|
if (sv_smartaim < 3)
|
|
{
|
|
// don't autoaim at barrels and other shootable stuff unless no monsters have been found
|
|
aim.thing_other=th;
|
|
aim.pitch_other=thingpitch;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
linetarget=th;
|
|
aimpitch=thingpitch;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
linetarget=th;
|
|
aimpitch=thingpitch;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// P_AimLineAttack
|
|
//
|
|
//============================================================================
|
|
fixed_t P_AimLineAttack (AActor *t1, angle_t angle, fixed_t distance, fixed_t vrange, bool forcenosmart)
|
|
{
|
|
fixed_t x2;
|
|
fixed_t y2;
|
|
|
|
angle >>= ANGLETOFINESHIFT;
|
|
shootthing = t1;
|
|
|
|
x2 = t1->x + (distance>>FRACBITS)*finecosine[angle];
|
|
y2 = t1->y + (distance>>FRACBITS)*finesine[angle];
|
|
shootz = t1->z + (t1->height>>1) - t1->floorclip;
|
|
if (t1->player != NULL)
|
|
{
|
|
shootz += FixedMul (t1->player->mo->AttackZOffset, t1->player->crouchfactor);
|
|
}
|
|
else
|
|
{
|
|
shootz += 8*FRACUNIT;
|
|
}
|
|
|
|
// can't shoot outside view angles
|
|
if (vrange == 0)
|
|
{
|
|
if (t1->player == NULL || !level.IsFreelookAllowed())
|
|
{
|
|
vrange = ANGLE_1*35;
|
|
}
|
|
else
|
|
{
|
|
// 35 degrees is approximately what Doom used. You cannot have a
|
|
// vrange of 0 degrees, because then toppitch and bottompitch will
|
|
// be equal, and PTR_AimTraverse will never find anything to shoot at
|
|
// if it crosses a line.
|
|
vrange = clamp (t1->player->userinfo.aimdist, ANGLE_1/2, ANGLE_1*35);
|
|
}
|
|
}
|
|
aim.toppitch = t1->pitch - vrange;
|
|
aim.bottompitch = t1->pitch + vrange;
|
|
aim.notsmart = forcenosmart;
|
|
|
|
attackrange = distance;
|
|
linetarget = NULL;
|
|
|
|
// for smart aiming
|
|
aim.thing_friend=aim.thing_other=NULL;
|
|
|
|
// Information for tracking crossed 3D floors
|
|
aimpitch=t1->pitch;
|
|
P_PathTraverse (t1->x, t1->y, x2, y2, PT_ADDLINES|PT_ADDTHINGS, PTR_AimTraverse);
|
|
|
|
if (!linetarget)
|
|
{
|
|
if (aim.thing_other)
|
|
{
|
|
linetarget=aim.thing_other;
|
|
aimpitch=aim.pitch_other;
|
|
}
|
|
else if (aim.thing_friend)
|
|
{
|
|
linetarget=aim.thing_friend;
|
|
aimpitch=aim.pitch_friend;
|
|
}
|
|
}
|
|
return linetarget ? aimpitch : t1->pitch;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
=
|
|
= P_LineAttack
|
|
=
|
|
= if damage == 0, it is just a test trace that will leave linetarget set
|
|
=
|
|
=================
|
|
*/
|
|
|
|
static bool CheckForGhost (FTraceResults &res)
|
|
{
|
|
if (res.HitType != TRACE_HitActor)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check for physical attacks on a ghost
|
|
if (res.Actor->flags3 & MF3_GHOST || res.Actor->flags4 & MF4_SPECTRAL)
|
|
{
|
|
res.HitType = TRACE_HitNone;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool CheckForSpectral (FTraceResults &res)
|
|
{
|
|
if (res.HitType != TRACE_HitActor)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check for physical attacks on spectrals
|
|
if (res.Actor->flags4 & MF4_SPECTRAL)
|
|
{
|
|
res.HitType = TRACE_HitNone;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance,
|
|
int pitch, int damage, FName damageType, const PClass *pufftype)
|
|
{
|
|
fixed_t vx, vy, vz, shootz;
|
|
FTraceResults trace;
|
|
angle_t srcangle = angle;
|
|
int srcpitch = pitch;
|
|
bool hitGhosts;
|
|
bool killPuff = false;
|
|
AActor *puff = NULL;
|
|
|
|
angle >>= ANGLETOFINESHIFT;
|
|
pitch = (angle_t)(pitch) >> ANGLETOFINESHIFT;
|
|
|
|
vx = FixedMul (finecosine[pitch], finecosine[angle]);
|
|
vy = FixedMul (finecosine[pitch], finesine[angle]);
|
|
vz = -finesine[pitch];
|
|
|
|
shootz = t1->z - t1->floorclip + (t1->height>>1);
|
|
if (t1->player != NULL)
|
|
{
|
|
shootz += FixedMul (t1->player->mo->AttackZOffset, t1->player->crouchfactor);
|
|
}
|
|
else
|
|
{
|
|
shootz += 8*FRACUNIT;
|
|
}
|
|
attackrange = distance;
|
|
aimpitch = pitch;
|
|
|
|
hitGhosts = (t1->player != NULL &&
|
|
t1->player->ReadyWeapon != NULL &&
|
|
(t1->player->ReadyWeapon->flags2 & MF2_THRUGHOST));
|
|
|
|
if (!Trace (t1->x, t1->y, shootz, t1->Sector, vx, vy, vz, distance,
|
|
MF_SHOOTABLE, ML_BLOCKEVERYTHING, t1, trace,
|
|
TRACE_NoSky|TRACE_Impact, hitGhosts ? CheckForGhost : CheckForSpectral))
|
|
{ // hit nothing
|
|
AActor *puffDefaults = GetDefaultByType (pufftype);
|
|
if (puffDefaults->ActiveSound)
|
|
{ // Play miss sound
|
|
S_SoundID (t1, CHAN_WEAPON, puffDefaults->ActiveSound, 1, ATTN_NORM);
|
|
}
|
|
if (puffDefaults->flags3 & MF3_ALWAYSPUFF)
|
|
{ // Spawn the puff anyway
|
|
puff = P_SpawnPuff (pufftype, trace.X, trace.Y, trace.Z, angle - ANG180, 2);
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fixed_t hitx = 0, hity = 0, hitz = 0;
|
|
|
|
if (trace.HitType != TRACE_HitActor)
|
|
{
|
|
// position a bit closer for puffs
|
|
if (trace.HitType != TRACE_HitWall || trace.Line->special != Line_Horizon)
|
|
{
|
|
fixed_t closer = trace.Distance - 4*FRACUNIT;
|
|
puff = P_SpawnPuff (pufftype, t1->x + FixedMul (vx, closer),
|
|
t1->y + FixedMul (vy, closer),
|
|
shootz + FixedMul (vz, closer), angle - ANG90, 0);
|
|
}
|
|
|
|
// [RH] Spawn a decal
|
|
if (trace.HitType == TRACE_HitWall && trace.Line->special != Line_Horizon)
|
|
{
|
|
SpawnShootDecal (t1, trace);
|
|
}
|
|
else if (puff != NULL &&
|
|
trace.CrossedWater == NULL &&
|
|
trace.Sector->heightsec == NULL &&
|
|
trace.HitType == TRACE_HitFloor)
|
|
{
|
|
P_HitWater (puff, trace.Sector);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool bloodsplatter = (t1->flags5 & MF5_BLOODSPLATTER) ||
|
|
(t1->player != NULL && t1->player->ReadyWeapon != NULL &&
|
|
(t1->player->ReadyWeapon->WeaponFlags & WIF_AXEBLOOD));
|
|
|
|
bool axeBlood = (t1->player != NULL &&
|
|
t1->player->ReadyWeapon != NULL &&
|
|
(t1->player->ReadyWeapon->WeaponFlags & WIF_AXEBLOOD));
|
|
|
|
// Hit a thing, so it could be either a puff or blood
|
|
hitx = t1->x + FixedMul (vx, trace.Distance);
|
|
hity = t1->y + FixedMul (vy, trace.Distance);
|
|
hitz = shootz + FixedMul (vz, trace.Distance);
|
|
|
|
// Spawn bullet puffs or blood spots, depending on target type.
|
|
AActor *puffDefaults = GetDefaultByType (pufftype);
|
|
if ((puffDefaults->flags3 & MF3_PUFFONACTORS) ||
|
|
(trace.Actor->flags & MF_NOBLOOD) ||
|
|
(trace.Actor->flags2 & (MF2_INVULNERABLE|MF2_DORMANT)))
|
|
{
|
|
puff = P_SpawnPuff (pufftype, hitx, hity, hitz, angle - ANG180, 2, true);
|
|
}
|
|
if (!(GetDefaultByType(pufftype)->flags3&MF3_BLOODLESSIMPACT))
|
|
{
|
|
if (!bloodsplatter && !axeBlood &&
|
|
!(trace.Actor->flags & MF_NOBLOOD) &&
|
|
!(trace.Actor->flags2 & (MF2_INVULNERABLE|MF2_DORMANT)))
|
|
{
|
|
P_SpawnBlood (hitx, hity, hitz, angle - ANG180, damage, trace.Actor);
|
|
}
|
|
|
|
if (damage)
|
|
{
|
|
if (bloodsplatter || axeBlood)
|
|
{
|
|
if (!(trace.Actor->flags&MF_NOBLOOD) &&
|
|
!(trace.Actor->flags2&(MF2_INVULNERABLE|MF2_DORMANT)))
|
|
{
|
|
if (axeBlood)
|
|
{
|
|
P_BloodSplatter2 (hitx, hity, hitz, trace.Actor);
|
|
}
|
|
if (pr_lineattack() < 192)
|
|
{
|
|
P_BloodSplatter (hitx, hity, hitz, trace.Actor);
|
|
}
|
|
}
|
|
}
|
|
// [RH] Stick blood to walls
|
|
P_TraceBleed (damage, trace.X, trace.Y, trace.Z,
|
|
trace.Actor, srcangle, srcpitch);
|
|
}
|
|
}
|
|
if (damage)
|
|
{
|
|
int flags = DMG_INFLICTOR_IS_PUFF;
|
|
// Allow MF5_PIERCEARMOR on a weapon as well.
|
|
if (t1->player != NULL && t1->player->ReadyWeapon != NULL &&
|
|
t1->player->ReadyWeapon->flags5 & MF5_PIERCEARMOR)
|
|
{
|
|
flags |= DMG_NO_ARMOR;
|
|
}
|
|
|
|
if (puff == NULL)
|
|
{
|
|
// Since the puff is the damage inflictor we need it here
|
|
// regardless of whether it is displayed or not.
|
|
puff = P_SpawnPuff (pufftype, hitx, hity, hitz, angle - ANG180, 2, true, true);
|
|
killPuff = true;
|
|
}
|
|
P_DamageMobj (trace.Actor, puff ? puff : t1, t1, damage, damageType, flags);
|
|
}
|
|
}
|
|
if (trace.CrossedWater)
|
|
{
|
|
|
|
if (puff == NULL)
|
|
{ // Spawn puff just to get a mass for the splash
|
|
puff = P_SpawnPuff (pufftype, hitx, hity, hitz, angle - ANG180, 2, true, true);
|
|
killPuff = true;
|
|
}
|
|
SpawnDeepSplash (t1, trace, puff, vx, vy, vz);
|
|
}
|
|
}
|
|
if (killPuff && puff != NULL)
|
|
{
|
|
puff->Destroy();
|
|
puff = NULL;
|
|
}
|
|
return puff;
|
|
}
|
|
|
|
AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance,
|
|
int pitch, int damage, FName damageType, FName pufftype)
|
|
{
|
|
const PClass * type = PClass::FindClass(pufftype);
|
|
if (type == NULL)
|
|
{
|
|
Printf("Attempt to spawn unknown actor type '%s'\n", pufftype.GetChars());
|
|
}
|
|
else
|
|
{
|
|
return P_LineAttack(t1, angle, distance, pitch, damage, damageType, type);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void P_TraceBleed (int damage, fixed_t x, fixed_t y, fixed_t z, AActor *actor, angle_t angle, int pitch)
|
|
{
|
|
if (!cl_bloodsplats)
|
|
return;
|
|
|
|
const char *bloodType = "BloodSplat";
|
|
int count;
|
|
int noise;
|
|
|
|
if ((actor->flags & MF_NOBLOOD) ||
|
|
(actor->flags5 & MF5_NOBLOODDECALS) ||
|
|
(actor->flags2 & (MF2_INVULNERABLE|MF2_DORMANT)) ||
|
|
(actor->player && actor->player->cheats & CF_GODMODE))
|
|
{
|
|
return;
|
|
}
|
|
if (damage < 15)
|
|
{ // For low damages, there is a chance to not spray blood at all
|
|
if (damage <= 10)
|
|
{
|
|
if (pr_tracebleed() < 160)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
count = 1;
|
|
noise = 18;
|
|
}
|
|
else if (damage < 25)
|
|
{
|
|
count = 2;
|
|
noise = 19;
|
|
}
|
|
else
|
|
{ // For high damages, there is a chance to spray just one big glob of blood
|
|
if (pr_tracebleed() < 24)
|
|
{
|
|
bloodType = "BloodSmear";
|
|
count = 1;
|
|
noise = 20;
|
|
}
|
|
else
|
|
{
|
|
count = 3;
|
|
noise = 20;
|
|
}
|
|
}
|
|
|
|
for (; count; --count)
|
|
{
|
|
FTraceResults bleedtrace;
|
|
|
|
angle_t bleedang = (angle + ((pr_tracebleed()-128) << noise)) >> ANGLETOFINESHIFT;
|
|
angle_t bleedpitch = (angle_t)(pitch + ((pr_tracebleed()-128) << noise)) >> ANGLETOFINESHIFT;
|
|
fixed_t vx = FixedMul (finecosine[bleedpitch], finecosine[bleedang]);
|
|
fixed_t vy = FixedMul (finecosine[bleedpitch], finesine[bleedang]);
|
|
fixed_t vz = -finesine[bleedpitch];
|
|
|
|
if (Trace (x, y, z, actor->Sector,
|
|
vx, vy, vz, 172*FRACUNIT, 0, ML_BLOCKEVERYTHING, actor,
|
|
bleedtrace, TRACE_NoSky))
|
|
{
|
|
if (bleedtrace.HitType == TRACE_HitWall)
|
|
{
|
|
PalEntry bloodcolor = (PalEntry)actor->GetClass()->Meta.GetMetaInt(AMETA_BloodColor);
|
|
if (bloodcolor != 0)
|
|
{
|
|
bloodcolor.r>>=1; // the full color is too bright for blood decals
|
|
bloodcolor.g>>=1;
|
|
bloodcolor.b>>=1;
|
|
bloodcolor.a=1;
|
|
}
|
|
|
|
DImpactDecal::StaticCreate (bloodType,
|
|
bleedtrace.X, bleedtrace.Y, bleedtrace.Z,
|
|
sides + bleedtrace.Line->sidenum[bleedtrace.Side],
|
|
bloodcolor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void P_TraceBleed (int damage, AActor *target, angle_t angle, int pitch)
|
|
{
|
|
P_TraceBleed (damage, target->x, target->y, target->z + target->height/2,
|
|
target, angle, pitch);
|
|
}
|
|
|
|
void P_TraceBleed (int damage, AActor *target, AActor *missile)
|
|
{
|
|
int pitch;
|
|
|
|
if (target == NULL || missile->flags3 & MF3_BLOODLESSIMPACT)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (missile->momz != 0)
|
|
{
|
|
double aim;
|
|
|
|
aim = atan ((double)missile->momz / (double)P_AproxDistance (missile->x - target->x, missile->y - target->y));
|
|
pitch = -(int)(aim * ANGLE_180/PI);
|
|
}
|
|
else
|
|
{
|
|
pitch = 0;
|
|
}
|
|
P_TraceBleed (damage, target->x, target->y, target->z + target->height/2,
|
|
target, R_PointToAngle2 (missile->x, missile->y, target->x, target->y),
|
|
pitch);
|
|
}
|
|
|
|
void P_TraceBleed (int damage, AActor *target)
|
|
{
|
|
fixed_t one = pr_tracebleed() << 24;
|
|
fixed_t two = (pr_tracebleed()-128) << 16;
|
|
|
|
P_TraceBleed (damage, target->x, target->y, target->z + target->height/2,
|
|
target, one, two);
|
|
}
|
|
|
|
//
|
|
// [RH] Rail gun stuffage
|
|
//
|
|
struct SRailHit
|
|
{
|
|
AActor *HitActor;
|
|
fixed_t Distance;
|
|
};
|
|
static TArray<SRailHit> RailHits (16);
|
|
|
|
static bool ProcessRailHit (FTraceResults &res)
|
|
{
|
|
if (res.HitType != TRACE_HitActor)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Invulnerable things completely block the shot
|
|
if (res.Actor->flags2 & MF2_INVULNERABLE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Save this thing for damaging later, and continue the trace
|
|
SRailHit newhit;
|
|
newhit.HitActor = res.Actor;
|
|
newhit.Distance = res.Distance - 10*FRACUNIT; // put blood in front
|
|
RailHits.Push (newhit);
|
|
|
|
return true;
|
|
}
|
|
|
|
void P_RailAttack (AActor *source, int damage, int offset, int color1, int color2, float maxdiff, bool silent, FName puff)
|
|
{
|
|
fixed_t vx, vy, vz;
|
|
angle_t angle, pitch;
|
|
fixed_t x1, y1;
|
|
FVector3 start, end;
|
|
FTraceResults trace;
|
|
|
|
pitch = (angle_t)(-source->pitch) >> ANGLETOFINESHIFT;
|
|
angle = source->angle >> ANGLETOFINESHIFT;
|
|
|
|
vx = FixedMul (finecosine[pitch], finecosine[angle]);
|
|
vy = FixedMul (finecosine[pitch], finesine[angle]);
|
|
vz = finesine[pitch];
|
|
|
|
x1 = source->x;
|
|
y1 = source->y;
|
|
|
|
shootz = source->z - source->floorclip + (source->height >> 1);
|
|
|
|
if (source->player != NULL)
|
|
{
|
|
shootz += FixedMul (source->player->mo->AttackZOffset, source->player->crouchfactor);
|
|
}
|
|
else
|
|
{
|
|
shootz += 8*FRACUNIT;
|
|
}
|
|
|
|
angle = (source->angle - ANG90) >> ANGLETOFINESHIFT;
|
|
x1 += offset*finecosine[angle];
|
|
y1 += offset*finesine[angle];
|
|
|
|
RailHits.Clear ();
|
|
start.X = FIXED2FLOAT(x1);
|
|
start.Y = FIXED2FLOAT(y1);
|
|
start.Z = FIXED2FLOAT(shootz);
|
|
|
|
Trace (x1, y1, shootz, source->Sector, vx, vy, vz,
|
|
8192*FRACUNIT, MF_SHOOTABLE, ML_BLOCKEVERYTHING, source, trace,
|
|
TRACE_PCross|TRACE_Impact, ProcessRailHit);
|
|
|
|
if (trace.HitType == TRACE_HitWall)
|
|
{
|
|
SpawnShootDecal (source, trace);
|
|
}
|
|
if (trace.HitType == TRACE_HitFloor &&
|
|
trace.CrossedWater == NULL &&
|
|
trace.Sector->heightsec == NULL)
|
|
{
|
|
fixed_t savex, savey, savez;
|
|
fixed_t savefloor, saveceil, savedropoff;
|
|
int savefloorpic;
|
|
sector_t *savefloorsec;
|
|
int saveceilingpic;
|
|
sector_t *saveceilingsec;
|
|
|
|
savex = source->x;
|
|
savey = source->y;
|
|
savez = source->z;
|
|
savefloor = source->floorz;
|
|
saveceil = source->ceilingz;
|
|
savedropoff = source->dropoffz;
|
|
savefloorpic = source->floorpic;
|
|
savefloorsec = source->floorsector;
|
|
saveceilingpic = source->ceilingpic;
|
|
saveceilingsec = source->ceilingsector;
|
|
|
|
source->SetOrigin (trace.X, trace.Y, trace.Z);
|
|
P_HitWater (source, trace.Sector);
|
|
source->SetOrigin (savex, savey, savez);
|
|
|
|
source->floorz = savefloor;
|
|
source->ceilingz = saveceil;
|
|
source->dropoffz = savedropoff;
|
|
source->floorpic = savefloorpic;
|
|
source->floorsector = savefloorsec;
|
|
source->ceilingpic = saveceilingpic;
|
|
source->ceilingsector = saveceilingsec;
|
|
}
|
|
if (trace.CrossedWater)
|
|
{
|
|
AActor *thepuff = Spawn ("BulletPuff", 0, 0, 0, ALLOW_REPLACE);
|
|
if (thepuff != NULL)
|
|
{
|
|
SpawnDeepSplash (source, trace, thepuff, vx, vy, vz);
|
|
thepuff->Destroy ();
|
|
}
|
|
}
|
|
|
|
// Now hurt anything the trace hit
|
|
unsigned int i;
|
|
const PClass *puffclass = PClass::FindClass(puff);
|
|
AActor *puffDefaults = puffclass == NULL? NULL : GetDefaultByType (puffclass);
|
|
FName damagetype = (puffDefaults == NULL || puffDefaults->DamageType == NAME_None) ? FName(NAME_Railgun) : puffDefaults->DamageType;
|
|
|
|
for (i = 0; i < RailHits.Size (); i++)
|
|
{
|
|
fixed_t x, y, z;
|
|
|
|
x = x1 + FixedMul (RailHits[i].Distance, vx);
|
|
y = y1 + FixedMul (RailHits[i].Distance, vy);
|
|
z = shootz + FixedMul (RailHits[i].Distance, vz);
|
|
|
|
if ((RailHits[i].HitActor->flags & MF_NOBLOOD) ||
|
|
(RailHits[i].HitActor->flags2 & (MF2_DORMANT|MF2_INVULNERABLE)))
|
|
{
|
|
if (puffclass != NULL) P_SpawnPuff (puffclass, x, y, z, source->angle - ANG180, 1, true);
|
|
}
|
|
else
|
|
{
|
|
P_SpawnBlood (x, y, z, source->angle - ANG180, damage, RailHits[i].HitActor);
|
|
}
|
|
P_DamageMobj (RailHits[i].HitActor, source, source, damage, damagetype);
|
|
P_TraceBleed (damage, x, y, z, RailHits[i].HitActor, angle, pitch);
|
|
}
|
|
|
|
end.X = FIXED2FLOAT(trace.X);
|
|
end.Y = FIXED2FLOAT(trace.Y);
|
|
end.Z = FIXED2FLOAT(trace.Z);
|
|
P_DrawRailTrail (source, start, end, color1, color2, maxdiff, silent);
|
|
}
|
|
|
|
//
|
|
// [RH] P_AimCamera
|
|
//
|
|
CVAR (Float, chase_height, -8.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
|
CVAR (Float, chase_dist, 90.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
|
fixed_t CameraX, CameraY, CameraZ;
|
|
sector_t *CameraSector;
|
|
|
|
void P_AimCamera (AActor *t1)
|
|
{
|
|
fixed_t distance = (fixed_t)(chase_dist * FRACUNIT);
|
|
angle_t angle = (t1->angle - ANG180) >> ANGLETOFINESHIFT;
|
|
angle_t pitch = (angle_t)(t1->pitch) >> ANGLETOFINESHIFT;
|
|
FTraceResults trace;
|
|
fixed_t vx, vy, vz, sz;
|
|
|
|
vx = FixedMul (finecosine[pitch], finecosine[angle]);
|
|
vy = FixedMul (finecosine[pitch], finesine[angle]);
|
|
vz = finesine[pitch];
|
|
|
|
sz = t1->z - t1->floorclip + t1->height + (fixed_t)(chase_height * FRACUNIT);
|
|
|
|
if (Trace (t1->x, t1->y, sz, t1->Sector,
|
|
vx, vy, vz, distance, 0, 0, NULL, trace) &&
|
|
trace.Distance > 10*FRACUNIT)
|
|
{
|
|
// Position camera slightly in front of hit thing
|
|
fixed_t dist = trace.Distance - 5*FRACUNIT;
|
|
CameraX = t1->x + FixedMul (vx, dist);
|
|
CameraY = t1->y + FixedMul (vy, dist);
|
|
CameraZ = sz + FixedMul (vz, dist);
|
|
}
|
|
else
|
|
{
|
|
CameraX = trace.X;
|
|
CameraY = trace.Y;
|
|
CameraZ = trace.Z;
|
|
}
|
|
CameraSector = trace.Sector;
|
|
}
|
|
|
|
|
|
//
|
|
// USE LINES
|
|
//
|
|
AActor *usething;
|
|
bool foundline;
|
|
|
|
bool PTR_UseTraverse (intercept_t *in)
|
|
{
|
|
// [RH] Check for things to talk with or use a puzzle item on
|
|
if (!in->isaline)
|
|
{
|
|
if (usething==in->d.thing) return true;
|
|
// Check thing
|
|
|
|
// Check for puzzle item use or USESPECIAL flag
|
|
if (in->d.thing->flags5 & MF5_USESPECIAL || in->d.thing->special == USE_PUZZLE_ITEM_SPECIAL)
|
|
{
|
|
if (LineSpecials[in->d.thing->special] (NULL, usething, false,
|
|
in->d.thing->args[0], in->d.thing->args[1], in->d.thing->args[2],
|
|
in->d.thing->args[3], in->d.thing->args[4]))
|
|
return false;
|
|
}
|
|
// Dead things can't talk.
|
|
if (in->d.thing->health <= 0)
|
|
{
|
|
return true;
|
|
}
|
|
// Fighting things don't talk either.
|
|
if (in->d.thing->flags4 & MF4_INCOMBAT)
|
|
{
|
|
return true;
|
|
}
|
|
if (in->d.thing->Conversation != NULL)
|
|
{
|
|
// Give the NPC a chance to play a brief animation
|
|
in->d.thing->ConversationAnimation (0);
|
|
P_StartConversation (in->d.thing, usething, true, true);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// [RH] The range passed to P_PathTraverse was doubled so that it could
|
|
// find things up to 128 units away (for Strife), but it should still reject
|
|
// lines further than 64 units away.
|
|
if (in->frac > FRACUNIT/2)
|
|
{
|
|
P_LineOpening (in->d.line, trace.x + FixedMul (trace.dx, in->frac),
|
|
trace.y + FixedMul (trace.dy, in->frac));
|
|
return openrange>0;
|
|
}
|
|
|
|
if (in->d.line->special == 0 || (GET_SPAC(in->d.line->flags) != SPAC_USETHROUGH &&
|
|
GET_SPAC(in->d.line->flags) != SPAC_USE))
|
|
{
|
|
blocked:
|
|
if (in->d.line->flags & ML_BLOCKEVERYTHING)
|
|
{
|
|
openrange = 0;
|
|
}
|
|
else
|
|
{
|
|
P_LineOpening (in->d.line, trace.x + FixedMul (trace.dx, in->frac),
|
|
trace.y + FixedMul (trace.dy, in->frac));
|
|
}
|
|
if (openrange <= 0 ||
|
|
(in->d.line->special != 0 && (i_compatflags & COMPATF_USEBLOCKING)))
|
|
{
|
|
// [RH] Give sector a chance to intercept the use
|
|
|
|
sector_t * sec;
|
|
|
|
sec = usething->Sector;
|
|
|
|
if (sec->SecActTarget && sec->SecActTarget->TriggerAction (usething, SECSPAC_Use))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
sec = P_PointOnLineSide(usething->x, usething->y, in->d.line) == 0?
|
|
in->d.line->frontsector : in->d.line->backsector;
|
|
|
|
if (sec != NULL && sec->SecActTarget &&
|
|
sec->SecActTarget->TriggerAction (usething, SECSPAC_UseWall))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (usething->player)
|
|
{
|
|
S_Sound (usething, CHAN_VOICE, "*usefail", 1, ATTN_IDLE);
|
|
}
|
|
return false; // can't use through a wall
|
|
}
|
|
foundline = true;
|
|
return true; // not a special line, but keep checking
|
|
}
|
|
|
|
if (P_PointOnLineSide (usething->x, usething->y, in->d.line) == 1)
|
|
// [RH] continue traversal for two-sided lines
|
|
//return in->d.line->backsector != NULL; // don't use back side
|
|
goto blocked; // do a proper check for back sides of triggers
|
|
|
|
P_ActivateLine (in->d.line, usething, 0, SPAC_USE);
|
|
|
|
//WAS can't use more than one special line in a row
|
|
//jff 3/21/98 NOW multiple use allowed with enabling line flag
|
|
//[RH] And now I've changed it again. If the line is of type
|
|
// SPAC_USE, then it eats the use. Everything else passes
|
|
// it through, including SPAC_USETHROUGH.
|
|
if (i_compatflags & COMPATF_USEBLOCKING)
|
|
{
|
|
return GET_SPAC(in->d.line->flags) == SPAC_USETHROUGH;
|
|
}
|
|
else
|
|
{
|
|
return GET_SPAC(in->d.line->flags) != SPAC_USE;
|
|
}
|
|
}
|
|
|
|
// Returns false if a "oof" sound should be made because of a blocking
|
|
// linedef. Makes 2s middles which are impassable, as well as 2s uppers
|
|
// and lowers which block the player, cause the sound effect when the
|
|
// player tries to activate them. Specials are excluded, although it is
|
|
// assumed that all special linedefs within reach have been considered
|
|
// and rejected already (see P_UseLines).
|
|
//
|
|
// by Lee Killough
|
|
//
|
|
|
|
bool PTR_NoWayTraverse (intercept_t *in)
|
|
{
|
|
line_t *ld = in->d.line;
|
|
|
|
// [GrafZahl] de-obfuscated. Was I the only one who was unable to makes sense out of
|
|
// this convoluted mess?
|
|
if (ld->special) return true;
|
|
if (ld->flags&(ML_BLOCKING|ML_BLOCKEVERYTHING|ML_BLOCK_PLAYERS)) return false;
|
|
P_LineOpening(ld, trace.x+FixedMul(trace.dx, in->frac),trace.y+FixedMul(trace.dy, in->frac));
|
|
return openrange >0 &&
|
|
openbottom <= usething->z + usething->MaxStepHeight &&
|
|
opentop >= usething->z + usething->height;
|
|
}
|
|
|
|
/*
|
|
================
|
|
=
|
|
= P_UseLines
|
|
=
|
|
= Looks for special lines in front of the player to activate
|
|
================
|
|
*/
|
|
|
|
void P_UseLines (player_t *player)
|
|
{
|
|
angle_t angle;
|
|
fixed_t x1, y1, x2, y2;
|
|
|
|
usething = player->mo;
|
|
foundline = false;
|
|
|
|
angle = player->mo->angle >> ANGLETOFINESHIFT;
|
|
x1 = player->mo->x;
|
|
y1 = player->mo->y;
|
|
x2 = x1 + (USERANGE>>FRACBITS)*finecosine[angle]*2;
|
|
y2 = y1 + (USERANGE>>FRACBITS)*finesine[angle]*2;
|
|
|
|
// old code:
|
|
//
|
|
// P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse );
|
|
//
|
|
// This added test makes the "oof" sound work on 2s lines -- killough:
|
|
|
|
if (P_PathTraverse (x1, y1, x2, y2, PT_ADDLINES|PT_ADDTHINGS, PTR_UseTraverse))
|
|
{ // [RH] Give sector a chance to eat the use
|
|
sector_t *sec = usething->Sector;
|
|
int spac = SECSPAC_Use;
|
|
if (foundline)
|
|
spac |= SECSPAC_UseWall;
|
|
if ((!sec->SecActTarget ||
|
|
!sec->SecActTarget->TriggerAction (usething, spac)) &&
|
|
!P_PathTraverse (x1, y1, x2, y2, PT_ADDLINES, PTR_NoWayTraverse))
|
|
{
|
|
S_Sound (usething, CHAN_VOICE, "*usefail", 1, ATTN_IDLE);
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PTR_PuzzleItemTraverse
|
|
//
|
|
//==========================================================================
|
|
|
|
static AActor *PuzzleItemUser;
|
|
static int PuzzleItemType;
|
|
static bool PuzzleActivated;
|
|
|
|
bool PTR_PuzzleItemTraverse (intercept_t *in)
|
|
{
|
|
AActor *mobj;
|
|
|
|
if (in->isaline)
|
|
{ // Check line
|
|
if (in->d.line->special != USE_PUZZLE_ITEM_SPECIAL)
|
|
{
|
|
P_LineOpening (in->d.line, trace.x + FixedMul (trace.dx, in->frac),
|
|
trace.y + FixedMul (trace.dy, in->frac));
|
|
if (openrange <= 0)
|
|
{
|
|
return false; // can't use through a wall
|
|
}
|
|
return true; // Continue searching
|
|
}
|
|
if (P_PointOnLineSide (PuzzleItemUser->x, PuzzleItemUser->y,
|
|
in->d.line) == 1)
|
|
{ // Don't use back sides
|
|
return false;
|
|
}
|
|
if (PuzzleItemType != in->d.line->args[0])
|
|
{ // Item type doesn't match
|
|
return false;
|
|
}
|
|
P_StartScript (PuzzleItemUser, in->d.line, in->d.line->args[1], NULL, 0,
|
|
in->d.line->args[2], in->d.line->args[3], in->d.line->args[4], true, false);
|
|
in->d.line->special = 0;
|
|
PuzzleActivated = true;
|
|
return false; // Stop searching
|
|
}
|
|
// Check thing
|
|
mobj = in->d.thing;
|
|
if (mobj->special != USE_PUZZLE_ITEM_SPECIAL)
|
|
{ // Wrong special
|
|
return true;
|
|
}
|
|
if (PuzzleItemType != mobj->args[0])
|
|
{ // Item type doesn't match
|
|
return true;
|
|
}
|
|
P_StartScript (PuzzleItemUser, NULL, mobj->args[1], NULL, 0,
|
|
mobj->args[2], mobj->args[3], mobj->args[4], true, false);
|
|
mobj->special = 0;
|
|
PuzzleActivated = true;
|
|
return false; // Stop searching
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// P_UsePuzzleItem
|
|
//
|
|
// Returns true if the puzzle item was used on a line or a thing.
|
|
//
|
|
//==========================================================================
|
|
|
|
bool P_UsePuzzleItem (AActor *actor, int itemType)
|
|
{
|
|
int angle;
|
|
fixed_t x1, y1, x2, y2;
|
|
|
|
PuzzleItemType = itemType;
|
|
PuzzleItemUser = actor;
|
|
PuzzleActivated = false;
|
|
angle = actor->angle>>ANGLETOFINESHIFT;
|
|
x1 = actor->x;
|
|
y1 = actor->y;
|
|
x2 = x1+(USERANGE>>FRACBITS)*finecosine[angle];
|
|
y2 = y1+(USERANGE>>FRACBITS)*finesine[angle];
|
|
P_PathTraverse (x1, y1, x2, y2, PT_ADDLINES|PT_ADDTHINGS,
|
|
PTR_PuzzleItemTraverse);
|
|
return PuzzleActivated;
|
|
}
|
|
|
|
//
|
|
// RADIUS ATTACK
|
|
//
|
|
AActor* bombsource;
|
|
AActor* bombspot;
|
|
int bombdamage;
|
|
float bombdamagefloat;
|
|
int bombdistance;
|
|
float bombdistancefloat;
|
|
bool DamageSource;
|
|
FName bombmod;
|
|
FVector3 bombvec;
|
|
bool bombdodamage;
|
|
|
|
//=============================================================================
|
|
//
|
|
// PIT_RadiusAttack
|
|
//
|
|
// "bombsource" is the creature that caused the explosion at "bombspot".
|
|
// [RH] Now it knows about vertical distances and can thrust things vertically.
|
|
//=============================================================================
|
|
|
|
// [RH] Damage scale to apply to thing that shot the missile.
|
|
static float selfthrustscale;
|
|
|
|
CUSTOM_CVAR (Float, splashfactor, 1.f, CVAR_SERVERINFO)
|
|
{
|
|
if (self <= 0.f)
|
|
self = 1.f;
|
|
else
|
|
selfthrustscale = 1.f / self;
|
|
}
|
|
|
|
bool PIT_RadiusAttack (AActor *thing)
|
|
{
|
|
if (!(thing->flags & MF_SHOOTABLE) )
|
|
return true;
|
|
|
|
// Boss spider and cyborg and Heretic's ep >= 2 bosses
|
|
// take no damage from concussion.
|
|
if (thing->flags3 & MF3_NORADIUSDMG && !(bombspot->flags4 & MF4_FORCERADIUSDMG))
|
|
return true;
|
|
|
|
if (!DamageSource && thing == bombsource)
|
|
{ // don't damage the source of the explosion
|
|
return true;
|
|
}
|
|
|
|
// a much needed option: monsters that fire explosive projectiles cannot
|
|
// be hurt by projectiles fired by a monster of the same type.
|
|
// Controlled by the DONTHURTSPECIES flag.
|
|
if (bombsource &&
|
|
thing->GetClass() == bombsource->GetClass() &&
|
|
!thing->player &&
|
|
bombsource->flags4 & MF4_DONTHURTSPECIES
|
|
) return true;
|
|
|
|
// Barrels always use the original code, since this makes
|
|
// them far too "active." BossBrains also use the old code
|
|
// because some user levels require they have a height of 16,
|
|
// which can make them near impossible to hit with the new code.
|
|
if (!bombdodamage || !((bombspot->flags5 | thing->flags5) & MF5_OLDRADIUSDMG))
|
|
{
|
|
// [RH] New code. The bounding box only covers the
|
|
// height of the thing and not the height of the map.
|
|
float points;
|
|
float len;
|
|
fixed_t dx, dy;
|
|
float boxradius;
|
|
|
|
dx = abs (thing->x - bombspot->x);
|
|
dy = abs (thing->y - bombspot->y);
|
|
boxradius = float (thing->radius);
|
|
|
|
// The damage pattern is square, not circular.
|
|
len = float (dx > dy ? dx : dy);
|
|
|
|
if (bombspot->z < thing->z || bombspot->z >= thing->z + thing->height)
|
|
{
|
|
float dz;
|
|
|
|
if (bombspot->z > thing->z)
|
|
{
|
|
dz = float (bombspot->z - thing->z - thing->height);
|
|
}
|
|
else
|
|
{
|
|
dz = float (thing->z - bombspot->z);
|
|
}
|
|
if (len <= boxradius)
|
|
{
|
|
len = dz;
|
|
}
|
|
else
|
|
{
|
|
len -= boxradius;
|
|
len = sqrtf (len*len + dz*dz);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
len -= boxradius;
|
|
if (len < 0.f)
|
|
len = 0.f;
|
|
}
|
|
len /= FRACUNIT;
|
|
points = bombdamagefloat * (1.f - len * bombdistancefloat);
|
|
if (thing == bombsource)
|
|
{
|
|
points = points * splashfactor;
|
|
}
|
|
points *= thing->GetClass()->Meta.GetMetaFixed(AMETA_RDFactor, FRACUNIT)/(float)FRACUNIT;
|
|
|
|
if (points > 0.f && P_CheckSight (thing, bombspot, 1))
|
|
{ // OK to damage; target is in direct path
|
|
float momz;
|
|
float thrust;
|
|
int damage = (int)points;
|
|
|
|
if (bombdodamage) P_DamageMobj (thing, bombspot, bombsource, damage, bombmod);
|
|
else if (thing->player == NULL) thing->flags2 |= MF2_BLASTED;
|
|
|
|
if (!(thing->flags & MF_ICECORPSE))
|
|
{
|
|
if (bombdodamage && !(bombspot->flags3 & MF3_BLOODLESSIMPACT)) P_TraceBleed (damage, thing, bombspot);
|
|
|
|
if (!bombdodamage || !(bombspot->flags2 & MF2_NODMGTHRUST))
|
|
{
|
|
thrust = points * 0.5f / (float)thing->Mass;
|
|
if (bombsource == thing)
|
|
{
|
|
thrust *= selfthrustscale;
|
|
}
|
|
momz = (float)(thing->z + (thing->height>>1) - bombspot->z) * thrust;
|
|
if (bombsource != thing)
|
|
{
|
|
momz *= 0.5f;
|
|
}
|
|
else
|
|
{
|
|
momz *= 0.8f;
|
|
}
|
|
angle_t ang = R_PointToAngle2 (bombspot->x, bombspot->y, thing->x, thing->y) >> ANGLETOFINESHIFT;
|
|
thing->momx += fixed_t (finecosine[ang] * thrust);
|
|
thing->momy += fixed_t (finesine[ang] * thrust);
|
|
if (bombdodamage) thing->momz += (fixed_t)momz; // this really doesn't work well
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// [RH] Old code just for barrels
|
|
fixed_t dx, dy, dist;
|
|
|
|
dx = abs (thing->x - bombspot->x);
|
|
dy = abs (thing->y - bombspot->y);
|
|
|
|
dist = dx>dy ? dx : dy;
|
|
dist = (dist - thing->radius) >> FRACBITS;
|
|
|
|
if (dist < 0)
|
|
dist = 0;
|
|
|
|
if (dist >= bombdistance)
|
|
return true; // out of range
|
|
|
|
if (P_CheckSight (thing, bombspot, 1))
|
|
{ // OK to damage; target is in direct path
|
|
int damage = Scale (bombdamage, bombdistance-dist, bombdistance);
|
|
damage = (int)((float)damage * splashfactor);
|
|
|
|
damage = Scale(damage, thing->GetClass()->Meta.GetMetaFixed(AMETA_RDFactor, FRACUNIT), FRACUNIT);
|
|
if (damage > 0)
|
|
{
|
|
P_DamageMobj (thing, bombspot, bombsource, damage, bombmod);
|
|
P_TraceBleed (damage, thing, bombspot);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// P_RadiusAttack
|
|
// Source is the creature that caused the explosion at spot.
|
|
//
|
|
void P_RadiusAttack (AActor *spot, AActor *source, int damage, int distance, FName damageType,
|
|
bool hurtSource, bool dodamage)
|
|
{
|
|
static TArray<AActor *> radbt;
|
|
|
|
int x, y;
|
|
int xl, xh, yl, yh;
|
|
fixed_t dist;
|
|
|
|
if (distance <= 0)
|
|
return;
|
|
|
|
dist = (distance + MAXRADIUS)<<FRACBITS;
|
|
yh = (spot->y + dist - bmaporgy)>>MAPBLOCKSHIFT;
|
|
yl = (spot->y - dist - bmaporgy)>>MAPBLOCKSHIFT;
|
|
xh = (spot->x + dist - bmaporgx)>>MAPBLOCKSHIFT;
|
|
xl = (spot->x - dist - bmaporgx)>>MAPBLOCKSHIFT;
|
|
bombspot = spot;
|
|
bombsource = source;
|
|
bombdamage = damage;
|
|
bombdistance = distance;
|
|
bombdistancefloat = 1.f / (float)distance;
|
|
DamageSource = hurtSource;
|
|
bombdamagefloat = (float)damage;
|
|
bombmod = damageType;
|
|
bombdodamage = dodamage;
|
|
bombvec.X = FIXED2FLOAT(spot->x);
|
|
bombvec.Y = FIXED2FLOAT(spot->y);
|
|
bombvec.Z = FIXED2FLOAT(spot->z);
|
|
|
|
radbt.Clear();
|
|
|
|
for (y = yl; y <= yh; y++)
|
|
for (x = xl; x <= xh; x++)
|
|
P_BlockThingsIterator (x, y, PIT_RadiusAttack, radbt);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// SECTOR HEIGHT CHANGING
|
|
// After modifying a sector's floor or ceiling height,
|
|
// call this routine to adjust the positions
|
|
// of all things that touch the sector.
|
|
//
|
|
// If anything doesn't fit anymore, true will be returned.
|
|
//
|
|
// [RH] If crushchange is non-negative, they will take the
|
|
// specified amount of damage as they are being crushed.
|
|
// If crushchange is negative, you should set the sector
|
|
// height back the way it was and call P_ChangeSector()
|
|
// again to undo the changes.
|
|
// Note that this is very different from the original
|
|
// true/false usage of crushchange! If you want regular
|
|
// DOOM crushing behavior set crushchange to 10 or -1
|
|
// if no crushing is desired.
|
|
//
|
|
static int moveamt;
|
|
int crushchange;
|
|
static sector_t *movesec;
|
|
bool nofit;
|
|
TArray<AActor *> intersectors;
|
|
|
|
EXTERN_CVAR (Int, cl_bloodtype)
|
|
|
|
//=============================================================================
|
|
//
|
|
// P_AdjustFloorCeil
|
|
//
|
|
//=============================================================================
|
|
|
|
bool P_AdjustFloorCeil (AActor *thing)
|
|
{
|
|
bool isgood = P_CheckPosition (thing, thing->x, thing->y);
|
|
thing->floorz = tmfloorz;
|
|
thing->ceilingz = tmceilingz;
|
|
thing->dropoffz = tmdropoffz; // killough 11/98: remember dropoffs
|
|
thing->floorpic = tmfloorpic;
|
|
thing->floorsector = tmfloorsector;
|
|
thing->ceilingpic = tmceilingpic;
|
|
thing->ceilingsector = tmceilingsector;
|
|
return isgood;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// PIT_FindAboveIntersectors
|
|
//
|
|
//=============================================================================
|
|
|
|
bool PIT_FindAboveIntersectors (AActor *thing)
|
|
{
|
|
if (!(thing->flags & MF_SOLID))
|
|
{ // Can't hit thing
|
|
return true;
|
|
}
|
|
if (thing->flags & (MF_CORPSE|MF_SPECIAL))
|
|
{ // [RH] Corpses and specials don't block moves
|
|
return true;
|
|
}
|
|
if (thing == tmthing)
|
|
{ // Don't clip against self
|
|
return true;
|
|
}
|
|
fixed_t blockdist = thing->radius+tmthing->radius;
|
|
if (abs(thing->x-tmx) >= blockdist || abs(thing->y-tmy) >= blockdist)
|
|
{ // Didn't hit thing
|
|
return true;
|
|
}
|
|
if (thing->z >= tmthing->z &&
|
|
thing->z <= tmthing->z + tmthing->height)
|
|
{ // Thing intersects above the base
|
|
intersectors.Push (thing);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// PIT_FindBelowIntersectors
|
|
//
|
|
//=============================================================================
|
|
|
|
bool PIT_FindBelowIntersectors (AActor *thing)
|
|
{
|
|
if (!(thing->flags & MF_SOLID))
|
|
{ // Can't hit thing
|
|
return true;
|
|
}
|
|
if (thing->flags & (MF_CORPSE|MF_SPECIAL))
|
|
{ // [RH] Corpses and specials don't block moves
|
|
return true;
|
|
}
|
|
if (thing == tmthing)
|
|
{ // Don't clip against self
|
|
return true;
|
|
}
|
|
fixed_t blockdist = thing->radius+tmthing->radius;
|
|
if (abs(thing->x-tmx) >= blockdist || abs(thing->y-tmy) >= blockdist)
|
|
{ // Didn't hit thing
|
|
return true;
|
|
}
|
|
if (thing->z + thing->height <= tmthing->z + tmthing->height &&
|
|
thing->z + thing->height > tmthing->z)
|
|
{ // Thing intersects below the base
|
|
intersectors.Push (thing);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// P_FindAboveIntersectors
|
|
//
|
|
//=============================================================================
|
|
|
|
void P_FindAboveIntersectors (AActor *actor)
|
|
{
|
|
static TArray<AActor *> abovebt;
|
|
|
|
int xl,xh,yl,yh,bx,by;
|
|
fixed_t x, y;
|
|
|
|
if (actor->flags & MF_NOCLIP)
|
|
return;
|
|
|
|
if (!(actor->flags & MF_SOLID))
|
|
return;
|
|
|
|
tmx = x = actor->x;
|
|
tmy = y = actor->y;
|
|
tmthing = actor;
|
|
|
|
tmbbox[BOXTOP] = y + actor->radius;
|
|
tmbbox[BOXBOTTOM] = y - actor->radius;
|
|
tmbbox[BOXRIGHT] = x + actor->radius;
|
|
tmbbox[BOXLEFT] = x - actor->radius;
|
|
//
|
|
// the bounding box is extended by MAXRADIUS because actors are grouped
|
|
// into mapblocks based on their origin point, and can overlap into adjacent
|
|
// blocks by up to MAXRADIUS units
|
|
//
|
|
xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
|
|
abovebt.Clear();
|
|
|
|
for (bx = xl; bx <= xh; bx++)
|
|
for (by = yl; by <= yh; by++)
|
|
if (!P_BlockThingsIterator (bx, by, PIT_FindAboveIntersectors, abovebt))
|
|
return;
|
|
|
|
return;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// P_FindBelowIntersectors
|
|
//
|
|
//=============================================================================
|
|
|
|
void P_FindBelowIntersectors (AActor *actor)
|
|
{
|
|
static TArray<AActor *> belowbt;
|
|
|
|
int xl,xh,yl,yh,bx,by;
|
|
fixed_t x, y;
|
|
|
|
if (actor->flags & MF_NOCLIP)
|
|
return;
|
|
|
|
if (!(actor->flags & MF_SOLID))
|
|
return;
|
|
|
|
tmx = x = actor->x;
|
|
tmy = y = actor->y;
|
|
tmthing = actor;
|
|
|
|
tmbbox[BOXTOP] = y + actor->radius;
|
|
tmbbox[BOXBOTTOM] = y - actor->radius;
|
|
tmbbox[BOXRIGHT] = x + actor->radius;
|
|
tmbbox[BOXLEFT] = x - actor->radius;
|
|
//
|
|
// the bounding box is extended by MAXRADIUS because actors are grouped
|
|
// into mapblocks based on their origin point, and can overlap into adjacent
|
|
// blocks by up to MAXRADIUS units
|
|
//
|
|
xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
|
|
belowbt.Clear();
|
|
|
|
for (bx = xl; bx <= xh; bx++)
|
|
for (by = yl; by <= yh; by++)
|
|
if (!P_BlockThingsIterator (bx, by, PIT_FindBelowIntersectors, belowbt))
|
|
return;
|
|
|
|
return;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// P_DoCrunch
|
|
//
|
|
//=============================================================================
|
|
|
|
void P_DoCrunch (AActor *thing)
|
|
{
|
|
// crunch bodies to giblets
|
|
if ((thing->flags & MF_CORPSE) &&
|
|
!(thing->flags3 & MF3_DONTGIB) &&
|
|
(thing->health <= 0))
|
|
{
|
|
FState * state = thing->FindState(NAME_Crush);
|
|
if (state != NULL && !(thing->flags & MF_ICECORPSE))
|
|
{
|
|
// Clear MF_CORPSE so that this isn't done more than once
|
|
thing->flags &= ~(MF_CORPSE|MF_SOLID);
|
|
thing->height = thing->radius = 0;
|
|
thing->SetState (state);
|
|
return;
|
|
}
|
|
if (!(thing->flags & MF_NOBLOOD))
|
|
{
|
|
AActor *gib = Spawn ("RealGibs", thing->x, thing->y, thing->z, ALLOW_REPLACE);
|
|
gib->RenderStyle = thing->RenderStyle;
|
|
gib->alpha = thing->alpha;
|
|
gib->height = 0;
|
|
gib->radius = 0;
|
|
S_Sound (thing, CHAN_BODY, "misc/fallingsplat", 1, ATTN_IDLE);
|
|
|
|
PalEntry bloodcolor = (PalEntry)thing->GetClass()->Meta.GetMetaInt(AMETA_BloodColor);
|
|
if (bloodcolor!=0) gib->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a);
|
|
}
|
|
if (thing->flags & MF_ICECORPSE)
|
|
{
|
|
thing->tics = 1;
|
|
thing->momx = thing->momy = thing->momz = 0;
|
|
}
|
|
else if (thing->player)
|
|
{
|
|
thing->flags |= MF_NOCLIP;
|
|
thing->flags3 |= MF3_DONTGIB;
|
|
thing->renderflags |= RF_INVISIBLE;
|
|
}
|
|
else
|
|
{
|
|
thing->Destroy ();
|
|
}
|
|
return; // keep checking
|
|
}
|
|
|
|
// crunch dropped items
|
|
if (thing->flags & MF_DROPPED)
|
|
{
|
|
thing->Destroy ();
|
|
return; // keep checking
|
|
}
|
|
|
|
if (!(thing->flags & MF_SOLID) || (thing->flags & MF_NOCLIP))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!(thing->flags & MF_SHOOTABLE))
|
|
{
|
|
return; // assume it is bloody gibs or something
|
|
}
|
|
|
|
nofit = true;
|
|
|
|
if ((crushchange > 0) && !(level.maptime & 3))
|
|
{
|
|
P_DamageMobj (thing, NULL, NULL, crushchange, NAME_Crush);
|
|
|
|
// spray blood in a random direction
|
|
if ((!(thing->flags&MF_NOBLOOD)) &&
|
|
(!(thing->flags2&(MF2_INVULNERABLE|MF2_DORMANT))))
|
|
{
|
|
PalEntry bloodcolor = (PalEntry)thing->GetClass()->Meta.GetMetaInt(AMETA_BloodColor);
|
|
const PClass *bloodcls = PClass::FindClass((ENamedName)thing->GetClass()->Meta.GetMetaInt(AMETA_BloodType, NAME_Blood));
|
|
|
|
P_TraceBleed (crushchange, thing);
|
|
if (cl_bloodtype <= 1 && bloodcls != NULL)
|
|
{
|
|
AActor *mo;
|
|
|
|
mo = Spawn (bloodcls, thing->x, thing->y,
|
|
thing->z + thing->height/2, ALLOW_REPLACE);
|
|
|
|
mo->momx = pr_crunch.Random2 () << 12;
|
|
mo->momy = pr_crunch.Random2 () << 12;
|
|
if (bloodcolor != 0 && !(mo->flags2 & MF2_DONTTRANSLATE))
|
|
{
|
|
mo->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a);
|
|
}
|
|
}
|
|
if (cl_bloodtype >= 1)
|
|
{
|
|
angle_t an;
|
|
|
|
an = (M_Random () - 128) << 24;
|
|
P_DrawSplash2 (32, thing->x, thing->y,
|
|
thing->z + thing->height/2, an, 2, bloodcolor);
|
|
}
|
|
}
|
|
}
|
|
|
|
// keep checking (crush other things)
|
|
return;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// P_PushUp
|
|
//
|
|
// Returns 0 if thing fits, 1 if ceiling got in the way, or 2 if something
|
|
// above it didn't fit.
|
|
//=============================================================================
|
|
|
|
int P_PushUp (AActor *thing)
|
|
{
|
|
unsigned int firstintersect = intersectors.Size ();
|
|
unsigned int lastintersect;
|
|
int mymass = thing->Mass;
|
|
|
|
if (thing->z + thing->height > thing->ceilingz)
|
|
{
|
|
return 1;
|
|
}
|
|
P_FindAboveIntersectors (thing);
|
|
lastintersect = intersectors.Size ();
|
|
for (; firstintersect < lastintersect; firstintersect++)
|
|
{
|
|
AActor *intersect = intersectors[firstintersect];
|
|
if (!(intersect->flags2 & MF2_PASSMOBJ) ||
|
|
(!(intersect->flags3 & MF3_ISMONSTER) &&
|
|
intersect->Mass > mymass))
|
|
{ // Can't push things more massive than ourself
|
|
return 2;
|
|
}
|
|
fixed_t oldz = intersect->z;
|
|
P_AdjustFloorCeil (intersect);
|
|
intersect->z = thing->z + thing->height + 1;
|
|
if (P_PushUp (intersect))
|
|
{ // Move blocked
|
|
P_DoCrunch (intersect);
|
|
intersect->z = oldz;
|
|
return 2;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// P_PushDown
|
|
//
|
|
// Returns 0 if thing fits, 1 if floor got in the way, or 2 if something
|
|
// below it didn't fit.
|
|
//=============================================================================
|
|
|
|
int P_PushDown (AActor *thing)
|
|
{
|
|
unsigned int firstintersect = intersectors.Size ();
|
|
unsigned int lastintersect;
|
|
int mymass = thing->Mass;
|
|
|
|
if (thing->z <= thing->floorz)
|
|
{
|
|
return 1;
|
|
}
|
|
P_FindBelowIntersectors (thing);
|
|
lastintersect = intersectors.Size ();
|
|
for (; firstintersect < lastintersect; firstintersect++)
|
|
{
|
|
AActor *intersect = intersectors[firstintersect];
|
|
if (!(intersect->flags2 & MF2_PASSMOBJ) ||
|
|
(!(intersect->flags3 & MF3_ISMONSTER) &&
|
|
intersect->Mass > mymass))
|
|
{ // Can't push things more massive than ourself
|
|
return 2;
|
|
}
|
|
fixed_t oldz = intersect->z;
|
|
P_AdjustFloorCeil (intersect);
|
|
if (oldz > thing->z - intersect->height)
|
|
{ // Only push things down, not up.
|
|
intersect->z = thing->z - intersect->height;
|
|
if (P_PushDown (intersect))
|
|
{ // Move blocked
|
|
P_DoCrunch (intersect);
|
|
intersect->z = oldz;
|
|
return 2;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// PIT_FloorDrop
|
|
//
|
|
//=============================================================================
|
|
|
|
void PIT_FloorDrop (AActor *thing)
|
|
{
|
|
fixed_t oldfloorz = thing->floorz;
|
|
|
|
P_AdjustFloorCeil (thing);
|
|
|
|
if (thing->momz == 0 &&
|
|
(!(thing->flags & MF_NOGRAVITY) ||
|
|
(thing->z == oldfloorz && !(thing->flags & MF_NOLIFTDROP))))
|
|
{
|
|
fixed_t oldz = thing->z;
|
|
|
|
// If float bob, always stay the same approximate distance above
|
|
// the floor; otherwise only move things standing on the floor,
|
|
// and only do it if the drop is slow enough.
|
|
if (thing->flags2 & MF2_FLOATBOB)
|
|
{
|
|
thing->z = thing->z - oldfloorz + thing->floorz;
|
|
P_CheckFakeFloorTriggers (thing, oldz);
|
|
}
|
|
else if ((thing->flags & MF_NOGRAVITY) ||
|
|
((!(level.flags & LEVEL_HEXENFORMAT) || moveamt < 9*FRACUNIT)
|
|
&& thing->z - thing->floorz <= moveamt))
|
|
{
|
|
thing->z = thing->floorz;
|
|
P_CheckFakeFloorTriggers (thing, oldz);
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// PIT_FloorRaise
|
|
//
|
|
//=============================================================================
|
|
|
|
void PIT_FloorRaise (AActor *thing)
|
|
{
|
|
fixed_t oldfloorz = thing->floorz;
|
|
|
|
P_AdjustFloorCeil (thing);
|
|
|
|
// Move things intersecting the floor up
|
|
if (thing->z <= thing->floorz ||
|
|
(!(thing->flags & MF_NOGRAVITY) && (thing->flags2 & MF2_FLOATBOB)))
|
|
{
|
|
intersectors.Clear ();
|
|
fixed_t oldz = thing->z;
|
|
if (!(thing->flags2 & MF2_FLOATBOB))
|
|
{
|
|
thing->z = thing->floorz;
|
|
}
|
|
else
|
|
{
|
|
thing->z = thing->z - oldfloorz + thing->floorz;
|
|
}
|
|
switch (P_PushUp (thing))
|
|
{
|
|
default:
|
|
P_CheckFakeFloorTriggers (thing, oldz);
|
|
break;
|
|
case 1:
|
|
P_DoCrunch (thing);
|
|
P_CheckFakeFloorTriggers (thing, oldz);
|
|
break;
|
|
case 2:
|
|
P_DoCrunch (thing);
|
|
thing->z = oldz;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// PIT_CeilingLower
|
|
//
|
|
//=============================================================================
|
|
|
|
void PIT_CeilingLower (AActor *thing)
|
|
{
|
|
bool onfloor;
|
|
|
|
onfloor = thing->z <= thing->floorz;
|
|
P_AdjustFloorCeil (thing);
|
|
|
|
if (thing->z + thing->height > thing->ceilingz)
|
|
{
|
|
intersectors.Clear ();
|
|
fixed_t oldz = thing->z;
|
|
if (thing->ceilingz - thing->height >= thing->floorz)
|
|
{
|
|
thing->z = thing->ceilingz - thing->height;
|
|
}
|
|
else
|
|
{
|
|
thing->z = thing->floorz;
|
|
}
|
|
switch (P_PushDown (thing))
|
|
{
|
|
case 2:
|
|
// intentional fall-through
|
|
case 1:
|
|
if (onfloor)
|
|
thing->z = thing->floorz;
|
|
P_DoCrunch (thing);
|
|
P_CheckFakeFloorTriggers (thing, oldz);
|
|
break;
|
|
default:
|
|
P_CheckFakeFloorTriggers (thing, oldz);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// PIT_CeilingRaise
|
|
//
|
|
//=============================================================================
|
|
|
|
void PIT_CeilingRaise (AActor *thing)
|
|
{
|
|
bool isgood = P_AdjustFloorCeil (thing);
|
|
|
|
// For DOOM compatibility, only move things that are inside the floor.
|
|
// (or something else?) Things marked as hanging from the ceiling will
|
|
// stay where they are.
|
|
if (thing->z < thing->floorz &&
|
|
thing->z + thing->height >= thing->ceilingz - moveamt &&
|
|
!(thing->flags & MF_NOLIFTDROP))
|
|
{
|
|
fixed_t oldz = thing->z;
|
|
thing->z = thing->floorz;
|
|
if (thing->z + thing->height > thing->ceilingz)
|
|
{
|
|
thing->z = thing->ceilingz - thing->height;
|
|
}
|
|
P_CheckFakeFloorTriggers (thing, oldz);
|
|
}
|
|
else if ((thing->flags2 & MF2_PASSMOBJ) && !isgood && thing->z + thing->height < thing->ceilingz)
|
|
{
|
|
if (!P_TestMobjZ (thing) && onmobj->z <= thing->z)
|
|
{
|
|
thing->z = MIN (thing->ceilingz - thing->height,
|
|
onmobj->z + onmobj->height);
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// P_ChangeSector [RH] Was P_CheckSector in BOOM
|
|
//
|
|
// jff 3/19/98 added to just check monsters on the periphery
|
|
// of a moving sector instead of all in bounding box of the
|
|
// sector. Both more accurate and faster.
|
|
//
|
|
//=============================================================================
|
|
|
|
bool P_ChangeSector (sector_t *sector, int crunch, int amt, int floorOrCeil)
|
|
{
|
|
void (*iterator)(AActor *);
|
|
msecnode_t *n;
|
|
|
|
nofit = false;
|
|
crushchange = crunch;
|
|
moveamt = abs (amt);
|
|
movesec = sector;
|
|
|
|
// [RH] Use different functions for the four different types of sector
|
|
// movement. Also update the soundorg's z-coordinate for 3D sound.
|
|
if (floorOrCeil == 0)
|
|
{ // floor
|
|
iterator = (amt < 0) ? PIT_FloorDrop : PIT_FloorRaise;
|
|
sector->soundorg[2] = sector->floorplane.ZatPoint (sector->soundorg[0], sector->soundorg[1]);
|
|
}
|
|
else
|
|
{ // ceiling
|
|
iterator = (amt < 0) ? PIT_CeilingLower : PIT_CeilingRaise;
|
|
sector->soundorg[2] = sector->ceilingplane.ZatPoint (sector->soundorg[0], sector->soundorg[1]);
|
|
}
|
|
|
|
// killough 4/4/98: scan list front-to-back until empty or exhausted,
|
|
// restarting from beginning after each thing is processed. Avoids
|
|
// crashes, and is sure to examine all things in the sector, and only
|
|
// the things which are in the sector, until a steady-state is reached.
|
|
// Things can arbitrarily be inserted and removed and it won't mess up.
|
|
//
|
|
// killough 4/7/98: simplified to avoid using complicated counter
|
|
|
|
// Mark all things invalid
|
|
|
|
for (n = sector->touching_thinglist; n; n = n->m_snext)
|
|
n->visited = false;
|
|
|
|
do
|
|
{
|
|
for (n = sector->touching_thinglist; n; n = n->m_snext) // go through list
|
|
{
|
|
if (!n->visited) // unprocessed thing found
|
|
{
|
|
n->visited = true; // mark thing as processed
|
|
if (!(n->m_thing->flags & MF_NOBLOCKMAP)) //jff 4/7/98 don't do these
|
|
iterator (n->m_thing); // process it
|
|
break; // exit and start over
|
|
}
|
|
}
|
|
} while (n); // repeat from scratch until all things left are marked valid
|
|
|
|
return nofit;
|
|
}
|
|
|
|
//=============================================================================
|
|
// phares 3/21/98
|
|
//
|
|
// Maintain a freelist of msecnode_t's to reduce memory allocs and frees.
|
|
//=============================================================================
|
|
|
|
msecnode_t *headsecnode = NULL;
|
|
|
|
//=============================================================================
|
|
//
|
|
// P_GetSecnode
|
|
//
|
|
// Retrieve a node from the freelist. The calling routine
|
|
// should make sure it sets all fields properly.
|
|
//
|
|
//=============================================================================
|
|
|
|
msecnode_t *P_GetSecnode()
|
|
{
|
|
msecnode_t *node;
|
|
|
|
if (headsecnode)
|
|
{
|
|
node = headsecnode;
|
|
headsecnode = headsecnode->m_snext;
|
|
}
|
|
else
|
|
{
|
|
node = (msecnode_t *)M_Malloc (sizeof(*node));
|
|
}
|
|
return node;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// P_PutSecnode
|
|
//
|
|
// Returns a node to the freelist.
|
|
//
|
|
//=============================================================================
|
|
|
|
void P_PutSecnode (msecnode_t *node)
|
|
{
|
|
node->m_snext = headsecnode;
|
|
headsecnode = node;
|
|
}
|
|
|
|
//=============================================================================
|
|
// phares 3/16/98
|
|
//
|
|
// P_AddSecnode
|
|
//
|
|
// Searches the current list to see if this sector is
|
|
// already there. If not, it adds a sector node at the head of the list of
|
|
// sectors this object appears in. This is called when creating a list of
|
|
// nodes that will get linked in later. Returns a pointer to the new node.
|
|
//
|
|
//=============================================================================
|
|
|
|
msecnode_t *P_AddSecnode (sector_t *s, AActor *thing, msecnode_t *nextnode)
|
|
{
|
|
msecnode_t *node;
|
|
|
|
if (s == 0)
|
|
{
|
|
I_FatalError ("AddSecnode of 0 for %s\n", thing->_StaticType.TypeName.GetChars());
|
|
}
|
|
|
|
node = nextnode;
|
|
while (node)
|
|
{
|
|
if (node->m_sector == s) // Already have a node for this sector?
|
|
{
|
|
node->m_thing = thing; // Yes. Setting m_thing says 'keep it'.
|
|
return nextnode;
|
|
}
|
|
node = node->m_tnext;
|
|
}
|
|
|
|
// Couldn't find an existing node for this sector. Add one at the head
|
|
// of the list.
|
|
|
|
node = P_GetSecnode();
|
|
|
|
// killough 4/4/98, 4/7/98: mark new nodes unvisited.
|
|
node->visited = 0;
|
|
|
|
node->m_sector = s; // sector
|
|
node->m_thing = thing; // mobj
|
|
node->m_tprev = NULL; // prev node on Thing thread
|
|
node->m_tnext = nextnode; // next node on Thing thread
|
|
if (nextnode)
|
|
nextnode->m_tprev = node; // set back link on Thing
|
|
|
|
// Add new node at head of sector thread starting at s->touching_thinglist
|
|
|
|
node->m_sprev = NULL; // prev node on sector thread
|
|
node->m_snext = s->touching_thinglist; // next node on sector thread
|
|
if (s->touching_thinglist)
|
|
node->m_snext->m_sprev = node;
|
|
s->touching_thinglist = node;
|
|
return node;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// P_DelSecnode
|
|
//
|
|
// Deletes a sector node from the list of
|
|
// sectors this object appears in. Returns a pointer to the next node
|
|
// on the linked list, or NULL.
|
|
//
|
|
//=============================================================================
|
|
|
|
msecnode_t *P_DelSecnode (msecnode_t *node)
|
|
{
|
|
msecnode_t* tp; // prev node on thing thread
|
|
msecnode_t* tn; // next node on thing thread
|
|
msecnode_t* sp; // prev node on sector thread
|
|
msecnode_t* sn; // next node on sector thread
|
|
|
|
if (node)
|
|
{
|
|
// Unlink from the Thing thread. The Thing thread begins at
|
|
// sector_list and not from AActor->touching_sectorlist.
|
|
|
|
tp = node->m_tprev;
|
|
tn = node->m_tnext;
|
|
if (tp)
|
|
tp->m_tnext = tn;
|
|
if (tn)
|
|
tn->m_tprev = tp;
|
|
|
|
// Unlink from the sector thread. This thread begins at
|
|
// sector_t->touching_thinglist.
|
|
|
|
sp = node->m_sprev;
|
|
sn = node->m_snext;
|
|
if (sp)
|
|
sp->m_snext = sn;
|
|
else
|
|
node->m_sector->touching_thinglist = sn;
|
|
if (sn)
|
|
sn->m_sprev = sp;
|
|
|
|
// Return this node to the freelist
|
|
|
|
P_PutSecnode(node);
|
|
return tn;
|
|
}
|
|
return NULL;
|
|
} // phares 3/13/98
|
|
|
|
//=============================================================================
|
|
//
|
|
// P_DelSector_List
|
|
//
|
|
// Deletes the sector_list and NULLs it.
|
|
//
|
|
//=============================================================================
|
|
|
|
void P_DelSector_List ()
|
|
{
|
|
if (sector_list != NULL)
|
|
{
|
|
P_DelSeclist (sector_list);
|
|
sector_list = NULL;
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// P_DelSeclist
|
|
//
|
|
// Delete an entire sector list
|
|
//
|
|
//=============================================================================
|
|
|
|
void P_DelSeclist (msecnode_t *node)
|
|
{
|
|
while (node)
|
|
node = P_DelSecnode (node);
|
|
}
|
|
|
|
//=============================================================================
|
|
// phares 3/14/98
|
|
//
|
|
// PIT_GetSectors
|
|
//
|
|
// Locates all the sectors the object is in by looking at the lines that
|
|
// cross through it. You have already decided that the object is allowed
|
|
// at this location, so don't bother with checking impassable or
|
|
// blocking lines.
|
|
//=============================================================================
|
|
|
|
bool PIT_GetSectors (line_t *ld)
|
|
{
|
|
if (tmbbox[BOXRIGHT] <= ld->bbox[BOXLEFT] ||
|
|
tmbbox[BOXLEFT] >= ld->bbox[BOXRIGHT] ||
|
|
tmbbox[BOXTOP] <= ld->bbox[BOXBOTTOM] ||
|
|
tmbbox[BOXBOTTOM] >= ld->bbox[BOXTOP])
|
|
return true;
|
|
|
|
if (P_BoxOnLineSide (tmbbox, ld) != -1)
|
|
return true;
|
|
|
|
// This line crosses through the object.
|
|
|
|
// Collect the sector(s) from the line and add to the
|
|
// sector_list you're examining. If the Thing ends up being
|
|
// allowed to move to this position, then the sector_list
|
|
// will be attached to the Thing's AActor at touching_sectorlist.
|
|
|
|
sector_list = P_AddSecnode (ld->frontsector,tmthing,sector_list);
|
|
|
|
// Don't assume all lines are 2-sided, since some Things
|
|
// like MT_TFOG are allowed regardless of whether their radius takes
|
|
// them beyond an impassable linedef.
|
|
|
|
// killough 3/27/98, 4/4/98:
|
|
// Use sidedefs instead of 2s flag to determine two-sidedness.
|
|
|
|
if (ld->backsector)
|
|
sector_list = P_AddSecnode(ld->backsector, tmthing, sector_list);
|
|
|
|
return true;
|
|
}
|
|
|
|
//=============================================================================
|
|
// phares 3/14/98
|
|
//
|
|
// P_CreateSecNodeList
|
|
//
|
|
// Alters/creates the sector_list that shows what sectors the object resides in
|
|
//
|
|
//=============================================================================
|
|
|
|
void P_CreateSecNodeList (AActor *thing, fixed_t x, fixed_t y)
|
|
{
|
|
int xl, xh, yl, yh, bx, by;
|
|
msecnode_t *node;
|
|
|
|
// [RH] Save old tm* values.
|
|
AActor *thingsave = tmthing;
|
|
int flagssave = tmflags;
|
|
fixed_t xsave = tmx;
|
|
fixed_t ysave = tmy;
|
|
fixed_t bboxsave[4];
|
|
|
|
memcpy (bboxsave, tmbbox, sizeof(bboxsave));
|
|
|
|
// First, clear out the existing m_thing fields. As each node is
|
|
// added or verified as needed, m_thing will be set properly. When
|
|
// finished, delete all nodes where m_thing is still NULL. These
|
|
// represent the sectors the Thing has vacated.
|
|
|
|
node = sector_list;
|
|
while (node)
|
|
{
|
|
node->m_thing = NULL;
|
|
node = node->m_tnext;
|
|
}
|
|
|
|
tmthing = thing;
|
|
tmflags = thing->flags;
|
|
|
|
tmx = x;
|
|
tmy = y;
|
|
|
|
tmbbox[BOXTOP] = y + tmthing->radius;
|
|
tmbbox[BOXBOTTOM] = y - tmthing->radius;
|
|
tmbbox[BOXRIGHT] = x + tmthing->radius;
|
|
tmbbox[BOXLEFT] = x - tmthing->radius;
|
|
|
|
validcount++; // used to make sure we only process a line once
|
|
|
|
xl = (tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT;
|
|
xh = (tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT;
|
|
yl = (tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT;
|
|
yh = (tmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT;
|
|
|
|
for (bx = xl; bx <= xh; bx++)
|
|
for (by = yl; by <= yh; by++)
|
|
P_BlockLinesIterator (bx,by,PIT_GetSectors);
|
|
|
|
// Add the sector of the (x,y) point to sector_list.
|
|
|
|
sector_list = P_AddSecnode (thing->Sector, thing, sector_list);
|
|
|
|
// Now delete any nodes that won't be used. These are the ones where
|
|
// m_thing is still NULL.
|
|
|
|
node = sector_list;
|
|
while (node)
|
|
{
|
|
if (node->m_thing == NULL)
|
|
{
|
|
if (node == sector_list)
|
|
sector_list = node->m_tnext;
|
|
node = P_DelSecnode (node);
|
|
}
|
|
else
|
|
{
|
|
node = node->m_tnext;
|
|
}
|
|
}
|
|
|
|
// [RH] Restore old tm* values.
|
|
tmthing = thingsave;
|
|
tmflags = flagssave;
|
|
tmx = xsave;
|
|
tmy = ysave;
|
|
memcpy (tmbbox, bboxsave, sizeof(bboxsave));
|
|
}
|
|
|
|
void SpawnShootDecal (AActor *t1, const FTraceResults &trace)
|
|
{
|
|
FDecalBase *decalbase = NULL;
|
|
|
|
if (t1->player != NULL && t1->player->ReadyWeapon != NULL)
|
|
{
|
|
decalbase = t1->player->ReadyWeapon->GetDefault()->DecalGenerator;
|
|
}
|
|
else
|
|
{
|
|
decalbase = t1->DecalGenerator;
|
|
}
|
|
if (decalbase != NULL)
|
|
{
|
|
DImpactDecal::StaticCreate (decalbase->GetDecal (),
|
|
trace.X, trace.Y, trace.Z, sides + trace.Line->sidenum[trace.Side]);
|
|
}
|
|
}
|
|
|
|
static void SpawnDeepSplash (AActor *t1, const FTraceResults &trace, AActor *puff,
|
|
fixed_t vx, fixed_t vy, fixed_t vz)
|
|
{
|
|
fixed_t num, den, hitdist;
|
|
const secplane_t *plane = &trace.CrossedWater->heightsec->floorplane;
|
|
|
|
den = TMulScale16 (plane->a, vx, plane->b, vy, plane->c, vz);
|
|
if (den != 0)
|
|
{
|
|
num = TMulScale16 (plane->a, t1->x, plane->b, t1->y, plane->c, shootz) + plane->d;
|
|
hitdist = FixedDiv (-num, den);
|
|
|
|
if (hitdist >= 0 && hitdist <= trace.Distance)
|
|
{
|
|
fixed_t savex, savey, savez;
|
|
|
|
// Move the puff onto the water surface, splash, then move it back.
|
|
if (puff == NULL)
|
|
{
|
|
puff = t1;
|
|
}
|
|
|
|
savex = puff->x;
|
|
savey = puff->y;
|
|
savez = puff->z;
|
|
puff->SetOrigin (t1->x+FixedMul (vx, hitdist),
|
|
t1->y+FixedMul (vy, hitdist),
|
|
shootz+FixedMul (vz, hitdist));
|
|
P_HitWater (puff, puff->Sector);
|
|
puff->SetOrigin (savex, savey, savez);
|
|
}
|
|
}
|
|
}
|