qzdoom/src/p_map.cpp

6024 lines
174 KiB
C++
Raw Normal View History

// 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 "m_bbox.h"
#include "m_random.h"
#include "i_system.h"
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
#include "c_dispatch.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 "p_conversation.h"
#include "r_data/r_translate.h"
#include "g_level.h"
#include "r_sky.h"
CVAR(Bool, cl_bloodsplats, true, CVAR_ARCHIVE)
CVAR(Int, sv_smartaim, 0, CVAR_ARCHIVE | CVAR_SERVERINFO)
CVAR(Bool, cl_doautoaim, false, CVAR_ARCHIVE)
static void CheckForPushSpecial(line_t *line, int side, AActor *mobj, bool windowcheck);
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 shootz, bool ffloor = false);
static FRandom pr_tracebleed("TraceBleed");
static FRandom pr_checkthing("CheckThing");
static FRandom pr_lineattack("LineAttack");
static FRandom pr_crunch("DoCrunch");
// keep track of special lines as they are hit,
// but don't process them until the move is proven valid
TArray<line_t *> spechit;
// Temporary holder for thing_sectorlist threads
msecnode_t* sector_list = NULL; // phares 3/16/98
//==========================================================================
//
// GetCoefficientClosestPointInLine24
//
// Formula: (dotProduct(ldv1 - tm, ld) << 24) / dotProduct(ld, ld)
// with: ldv1 = (ld->v1->x, ld->v1->y), tm = (tm.x, tm.y)
// and ld = (ld->dx, ld->dy)
// Returns truncated to range [0, 1 << 24].
//
//==========================================================================
static fixed_t GetCoefficientClosestPointInLine24(line_t *ld, FCheckPosition &tm)
{
// [EP] Use 64 bit integers in order to keep the exact result of the
// multiplication, because in the case the vertexes have both the
// distance coordinates equal to the map limit (32767 units, which is
// 2147418112 in fixed_t notation), the product result would occupy
// 62 bits and the sum of two products would occupy 63 bits
// in the worst case. If instead the vertexes are very close (1 in
// fixed_t notation, which is 1.52587890625e-05 in float notation), the
// product and the sum can be 1 in the worst case, which is very tiny.
SQWORD r_num = ((SQWORD(tm.x - ld->v1->x)*ld->dx) +
(SQWORD(tm.y - ld->v1->y)*ld->dy));
// The denominator is always positive. Use this to avoid useless
// calculations.
SQWORD r_den = (SQWORD(ld->dx)*ld->dx + SQWORD(ld->dy)*ld->dy);
if (r_num <= 0) {
// [EP] The numerator is less or equal to zero, hence the closest
// point on the line is the first vertex. Truncate the result to 0.
return 0;
}
if (r_num >= r_den) {
// [EP] The division is greater or equal to 1, hence the closest
// point on the line is the second vertex. Truncate the result to
// 1 << 24.
return (1 << 24);
}
// [EP] Deal with the limited bits. The original formula is:
// r = (r_num << 24) / r_den,
// but r_num might be big enough to make the shift overflow.
// Since the numerator can't be saved in a 128bit integer,
// the denominator must be right shifted. If the denominator is
// less than (1 << 24), there would be a division by zero.
// Thanks to the fact that in this code path the denominator is greater
// than the numerator, it's possible to avoid this bad situation by
// just checking the last 24 bits of the numerator.
if ((r_num >> (63-24)) != 0) {
// [EP] In fact, if the numerator is greater than
// (1 << (63-24)), the denominator must be greater than
// (1 << (63-24)), hence the denominator won't be zero after
// the right shift by 24 places.
return (fixed_t)(r_num/(r_den >> 24));
}
2014-11-25 18:22:56 +00:00
// [EP] Having the last 24 bits all zero allows left shifting
// the numerator by 24 bits without overflow.
return (fixed_t)((r_num << 24)/r_den);
}
//==========================================================================
//
// PIT_FindFloorCeiling
//
// only3d set means to only check against 3D floors and midtexes.
//
//==========================================================================
static bool PIT_FindFloorCeiling(line_t *ld, const FBoundingBox &box, FCheckPosition &tmf, int flags)
{
if (box.Right() <= ld->bbox[BOXLEFT]
|| box.Left() >= ld->bbox[BOXRIGHT]
|| box.Top() <= ld->bbox[BOXBOTTOM]
|| box.Bottom() >= ld->bbox[BOXTOP])
return true;
if (box.BoxOnLineSide(ld) != -1)
return true;
// A line has been hit
if (!ld->backsector)
{ // One sided line
return true;
}
fixed_t sx, sy;
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
FLineOpening open;
// 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)
&& ld->backsector->e->XFloor.ffloors.Size() == 0 && ld->frontsector->e->XFloor.ffloors.Size() == 0)
{
P_LineOpening(open, tmf.thing, ld, sx = tmf.x, sy = tmf.y, tmf.x, tmf.y, flags);
}
else
{ // Find the point on the line closest to the actor's center, and use
// that to calculate openings
double dx = ld->dx;
double dy = ld->dy;
fixed_t r = xs_CRoundToInt(((double)(tmf.x - ld->v1->x) * dx +
(double)(tmf.y - ld->v1->y) * dy) /
(dx*dx + dy*dy) * 16777216.f);
if (r <= 0)
{
P_LineOpening(open, tmf.thing, ld, sx = ld->v1->x, sy = ld->v1->y, tmf.x, tmf.y, flags);
}
else if (r >= (1 << 24))
{
P_LineOpening(open, tmf.thing, ld, sx = ld->v2->x, sy = ld->v2->y, tmf.thing->X(), tmf.thing->Y(), flags);
}
else
{
P_LineOpening(open, tmf.thing, ld, sx = ld->v1->x + MulScale24(r, ld->dx),
sy = ld->v1->y + MulScale24(r, ld->dy), tmf.x, tmf.y, flags);
}
}
// adjust floor / ceiling heights
if (open.top < tmf.ceilingz)
{
tmf.ceilingz = open.top;
}
if (open.bottom > tmf.floorz)
{
tmf.floorz = open.bottom;
if (open.bottomsec != NULL) tmf.floorsector = open.bottomsec;
tmf.touchmidtex = open.touchmidtex;
tmf.abovemidtex = open.abovemidtex;
}
else if (open.bottom == tmf.floorz)
{
tmf.touchmidtex |= open.touchmidtex;
tmf.abovemidtex |= open.abovemidtex;
}
if (open.lowfloor < tmf.dropoffz)
tmf.dropoffz = open.lowfloor;
return true;
}
//==========================================================================
//
//
//
//==========================================================================
void P_GetFloorCeilingZ(FCheckPosition &tmf, int flags)
{
sector_t *sec;
if (!(flags & FFCF_ONLYSPAWNPOS))
{
sec = !(flags & FFCF_SAMESECTOR) ? P_PointInSector(tmf.x, tmf.y) : tmf.thing->Sector;
tmf.floorsector = sec;
tmf.ceilingsector = sec;
tmf.floorz = tmf.dropoffz = sec->floorplane.ZatPoint(tmf.x, tmf.y);
tmf.ceilingz = sec->ceilingplane.ZatPoint(tmf.x, tmf.y);
tmf.floorpic = sec->GetTexture(sector_t::floor);
tmf.floorterrain = sec->GetTerrain(sector_t::floor);
tmf.ceilingpic = sec->GetTexture(sector_t::ceiling);
}
else
{
sec = tmf.thing->Sector;
}
for (unsigned int i = 0; i<sec->e->XFloor.ffloors.Size(); i++)
{
F3DFloor* rover = sec->e->XFloor.ffloors[i];
if (!(rover->flags & FF_SOLID) || !(rover->flags & FF_EXISTS)) continue;
fixed_t ff_bottom = rover->bottom.plane->ZatPoint(tmf.x, tmf.y);
fixed_t ff_top = rover->top.plane->ZatPoint(tmf.x, tmf.y);
if (ff_top > tmf.floorz)
{
2014-10-24 22:01:04 +00:00
if (ff_top <= tmf.z || (!(flags & FFCF_3DRESTRICT) && (tmf.thing != NULL && ff_bottom < tmf.z && ff_top < tmf.z + tmf.thing->MaxStepHeight)))
{
tmf.dropoffz = tmf.floorz = ff_top;
tmf.floorpic = *rover->top.texture;
tmf.floorterrain = rover->model->GetTerrain(rover->top.isceiling);
}
}
if (ff_bottom <= tmf.ceilingz && ff_bottom > tmf.z + tmf.thing->height)
{
tmf.ceilingz = ff_bottom;
tmf.ceilingpic = *rover->bottom.texture;
}
}
}
//==========================================================================
//
// P_FindFloorCeiling
//
//==========================================================================
void P_FindFloorCeiling(AActor *actor, int flags)
{
FCheckPosition tmf;
tmf.thing = actor;
tmf.x = actor->X();
tmf.y = actor->Y();
tmf.z = actor->Z();
if (flags & FFCF_ONLYSPAWNPOS)
{
flags |= FFCF_3DRESTRICT;
}
if (!(flags & FFCF_ONLYSPAWNPOS))
{
P_GetFloorCeilingZ(tmf, flags);
}
else
{
tmf.ceilingsector = tmf.floorsector = actor->Sector;
tmf.floorz = tmf.dropoffz = actor->floorz;
tmf.ceilingz = actor->ceilingz;
tmf.floorpic = actor->floorpic;
tmf.floorterrain = actor->floorterrain;
tmf.ceilingpic = actor->ceilingpic;
P_GetFloorCeilingZ(tmf, flags);
}
actor->floorz = tmf.floorz;
actor->dropoffz = tmf.dropoffz;
actor->ceilingz = tmf.ceilingz;
actor->floorpic = tmf.floorpic;
actor->floorterrain = tmf.floorterrain;
actor->floorsector = tmf.floorsector;
actor->ceilingpic = tmf.ceilingpic;
actor->ceilingsector = tmf.ceilingsector;
FBoundingBox box(tmf.x, tmf.y, actor->radius);
tmf.touchmidtex = false;
tmf.abovemidtex = false;
validcount++;
FBlockLinesIterator it(box);
line_t *ld;
while ((ld = it.Next()))
{
PIT_FindFloorCeiling(ld, box, tmf, flags);
}
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
if (tmf.touchmidtex) tmf.dropoffz = tmf.floorz;
if (!(flags & FFCF_ONLYSPAWNPOS) || (tmf.abovemidtex && (tmf.floorz <= actor->Z())))
{
actor->floorz = tmf.floorz;
actor->dropoffz = tmf.dropoffz;
actor->ceilingz = tmf.ceilingz;
actor->floorpic = tmf.floorpic;
actor->floorterrain = tmf.floorterrain;
actor->floorsector = tmf.floorsector;
actor->ceilingpic = tmf.ceilingpic;
actor->ceilingsector = tmf.ceilingsector;
}
else
{
actor->floorsector = actor->ceilingsector = actor->Sector;
// [BB] Don't forget to update floorpic and ceilingpic.
if (actor->Sector != NULL)
{
actor->floorpic = actor->Sector->GetTexture(sector_t::floor);
actor->floorterrain = actor->Sector->GetTerrain(sector_t::floor);
actor->ceilingpic = actor->Sector->GetTexture(sector_t::ceiling);
}
}
}
//==========================================================================
//
// TELEPORT MOVE
//
//
// 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, bool modifyactor)
{
FCheckPosition tmf;
sector_t *oldsec = thing->Sector;
// kill anything occupying the position
// The base floor/ceiling is from the subsector that contains the point.
// Any contacted lines the step closer together will adjust them.
tmf.thing = thing;
tmf.x = x;
tmf.y = y;
tmf.z = z;
tmf.touchmidtex = false;
tmf.abovemidtex = false;
P_GetFloorCeilingZ(tmf, 0);
spechit.Clear();
bool StompAlwaysFrags = ((thing->flags2 & MF2_TELESTOMP) || (level.flags & LEVEL_MONSTERSTELEFRAG) || telefrag) && !(thing->flags7 & MF7_NOTELESTOMP);
FBoundingBox box(x, y, thing->radius);
FBlockLinesIterator it(box);
line_t *ld;
// P_LineOpening requires the thing's z to be the destination z in order to work.
fixed_t savedz = thing->Z();
thing->SetZ(z);
while ((ld = it.Next()))
{
PIT_FindFloorCeiling(ld, box, tmf, 0);
}
thing->SetZ(savedz);
if (tmf.touchmidtex) tmf.dropoffz = tmf.floorz;
FBlockThingsIterator it2(FBoundingBox(x, y, thing->radius));
AActor *th;
while ((th = it2.Next()))
{
if (!(th->flags & MF_SHOOTABLE))
continue;
// don't clip against self
if (th == thing)
continue;
fixed_t blockdist = th->radius + tmf.thing->radius;
if (abs(th->X() - tmf.x) >= blockdist || abs(th->Y() - tmf.y) >= blockdist)
continue;
if ((th->flags2 | tmf.thing->flags2) & MF2_THRUACTORS)
continue;
if (tmf.thing->flags6 & MF6_THRUSPECIES && tmf.thing->GetSpecies() == th->GetSpecies())
continue;
// [RH] Z-Check
// But not if not MF2_PASSMOBJ or MF3_DONTOVERLAP are set!
// Otherwise those things would get stuck inside each other.
if ((thing->flags2 & MF2_PASSMOBJ || th->flags4 & MF4_ACTLIKEBRIDGE) && !(i_compatflags & COMPATF_NO_PASSMOBJ))
{
if (!(th->flags3 & thing->flags3 & MF3_DONTOVERLAP))
{
if (z > th->Top() || // overhead
z + thing->height < th->Z()) // underneath
continue;
}
}
// monsters don't stomp things except on boss level
// [RH] Some Heretic/Hexen monsters can telestomp
// ... and some items can never be telefragged while others will be telefragged by everything that teleports upon them.
if ((StompAlwaysFrags && !(th->flags6 & MF6_NOTELEFRAG)) || (th->flags7 & MF7_ALWAYSTELEFRAG))
{
// Don't actually damage if predicting a teleport
if (thing->player == NULL || !(thing->player->cheats & CF_PREDICTING))
P_DamageMobj(th, thing, thing, TELEFRAG_DAMAGE, NAME_Telefrag, DMG_THRUSTLESS);
continue;
}
return false;
}
if (modifyactor)
{
// the move is ok, so link the thing into its new position
thing->SetOrigin(x, y, z, false);
thing->floorz = tmf.floorz;
thing->ceilingz = tmf.ceilingz;
thing->floorsector = tmf.floorsector;
thing->floorpic = tmf.floorpic;
thing->floorterrain = tmf.floorterrain;
thing->ceilingsector = tmf.ceilingsector;
thing->ceilingpic = tmf.ceilingpic;
thing->dropoffz = tmf.dropoffz; // killough 11/98
thing->BlockingLine = NULL;
if (thing->flags2 & MF2_FLOORCLIP)
{
thing->AdjustFloorClip();
}
if (thing == players[consoleplayer].camera)
{
R_ResetViewInterpolation();
}
thing->PrevX = x;
thing->PrevY = y;
thing->PrevZ = z;
// If this teleport was caused by a move, P_TryMove() will handle the
// sector transition messages better than we can here.
if (!(thing->flags6 & MF6_INTRYMOVE))
{
thing->CheckSectorTransition(oldsec);
}
}
return true;
}
//==========================================================================
//
// [RH] P_PlayerStartStomp
//
// Like P_TeleportMove, but it doesn't move anything, and only monsters and other
// players get telefragged.
//
//==========================================================================
void P_PlayerStartStomp(AActor *actor, bool mononly)
{
AActor *th;
FBlockThingsIterator it(FBoundingBox(actor->X(), actor->Y(), actor->radius));
while ((th = it.Next()))
{
if (!(th->flags & MF_SHOOTABLE))
continue;
// don't clip against self, and don't kill your own voodoo dolls
if (th == actor || (th->player == actor->player && th->player != NULL))
continue;
if (!th->intersects(actor))
continue;
// only kill monsters and other players
if (th->player == NULL && !(th->flags3 & MF3_ISMONSTER))
continue;
if (th->player != NULL && mononly)
continue;
if (actor->Z() > th->Top())
continue; // overhead
if (actor->Top() < th->Z())
continue; // underneath
P_DamageMobj(th, actor, actor, TELEFRAG_DAMAGE, NAME_Telefrag);
}
}
//==========================================================================
//
//
//
//==========================================================================
inline fixed_t secfriction(const sector_t *sec, int plane = sector_t::floor)
{
if (sec->Flags & SECF_FRICTION) return sec->friction;
fixed_t friction = Terrains[sec->GetTerrain(plane)].Friction;
return friction != 0 ? friction : ORIG_FRICTION;
}
inline fixed_t secmovefac(const sector_t *sec, int plane = sector_t::floor)
{
if (sec->Flags & SECF_FRICTION) return sec->movefactor;
fixed_t movefactor = Terrains[sec->GetTerrain(plane)].MoveFactor;
return movefactor != 0 ? movefactor : ORIG_FRICTION_FACTOR;
}
//==========================================================================
//
// 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;
fixed_t newfriction;
const msecnode_t *m;
const sector_t *sec;
if (mo->IsNoClip2())
{
// The default values are fine for noclip2 mode
}
else 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;
// Check 3D floors -- might be the source of the waterlevel
for (unsigned i = 0; i < mo->Sector->e->XFloor.ffloors.Size(); i++)
{
F3DFloor *rover = mo->Sector->e->XFloor.ffloors[i];
if (!(rover->flags & FF_EXISTS)) continue;
if (!(rover->flags & FF_SWIMMABLE)) continue;
if (mo->Z() > rover->top.plane->ZatPoint(mo) ||
mo->Z() < rover->bottom.plane->ZatPoint(mo))
continue;
newfriction = secfriction(rover->model, rover->top.isceiling);
if (newfriction < friction || friction == ORIG_FRICTION)
{
friction = newfriction;
movefactor = secmovefac(rover->model, rover->top.isceiling) >> 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;
// 3D floors must be checked, too
for (unsigned i = 0; i < sec->e->XFloor.ffloors.Size(); i++)
{
F3DFloor *rover = sec->e->XFloor.ffloors[i];
if (!(rover->flags & FF_EXISTS)) continue;
if (rover->flags & FF_SOLID)
{
// Must be standing on a solid floor
if (mo->Z() != rover->top.plane->ZatPoint(mo)) continue;
}
else if (rover->flags & FF_SWIMMABLE)
{
// Or on or inside a swimmable floor (e.g. in shallow water)
if (mo->Z() > rover->top.plane->ZatPoint(mo) ||
(mo->Top()) < rover->bottom.plane->ZatPoint(mo))
continue;
}
else
continue;
newfriction = secfriction(rover->model, rover->top.isceiling);
if (newfriction < friction || friction == ORIG_FRICTION)
{
friction = newfriction;
movefactor = secmovefac(rover->model, rover->top.isceiling);
}
}
if (!(sec->Flags & SECF_FRICTION) &&
Terrains[sec->GetTerrain(sector_t::floor)].Friction == 0)
{
continue;
}
newfriction = secfriction(sec);
if ((newfriction < friction || friction == ORIG_FRICTION) &&
(mo->Z() <= sec->floorplane.ZatPoint(mo) ||
(sec->GetHeightSec() != NULL &&
mo->Z() <= sec->heightsec->floorplane.ZatPoint(mo))))
{
friction = newfriction;
movefactor = secmovefac(sec);
}
}
}
if (mo->Friction != FRACUNIT)
{
friction = clamp(FixedMul(friction, mo->Friction), 0, FRACUNIT);
movefactor = FrictionToMoveFactor(friction);
}
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 velocity = P_AproxDistance(mo->velx, mo->vely);
if (velocity > MORE_FRICTION_VELOCITY << 2)
movefactor <<= 3;
else if (velocity > MORE_FRICTION_VELOCITY << 1)
movefactor <<= 2;
else if (velocity > MORE_FRICTION_VELOCITY)
movefactor <<= 1;
}
if (frictionp)
*frictionp = friction;
return movefactor;
}
//
// MOVEMENT ITERATOR FUNCTIONS
//
//==========================================================================
//
//
// PIT_CheckLine
// Adjusts tmfloorz and tmceilingz as lines are contacted
//
//
//==========================================================================
static // killough 3/26/98: make static
bool PIT_CheckLine(line_t *ld, const FBoundingBox &box, FCheckPosition &tm)
{
bool rail = false;
if (box.Right() <= ld->bbox[BOXLEFT]
|| box.Left() >= ld->bbox[BOXRIGHT]
|| box.Top() <= ld->bbox[BOXBOTTOM]
|| box.Bottom() >= ld->bbox[BOXTOP])
return true;
if (box.BoxOnLineSide(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 (tm.thing->flags2 & MF2_BLASTED)
{
P_DamageMobj(tm.thing, NULL, NULL, tm.thing->Mass >> 5, NAME_Melee);
}
tm.thing->BlockingLine = ld;
CheckForPushSpecial(ld, 0, tm.thing, false);
return false;
}
// MBF bouncers are treated as missiles here.
bool Projectile = (tm.thing->flags & MF_MISSILE || tm.thing->BounceFlags & BOUNCE_MBF);
// MBF considers that friendly monsters are not blocked by monster-blocking lines.
// This is added here as a compatibility option. Note that monsters that are dehacked
// into being friendly with the MBF flag automatically gain MF3_NOBLOCKMONST, so this
// just optionally generalizes the behavior to other friendly monsters.
bool NotBlocked = ((tm.thing->flags3 & MF3_NOBLOCKMONST)
|| ((i_compatflags & COMPATF_NOBLOCKFRIENDS) && (tm.thing->flags & MF_FRIENDLY)));
fixedvec3 pos = tm.thing->PosRelative(ld);
if (!(Projectile) || (ld->flags & (ML_BLOCKEVERYTHING | ML_BLOCKPROJECTILE)))
{
if (ld->flags & ML_RAILING)
{
rail = true;
}
else if ((ld->flags & (ML_BLOCKING | ML_BLOCKEVERYTHING)) || // explicitly blocking everything
(!(NotBlocked) && (ld->flags & ML_BLOCKMONSTERS)) || // block monsters only
(tm.thing->player != NULL && (ld->flags & ML_BLOCK_PLAYERS)) || // block players
((Projectile) && (ld->flags & ML_BLOCKPROJECTILE)) || // block projectiles
((tm.thing->flags & MF_FLOAT) && (ld->flags & ML_BLOCK_FLOATERS))) // block floaters
{
if (tm.thing->flags2 & MF2_BLASTED)
{
P_DamageMobj(tm.thing, NULL, NULL, tm.thing->Mass >> 5, NAME_Melee);
}
tm.thing->BlockingLine = ld;
// Calculate line side based on the actor's original position, not the new one.
CheckForPushSpecial(ld, P_PointOnLineSide(pos.x, pos.y, ld), tm.thing, false);
return false;
}
}
// [RH] Steep sectors count as dropoffs (unless already in one)
if (!(tm.thing->flags & MF_DROPOFF) &&
!(tm.thing->flags & (MF_NOGRAVITY | MF_NOCLIP)))
{
2015-02-08 09:03:49 +00:00
secplane_t frontplane, backplane;
// Check 3D floors as well
frontplane = P_FindFloorPlane(ld->frontsector, pos.x, pos.y, tm.thing->floorz);
backplane = P_FindFloorPlane(ld->backsector, pos.x, pos.y, tm.thing->floorz);
if (frontplane.c < STEEPSLOPE || backplane.c < STEEPSLOPE)
{
const msecnode_t *node = tm.thing->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 = 0, sy = 0;
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
FLineOpening open;
// 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)
&& ld->backsector->e->XFloor.ffloors.Size() == 0 && ld->frontsector->e->XFloor.ffloors.Size() == 0)
{
P_LineOpening(open, tm.thing, ld, sx = tm.x, sy = tm.y, tm.x, tm.y);
}
else
{ // Find the point on the line closest to the actor's center, and use
// that to calculate openings
fixed_t r = GetCoefficientClosestPointInLine24(ld, tm);
/* 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(open, tm.thing, ld, sx = ld->v1->x, sy = ld->v1->y, tm.x, tm.y);
}
else if (r >= (1 << 24))
{
P_LineOpening(open, tm.thing, ld, sx = ld->v2->x, sy = ld->v2->y, pos.x, pos.y);
}
else
{
P_LineOpening(open, tm.thing, ld, sx = ld->v1->x + MulScale24(r, ld->dx),
sy = ld->v1->y + MulScale24(r, ld->dy), tm.x, tm.y);
}
// the floorplane on both sides is identical with the current one
// so don't mess around with the z-position
if (ld->frontsector->floorplane == ld->backsector->floorplane &&
ld->frontsector->floorplane == tm.thing->Sector->floorplane &&
!ld->frontsector->e->XFloor.ffloors.Size() && !ld->backsector->e->XFloor.ffloors.Size() &&
!open.abovemidtex)
{
open.bottom = INT_MIN;
}
/* 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.
(!(level.flags2 & LEVEL2_RAILINGHACK) ||
open.bottom == tm.thing->Sector->floorplane.ZatPoint(sx, sy)))
{
open.bottom += 32 * FRACUNIT;
}
// adjust floor / ceiling heights
if (open.top < tm.ceilingz)
{
tm.ceilingz = open.top;
tm.ceilingsector = open.topsec;
tm.ceilingpic = open.ceilingpic;
tm.ceilingline = ld;
tm.thing->BlockingLine = ld;
}
if (open.bottom > tm.floorz)
{
tm.floorz = open.bottom;
tm.floorsector = open.bottomsec;
tm.floorpic = open.floorpic;
tm.floorterrain = open.floorterrain;
tm.touchmidtex = open.touchmidtex;
tm.abovemidtex = open.abovemidtex;
tm.thing->BlockingLine = ld;
}
else if (open.bottom == tm.floorz)
{
tm.touchmidtex |= open.touchmidtex;
tm.abovemidtex |= open.abovemidtex;
}
if (open.lowfloor < tm.dropoffz)
tm.dropoffz = open.lowfloor;
// if contacted a special line, add it to the list
if (ld->special)
{
spechit.Push(ld);
}
return true;
}
//==========================================================================
//
// Isolated to keep the code readable and fix the logic
//
//==========================================================================
static bool CheckRipLevel(AActor *victim, AActor *projectile)
{
if (victim->RipLevelMin > 0 && projectile->RipperLevel < victim->RipLevelMin) return false;
if (victim->RipLevelMax > 0 && projectile->RipperLevel > victim->RipLevelMax) return false;
return true;
}
//==========================================================================
//
// Isolated to keep the code readable and allow reuse in other attacks
//
//==========================================================================
static bool CanAttackHurt(AActor *victim, AActor *shooter)
{
// players are never subject to infighting settings and are always allowed
// to harm / be harmed by anything.
if (!victim->player && !shooter->player)
{
int infight = G_SkillProperty(SKILLP_Infight);
if (infight < 0)
{
// -1: Monsters cannot hurt each other, but make exceptions for
// friendliness and hate status.
if (shooter->flags & MF_SHOOTABLE)
{
// Question: Should monsters be allowed to shoot barrels in this mode?
// The old code does not.
if (victim->flags3 & MF3_ISMONSTER)
{
// Monsters that are clearly hostile can always hurt each other
if (!victim->IsHostile(shooter))
{
// The same if the shooter hates the target
if (victim->tid == 0 || shooter->TIDtoHate != victim->tid)
{
return false;
}
}
}
}
}
else if (infight == 0)
{
// 0: Monsters cannot hurt same species except
// cases where they are clearly supposed to do that
if (victim->IsFriend(shooter))
{
// Friends never harm each other, unless the shooter has the HARMFRIENDS set.
if (!(shooter->flags7 & MF7_HARMFRIENDS)) return false;
}
else
{
if (victim->TIDtoHate != 0 && victim->TIDtoHate == shooter->TIDtoHate)
{
// [RH] Don't hurt monsters that hate the same victim as you do
return false;
}
if (victim->GetSpecies() == shooter->GetSpecies() && !(victim->flags6 & MF6_DOHARMSPECIES))
{
// Don't hurt same species or any relative -
// but only if the target isn't one's hostile.
if (!victim->IsHostile(shooter))
{
// Allow hurting monsters the shooter hates.
if (victim->tid == 0 || shooter->TIDtoHate != victim->tid)
{
return false;
}
}
}
}
}
// else if (infight==1) every shot hurts anything - no further tests needed
}
return true;
}
//==========================================================================
//
// PIT_CheckThing
//
//==========================================================================
bool PIT_CheckThing(AActor *thing, FCheckPosition &tm)
{
fixed_t topz;
bool solid;
int damage;
// don't clip against self
if (thing == tm.thing)
return true;
if (!((thing->flags & (MF_SOLID | MF_SPECIAL | MF_SHOOTABLE)) || thing->flags6 & MF6_TOUCHY))
return true; // can't hit thing
fixedvec3 thingpos = thing->PosRelative(tm.thing);
fixed_t blockdist = thing->radius + tm.thing->radius;
if (abs(thingpos.x - tm.x) >= blockdist || abs(thingpos.y - tm.y) >= blockdist)
return true;
if ((thing->flags2 | tm.thing->flags2) & MF2_THRUACTORS)
return true;
if ((tm.thing->flags6 & MF6_THRUSPECIES) && (tm.thing->GetSpecies() == thing->GetSpecies()))
return true;
tm.thing->BlockingMobj = thing;
topz = thing->Top();
if (!(i_compatflags & COMPATF_NO_PASSMOBJ) && !(tm.thing->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 ((tm.thing->flags3 & MF3_ISMONSTER) &&
topz >= tm.floorz && topz <= tm.thing->Z() + tm.thing->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)
{
tm.stepthing = thing;
tm.floorz = topz;
}
}
}
// Both things overlap in x or y direction
bool unblocking = false;
if ((tm.FromPMove || tm.thing->player != NULL) && thing->flags&MF_SOLID)
{
// Both actors already overlap. To prevent them from remaining stuck allow the move if it
// takes them further apart or the move does not change the position (when called from P_ChangeSector.)
if (tm.x == tm.thing->X() && tm.y == tm.thing->Y())
{
unblocking = true;
}
else if (abs(thingpos.x - tm.thing->X()) < (thing->radius+tm.thing->radius) &&
abs(thingpos.y - tm.thing->Y()) < (thing->radius+tm.thing->radius))
{
fixed_t newdist = thing->AproxDistance(tm.x, tm.y, tm.thing);
fixed_t olddist = thing->AproxDistance(tm.thing);
if (newdist > olddist)
{
// ... but not if they did not overlap in z-direction before but would after the move.
unblocking = !((tm.thing->Z() >= topz && tm.z < topz) ||
(tm.thing->Top() <= thingpos.z && tm.thing->Top() > thingpos.z));
}
}
}
// [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 ((tm.thing->flags2 & MF2_PASSMOBJ || thing->flags4 & MF4_ACTLIKEBRIDGE) && !(i_compatflags & COMPATF_NO_PASSMOBJ))
{ // check if a mobj passed over/under another object
if (!(tm.thing->flags & MF_MISSILE) ||
!(tm.thing->flags2 & MF2_RIP) ||
(thing->flags5 & MF5_DONTRIP) ||
((tm.thing->flags6 & MF6_NOBOSSRIP) && (thing->flags2 & MF2_BOSS)))
{
if (tm.thing->flags3 & thing->flags3 & MF3_DONTOVERLAP)
{ // Some things prefer not to overlap each other, if possible
return unblocking;
}
if ((tm.thing->Z() >= topz) || (tm.thing->Top() <= thing->Z()))
return true;
}
}
if (tm.thing->player == NULL || !(tm.thing->player->cheats & CF_PREDICTING))
{
// touchy object is alive, toucher is solid
if (thing->flags6 & MF6_TOUCHY && tm.thing->flags & MF_SOLID && thing->health > 0 &&
// Thing is an armed mine or a sentient thing
(thing->flags6 & MF6_ARMED || thing->IsSentient()) &&
// either different classes or players
(thing->player || thing->GetClass() != tm.thing->GetClass()) &&
// or different species if DONTHARMSPECIES
(!(thing->flags6 & MF6_DONTHARMSPECIES) || thing->GetSpecies() != tm.thing->GetSpecies()) &&
// touches vertically
topz >= tm.thing->Z() && tm.thing->Z() + tm.thing->height >= thingpos.z &&
// prevents lost souls from exploding when fired by pain elementals
(thing->master != tm.thing && tm.thing->master != thing))
// Difference with MBF: MBF hardcodes the LS/PE check and lets actors of the same species
// but different classes trigger the touchiness, but that seems less straightforwards.
{
thing->flags6 &= ~MF6_ARMED; // Disarm
P_DamageMobj(thing, NULL, NULL, thing->health, NAME_None, DMG_FORCED); // kill object
return true;
}
// Check for MF6_BUMPSPECIAL
// By default, only players can activate things by bumping into them
if ((thing->flags6 & MF6_BUMPSPECIAL) && ((tm.thing->player != NULL)
|| ((thing->activationtype & THINGSPEC_MonsterTrigger) && (tm.thing->flags3 & MF3_ISMONSTER))
|| ((thing->activationtype & THINGSPEC_MissileTrigger) && (tm.thing->flags & MF_MISSILE))
) && (level.maptime > thing->lastbump)) // Leave the bumper enough time to go away
{
if (P_ActivateThingSpecial(thing, tm.thing))
thing->lastbump = level.maptime + TICRATE;
}
}
// Check for skulls slamming into things
if (tm.thing->flags & MF_SKULLFLY)
{
bool res = tm.thing->Slam(tm.thing->BlockingMobj);
tm.thing->BlockingMobj = NULL;
return res;
}
// [ED850] Player Prediction ends here. There is nothing else they could/should do.
if (tm.thing->player != NULL && (tm.thing->player->cheats & CF_PREDICTING))
{
solid = (thing->flags & MF_SOLID) &&
!(thing->flags & MF_NOCLIP) &&
((tm.thing->flags & MF_SOLID) || (tm.thing->flags6 & MF6_BLOCKEDBYSOLIDACTORS));
return !solid || unblocking;
}
// Check for blasted thing running into another
if ((tm.thing->flags2 & MF2_BLASTED) && (thing->flags & MF_SHOOTABLE))
{
if (!(thing->flags2 & MF2_BOSS) && (thing->flags3 & MF3_ISMONSTER) && !(thing->flags3 & MF3_DONTBLAST))
{
// ideally this should take the mass factor into account
thing->velx += tm.thing->velx;
thing->vely += tm.thing->vely;
if ((thing->velx + thing->vely) > 3 * FRACUNIT)
{
int newdam;
damage = (tm.thing->Mass / 100) + 1;
newdam = P_DamageMobj(thing, tm.thing, tm.thing, damage, tm.thing->DamageType);
P_TraceBleed(newdam > 0 ? newdam : damage, thing, tm.thing);
damage = (thing->Mass / 100) + 1;
newdam = P_DamageMobj(tm.thing, thing, thing, damage >> 2, tm.thing->DamageType);
P_TraceBleed(newdam > 0 ? newdam : damage, tm.thing, thing);
}
return false;
}
}
// Check for missile or non-solid MBF bouncer
if (tm.thing->flags & MF_MISSILE || ((tm.thing->BounceFlags & BOUNCE_MBF) && !(tm.thing->flags & MF_SOLID)))
{
// Check for a non-shootable mobj
if (thing->flags2 & MF2_NONSHOOTABLE)
{
return true;
}
// Check for passing through a ghost
if ((thing->flags3 & MF3_GHOST) && (tm.thing->flags2 & MF2_THRUGHOST))
{
return true;
}
if ((tm.thing->flags6 & MF6_MTHRUSPECIES)
&& tm.thing->target // NULL pointer check
&& (tm.thing->target->GetSpecies() == thing->GetSpecies()))
return true;
// Check for rippers passing through corpses
if ((thing->flags & MF_CORPSE) && (tm.thing->flags2 & MF2_RIP) && !(thing->flags & MF_SHOOTABLE))
{
return true;
}
int clipheight;
if (thing->projectilepassheight > 0)
{
clipheight = thing->projectilepassheight;
}
else if (thing->projectilepassheight < 0 && (i_compatflags & COMPATF_MISSILECLIP))
{
clipheight = -thing->projectilepassheight;
}
else
{
clipheight = thing->height;
}
// Check if it went over / under
if (tm.thing->Z() > thingpos.z + clipheight)
{ // Over thing
return true;
}
if (tm.thing->Top() < thingpos.z)
{ // Under thing
return true;
}
// [RH] What is the point of this check, again? In Hexen, it is unconditional,
// but here we only do it if the missile's damage is 0.
// MBF bouncer might have a non-0 damage value, but they must not deal damage on impact either.
if ((tm.thing->BounceFlags & BOUNCE_Actors) && (tm.thing->Damage == 0 || !(tm.thing->flags & MF_MISSILE)))
{
return (tm.thing->target == thing || !(thing->flags & MF_SOLID));
}
switch (tm.thing->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
if (tm.thing->target != NULL)
{
if (thing == tm.thing->target)
{ // Don't missile self
return true;
}
if (!CanAttackHurt(thing, tm.thing->target))
{
return false;
}
}
if (!(thing->flags & MF_SHOOTABLE))
{ // Didn't do any damage
return !(thing->flags & MF_SOLID);
}
if ((thing->flags4 & MF4_SPECTRAL) && !(tm.thing->flags4 & MF4_SPECTRAL))
{
return true;
}
if ((tm.DoRipping && !(thing->flags5 & MF5_DONTRIP)) && CheckRipLevel(thing, tm.thing))
{
if (!(tm.thing->flags6 & MF6_NOBOSSRIP) || !(thing->flags2 & MF2_BOSS))
{
bool *check = tm.LastRipped.CheckKey(thing);
if (check == NULL || !*check)
{
tm.LastRipped[thing] = true;
if (!(thing->flags & MF_NOBLOOD) &&
!(thing->flags2 & MF2_REFLECTIVE) &&
!(tm.thing->flags3 & MF3_BLOODLESSIMPACT) &&
!(thing->flags2 & (MF2_INVULNERABLE | MF2_DORMANT)))
{ // Ok to spawn blood
P_RipperBlood(tm.thing, thing);
}
S_Sound(tm.thing, CHAN_BODY, "misc/ripslop", 1, ATTN_IDLE);
// Do poisoning (if using new style poison)
if (tm.thing->PoisonDamage > 0 && tm.thing->PoisonDuration != INT_MIN)
{
P_PoisonMobj(thing, tm.thing, tm.thing->target, tm.thing->PoisonDamage, tm.thing->PoisonDuration, tm.thing->PoisonPeriod, tm.thing->PoisonDamageType);
}
damage = tm.thing->GetMissileDamage(3, 2);
int newdam = P_DamageMobj(thing, tm.thing, tm.thing->target, damage, tm.thing->DamageType);
if (!(tm.thing->flags3 & MF3_BLOODLESSIMPACT))
{
P_TraceBleed(newdam > 0 ? newdam : damage, thing, tm.thing);
}
if (thing->flags2 & MF2_PUSHABLE
&& !(tm.thing->flags2 & MF2_CANNOTPUSH))
{ // Push thing
if (thing->lastpush != tm.PushTime)
{
thing->velx += FixedMul(tm.thing->velx, thing->pushfactor);
thing->vely += FixedMul(tm.thing->vely, thing->pushfactor);
thing->lastpush = tm.PushTime;
}
}
}
spechit.Clear();
return true;
}
}
// Do poisoning (if using new style poison)
if (tm.thing->PoisonDamage > 0 && tm.thing->PoisonDuration != INT_MIN)
{
P_PoisonMobj(thing, tm.thing, tm.thing->target, tm.thing->PoisonDamage, tm.thing->PoisonDuration, tm.thing->PoisonPeriod, tm.thing->PoisonDamageType);
}
// Do damage
damage = tm.thing->GetMissileDamage((tm.thing->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1);
if ((damage > 0) || (tm.thing->flags6 & MF6_FORCEPAIN) || (tm.thing->flags7 & MF7_CAUSEPAIN))
{
int newdam = P_DamageMobj(thing, tm.thing, tm.thing->target, damage, tm.thing->DamageType);
if (damage > 0)
{
if ((tm.thing->flags5 & MF5_BLOODSPLATTER) &&
!(thing->flags & MF_NOBLOOD) &&
!(thing->flags2 & MF2_REFLECTIVE) &&
!(thing->flags2 & (MF2_INVULNERABLE | MF2_DORMANT)) &&
!(tm.thing->flags3 & MF3_BLOODLESSIMPACT) &&
(pr_checkthing() < 192))
{
P_BloodSplatter(tm.thing->X(), tm.thing->Y(), tm.thing->Z(), thing);
}
if (!(tm.thing->flags3 & MF3_BLOODLESSIMPACT))
{
P_TraceBleed(newdam > 0 ? newdam : damage, thing, tm.thing);
}
}
}
else
{
P_GiveBody(thing, -damage);
}
if ((thing->flags7 & MF7_THRUREFLECT) && (thing->flags2 & MF2_REFLECTIVE) && (tm.thing->flags & MF_MISSILE))
{
if (tm.thing->flags2 & MF2_SEEKERMISSILE)
{
tm.thing->tracer = tm.thing->target;
}
tm.thing->target = thing;
return true;
}
return false; // don't traverse any more
}
if (thing->flags2 & MF2_PUSHABLE && !(tm.thing->flags2 & MF2_CANNOTPUSH))
{ // Push thing
if (thing->lastpush != tm.PushTime)
{
thing->velx += FixedMul(tm.thing->velx, thing->pushfactor);
thing->vely += FixedMul(tm.thing->vely, thing->pushfactor);
thing->lastpush = tm.PushTime;
}
}
solid = (thing->flags & MF_SOLID) &&
!(thing->flags & MF_NOCLIP) &&
((tm.thing->flags & MF_SOLID) || (tm.thing->flags6 & MF6_BLOCKEDBYSOLIDACTORS));
// Check for special pickup
if ((thing->flags & MF_SPECIAL) && (tm.thing->flags & 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.
&& thingpos.z < tm.thing->Top() - tm.thing->MaxStepHeight)
{ // Can be picked up by tmthing
P_TouchSpecialThing(thing, tm.thing); // 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 || unblocking;
// return !(thing->flags & MF_SOLID); // old code -- killough
}
/*
===============================================================================
MOVEMENT CLIPPING
===============================================================================
*/
//==========================================================================
//
// 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, FCheckPosition &tm, bool actorsonly)
{
sector_t *newsec;
AActor *thingblocker;
fixed_t realheight = thing->height;
tm.thing = thing;
tm.x = x;
tm.y = y;
newsec = P_PointInSector(x, y);
tm.ceilingline = thing->BlockingLine = NULL;
// The base floor / ceiling is from the subsector that contains the point.
// Any contacted lines the step closer together will adjust them.
tm.floorz = tm.dropoffz = newsec->floorplane.ZatPoint(x, y);
tm.ceilingz = newsec->ceilingplane.ZatPoint(x, y);
tm.floorpic = newsec->GetTexture(sector_t::floor);
tm.floorterrain = newsec->GetTerrain(sector_t::floor);
tm.floorsector = newsec;
tm.ceilingpic = newsec->GetTexture(sector_t::ceiling);
tm.ceilingsector = newsec;
tm.touchmidtex = false;
tm.abovemidtex = false;
//Added by MC: Fill the tmsector.
tm.sector = newsec;
//Check 3D floors
if (!thing->IsNoClip2() && newsec->e->XFloor.ffloors.Size())
{
F3DFloor* rover;
fixed_t delta1;
fixed_t delta2;
int thingtop = thing->Z() + (thing->height == 0 ? 1 : thing->height);
for (unsigned i = 0; i<newsec->e->XFloor.ffloors.Size(); i++)
{
rover = newsec->e->XFloor.ffloors[i];
if (!(rover->flags & FF_SOLID) || !(rover->flags & FF_EXISTS)) continue;
fixed_t ff_bottom = rover->bottom.plane->ZatPoint(x, y);
fixed_t ff_top = rover->top.plane->ZatPoint(x, y);
delta1 = thing->Z() - (ff_bottom + ((ff_top - ff_bottom) / 2));
delta2 = thingtop - (ff_bottom + ((ff_top - ff_bottom) / 2));
if (ff_top > tm.floorz && abs(delta1) < abs(delta2))
{
tm.floorz = tm.dropoffz = ff_top;
tm.floorpic = *rover->top.texture;
tm.floorterrain = rover->model->GetTerrain(rover->top.isceiling);
}
if (ff_bottom < tm.ceilingz && abs(delta1) >= abs(delta2))
{
tm.ceilingz = ff_bottom;
tm.ceilingpic = *rover->bottom.texture;
}
}
}
validcount++;
spechit.Clear();
if ((thing->flags & MF_NOCLIP) && !(thing->flags & MF_SKULLFLY))
return true;
// Check things first, possibly picking things up.
thing->BlockingMobj = NULL;
thingblocker = NULL;
if (thing->player)
{ // [RH] Fake taller height to catch stepping up into things.
thing->height = realheight + thing->MaxStepHeight;
}
2006-04-11 08:36:23 +00:00
tm.stepthing = NULL;
FBoundingBox box(x, y, thing->radius);
{
FBlockThingsIterator it2(box);
AActor *th;
while ((th = it2.Next()))
{
if (!PIT_CheckThing(th, tm))
{ // [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.
AActor *BlockingMobj = thing->BlockingMobj;
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->Top() - thing->Z() <= thing->MaxStepHeight)
{
if (thingblocker == NULL ||
BlockingMobj->Z() > thingblocker->Z())
{
thingblocker = BlockingMobj;
}
thing->BlockingMobj = NULL;
}
else if (thing->player &&
thing->Top() - 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.
thing->BlockingMobj = NULL;
}
else
{ // Definitely blocking
thing->height = realheight;
return false;
}
}
}
}
// 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++;
thing->BlockingMobj = NULL;
thing->height = realheight;
if (actorsonly || (thing->flags & MF_NOCLIP))
return (thing->BlockingMobj = thingblocker) == NULL;
FBlockLinesIterator it(box);
line_t *ld;
fixed_t thingdropoffz = tm.floorz;
//bool onthing = (thingdropoffz != tmdropoffz);
tm.floorz = tm.dropoffz;
bool good = true;
while ((ld = it.Next()))
{
good &= PIT_CheckLine(ld, box, tm);
}
if (!good)
{
return false;
}
if (tm.ceilingz - tm.floorz < thing->height)
{
return false;
}
if (tm.touchmidtex)
{
tm.dropoffz = tm.floorz;
}
else if (tm.stepthing != NULL)
{
tm.dropoffz = thingdropoffz;
}
return (thing->BlockingMobj = thingblocker) == NULL;
}
bool P_CheckPosition(AActor *thing, fixed_t x, fixed_t y, bool actorsonly)
{
FCheckPosition tm;
return P_CheckPosition(thing, x, y, tm, actorsonly);
}
//----------------------------------------------------------------------------
//
// 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)
{
2015-04-04 16:40:43 +00:00
ActorFlags 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;
if ((mobj->Z() < mobj->floorz) || (mobj->Top() > mobj->ceilingz))
{ // Bad Z
return false;
}
return true;
}
mobj->flags = flags;
return false;
}
//=============================================================================
//
// P_CheckOnmobj(AActor *thing)
//
// Checks if the new Z position is legal
//=============================================================================
AActor *P_CheckOnmobj(AActor *thing)
{
fixed_t oldz;
bool good;
AActor *onmobj;
oldz = thing->Z();
P_FakeZMovement(thing);
good = P_TestMobjZ(thing, false, &onmobj);
thing->SetZ(oldz);
return good ? NULL : onmobj;
}
//=============================================================================
//
// P_TestMobjZ
//
//=============================================================================
bool P_TestMobjZ(AActor *actor, bool quick, AActor **pOnmobj)
{
AActor *onmobj = NULL;
if (actor->flags & MF_NOCLIP)
{
if (pOnmobj) *pOnmobj = NULL;
return true;
}
FBlockThingsIterator it(FBoundingBox(actor->X(), actor->Y(), actor->radius));
AActor *thing;
while ((thing = it.Next()))
{
if (!thing->intersects(actor))
{
continue;
}
if ((actor->flags2 | thing->flags2) & MF2_THRUACTORS)
{
continue;
}
if ((actor->flags6 & MF6_THRUSPECIES) && (thing->GetSpecies() == actor->GetSpecies()))
{
continue;
}
if (!(thing->flags & MF_SOLID))
{ // Can't hit thing
continue;
}
if (thing->flags & (MF_SPECIAL | MF_NOCLIP))
{ // [RH] Specials and noclippers don't block moves
continue;
}
if (thing->flags & (MF_CORPSE))
{ // Corpses need a few more checks
if (!(actor->flags & MF_ICECORPSE))
continue;
}
if (!(thing->flags4 & MF4_ACTLIKEBRIDGE) && (actor->flags & MF_SPECIAL))
{ // [RH] Only bridges block pickup items
continue;
}
if (thing == actor)
{ // Don't clip against self
continue;
}
if ((actor->flags & MF_MISSILE) && (thing == actor->target))
{ // Don't clip against whoever shot the missile.
continue;
}
if (actor->Z() > thing->Top())
{ // over thing
continue;
}
else if (actor->Top() <= thing->Z())
{ // under thing
continue;
}
else if (!quick && onmobj != NULL && thing->Top() < onmobj->Top())
{ // something higher is in the way
continue;
}
onmobj = thing;
if (quick) break;
}
if (pOnmobj) *pOnmobj = onmobj;
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->AddZ(mo->velz);
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 = mo->AproxDistance(mo->target);
fixed_t delta = (mo->target->Z() + (mo->height >> 1)) - mo->Z();
if (delta < 0 && dist < -(delta * 3))
mo->AddZ(-mo->FloatSpeed);
else if (delta > 0 && dist < (delta * 3))
mo->AddZ(mo->FloatSpeed);
}
}
if (mo->player && mo->flags&MF_NOGRAVITY && (mo->Z() > mo->floorz) && !mo->IsNoClip2())
{
mo->AddZ(finesine[(FINEANGLES / 80 * level.maptime)&FINEMASK] / 8);
}
//
// clip movement
//
if (mo->Z() <= mo->floorz)
{ // hit the floor
mo->SetZ(mo->floorz);
}
if (mo->Top() > mo->ceilingz)
{ // hit the ceiling
mo->SetZ(mo->ceilingz - mo->height);
}
}
//===========================================================================
//
// CheckForPushSpecial
//
//===========================================================================
static void CheckForPushSpecial(line_t *line, int side, AActor *mobj, bool windowcheck)
{
if (line->special && !(mobj->flags6 & MF6_NOTRIGGER))
{
if (windowcheck && !(ib_compatflags & BCOMPATF_NOWINDOWCHECK) && line->backsector != NULL)
{ // Make sure this line actually blocks us and is not a window
// or similar construct we are standing inside of.
fixed_t fzt = line->frontsector->ceilingplane.ZatPoint(mobj);
fixed_t fzb = line->frontsector->floorplane.ZatPoint(mobj);
fixed_t bzt = line->backsector->ceilingplane.ZatPoint(mobj);
fixed_t bzb = line->backsector->floorplane.ZatPoint(mobj);
if (fzt >= mobj->Top() && bzt >= mobj->Top() &&
fzb <= mobj->Z() && bzb <= mobj->Z())
{
// we must also check if some 3D floor in the backsector may be blocking
for (unsigned int i = 0; i<line->backsector->e->XFloor.ffloors.Size(); i++)
{
F3DFloor* rover = line->backsector->e->XFloor.ffloors[i];
if (!(rover->flags & FF_SOLID) || !(rover->flags & FF_EXISTS)) continue;
fixed_t ff_bottom = rover->bottom.plane->ZatPoint(mobj);
fixed_t ff_top = rover->top.plane->ZatPoint(mobj);
if (ff_bottom < mobj->Top() && ff_top > mobj->Z())
{
goto isblocking;
}
}
return;
}
}
isblocking:
if (mobj->flags2 & MF2_PUSHWALL)
{
P_ActivateLine(line, mobj, side, SPAC_Push);
}
else if (mobj->flags2 & MF2_IMPACT)
{
if ((level.flags2 & LEVEL2_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,
int dropoff, // killough 3/15/98: allow dropoff as option
const secplane_t *onfloor, // [RH] Let P_TryMove keep the thing on the floor
FCheckPosition &tm,
bool missileCheck) // [GZ] Fired missiles ignore the drop-off test
{
fixedvec3 oldpos;
sector_t *oldsector;
fixed_t oldz;
int side;
int oldside;
line_t* ld;
sector_t* oldsec = thing->Sector; // [RH] for sector actions
sector_t* newsec;
tm.floatok = false;
oldz = thing->Z();
if (onfloor)
{
thing->SetZ(onfloor->ZatPoint(x, y));
}
thing->flags6 |= MF6_INTRYMOVE;
if (!P_CheckPosition(thing, x, y, tm))
{
AActor *BlockingMobj = thing->BlockingMobj;
// Solid wall or thing
if (!BlockingMobj || BlockingMobj->player || !thing->player)
{
goto pushline;
}
else
{
if (BlockingMobj->player || !thing->player)
{
goto pushline;
}
else if (BlockingMobj->Top() - thing->Z() > thing->MaxStepHeight
|| (BlockingMobj->Sector->ceilingplane.ZatPoint(x, y) - (BlockingMobj->Top()) < thing->height)
|| (tm.ceilingz - (BlockingMobj->Top()) < thing->height))
{
goto pushline;
}
}
if (!(tm.thing->flags2 & MF2_PASSMOBJ) || (i_compatflags & COMPATF_NO_PASSMOBJ))
{
thing->SetZ(oldz);
thing->flags6 &= ~MF6_INTRYMOVE;
return false;
}
}
if (thing->flags3 & MF3_FLOORHUGGER)
{
thing->SetZ(tm.floorz);
}
else if (thing->flags3 & MF3_CEILINGHUGGER)
{
thing->SetZ(tm.ceilingz - thing->height);
}
if (onfloor && tm.floorsector == thing->floorsector)
{
thing->SetZ(tm.floorz);
}
if (!(thing->flags & MF_NOCLIP))
{
if (tm.ceilingz - tm.floorz < thing->height)
{
goto pushline; // doesn't fit
}
tm.floatok = true;
if (!(thing->flags & MF_TELEPORT)
&& tm.ceilingz - 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->Top() > tm.ceilingz)
goto pushline;
#else
// When flying, slide up or down blocking lines until the actor
// is not blocked.
if (thing->Top() > tm.ceilingz)
{
thing->velz = -8 * FRACUNIT;
goto pushline;
}
else if (thing->Z() < tm.floorz && tm.floorz - tm.dropoffz > thing->MaxDropOffHeight)
{
thing->velz = 8 * FRACUNIT;
goto pushline;
}
#endif
}
if (!(thing->flags & MF_TELEPORT) && !(thing->flags3 & MF3_FLOORHUGGER))
{
if ((thing->flags & MF_MISSILE) && !(thing->flags6 & MF6_STEPMISSILE) && tm.floorz > thing->Z())
{ // [RH] Don't let normal missiles climb steps
goto pushline;
}
if (tm.floorz - thing->Z() > thing->MaxStepHeight)
{ // too big a step up
goto pushline;
}
else if (thing->Z() < tm.floorz)
{ // [RH] Check to make sure there's nothing in the way for the step up
fixed_t savedz = thing->Z();
bool good;
thing->SetZ(tm.floorz);
good = P_TestMobjZ(thing);
thing->SetZ(savedz);
if (!good)
{
goto pushline;
}
if (thing->flags6 & MF6_STEPMISSILE)
{
thing->SetZ(tm.floorz);
// If moving down, cancel vertical component of the velocity
if (thing->velz < 0)
{
// If it's a bouncer, let it bounce off its new floor, too.
if (thing->BounceFlags & BOUNCE_Floors)
{
thing->FloorBounceMissile(tm.floorsector->floorplane);
}
else
{
thing->velz = 0;
}
}
}
}
}
// compatibility check: Doom originally did not allow monsters to cross dropoffs at all.
// If the compatibility flag is on, only allow this when the velocity comes from a scroller
if ((i_compatflags & COMPATF_CROSSDROPOFF) && !(thing->flags4 & MF4_SCROLLMOVE))
{
dropoff = false;
}
if (dropoff == 2 && // large jump down (e.g. dogs)
(tm.floorz - tm.dropoffz > 128 * FRACUNIT || thing->target == NULL || thing->target->Z() >tm.dropoffz))
{
dropoff = false;
}
// killough 3/15/98: Allow certain objects to drop off
if ((!dropoff && !(thing->flags & (MF_DROPOFF | MF_FLOAT | MF_MISSILE))) || (thing->flags5&MF5_NODROPOFF))
{
2006-04-24 14:26:06 +00:00
if (!(thing->flags5&MF5_AVOIDINGDROPOFF))
{
fixed_t floorz = tm.floorz;
// [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(), tm.floorz);
}
if (floorz - tm.dropoffz > thing->MaxDropOffHeight &&
!(thing->flags2 & MF2_BLASTED) && !missileCheck)
2006-04-24 14:26:06 +00:00
{ // Can't move over a dropoff unless it's been blasted
// [GZ] Or missile-spawned
thing->SetZ(oldz);
thing->flags6 &= ~MF6_INTRYMOVE;
2006-04-24 14:26:06 +00:00
return false;
}
}
else
{
// special logic to move a monster off a dropoff
// this intentionally does not check for standing on things.
if (thing->floorz - tm.floorz > thing->MaxDropOffHeight ||
thing->dropoffz - tm.dropoffz > thing->MaxDropOffHeight)
{
thing->flags6 &= ~MF6_INTRYMOVE;
return false;
}
}
}
if (thing->flags2 & MF2_CANTLEAVEFLOORPIC
&& (tm.floorpic != thing->floorpic
|| tm.floorz - thing->Z() != 0))
{ // must stay within a sector of a certain floor type
thing->SetZ(oldz);
thing->flags6 &= ~MF6_INTRYMOVE;
return false;
}
//Added by MC: To prevent bot from getting into dangerous sectors.
if (thing->player && thing->player->Bot != NULL && thing->flags & MF_SHOOTABLE)
{
if (tm.sector != thing->Sector
&& bglobal.IsDangerous(tm.sector))
{
thing->player->Bot->prev = thing->player->Bot->dest;
thing->player->Bot->dest = NULL;
thing->velx = 0;
thing->vely = 0;
thing->SetZ(oldz);
thing->flags6 &= ~MF6_INTRYMOVE;
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);
oldAboveFakeCeiling = eyez > oldsec->heightsec->ceilingplane.ZatPoint(thing);
}
// Borrowed from MBF:
if (thing->BounceFlags & BOUNCE_MBF && // killough 8/13/98
!(thing->flags & (MF_MISSILE | MF_NOGRAVITY)) &&
!thing->IsSentient() && tm.floorz - thing->Z() > 16 * FRACUNIT)
{ // too big a step up for MBF bouncers under gravity
thing->flags6 &= ~MF6_INTRYMOVE;
return false;
}
// the move is ok, so link the thing into its new position
thing->UnlinkFromWorld();
oldpos = thing->Pos();
oldsector = thing->Sector;
thing->floorz = tm.floorz;
thing->ceilingz = tm.ceilingz;
thing->dropoffz = tm.dropoffz; // killough 11/98: keep track of dropoffs
thing->floorpic = tm.floorpic;
thing->floorterrain = tm.floorterrain;
thing->floorsector = tm.floorsector;
thing->ceilingpic = tm.ceilingpic;
thing->ceilingsector = tm.ceilingsector;
thing->SetXY(x, y);
thing->LinkToWorld();
if (thing->flags2 & MF2_FLOORCLIP)
{
thing->AdjustFloorClip();
}
// if any special lines were hit, do the effect
if (!(thing->flags & (MF_TELEPORT | MF_NOCLIP)))
{
while (spechit.Pop(ld))
{
fixedvec3 thingpos = thing->PosRelative(ld);
fixedvec3 oldrelpos = PosRelative(oldpos, ld, oldsector);
// see if the line was crossed
side = P_PointOnLineSide(thingpos.x, thingpos.y, ld);
oldside = P_PointOnLineSide(oldrelpos.x, oldrelpos.y, ld);
if (side != oldside && ld->special && !(thing->flags6 & MF6_NOTRIGGER))
{
if (thing->player && (thing->player->cheats & CF_PREDICTING))
{
P_PredictLine(ld, thing, oldside, SPAC_Cross);
}
else 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
{
P_ActivateLine(ld, thing, oldside, SPAC_AnyCross);
}
}
}
}
// [RH] Don't activate anything if just predicting
if (thing->player && (thing->player->cheats & CF_PREDICTING))
{
thing->flags6 &= ~MF6_INTRYMOVE;
return true;
}
// [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
thing->CheckSectorTransition(oldsec);
thing->flags6 &= ~MF6_INTRYMOVE;
return true;
pushline:
thing->flags6 &= ~MF6_INTRYMOVE;
// [RH] Don't activate anything if just predicting
if (thing->player && (thing->player->cheats & CF_PREDICTING))
{
return false;
}
thing->SetZ(oldz);
if (!(thing->flags&(MF_TELEPORT | MF_NOCLIP)))
{
int numSpecHitTemp;
if (tm.thing->flags2 & MF2_BLASTED)
{
P_DamageMobj(tm.thing, NULL, NULL, tm.thing->Mass >> 5, NAME_Melee);
}
numSpecHitTemp = (int)spechit.Size();
while (numSpecHitTemp > 0)
{
// see which lines were pushed
ld = spechit[--numSpecHitTemp];
fixedvec3 pos = thing->PosRelative(ld);
side = P_PointOnLineSide(pos.x, pos.y, ld);
CheckForPushSpecial(ld, side, thing, true);
}
}
return false;
}
bool P_TryMove(AActor *thing, fixed_t x, fixed_t y,
int dropoff, // killough 3/15/98: allow dropoff as option
const secplane_t *onfloor) // [RH] Let P_TryMove keep the thing on the floor
{
FCheckPosition tm;
return P_TryMove(thing, x, y, dropoff, onfloor, tm);
}
//==========================================================================
//
// P_CheckMove
// Similar to P_TryMove but doesn't actually move the actor. Used for polyobject crushing
//
//==========================================================================
bool P_CheckMove(AActor *thing, fixed_t x, fixed_t y)
{
FCheckPosition tm;
fixed_t newz = thing->Z();
if (!P_CheckPosition(thing, x, y, tm))
{
return false;
}
if (thing->flags3 & MF3_FLOORHUGGER)
{
newz = tm.floorz;
}
else if (thing->flags3 & MF3_CEILINGHUGGER)
{
newz = tm.ceilingz - thing->height;
}
if (!(thing->flags & MF_NOCLIP))
{
if (tm.ceilingz - tm.floorz < thing->height)
{
return false;
}
if (!(thing->flags & MF_TELEPORT)
&& tm.ceilingz - newz < thing->height
&& !(thing->flags3 & MF3_CEILINGHUGGER)
&& (!(thing->flags2 & MF2_FLY) || !(thing->flags & MF_NOGRAVITY)))
{
return false;
}
if (thing->flags2 & MF2_FLY && thing->flags & MF_NOGRAVITY)
{
if (thing->Top() > tm.ceilingz)
return false;
}
if (!(thing->flags & MF_TELEPORT) && !(thing->flags3 & MF3_FLOORHUGGER))
{
if (tm.floorz - newz > thing->MaxStepHeight)
{ // too big a step up
return false;
}
2014-10-24 22:01:04 +00:00
else if ((thing->flags & MF_MISSILE) && !(thing->flags6 & MF6_STEPMISSILE) && tm.floorz > newz)
{ // [RH] Don't let normal missiles climb steps
return false;
}
else if (newz < tm.floorz)
{ // [RH] Check to make sure there's nothing in the way for the step up
fixed_t savedz = thing->Z();
thing->SetZ(newz = tm.floorz);
bool good = P_TestMobjZ(thing);
thing->SetZ(savedz);
if (!good)
{
return false;
}
}
}
if (thing->flags2 & MF2_CANTLEAVEFLOORPIC
&& (tm.floorpic != thing->floorpic
|| tm.floorz - newz != 0))
{ // must stay within a sector of a certain floor type
return false;
}
}
return true;
}
//==========================================================================
//
// SLIDE MOVE
// Allows the player to slide along any angled walls.
//
//==========================================================================
struct FSlide
{
fixed_t bestslidefrac;
fixed_t secondslidefrac;
line_t* bestslideline;
line_t* secondslideline;
AActor* slidemo;
fixed_t tmxmove;
fixed_t tmymove;
void HitSlideLine(line_t *ld);
void SlideTraverse(fixed_t startx, fixed_t starty, fixed_t endx, fixed_t endy);
void SlideMove(AActor *mo, fixed_t tryx, fixed_t tryy, int numsteps);
// The bouncing code uses the same data structure
bool BounceTraverse(fixed_t startx, fixed_t starty, fixed_t endx, fixed_t endy);
bool BounceWall(AActor *mo);
};
//==========================================================================
//
// 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 FSlide::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 velocity. 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->dx == 0)
{ // ST_VERTICAL
if (icyfloor && (abs(tmxmove) > abs(tmymove)))
{
tmxmove = -tmxmove / 2; // absorb half the velocity
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;
}
if (ld->dy == 0)
{ // ST_HORIZONTAL
if (icyfloor && (abs(tmymove) > abs(tmxmove)))
{
tmxmove /= 2; // absorb half the velocity
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;
}
// The wall is angled. Bounce if the angle of approach is // phares
// less than 45 degrees. // phares
fixedvec3 pos = slidemo->PosRelative(ld);
side = P_PointOnLineSide(pos.x, pos.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
// Doom's original algorithm here does not work well due to imprecisions of the sine table.
// However, keep it active if the wallrunning compatibility flag is on
if (i_compatflags & COMPATF_WALLRUN)
{
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 = pos.x;
dlv.y = pos.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;
}
}
} // phares
}
//==========================================================================
//
// PTR_SlideTraverse
//
//==========================================================================
void FSlide::SlideTraverse(fixed_t startx, fixed_t starty, fixed_t endx, fixed_t endy)
{
FLineOpening open;
FPathTraverse it(startx, starty, endx, endy, PT_ADDLINES);
intercept_t *in;
while ((in = it.Next()))
{
line_t* li;
if (!in->isaline)
{
// should never happen
Printf("PTR_SlideTraverse: not a line?");
continue;
}
li = in->d.line;
if (!(li->flags & ML_TWOSIDED) || !li->backsector)
{
fixedvec3 pos = slidemo->PosRelative(li);
if (P_PointOnLineSide(pos.x, pos.y, li))
{
// don't hit the back side
continue;
}
goto isblocking;
}
if (li->flags & (ML_BLOCKING | ML_BLOCKEVERYTHING))
{
goto isblocking;
}
if (li->flags & ML_BLOCK_PLAYERS && slidemo->player != NULL)
{
goto isblocking;
}
if (li->flags & ML_BLOCKMONSTERS && !((slidemo->flags3 & MF3_NOBLOCKMONST)
|| ((i_compatflags & COMPATF_NOBLOCKFRIENDS) && (slidemo->flags & MF_FRIENDLY))))
{
goto isblocking;
}
// set openrange, opentop, openbottom
P_LineOpening(open, slidemo, li, it.Trace().x + FixedMul(it.Trace().dx, in->frac),
it.Trace().y + FixedMul(it.Trace().dy, in->frac));
if (open.range < slidemo->height)
goto isblocking; // doesn't fit
if (open.top - slidemo->Z() < slidemo->height)
goto isblocking; // mobj is too high
if (open.bottom - slidemo->Z() > slidemo->MaxStepHeight)
{
goto isblocking; // too big a step up
}
else if (slidemo->Z() < open.bottom)
{ // [RH] Check to make sure there's nothing in the way for the step up
fixed_t savedz = slidemo->Z();
slidemo->SetZ(open.bottom);
bool good = P_TestMobjZ(slidemo);
slidemo->SetZ(savedz);
if (!good)
{
goto isblocking;
}
}
// this line doesn't block movement
continue;
// 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; // stop
}
}
//==========================================================================
//
// P_SlideMove
//
// The velx / vely 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 FSlide::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;
const secplane_t * walkplane;
int hitcount;
hitcount = 3;
slidemo = mo;
if (mo->player && mo->player->mo == mo && 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;
SlideTraverse(leadx, leady, leadx + tryx, leady + tryy);
SlideTraverse(trailx, leady, trailx + tryx, leady + tryy);
SlideTraverse(leadx, traily, leadx + tryx, traily + tryy);
// 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);
// [BL] We need to abandon this function if we end up going through a teleporter
const fixed_t startvelx = mo->velx;
const fixed_t startvely = mo->vely;
// killough 3/15/98: Allow objects to drop off ledges
if (!P_TryMove(mo, mo->X() + newx, mo->Y() + newy, true))
goto stairstep;
if (mo->velx != startvelx || mo->vely != startvely)
return;
}
// 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);
HitSlideLine(bestslideline); // clip the moves
mo->velx = tmxmove * numsteps;
mo->vely = 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->velx) > abs(mo->velx))
mo->player->velx = mo->velx;
if (abs(mo->player->vely) > abs(mo->vely))
mo->player->vely = mo->vely;
}
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;
}
}
void P_SlideMove(AActor *mo, fixed_t tryx, fixed_t tryy, int numsteps)
{
FSlide slide;
slide.SlideMove(mo, tryx, tryy, numsteps);
}
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
//============================================================================
//
// P_CheckSlopeWalk
//
//============================================================================
const secplane_t * P_CheckSlopeWalk(AActor *actor, fixed_t &xmove, fixed_t &ymove)
{
static secplane_t copyplane;
if (actor->flags & MF_NOGRAVITY)
{
return NULL;
}
const secplane_t *plane = &actor->floorsector->floorplane;
fixed_t planezhere = plane->ZatPoint(actor);
for (unsigned int i = 0; i<actor->floorsector->e->XFloor.ffloors.Size(); i++)
{
F3DFloor * rover = actor->floorsector->e->XFloor.ffloors[i];
if (!(rover->flags & FF_SOLID) || !(rover->flags & FF_EXISTS)) continue;
fixed_t thisplanez = rover->top.plane->ZatPoint(actor);
if (thisplanez>planezhere && thisplanez <= actor->Z() + actor->MaxStepHeight)
{
copyplane = *rover->top.plane;
if (copyplane.c<0) copyplane.FlipVert();
plane = &copyplane;
planezhere = thisplanez;
}
}
if (actor->floorsector != actor->Sector)
{
for (unsigned int i = 0; i<actor->Sector->e->XFloor.ffloors.Size(); i++)
{
F3DFloor * rover = actor->Sector->e->XFloor.ffloors[i];
if (!(rover->flags & FF_SOLID) || !(rover->flags & FF_EXISTS)) continue;
fixed_t thisplanez = rover->top.plane->ZatPoint(actor);
if (thisplanez>planezhere && thisplanez <= actor->Z() + actor->MaxStepHeight)
{
copyplane = *rover->top.plane;
if (copyplane.c<0) copyplane.FlipVert();
plane = &copyplane;
planezhere = thisplanez;
}
}
}
if (actor->floorsector != actor->Sector)
{
2006-04-11 08:36:23 +00:00
// this additional check prevents sliding on sloped dropoffs
if (planezhere>actor->floorz + 4 * FRACUNIT)
return NULL;
}
if (actor->Z() - planezhere > FRACUNIT)
{ // not on floor
return NULL;
}
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) ? plane : NULL;
}
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->velx = plane->a * 2;
ymove = actor->vely = plane->b * 2;
}
return (actor->floorsector == actor->Sector) ? plane : NULL;
}
}
// 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) ? plane : NULL;
}
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 : NULL;
}
}
}
return NULL;
}
//============================================================================
//
// PTR_BounceTraverse
//
//============================================================================
bool FSlide::BounceTraverse(fixed_t startx, fixed_t starty, fixed_t endx, fixed_t endy)
{
FLineOpening open;
FPathTraverse it(startx, starty, endx, endy, PT_ADDLINES);
intercept_t *in;
while ((in = it.Next()))
{
line_t *li;
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
if (!in->isaline)
{
Printf("PTR_BounceTraverse: not a line?");
continue;
}
li = in->d.line;
assert(((size_t)li - (size_t)lines) % sizeof(line_t) == 0);
if (li->flags & ML_BLOCKEVERYTHING)
{
goto bounceblocking;
}
if (!(li->flags&ML_TWOSIDED) || !li->backsector)
{
if (P_PointOnLineSide(slidemo->X(), slidemo->Y(), li))
continue; // don't hit the back side
goto bounceblocking;
}
P_LineOpening(open, slidemo, li, it.Trace().x + FixedMul(it.Trace().dx, in->frac),
it.Trace().y + FixedMul(it.Trace().dy, in->frac)); // set openrange, opentop, openbottom
if (open.range < slidemo->height)
goto bounceblocking; // doesn't fit
if (open.top - slidemo->Z() < slidemo->height)
goto bounceblocking; // mobj is too high
if (open.bottom > slidemo->Z())
goto bounceblocking; // mobj is too low
continue; // 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
}
return true;
}
//============================================================================
//
// P_BounceWall
//
//============================================================================
bool FSlide::BounceWall(AActor *mo)
{
fixed_t leadx, leady;
int side;
angle_t lineangle, moveangle, deltaangle;
fixed_t movelen;
line_t *line;
if (!(mo->BounceFlags & BOUNCE_Walls))
{
return false;
}
slidemo = mo;
//
// trace along the three leading corners
//
if (mo->velx > 0)
{
leadx = mo->X() + mo->radius;
}
else
{
leadx = mo->X() - mo->radius;
}
if (mo->vely > 0)
{
leady = mo->Y() + mo->radius;
}
else
{
leady = mo->Y() - mo->radius;
}
bestslidefrac = FRACUNIT + 1;
bestslideline = mo->BlockingLine;
if (BounceTraverse(leadx, leady, leadx + mo->velx, leady + mo->vely) && mo->BlockingLine == NULL)
{ // 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;
}
}
line = bestslideline;
if (line->special == Line_Horizon)
{
mo->SeeSound = 0; // it might make a sound otherwise
mo->Destroy();
return true;
}
// The amount of bounces is limited
if (mo->bouncecount>0 && --mo->bouncecount == 0)
{
if (mo->flags & MF_MISSILE)
P_ExplodeMissile(mo, line, NULL);
else
mo->Die(NULL, NULL);
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->velx, mo->vely);
deltaangle = (2 * lineangle) - moveangle;
mo->angle = deltaangle;
deltaangle >>= ANGLETOFINESHIFT;
movelen = fixed_t(sqrt(double(mo->velx)*mo->velx + double(mo->vely)*mo->vely));
movelen = FixedMul(movelen, mo->wallbouncefactor);
FBoundingBox box(mo->X(), mo->Y(), mo->radius);
if (box.BoxOnLineSide(line) == -1)
{
fixedvec3 pos = mo->Vec3Offset(
FixedMul(mo->radius, finecosine[deltaangle]),
FixedMul(mo->radius, finesine[deltaangle]), 0);
mo->SetOrigin(pos, true);
}
if (movelen < FRACUNIT)
{
movelen = 2 * FRACUNIT;
}
mo->velx = FixedMul(movelen, finecosine[deltaangle]);
mo->vely = FixedMul(movelen, finesine[deltaangle]);
if (mo->BounceFlags & BOUNCE_UseBounceState)
{
FState *bouncestate = mo->FindState(NAME_Bounce, NAME_Wall);
if (bouncestate != NULL)
{
mo->SetState(bouncestate);
}
}
return true;
}
bool P_BounceWall(AActor *mo)
{
FSlide slide;
return slide.BounceWall(mo);
}
//==========================================================================
//
//
//
//==========================================================================
extern FRandom pr_bounce;
bool P_BounceActor(AActor *mo, AActor *BlockingMobj, bool ontop)
{
//Don't go through all of this if the actor is reflective and wants things to pass through them.
if (BlockingMobj && ((BlockingMobj->flags2 & MF2_REFLECTIVE) && (BlockingMobj->flags7 & MF7_THRUREFLECT))) return true;
if (mo && BlockingMobj && ((mo->BounceFlags & BOUNCE_AllActors)
|| ((mo->flags & MF_MISSILE) && (!(mo->flags2 & MF2_RIP)
|| (BlockingMobj->flags5 & MF5_DONTRIP)
|| ((mo->flags6 & MF6_NOBOSSRIP) && (BlockingMobj->flags2 & MF2_BOSS))) && (BlockingMobj->flags2 & MF2_REFLECTIVE))
|| ((BlockingMobj->player == NULL) && (!(BlockingMobj->flags3 & MF3_ISMONSTER)))))
{
if (mo->bouncecount > 0 && --mo->bouncecount == 0) return false;
if (mo->flags7 & MF7_HITTARGET) mo->target = BlockingMobj;
if (mo->flags7 & MF7_HITMASTER) mo->master = BlockingMobj;
if (mo->flags7 & MF7_HITTRACER) mo->tracer = BlockingMobj;
if (!ontop)
{
fixed_t speed;
angle_t angle = BlockingMobj->AngleTo(mo) + ANGLE_1*((pr_bounce() % 16) - 8);
speed = P_AproxDistance(mo->velx, mo->vely);
speed = FixedMul(speed, mo->wallbouncefactor); // [GZ] was 0.75, using wallbouncefactor seems more consistent
mo->angle = angle;
angle >>= ANGLETOFINESHIFT;
mo->velx = FixedMul(speed, finecosine[angle]);
mo->vely = FixedMul(speed, finesine[angle]);
mo->PlayBounceSound(true);
if (mo->BounceFlags & BOUNCE_UseBounceState)
{
FName names[] = { NAME_Bounce, NAME_Actor, NAME_Creature };
FState *bouncestate;
int count = 2;
if ((BlockingMobj->flags & MF_SHOOTABLE) && !(BlockingMobj->flags & MF_NOBLOOD))
{
count = 3;
}
bouncestate = mo->FindState(count, names);
if (bouncestate != NULL)
{
mo->SetState(bouncestate);
}
}
}
else
{
fixed_t dot = mo->velz;
if (mo->BounceFlags & (BOUNCE_HereticType | BOUNCE_MBF))
{
mo->velz -= MulScale15(FRACUNIT, dot);
if (!(mo->BounceFlags & BOUNCE_MBF)) // Heretic projectiles die, MBF projectiles don't.
{
mo->flags |= MF_INBOUNCE;
mo->SetState(mo->FindState(NAME_Death));
mo->flags &= ~MF_INBOUNCE;
return false;
}
else
{
mo->velz = FixedMul(mo->velz, mo->bouncefactor);
}
}
else // Don't run through this for MBF-style bounces
{
// The reflected velocity keeps only about 70% of its original speed
mo->velz = FixedMul(mo->velz - MulScale15(FRACUNIT, dot), mo->bouncefactor);
}
mo->PlayBounceSound(true);
if (mo->BounceFlags & BOUNCE_MBF) // Bring it to rest below a certain speed
{
if (abs(mo->velz) < (fixed_t)(mo->Mass * mo->GetGravity() / 64))
mo->velz = 0;
}
else if (mo->BounceFlags & (BOUNCE_AutoOff | BOUNCE_AutoOffFloorOnly))
{
if (!(mo->flags & MF_NOGRAVITY) && (mo->velz < 3 * FRACUNIT))
mo->BounceFlags &= ~BOUNCE_TypeMask;
}
}
return true;
}
return false;
}
//============================================================================
//
// Aiming
//
//============================================================================
struct aim_t
{
fixed_t aimpitch;
fixed_t attackrange;
fixed_t shootz; // Height if not aiming up or down
AActor* shootthing;
AActor* friender; // actor to check friendliness again
fixed_t toppitch, bottompitch;
AActor * linetarget;
AActor * thing_friend, *thing_other;
angle_t pitch_friend, pitch_other;
int flags;
sector_t * lastsector;
secplane_t * lastfloorplane;
secplane_t * lastceilingplane;
bool crossedffloors;
bool AimTraverse3DFloors(const divline_t &trace, intercept_t * in);
void AimTraverse(fixed_t startx, fixed_t starty, fixed_t endx, fixed_t endy, AActor *target = NULL);
};
//============================================================================
//
// AimTraverse3DFloors
//
//============================================================================
bool aim_t::AimTraverse3DFloors(const divline_t &trace, intercept_t * in)
{
sector_t * nextsector;
secplane_t * nexttopplane, *nextbottomplane;
line_t * li = in->d.line;
nextsector = NULL;
nexttopplane = nextbottomplane = NULL;
if (li->backsector == NULL) return true; // shouldn't really happen but crashed once for me...
if (li->frontsector->e->XFloor.ffloors.Size() || li->backsector->e->XFloor.ffloors.Size())
{
int frontflag;
F3DFloor* rover;
int highpitch, lowpitch;
fixed_t trX = trace.x + FixedMul(trace.dx, in->frac);
fixed_t trY = trace.y + FixedMul(trace.dy, in->frac);
fixed_t dist = FixedMul(attackrange, in->frac);
frontflag = P_PointOnLineSide(shootthing->X(), shootthing->Y(), li);
// 3D floor check. This is not 100% accurate but normally sufficient when
// combined with a final sight check
for (int i = 1; i <= 2; i++)
{
sector_t * s = i == 1 ? li->frontsector : li->backsector;
for (unsigned k = 0; k<s->e->XFloor.ffloors.Size(); k++)
{
crossedffloors = true;
rover = s->e->XFloor.ffloors[k];
if ((rover->flags & FF_SHOOTTHROUGH) || !(rover->flags & FF_EXISTS)) continue;
fixed_t ff_bottom = rover->bottom.plane->ZatPoint(trX, trY);
fixed_t ff_top = rover->top.plane->ZatPoint(trX, trY);
highpitch = -(int)R_PointToAngle2(0, shootz, dist, ff_top);
lowpitch = -(int)R_PointToAngle2(0, shootz, dist, ff_bottom);
if (highpitch <= toppitch)
{
// blocks completely
if (lowpitch >= bottompitch) return false;
// blocks upper edge of view
if (lowpitch>toppitch)
{
toppitch = lowpitch;
if (frontflag != i - 1)
{
nexttopplane = rover->bottom.plane;
}
}
}
else if (lowpitch >= bottompitch)
{
// blocks lower edge of view
if (highpitch<bottompitch)
{
bottompitch = highpitch;
if (frontflag != i - 1)
{
nextbottomplane = rover->top.plane;
}
}
}
// trace is leaving a sector with a 3d-floor
if (frontflag == i - 1)
{
if (s == lastsector)
{
// upper slope intersects with this 3d-floor
if (rover->bottom.plane == lastceilingplane && lowpitch > toppitch)
{
toppitch = lowpitch;
}
// lower slope intersects with this 3d-floor
if (rover->top.plane == lastfloorplane && highpitch < bottompitch)
{
bottompitch = highpitch;
}
}
}
if (toppitch >= bottompitch) return false; // stop
}
}
}
lastsector = nextsector;
lastceilingplane = nexttopplane;
lastfloorplane = nextbottomplane;
return true;
}
//============================================================================
//
// PTR_AimTraverse
// Sets linetaget and aimpitch when a target is aimed at.
//
//============================================================================
void aim_t::AimTraverse(fixed_t startx, fixed_t starty, fixed_t endx, fixed_t endy, AActor *target)
{
FPathTraverse it(startx, starty, endx, endy, PT_ADDLINES | PT_ADDTHINGS | PT_COMPATIBLE);
intercept_t *in;
while ((in = it.Next()))
{
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; // stop
// Crosses a two sided line.
// A two sided line will restrict the possible target ranges.
FLineOpening open;
P_LineOpening(open, NULL, li, it.Trace().x + FixedMul(it.Trace().dx, in->frac),
it.Trace().y + FixedMul(it.Trace().dy, in->frac));
if (open.bottom >= open.top)
return; // stop
dist = FixedMul(attackrange, in->frac);
pitch = -(int)R_PointToAngle2(0, shootz, dist, open.bottom);
if (pitch < bottompitch)
bottompitch = pitch;
pitch = -(int)R_PointToAngle2(0, shootz, dist, open.top);
if (pitch > toppitch)
toppitch = pitch;
if (toppitch >= bottompitch)
return; // stop
if (!AimTraverse3DFloors(it.Trace(), in)) return;
continue; // shot continues
}
// shoot a thing
th = in->d.thing;
if (th == shootthing)
continue; // can't shoot self
if (target != NULL && th != target)
continue; // only care about target, and you're not it
// If we want to start a conversation anything that has one should be
// found, regardless of other settings.
if (!(flags & ALF_CHECKCONVERSATION) || th->Conversation == NULL)
{
if (!(flags & ALF_CHECKNONSHOOTABLE)) // For info CCMD, ignore stuff about GHOST and SHOOTABLE flags
{
if (!(th->flags&MF_SHOOTABLE))
continue; // 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))
{
continue;
}
}
}
dist = FixedMul(attackrange, in->frac);
// Don't autoaim certain special actors
if (!cl_doautoaim && th->flags6 & MF6_NOTAUTOAIMED)
{
continue;
}
// we must do one last check whether the trace has crossed a 3D floor
if (lastsector == th->Sector && th->Sector->e->XFloor.ffloors.Size())
{
if (lastceilingplane)
{
fixed_t ff_top = lastceilingplane->ZatPoint(th);
fixed_t pitch = -(int)R_PointToAngle2(0, shootz, dist, ff_top);
// upper slope intersects with this 3d-floor
if (pitch > toppitch)
{
toppitch = pitch;
}
}
if (lastfloorplane)
{
fixed_t ff_bottom = lastfloorplane->ZatPoint(th);
fixed_t pitch = -(int)R_PointToAngle2(0, shootz, dist, ff_bottom);
// lower slope intersects with this 3d-floor
if (pitch < bottompitch)
{
bottompitch = pitch;
}
}
}
// 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)
continue; // shot over the thing
thingbottompitch = -(int)R_PointToAngle2(0, shootz, dist, th->Z());
if (thingbottompitch < toppitch)
continue; // shot under the thing
if (crossedffloors)
{
// if 3D floors were in the way do an extra visibility check for safety
if (!P_CheckSight(shootthing, th, SF_IGNOREVISIBILITY | SF_IGNOREWATERBOUNDARY))
{
// the thing can't be seen so we can safely exclude its range from our aiming field
if (thingtoppitch<toppitch)
{
if (thingbottompitch>toppitch) toppitch = thingbottompitch;
}
else if (thingbottompitch>bottompitch)
{
if (thingtoppitch<bottompitch) bottompitch = thingtoppitch;
}
if (toppitch < bottompitch) continue;
else return;
}
}
// this thing can be hit!
if (thingtoppitch < toppitch)
thingtoppitch = toppitch;
if (thingbottompitch > bottompitch)
thingbottompitch = bottompitch;
thingpitch = thingtoppitch / 2 + thingbottompitch / 2;
if (flags & ALF_CHECK3D)
{
// We need to do a 3D distance check here because this is nearly always used in
// combination with P_LineAttack. P_LineAttack uses 3D distance but FPathTraverse
// only 2D. This causes some problems with Hexen's weapons that use different
// attack modes based on distance to target
fixed_t cosine = finecosine[thingpitch >> ANGLETOFINESHIFT];
if (cosine != 0)
{
fixed_t d3 = FixedDiv(FixedMul(P_AproxDistance(it.Trace().dx, it.Trace().dy), in->frac), cosine);
if (d3 > attackrange)
{
return;
}
}
}
if ((flags & ALF_NOFRIENDS) && th->IsFriend(friender))
{
continue;
}
else if (sv_smartaim != 0 && !(flags & ALF_FORCENOSMART))
{
// try to be a little smarter about what to aim at!
// In particular avoid autoaiming at friends and barrels.
if (th->IsFriend(friender))
{
if (sv_smartaim < 2)
{
// friends don't aim at friends (except players), at least not first
thing_friend = th;
pitch_friend = thingpitch;
}
}
else if (!(th->flags3 & MF3_ISMONSTER) && th->player == NULL)
{
if (sv_smartaim < 3)
{
// don't autoaim at barrels and other shootable stuff unless no monsters have been found
thing_other = th;
pitch_other = thingpitch;
}
}
else
{
linetarget = th;
aimpitch = thingpitch;
return;
}
}
else
{
linetarget = th;
aimpitch = thingpitch;
return;
}
}
}
//============================================================================
//
// P_AimLineAttack
//
//============================================================================
fixed_t P_AimLineAttack(AActor *t1, angle_t angle, fixed_t distance, AActor **pLineTarget, fixed_t vrange,
int flags, AActor *target, AActor *friender)
{
fixed_t x2;
fixed_t y2;
aim_t aim;
angle >>= ANGLETOFINESHIFT;
aim.flags = flags;
aim.shootthing = t1;
aim.friender = (friender == NULL) ? t1 : friender;
x2 = t1->X() + (distance >> FRACBITS)*finecosine[angle];
y2 = t1->Y() + (distance >> FRACBITS)*finesine[angle];
aim.shootz = t1->Z() + (t1->height >> 1) - t1->floorclip;
if (t1->player != NULL)
{
aim.shootz += FixedMul(t1->player->mo->AttackZOffset, t1->player->crouchfactor);
}
else
{
aim.shootz += 8 * FRACUNIT;
}
// can't shoot outside view angles
if (vrange == 0)
{
if (t1->player == NULL || !level.IsFreelookAllowed())
{
vrange = ANGLE_1 * 35;
}
else
{
// [BB] Disable autoaim on weapons with WIF_NOAUTOAIM.
AWeapon *weapon = t1->player->ReadyWeapon;
if (weapon && (weapon->WeaponFlags & WIF_NOAUTOAIM))
{
vrange = ANGLE_1 / 2;
}
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.GetAimDist(), ANGLE_1 / 2, ANGLE_1 * 35);
}
}
}
aim.toppitch = t1->pitch - vrange;
aim.bottompitch = t1->pitch + vrange;
aim.attackrange = distance;
aim.linetarget = NULL;
// for smart aiming
aim.thing_friend = aim.thing_other = NULL;
// Information for tracking crossed 3D floors
aim.aimpitch = t1->pitch;
aim.crossedffloors = t1->Sector->e->XFloor.ffloors.Size() != 0;
aim.lastsector = t1->Sector;
aim.lastfloorplane = aim.lastceilingplane = NULL;
// set initial 3d-floor info
for (unsigned i = 0; i<t1->Sector->e->XFloor.ffloors.Size(); i++)
{
F3DFloor * rover = t1->Sector->e->XFloor.ffloors[i];
fixed_t bottomz = rover->bottom.plane->ZatPoint(t1);
if (bottomz >= t1->Top()) aim.lastceilingplane = rover->bottom.plane;
bottomz = rover->top.plane->ZatPoint(t1);
if (bottomz <= t1->Z()) aim.lastfloorplane = rover->top.plane;
}
aim.AimTraverse(t1->X(), t1->Y(), x2, y2, target);
if (!aim.linetarget)
{
if (aim.thing_other)
{
aim.linetarget = aim.thing_other;
aim.aimpitch = aim.pitch_other;
}
else if (aim.thing_friend)
{
aim.linetarget = aim.thing_friend;
aim.aimpitch = aim.pitch_friend;
}
}
if (pLineTarget)
{
*pLineTarget = aim.linetarget;
}
return aim.linetarget ? aim.aimpitch : t1->pitch;
}
//==========================================================================
//
//
//
//==========================================================================
2014-08-07 13:50:21 +00:00
struct Origin
{
AActor *Caller;
bool hitGhosts;
bool hitSameSpecies;
2014-08-07 13:50:21 +00:00
};
static ETraceStatus CheckForActor(FTraceResults &res, void *userdata)
2014-08-07 13:50:21 +00:00
{
if (res.HitType != TRACE_HitActor)
{
return TRACE_Stop;
}
Origin *data = (Origin *)userdata;
// check for physical attacks on spectrals
if (res.Actor->flags4 & MF4_SPECTRAL)
2014-08-07 13:50:21 +00:00
{
return TRACE_Skip;
}
if (data->hitSameSpecies && res.Actor->GetSpecies() == data->Caller->GetSpecies())
{
return TRACE_Skip;
}
if (data->hitGhosts && res.Actor->flags3 & MF3_GHOST)
{
return TRACE_Skip;
}
return TRACE_Stop;
}
//==========================================================================
//
// P_LineAttack
//
// if damage == 0, it is just a test trace that will leave linetarget set
//
//==========================================================================
AActor *P_LineAttack(AActor *t1, angle_t angle, fixed_t distance,
int pitch, int damage, FName damageType, PClassActor *pufftype, int flags, AActor **victim, int *actualdamage)
{
fixed_t vx, vy, vz, shootz;
FTraceResults trace;
2014-08-07 13:50:21 +00:00
Origin TData;
TData.Caller = t1;
angle_t srcangle = angle;
int srcpitch = pitch;
bool killPuff = false;
AActor *puff = NULL;
int pflag = 0;
int puffFlags = (flags & LAF_ISMELEEATTACK) ? PF_MELEERANGE : 0;
if (flags & LAF_NORANDOMPUFFZ)
puffFlags |= PF_NORANDOMZ;
if (victim != NULL)
{
*victim = NULL;
}
if (actualdamage != NULL)
{
*actualdamage = 0;
}
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);
if (damageType == NAME_Melee || damageType == NAME_Hitscan)
{
// this is coming from a weapon attack function which needs to transfer information to the obituary code,
// We need to preserve this info from the damage type because the actual damage type can get overridden by the puff
pflag = DMG_PLAYERATTACK;
}
}
else
{
shootz += 8 * FRACUNIT;
}
// We need to check the defaults of the replacement here
AActor *puffDefaults = GetDefaultByType(pufftype->GetReplacement());
TData.hitGhosts = (t1->player != NULL &&
t1->player->ReadyWeapon != NULL &&
(t1->player->ReadyWeapon->flags2 & MF2_THRUGHOST)) ||
(puffDefaults && (puffDefaults->flags2 & MF2_THRUGHOST));
TData.hitSameSpecies = (puffDefaults && (puffDefaults->flags6 & MF6_MTHRUSPECIES));
2014-08-07 13:50:21 +00:00
// if the puff uses a non-standard damage type, this will override default, hitscan and melee damage type.
// All other explicitly passed damage types (currenty only MDK) will be preserved.
if ((damageType == NAME_None || damageType == NAME_Melee || damageType == NAME_Hitscan) &&
puffDefaults != NULL && puffDefaults->DamageType != NAME_None)
{
damageType = puffDefaults->DamageType;
}
int tflags;
if (puffDefaults != NULL && puffDefaults->flags6 & MF6_NOTRIGGER) tflags = TRACE_NoSky;
else tflags = TRACE_NoSky | TRACE_Impact;
if (!Trace(t1->X(), t1->Y(), shootz, t1->Sector, vx, vy, vz, distance,
MF_SHOOTABLE, ML_BLOCKEVERYTHING | ML_BLOCKHITSCAN, t1, trace,
tflags, CheckForActor, &TData))
{ // hit nothing
if (puffDefaults == NULL)
{
}
else if (puffDefaults->ActiveSound)
{ // Play miss sound
S_Sound(t1, CHAN_WEAPON, puffDefaults->ActiveSound, 1, ATTN_NORM);
}
if (puffDefaults != NULL && puffDefaults->flags3 & MF3_ALWAYSPUFF)
{ // Spawn the puff anyway
puff = P_SpawnPuff(t1, pufftype, trace.X, trace.Y, trace.Z, angle - ANG180, 2, puffFlags);
}
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;
fixedvec2 pos = t1->Vec2Offset(FixedMul(vx, closer), FixedMul(vy, closer));
puff = P_SpawnPuff(t1, pufftype, pos.x, pos.y,
shootz + FixedMul(vz, closer), angle - ANG90, 0, puffFlags);
}
// [RH] Spawn a decal
if (trace.HitType == TRACE_HitWall && trace.Line->special != Line_Horizon && !(flags & LAF_NOIMPACTDECAL) && !(puffDefaults->flags7 & MF7_NODECAL))
{
// [TN] If the actor or weapon has a decal defined, use that one.
if (t1->DecalGenerator != NULL ||
(t1->player != NULL && t1->player->ReadyWeapon != NULL && t1->player->ReadyWeapon->DecalGenerator != NULL))
{
// [ZK] If puff has FORCEDECAL set, do not use the weapon's decal
if (puffDefaults->flags7 & MF7_FORCEDECAL && puff != NULL && puff->DecalGenerator)
SpawnShootDecal(puff, trace);
else
SpawnShootDecal(t1, trace);
}
// Else, look if the bulletpuff has a decal defined.
else if (puff != NULL && puff->DecalGenerator)
{
SpawnShootDecal(puff, trace);
}
else
{
SpawnShootDecal(t1, trace);
}
}
else if (puff != NULL &&
trace.CrossedWater == NULL &&
trace.Sector->heightsec == NULL &&
trace.HitType == TRACE_HitFloor)
{
// Using the puff's position is not accurate enough.
// Instead make it splash at the actual hit position
hitx = t1->X() + FixedMul(vx, trace.Distance);
hity = t1->Y() + FixedMul(vy, trace.Distance);
hitz = shootz + FixedMul(vz, trace.Distance);
P_HitWater(puff, P_PointInSector(hitx, hity), hitx, hity, hitz);
}
}
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
fixed_t dist = trace.Distance;
// position a bit closer for puffs/blood if using compatibility mode.
if (i_compatflags & COMPATF_HITSCAN) dist -= 10 * FRACUNIT;
2016-01-20 12:48:05 +00:00
hitx = t1->X() + FixedMul(vx, dist);
hity = t1->Y() + FixedMul(vy, dist);
hitz = shootz + FixedMul(vz, dist);
// Spawn bullet puffs or blood spots, depending on target type.
if ((puffDefaults != NULL && puffDefaults->flags3 & MF3_PUFFONACTORS) ||
(trace.Actor->flags & MF_NOBLOOD) ||
(trace.Actor->flags2 & (MF2_INVULNERABLE | MF2_DORMANT)))
{
if (!(trace.Actor->flags & MF_NOBLOOD))
puffFlags |= PF_HITTHINGBLEED;
// We must pass the unreplaced puff type here
puff = P_SpawnPuff(t1, pufftype, hitx, hity, hitz, angle - ANG180, 2, puffFlags | PF_HITTHING, trace.Actor);
}
// Allow puffs to inflict poison damage, so that hitscans can poison, too.
if (puffDefaults != NULL && puffDefaults->PoisonDamage > 0 && puffDefaults->PoisonDuration != INT_MIN)
{
P_PoisonMobj(trace.Actor, puff ? puff : t1, t1, puffDefaults->PoisonDamage, puffDefaults->PoisonDuration, puffDefaults->PoisonPeriod, puffDefaults->PoisonDamageType);
}
// [GZ] If MF6_FORCEPAIN is set, we need to call P_DamageMobj even if damage is 0!
// Note: The puff may not yet be spawned here so we must check the class defaults, not the actor.
int newdam = damage;
if (damage || (puffDefaults != NULL && ((puffDefaults->flags6 & MF6_FORCEPAIN) || (puffDefaults->flags7 & MF7_CAUSEPAIN))))
{
int dmgflags = DMG_INFLICTOR_IS_PUFF | pflag;
// Allow MF5_PIERCEARMOR on a weapon as well.
if (t1->player != NULL && (dmgflags & DMG_PLAYERATTACK) && t1->player->ReadyWeapon != NULL &&
t1->player->ReadyWeapon->flags5 & MF5_PIERCEARMOR)
{
dmgflags |= 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(t1, pufftype, hitx, hity, hitz, angle - ANG180, 2, puffFlags | PF_HITTHING | PF_TEMPORARY);
killPuff = true;
}
newdam = P_DamageMobj(trace.Actor, puff ? puff : t1, t1, damage, damageType, dmgflags);
if (actualdamage != NULL)
{
*actualdamage = newdam;
}
}
if (!(puffDefaults != NULL && puffDefaults->flags3&MF3_BLOODLESSIMPACT))
{
if (!bloodsplatter && !axeBlood &&
2006-04-11 08:36:23 +00:00
!(trace.Actor->flags & MF_NOBLOOD) &&
!(trace.Actor->flags2 & (MF2_INVULNERABLE | MF2_DORMANT)))
{
P_SpawnBlood(hitx, hity, hitz, angle - ANG180, newdam > 0 ? newdam : damage, trace.Actor);
2006-04-11 08:36:23 +00:00
}
2006-04-11 08:36:23 +00:00
if (damage)
{
if (bloodsplatter || axeBlood)
{
2006-04-11 08:36:23 +00:00
if (!(trace.Actor->flags&MF_NOBLOOD) &&
!(trace.Actor->flags2&(MF2_INVULNERABLE | MF2_DORMANT)))
{
2006-04-11 08:36:23 +00:00
if (axeBlood)
{
P_BloodSplatter2(hitx, hity, hitz, trace.Actor);
2006-04-11 08:36:23 +00:00
}
if (pr_lineattack() < 192)
{
P_BloodSplatter(hitx, hity, hitz, trace.Actor);
2006-04-11 08:36:23 +00:00
}
}
}
2006-04-11 08:36:23 +00:00
// [RH] Stick blood to walls
P_TraceBleed(newdam > 0 ? newdam : damage, trace.X, trace.Y, trace.Z,
2006-04-11 08:36:23 +00:00
trace.Actor, srcangle, srcpitch);
}
}
if (victim != NULL)
{
*victim = trace.Actor;
}
}
if (trace.Crossed3DWater || trace.CrossedWater)
{
if (puff == NULL)
{ // Spawn puff just to get a mass for the splash
puff = P_SpawnPuff(t1, pufftype, hitx, hity, hitz, angle - ANG180, 2, puffFlags | PF_HITTHING | PF_TEMPORARY);
killPuff = true;
}
SpawnDeepSplash(t1, trace, puff, vx, vy, vz, shootz, trace.Crossed3DWater != NULL);
}
}
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, int flags, AActor **victim, int *actualdamage)
{
PClassActor *type = PClass::FindActor(pufftype);
if (victim != NULL)
{
*victim = NULL;
}
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, flags, victim, actualdamage);
}
return NULL;
}
2014-09-21 14:43:17 +00:00
//==========================================================================
//
// P_LinePickActor
//
//==========================================================================
AActor *P_LinePickActor(AActor *t1, angle_t angle, fixed_t distance, int pitch,
ActorFlags actorMask, DWORD wallMask)
2014-09-21 14:43:17 +00:00
{
fixed_t vx, vy, vz, shootz;
angle >>= ANGLETOFINESHIFT;
pitch = (angle_t)(pitch) >> ANGLETOFINESHIFT;
vx = FixedMul(finecosine[pitch], finecosine[angle]);
vy = FixedMul(finecosine[pitch], finesine[angle]);
vz = -finesine[pitch];
2016-01-20 12:48:05 +00:00
shootz = t1->Z() - t1->floorclip + (t1->height >> 1);
2014-09-21 14:43:17 +00:00
if (t1->player != NULL)
{
shootz += FixedMul(t1->player->mo->AttackZOffset, t1->player->crouchfactor);
}
else
{
shootz += 8 * FRACUNIT;
}
FTraceResults trace;
Origin TData;
TData.Caller = t1;
TData.hitGhosts = true;
2016-01-20 12:48:05 +00:00
if (Trace(t1->X(), t1->Y(), shootz, t1->Sector, vx, vy, vz, distance,
2014-09-21 14:43:17 +00:00
actorMask, wallMask, t1, trace, TRACE_NoSky, CheckForActor, &TData))
{
if (trace.HitType == TRACE_HitActor)
{
return trace.Actor;
}
}
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 = actor->GetBloodColor();
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,
bleedtrace.Line->sidedef[bleedtrace.Side],
bleedtrace.ffloor,
bloodcolor);
}
}
}
}
void P_TraceBleed(int damage, AActor *target, angle_t angle, int pitch)
{
2016-01-20 12:48:05 +00:00
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->velz != 0)
{
double aim;
2016-01-20 12:48:05 +00:00
aim = atan((double)missile->velz / (double)target->AproxDistance(missile));
pitch = -(int)(aim * ANGLE_180 / PI);
}
else
{
pitch = 0;
}
2016-01-20 12:48:05 +00:00
P_TraceBleed(damage, target->X(), target->Y(), target->Z() + target->height / 2,
target, missile->AngleTo(target),
pitch);
}
//==========================================================================
//
//
//
//==========================================================================
void P_TraceBleed(int damage, AActor *target)
{
if (target != NULL)
{
fixed_t one = pr_tracebleed() << 24;
fixed_t two = (pr_tracebleed() - 128) << 16;
2016-01-20 12:48:05 +00:00
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;
};
struct RailData
{
AActor *Caller;
TArray<SRailHit> RailHits;
bool StopAtOne;
bool StopAtInvul;
bool ThruSpecies;
};
static ETraceStatus ProcessRailHit(FTraceResults &res, void *userdata)
{
RailData *data = (RailData *)userdata;
if (res.HitType != TRACE_HitActor)
{
return TRACE_Stop;
}
// Invulnerable things completely block the shot
if (data->StopAtInvul && res.Actor->flags2 & MF2_INVULNERABLE)
{
return TRACE_Stop;
}
// Skip actors with the same species if the puff has MTHRUSPECIES.
if (data->ThruSpecies && res.Actor->GetSpecies() == data->Caller->GetSpecies())
{
return TRACE_Skip;
}
// 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
data->RailHits.Push(newhit);
return data->StopAtOne ? TRACE_Stop : TRACE_Continue;
}
//==========================================================================
//
//
//
//==========================================================================
void P_RailAttack(AActor *source, int damage, int offset_xy, fixed_t offset_z, int color1, int color2, double maxdiff, int railflags, PClassActor *puffclass, angle_t angleoffset, angle_t pitchoffset, fixed_t distance, int duration, double sparsity, double drift, PClassActor *spawnclass, int SpiralOffset)
{
fixed_t vx, vy, vz;
angle_t angle, pitch;
TVector3<double> start, end;
FTraceResults trace;
fixed_t shootz;
if (puffclass == NULL)
{
puffclass = PClass::FindActor(NAME_BulletPuff);
}
pitch = ((angle_t)(-source->pitch) + pitchoffset) >> ANGLETOFINESHIFT;
angle = (source->angle + angleoffset) >> ANGLETOFINESHIFT;
vx = FixedMul(finecosine[pitch], finecosine[angle]);
vy = FixedMul(finecosine[pitch], finesine[angle]);
vz = finesine[pitch];
2016-01-20 12:48:05 +00:00
shootz = source->Z() - source->floorclip + (source->height >> 1) + offset_z;
if (!(railflags & RAF_CENTERZ))
{
if (source->player != NULL)
{
shootz += FixedMul(source->player->mo->AttackZOffset, source->player->crouchfactor);
}
else
{
shootz += 8 * FRACUNIT;
}
}
angle = ((source->angle + angleoffset) - ANG90) >> ANGLETOFINESHIFT;
2016-01-20 12:48:05 +00:00
fixedvec2 xy = source->Vec2Offset(offset_xy * finecosine[angle], offset_xy * finesine[angle]);
RailData rail_data;
rail_data.Caller = source;
rail_data.StopAtOne = !!(railflags & RAF_NOPIERCE);
start.X = FIXED2DBL(xy.x);
start.Y = FIXED2DBL(xy.y);
start.Z = FIXED2DBL(shootz);
int flags;
assert(puffclass != NULL); // Because we set it to a default above
AActor *puffDefaults = GetDefaultByType(puffclass->GetReplacement()); //Contains all the flags such as FOILINVUL, etc.
flags = (puffDefaults->flags6 & MF6_NOTRIGGER) ? 0 : TRACE_PCross | TRACE_Impact;
rail_data.StopAtInvul = (puffDefaults->flags3 & MF3_FOILINVUL) ? false : true;
rail_data.ThruSpecies = (puffDefaults->flags6 & MF6_MTHRUSPECIES) ? true : false;
2016-01-20 12:48:05 +00:00
Trace(xy.x, xy.y, shootz, source->Sector, vx, vy, vz,
distance, MF_SHOOTABLE, ML_BLOCKEVERYTHING, source, trace,
flags, ProcessRailHit, &rail_data);
// Hurt anything the trace hit
unsigned int i;
FName damagetype = (puffDefaults == NULL || puffDefaults->DamageType == NAME_None) ? FName(NAME_Railgun) : puffDefaults->DamageType;
- fixed: WIF_STAFF2_KICKBACK did not work anymore because it depended on conditions that were changed some time ago. - fixed: The damage inflictor for a rail attack was the shooter, not the puff. - Fixed: Floor and ceiling huggers may not change their z-velocity when seeking. - Fixed: UDMF set the secret sector flag before parsing the sector's properties, resulting in it always being false. - Renamed sector's oldspecial variable to secretsector to better reflect its only use. - Fixed: A_BrainSpit stored as the SpawnShot's target the intended BossTarget, not itself contrarily to other projectile spawning functions. A_SpawnFly then used the target for CopyFriendliness, thinking it'll be the BossEye when in fact it wasn't. - Added Gez's submission for a DEHACKED hack introduced by Boom. (using code pointers of the form 'Pointer 0 (x statenumber)'. - fixed: Attaching 3DMidtex lines by sector tag did not work because lines were marked by index in the sector's line list but needed to be marked by line index in the global array. - fixed: On Linux ZDoom was creating a directory called "~.zdoom" for save files because of a missing slash. - fixed: UDMF was unable to read floating point values in exponential format because the C Mode scanner was missing a definition for them. - fixed: The recent changes for removing pointer aliasing got the end sequence info from an incorrect variable. To make this more robust the sequence index is now stored as a hexadecimal string to avoid storing binary data in a string. Also moved end sequence lookup from f_finale.cpp to the calling code so that the proper end sequences can be retrieved for secret exits, too. SVN r1777 (trunk)
2009-08-30 10:43:51 +00:00
// used as damage inflictor
AActor *thepuff = NULL;
2016-01-20 12:48:05 +00:00
if (puffclass != NULL) thepuff = Spawn(puffclass, source->Pos(), ALLOW_REPLACE);
for (i = 0; i < rail_data.RailHits.Size(); i++)
{
fixed_t x, y, z;
bool spawnpuff;
bool bleed = false;
int puffflags = PF_HITTHING;
AActor *hitactor = rail_data.RailHits[i].HitActor;
fixed_t hitdist = rail_data.RailHits[i].Distance;
2016-01-20 12:48:05 +00:00
x = xy.x + FixedMul(hitdist, vx);
y = xy.y + FixedMul(hitdist, vy);
z = shootz + FixedMul(hitdist, vz);
if ((hitactor->flags & MF_NOBLOOD) ||
(hitactor->flags2 & MF2_DORMANT || ((hitactor->flags2 & MF2_INVULNERABLE) && !(puffDefaults->flags3 & MF3_FOILINVUL))))
{
spawnpuff = (puffclass != NULL);
}
else
{
spawnpuff = (puffclass != NULL && puffDefaults->flags3 & MF3_ALWAYSPUFF);
puffflags |= PF_HITTHINGBLEED; // [XA] Allow for puffs to jump to XDeath state.
if (!(puffDefaults->flags3 & MF3_BLOODLESSIMPACT))
{
bleed = true;
}
}
if (spawnpuff)
{
P_SpawnPuff(source, puffclass, x, y, z, (source->angle + angleoffset) - ANG90, 1, puffflags, hitactor);
}
int dmgFlagPass = DMG_INFLICTOR_IS_PUFF;
if (puffDefaults != NULL) // is this even possible?
{
if (puffDefaults->PoisonDamage > 0 && puffDefaults->PoisonDuration != INT_MIN)
{
P_PoisonMobj(hitactor, thepuff ? thepuff : source, source, puffDefaults->PoisonDamage, puffDefaults->PoisonDuration, puffDefaults->PoisonPeriod, puffDefaults->PoisonDamageType);
}
if (puffDefaults->flags3 & MF3_FOILINVUL) dmgFlagPass |= DMG_FOILINVUL;
if (puffDefaults->flags7 & MF7_FOILBUDDHA) dmgFlagPass |= DMG_FOILBUDDHA;
}
int newdam = P_DamageMobj(hitactor, thepuff ? thepuff : source, source, damage, damagetype, dmgFlagPass);
if (bleed)
{
P_SpawnBlood(x, y, z, (source->angle + angleoffset) - ANG180, newdam > 0 ? newdam : damage, hitactor);
P_TraceBleed(newdam > 0 ? newdam : damage, x, y, z, hitactor, source->angle, pitch);
}
}
// Spawn a decal or puff at the point where the trace ended.
if (trace.HitType == TRACE_HitWall)
{
2015-06-25 00:53:46 +00:00
AActor* puff = NULL;
if (puffclass != NULL && puffDefaults->flags3 & MF3_ALWAYSPUFF)
{
2015-06-25 00:53:46 +00:00
puff = P_SpawnPuff(source, puffclass, trace.X, trace.Y, trace.Z, (source->angle + angleoffset) - ANG90, 1, 0);
2016-01-16 22:36:42 +00:00
if (puff && (trace.Line != NULL) && (trace.Line->special == Line_Horizon) && !(puff->flags3 & MF3_SKYEXPLODE))
puff->Destroy();
}
2015-06-25 00:53:46 +00:00
if (puff != NULL && puffDefaults->flags7 & MF7_FORCEDECAL && puff->DecalGenerator)
SpawnShootDecal(puff, trace);
else
SpawnShootDecal(source, trace);
}
if (trace.HitType == TRACE_HitFloor || trace.HitType == TRACE_HitCeiling)
{
AActor* puff = NULL;
if (puffclass != NULL && puffDefaults->flags3 & MF3_ALWAYSPUFF)
{
puff = P_SpawnPuff(source, puffclass, trace.X, trace.Y, trace.Z, (source->angle + angleoffset) - ANG90, 1, 0);
if (puff && !(puff->flags3 & MF3_SKYEXPLODE) &&
(((trace.HitType == TRACE_HitFloor) && (puff->floorpic == skyflatnum)) ||
((trace.HitType == TRACE_HitCeiling) && (puff->ceilingpic == skyflatnum))))
{
puff->Destroy();
}
}
}
if (thepuff != NULL)
{
if (trace.HitType == TRACE_HitFloor &&
trace.CrossedWater == NULL &&
trace.Sector->heightsec == NULL)
{
2016-01-20 12:48:05 +00:00
thepuff->SetOrigin(trace.X, trace.Y, trace.Z, false);
P_HitWater(thepuff, trace.Sector);
}
if (trace.Crossed3DWater || trace.CrossedWater)
{
SpawnDeepSplash(source, trace, thepuff, vx, vy, vz, shootz, trace.Crossed3DWater != NULL);
}
thepuff->Destroy();
}
// Draw the slug's trail.
end.X = FIXED2DBL(trace.X);
end.Y = FIXED2DBL(trace.Y);
end.Z = FIXED2DBL(trace.Z);
P_DrawRailTrail(source, start, end, color1, color2, maxdiff, railflags, spawnclass, source->angle + angleoffset, duration, sparsity, drift, SpiralOffset);
}
//==========================================================================
//
// [RH] P_AimCamera
//
//==========================================================================
CVAR(Float, chase_height, -8.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Float, chase_dist, 90.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
void P_AimCamera(AActor *t1, fixed_t &CameraX, fixed_t &CameraY, fixed_t &CameraZ, sector_t *&CameraSector)
{
fixed_t distance = (fixed_t)(clamp<double>(chase_dist, 0, 30000) * 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];
2016-01-20 12:48:05 +00:00
sz = t1->Z() - t1->floorclip + t1->height + (fixed_t)(clamp<double>(chase_height, -1000, 1000) * FRACUNIT);
2016-01-20 12:48:05 +00:00
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;
2016-01-20 12:48:05 +00:00
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;
}
//==========================================================================
//
// P_TalkFacing
//
// Looks for something within 5.625 degrees left or right of the player
// to talk to.
//
//==========================================================================
bool P_TalkFacing(AActor *player)
{
AActor *linetarget;
P_AimLineAttack(player, player->angle, TALKRANGE, &linetarget, ANGLE_1 * 35, ALF_FORCENOSMART | ALF_CHECKCONVERSATION);
if (linetarget == NULL)
{
P_AimLineAttack(player, player->angle + (ANGLE_90 >> 4), TALKRANGE, &linetarget, ANGLE_1 * 35, ALF_FORCENOSMART | ALF_CHECKCONVERSATION);
if (linetarget == NULL)
{
P_AimLineAttack(player, player->angle - (ANGLE_90 >> 4), TALKRANGE, &linetarget, ANGLE_1 * 35, ALF_FORCENOSMART | ALF_CHECKCONVERSATION);
if (linetarget == NULL)
{
return false;
}
}
}
// Dead things can't talk.
if (linetarget->health <= 0)
{
return false;
}
// Fighting things don't talk either.
if (linetarget->flags4 & MF4_INCOMBAT)
{
return false;
}
if (linetarget->Conversation != NULL)
{
// Give the NPC a chance to play a brief animation
linetarget->ConversationAnimation(0);
P_StartConversation(linetarget, player, true, true);
return true;
}
return false;
}
//==========================================================================
//
// USE LINES
//
//==========================================================================
bool P_UseTraverse(AActor *usething, fixed_t endx, fixed_t endy, bool &foundline)
{
2016-01-20 12:48:05 +00:00
FPathTraverse it(usething->X(), usething->Y(), endx, endy, PT_ADDLINES | PT_ADDTHINGS);
intercept_t *in;
while ((in = it.Next()))
{
// [RH] Check for things to talk with or use a puzzle item on
if (!in->isaline)
{
if (usething == in->d.thing)
continue;
// Check thing
// Check for puzzle item use or USESPECIAL flag
// Extended to use the same activationtype mechanism as BUMPSPECIAL does
if (in->d.thing->flags5 & MF5_USESPECIAL || in->d.thing->special == UsePuzzleItem)
{
if (P_ActivateThingSpecial(in->d.thing, usething))
return true;
}
continue;
}
FLineOpening open;
if (in->d.line->special == 0 || !(in->d.line->activation & (SPAC_Use | SPAC_UseThrough | SPAC_UseBack)))
{
blocked:
if (in->d.line->flags & (ML_BLOCKEVERYTHING | ML_BLOCKUSE))
{
open.range = 0;
}
else
{
P_LineOpening(open, NULL, in->d.line, it.Trace().x + FixedMul(it.Trace().dx, in->frac),
it.Trace().y + FixedMul(it.Trace().dy, in->frac));
}
if (open.range <= 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 true;
}
2016-01-20 12:48:05 +00:00
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 true;
}
if (usething->player)
{
S_Sound(usething, CHAN_VOICE, "*usefail", 1, ATTN_IDLE);
}
return true; // can't use through a wall
}
foundline = true;
continue; // not a special line, but keep checking
}
2016-01-20 12:48:05 +00:00
if (P_PointOnLineSide(usething->X(), usething->Y(), in->d.line) == 1)
{
if (!(in->d.line->activation & SPAC_UseBack))
{
// [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
}
else
{
P_ActivateLine(in->d.line, usething, 1, SPAC_UseBack);
return true;
}
}
else
{
if ((in->d.line->activation & (SPAC_Use | SPAC_UseThrough | SPAC_UseBack)) == SPAC_UseBack)
{
goto blocked; // Line cannot be used from front side so treat it as a non-trigger line
}
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)
{
if (in->d.line->activation & SPAC_UseThrough) continue;
else return true;
}
else
{
if (!(in->d.line->activation & SPAC_Use)) continue;
else return true;
}
}
}
return false;
}
//==========================================================================
//
// 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 P_NoWayTraverse(AActor *usething, fixed_t endx, fixed_t endy)
{
2016-01-20 12:48:05 +00:00
FPathTraverse it(usething->X(), usething->Y(), endx, endy, PT_ADDLINES);
intercept_t *in;
2006-04-11 08:36:23 +00:00
while ((in = it.Next()))
{
line_t *ld = in->d.line;
FLineOpening open;
// [GrafZahl] de-obfuscated. Was I the only one who was unable to make sense out of
// this convoluted mess?
if (ld->special) continue;
if (ld->flags&(ML_BLOCKING | ML_BLOCKEVERYTHING | ML_BLOCK_PLAYERS)) return true;
P_LineOpening(open, NULL, ld, it.Trace().x + FixedMul(it.Trace().dx, in->frac),
it.Trace().y + FixedMul(it.Trace().dy, in->frac));
if (open.range <= 0 ||
2016-01-20 12:48:05 +00:00
open.bottom > usething->Z() + usething->MaxStepHeight ||
open.top < usething->Top()) return true;
}
return false;
}
//==========================================================================
//
// P_UseLines
//
// Looks for special lines in front of the player to activate
//
//==========================================================================
void P_UseLines(player_t *player)
{
2016-01-20 12:48:05 +00:00
bool foundline = false;
// [NS] Now queries the Player's UseRange.
2016-01-20 12:48:05 +00:00
fixedvec2 end = player->mo->Vec2Angle(player->mo->UseRange, player->mo->angle, true);
// old code:
//
// P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse );
//
// This added test makes the "oof" sound work on 2s lines -- killough:
2016-01-20 12:48:05 +00:00
if (!P_UseTraverse(player->mo, end.x, end.y, foundline))
{ // [RH] Give sector a chance to eat the use
sector_t *sec = player->mo->Sector;
int spac = SECSPAC_Use;
if (foundline) spac |= SECSPAC_UseWall;
if ((!sec->SecActTarget || !sec->SecActTarget->TriggerAction(player->mo, spac)) &&
2016-01-20 12:48:05 +00:00
P_NoWayTraverse(player->mo, end.x, end.y))
{
S_Sound(player->mo, CHAN_VOICE, "*usefail", 1, ATTN_IDLE);
}
}
}
//==========================================================================
//
// P_UsePuzzleItem
//
// Returns true if the puzzle item was used on a line or a thing.
//
//==========================================================================
bool P_UsePuzzleItem(AActor *PuzzleItemUser, int PuzzleItemType)
{
int angle;
fixed_t x1, y1, x2, y2, usedist;
angle = PuzzleItemUser->angle >> ANGLETOFINESHIFT;
2016-01-20 12:48:05 +00:00
x1 = PuzzleItemUser->X();
y1 = PuzzleItemUser->Y();
// [NS] If it's a Player, get their UseRange.
if (PuzzleItemUser->player)
usedist = PuzzleItemUser->player->mo->UseRange;
else
usedist = USERANGE;
x2 = x1 + FixedMul(usedist, finecosine[angle]);
y2 = y1 + FixedMul(usedist, finesine[angle]);
FPathTraverse it(x1, y1, x2, y2, PT_ADDLINES | PT_ADDTHINGS);
intercept_t *in;
while ((in = it.Next()))
{
AActor *mobj;
FLineOpening open;
if (in->isaline)
{ // Check line
if (in->d.line->special != UsePuzzleItem)
{
P_LineOpening(open, NULL, in->d.line, it.Trace().x + FixedMul(it.Trace().dx, in->frac),
it.Trace().y + FixedMul(it.Trace().dy, in->frac));
if (open.range <= 0)
{
return false; // can't use through a wall
}
continue;
}
2016-01-20 12:48:05 +00:00
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;
}
int args[3] = { in->d.line->args[2], in->d.line->args[3], in->d.line->args[4] };
P_StartScript(PuzzleItemUser, in->d.line, in->d.line->args[1], NULL, args, 3, ACS_ALWAYS);
in->d.line->special = 0;
return true;
}
// Check thing
mobj = in->d.thing;
if (mobj->special != UsePuzzleItem)
{ // Wrong special
continue;
}
if (PuzzleItemType != mobj->args[0])
{ // Item type doesn't match
continue;
}
int args[3] = { mobj->args[2], mobj->args[3], mobj->args[4] };
P_StartScript(PuzzleItemUser, NULL, mobj->args[1], NULL, args, 3, ACS_ALWAYS);
mobj->special = 0;
return true;
}
return false;
}
//==========================================================================
//
// RADIUS ATTACK
//
//
//==========================================================================
// [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;
}
//==========================================================================
//
// P_RadiusAttack
// Source is the creature that caused the explosion at spot.
//
//==========================================================================
void P_RadiusAttack(AActor *bombspot, AActor *bombsource, int bombdamage, int bombdistance, FName bombmod,
int flags, int fulldamagedistance)
{
if (bombdistance <= 0)
return;
fulldamagedistance = clamp<int>(fulldamagedistance, 0, bombdistance - 1);
double bombdistancefloat = 1.f / (double)(bombdistance - fulldamagedistance);
double bombdamagefloat = (double)bombdamage;
2016-01-20 12:48:05 +00:00
FBlockThingsIterator it(FBoundingBox(bombspot->X(), bombspot->Y(), bombdistance << FRACBITS));
AActor *thing;
if (flags & RADF_SOURCEISSPOT)
{ // The source is actually the same as the spot, even if that wasn't what we received.
bombsource = bombspot;
}
while ((thing = it.Next()))
{
// Vulnerable actors can be damaged by radius attacks even if not shootable
// Used to emulate MBF's vulnerability of non-missile bouncers to explosions.
if (!((thing->flags & MF_SHOOTABLE) || (thing->flags6 & MF6_VULNERABLE)))
continue;
// Boss spider and cyborg and Heretic's ep >= 2 bosses
// take no damage from concussion.
if (thing->flags3 & MF3_NORADIUSDMG && !(bombspot->flags4 & MF4_FORCERADIUSDMG))
continue;
if (!(flags & RADF_HURTSOURCE) && (thing == bombsource || thing == bombspot))
{ // don't damage the source of the explosion
continue;
}
// 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 DONTHARMCLASS and DONTHARMSPECIES flags.
if ((bombsource && !thing->player) // code common to both checks
&& ( // Class check first
((bombsource->flags4 & MF4_DONTHARMCLASS) && (thing->GetClass() == bombsource->GetClass()))
|| // Nigh-identical species check second
((bombsource->flags6 & MF6_DONTHARMSPECIES) && (thing->GetSpecies() == bombsource->GetSpecies()))
)
) continue;
// 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 ((flags & RADF_NODAMAGE) || !((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.
double points;
double len;
fixed_t dx, dy;
double boxradius;
2016-01-20 12:48:05 +00:00
fixedvec2 vec = bombspot->Vec2To(thing);
dx = abs(vec.x);
dy = abs(vec.y);
boxradius = double(thing->radius);
// The damage pattern is square, not circular.
len = double(dx > dy ? dx : dy);
2016-01-20 12:48:05 +00:00
if (bombspot->Z() < thing->Z() || bombspot->Z() >= thing->Top())
{
double dz;
2016-01-20 12:48:05 +00:00
if (bombspot->Z() > thing->Z())
{
2016-01-20 12:48:05 +00:00
dz = double(bombspot->Z() - thing->Top());
}
else
{
2016-01-20 12:48:05 +00:00
dz = double(thing->Z() - bombspot->Z());
}
if (len <= boxradius)
{
len = dz;
}
else
{
len -= boxradius;
len = sqrt(len*len + dz*dz);
}
}
else
{
len -= boxradius;
if (len < 0.f)
len = 0.f;
}
len /= FRACUNIT;
len = clamp<double>(len - (double)fulldamagedistance, 0, len);
points = bombdamagefloat * (1.f - len * bombdistancefloat);
if (thing == bombsource)
{
points = points * splashfactor;
}
points *= thing->GetClass()->RDFactor / (float)FRACUNIT;
// points and bombdamage should be the same sign
if (((points * bombdamage) > 0) && P_CheckSight(thing, bombspot, SF_IGNOREVISIBILITY | SF_IGNOREWATERBOUNDARY))
{ // OK to damage; target is in direct path
double velz;
double thrust;
int damage = abs((int)points);
int newdam = damage;
if (!(flags & RADF_NODAMAGE))
newdam = P_DamageMobj(thing, bombspot, bombsource, damage, bombmod);
2014-10-28 07:40:34 +00:00
else if (thing->player == NULL && (!(flags & RADF_NOIMPACTDAMAGE) && !(thing->flags7 & MF7_DONTTHRUST)))
thing->flags2 |= MF2_BLASTED;
if (!(thing->flags & MF_ICECORPSE))
{
if (!(flags & RADF_NODAMAGE) && !(bombspot->flags3 & MF3_BLOODLESSIMPACT))
P_TraceBleed(newdam > 0 ? newdam : damage, thing, bombspot);
if ((flags & RADF_NODAMAGE) || !(bombspot->flags2 & MF2_NODMGTHRUST))
{
2011-05-11 04:29:19 +00:00
if (bombsource == NULL || !(bombsource->flags2 & MF2_NODMGTHRUST))
{
2014-10-28 07:40:34 +00:00
if (!(thing->flags7 & MF7_DONTTHRUST))
{
thrust = points * 0.5f / (double)thing->Mass;
if (bombsource == thing)
{
thrust *= selfthrustscale;
}
2016-01-20 12:48:05 +00:00
velz = (double)(thing->Z() + (thing->height >> 1) - bombspot->Z()) * thrust;
if (bombsource != thing)
{
velz *= 0.5f;
}
else
{
velz *= 0.8f;
}
angle_t ang = bombspot->AngleTo(thing) >> ANGLETOFINESHIFT;
thing->velx += fixed_t(finecosine[ang] * thrust);
thing->vely += fixed_t(finesine[ang] * thrust);
if (!(flags & RADF_NODAMAGE))
thing->velz += (fixed_t)velz; // this really doesn't work well
}
}
}
}
}
}
else
{
// [RH] Old code just for barrels
fixed_t dx, dy, dist;
2016-01-20 12:48:05 +00:00
fixedvec2 vec = bombspot->Vec2To(thing);
dx = abs(vec.x);
dy = abs(vec.y);
dist = dx>dy ? dx : dy;
dist = (dist - thing->radius) >> FRACBITS;
if (dist < 0)
dist = 0;
if (dist >= bombdistance)
continue; // out of range
if (P_CheckSight(thing, bombspot, SF_IGNOREVISIBILITY | SF_IGNOREWATERBOUNDARY))
{ // OK to damage; target is in direct path
dist = clamp<int>(dist - fulldamagedistance, 0, dist);
int damage = Scale(bombdamage, bombdistance - dist, bombdistance);
damage = (int)((double)damage * splashfactor);
damage = Scale(damage, thing->GetClass()->RDFactor, FRACUNIT);
if (damage > 0)
{
int newdam = P_DamageMobj(thing, bombspot, bombsource, damage, bombmod);
P_TraceBleed(newdam > 0 ? newdam : damage, thing, bombspot);
}
}
}
}
}
//==========================================================================
//
// 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.
//
//==========================================================================
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
struct FChangePosition
{
sector_t *sector;
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
int moveamt;
int crushchange;
bool nofit;
bool movemidtex;
};
TArray<AActor *> intersectors;
EXTERN_CVAR(Int, cl_bloodtype)
//=============================================================================
//
// P_AdjustFloorCeil
//
//=============================================================================
bool P_AdjustFloorCeil(AActor *thing, FChangePosition *cpos)
{
2015-04-04 16:40:43 +00:00
ActorFlags2 flags2 = thing->flags2 & MF2_PASSMOBJ;
FCheckPosition tm;
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
if ((thing->flags2 & MF2_PASSMOBJ) && (thing->flags3 & MF3_ISMONSTER))
{
tm.FromPMove = true;
}
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
if (cpos->movemidtex)
{
// From Eternity:
// ALL things must be treated as PASSMOBJ when moving
// 3DMidTex lines, otherwise you get stuck in them.
thing->flags2 |= MF2_PASSMOBJ;
}
2016-01-20 12:48:05 +00:00
bool isgood = P_CheckPosition(thing, thing->X(), thing->Y(), tm);
thing->floorz = tm.floorz;
thing->ceilingz = tm.ceilingz;
thing->dropoffz = tm.dropoffz; // killough 11/98: remember dropoffs
thing->floorpic = tm.floorpic;
thing->floorterrain = tm.floorterrain;
thing->floorsector = tm.floorsector;
thing->ceilingpic = tm.ceilingpic;
thing->ceilingsector = tm.ceilingsector;
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
// restore the PASSMOBJ flag but leave the other flags alone.
thing->flags2 = (thing->flags2 & ~MF2_PASSMOBJ) | flags2;
return isgood;
}
//=============================================================================
//
// P_FindAboveIntersectors
//
//=============================================================================
void P_FindAboveIntersectors(AActor *actor)
{
if (actor->flags & MF_NOCLIP)
return;
if (!(actor->flags & MF_SOLID))
return;
AActor *thing;
2016-01-20 12:48:05 +00:00
FBlockThingsIterator it(FBoundingBox(actor->X(), actor->Y(), actor->radius));
while ((thing = it.Next()))
{
if (!thing->intersects(actor))
{
continue;
}
if (!(thing->flags & MF_SOLID))
{ // Can't hit thing
continue;
}
if (thing->flags & (MF_SPECIAL))
{ // [RH] Corpses and specials don't block moves
continue;
}
if (thing->flags & (MF_CORPSE))
{ // Corpses need a few more checks
if (!(actor->flags & MF_ICECORPSE))
continue;
}
if (thing == actor)
{ // Don't clip against self
continue;
}
if (!((thing->flags2 | actor->flags2) & MF2_PASSMOBJ) && !((thing->flags3 | actor->flags3) & MF3_ISMONSTER))
{
// Don't bother if both things don't have MF2_PASSMOBJ set and aren't monsters.
// These things would always block each other which in nearly every situation is
// not what is wanted here.
continue;
}
2016-01-20 12:48:05 +00:00
if (thing->Z() >= actor->Z() &&
thing->Z() <= actor->Top())
{ // Thing intersects above the base
intersectors.Push(thing);
}
}
}
//=============================================================================
//
// P_FindBelowIntersectors
//
//=============================================================================
void P_FindBelowIntersectors(AActor *actor)
{
if (actor->flags & MF_NOCLIP)
return;
if (!(actor->flags & MF_SOLID))
return;
AActor *thing;
2016-01-20 12:48:05 +00:00
FBlockThingsIterator it(FBoundingBox(actor->X(), actor->Y(), actor->radius));
while ((thing = it.Next()))
{
if (!thing->intersects(actor))
{
continue;
}
if (!(thing->flags & MF_SOLID))
{ // Can't hit thing
continue;
}
if (thing->flags & (MF_SPECIAL))
{ // [RH] Corpses and specials don't block moves
continue;
}
if (thing->flags & (MF_CORPSE))
{ // Corpses need a few more checks
if (!(actor->flags & MF_ICECORPSE))
continue;
}
if (thing == actor)
{ // Don't clip against self
continue;
}
if (!((thing->flags2 | actor->flags2) & MF2_PASSMOBJ) && !((thing->flags3 | actor->flags3) & MF3_ISMONSTER))
{
// Don't bother if both things don't have MF2_PASSMOBJ set and aren't monsters.
// These things would always block each other which in nearly every situation is
// not what is wanted here.
continue;
}
2016-01-20 12:48:05 +00:00
if (thing->Top() <= actor->Top() &&
thing->Top() > actor->Z())
{ // Thing intersects below the base
intersectors.Push(thing);
}
}
}
//=============================================================================
//
// P_DoCrunch
//
//=============================================================================
void P_DoCrunch(AActor *thing, FChangePosition *cpos)
{
if (!(thing && thing->Grind(true) && cpos)) return;
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
cpos->nofit = true;
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
if ((cpos->crushchange > 0) && !(level.maptime & 3))
{
int newdam = P_DamageMobj(thing, NULL, NULL, cpos->crushchange, NAME_Crush);
// spray blood in a random direction
if (!(thing->flags2&(MF2_INVULNERABLE | MF2_DORMANT)))
{
if (!(thing->flags&MF_NOBLOOD))
{
PalEntry bloodcolor = thing->GetBloodColor();
PClassActor *bloodcls = thing->GetBloodType();
P_TraceBleed (newdam > 0 ? newdam : cpos->crushchange, thing);
if (bloodcls != NULL)
{
AActor *mo;
2016-01-20 12:48:05 +00:00
mo = Spawn(bloodcls, thing->PosPlusZ(thing->height / 2), ALLOW_REPLACE);
mo->velx = pr_crunch.Random2() << 12;
mo->vely = pr_crunch.Random2() << 12;
if (bloodcolor != 0 && !(mo->flags2 & MF2_DONTTRANSLATE))
{
mo->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a);
}
if (!(cl_bloodtype <= 1)) mo->renderflags |= RF_INVISIBLE;
}
angle_t an;
an = (M_Random() - 128) << 24;
if (cl_bloodtype >= 1)
{
2016-01-20 12:48:05 +00:00
P_DrawSplash2(32, thing->X(), thing->Y(), thing->Z() + thing->height / 2, an, 2, bloodcolor);
}
}
if (thing->CrushPainSound != 0 && !S_GetSoundPlayingInfo(thing, thing->CrushPainSound))
{
S_Sound(thing, CHAN_VOICE, thing->CrushPainSound, 1.f, ATTN_NORM);
}
}
}
// 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, FChangePosition *cpos)
{
unsigned int firstintersect = intersectors.Size();
unsigned int lastintersect;
int mymass = thing->Mass;
2016-01-20 12:48:05 +00:00
if (thing->Top() > thing->ceilingz)
{
return 1;
}
// [GZ] Skip thing intersect test for THRUACTORS things.
if (thing->flags2 & MF2_THRUACTORS)
return 0;
P_FindAboveIntersectors(thing);
lastintersect = intersectors.Size();
for (; firstintersect < lastintersect; firstintersect++)
{
AActor *intersect = intersectors[firstintersect];
// [GZ] Skip this iteration for THRUSPECIES things
// Should there be MF2_THRUGHOST / MF3_GHOST checks there too for consistency?
// Or would that risk breaking established behavior? THRUGHOST, like MTHRUSPECIES,
// is normally for projectiles which would have exploded by now anyway...
if (thing->flags6 & MF6_THRUSPECIES && thing->GetSpecies() == intersect->GetSpecies())
continue;
if ((thing->flags & MF_MISSILE) && (intersect->flags2 & MF2_REFLECTIVE) && (intersect->flags7 & MF7_THRUREFLECT))
continue;
if (!(intersect->flags2 & MF2_PASSMOBJ) ||
(!(intersect->flags3 & MF3_ISMONSTER) && intersect->Mass > mymass) ||
(intersect->flags4 & MF4_ACTLIKEBRIDGE)
)
{
// Can't push bridges or things more massive than ourself
return 2;
}
fixed_t oldz;
2016-01-20 12:48:05 +00:00
oldz = intersect->Z();
P_AdjustFloorCeil(intersect, cpos);
2016-01-20 12:48:05 +00:00
intersect->SetZ(thing->Top() + 1);
if (P_PushUp(intersect, cpos))
{ // Move blocked
P_DoCrunch(intersect, cpos);
2016-01-20 12:48:05 +00:00
intersect->SetZ(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, FChangePosition *cpos)
{
unsigned int firstintersect = intersectors.Size();
unsigned int lastintersect;
int mymass = thing->Mass;
2016-01-20 12:48:05 +00:00
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) ||
(intersect->flags4 & MF4_ACTLIKEBRIDGE)
)
{
// Can't push bridges or things more massive than ourself
return 2;
}
2016-01-20 12:48:05 +00:00
fixed_t oldz = intersect->Z();
P_AdjustFloorCeil(intersect, cpos);
2016-01-20 12:48:05 +00:00
if (oldz > thing->Z() - intersect->height)
{ // Only push things down, not up.
2016-01-20 12:48:05 +00:00
intersect->SetZ(thing->Z() - intersect->height);
if (P_PushDown(intersect, cpos))
{ // Move blocked
P_DoCrunch(intersect, cpos);
2016-01-20 12:48:05 +00:00
intersect->SetZ(oldz);
return 2;
}
}
}
return 0;
}
//=============================================================================
//
// PIT_FloorDrop
//
//=============================================================================
void PIT_FloorDrop(AActor *thing, FChangePosition *cpos)
{
fixed_t oldfloorz = thing->floorz;
fixed_t oldz = thing->Z();
P_AdjustFloorCeil(thing, cpos);
if (oldfloorz == thing->floorz) return;
if (thing->flags4 & MF4_ACTLIKEBRIDGE) return; // do not move bridge things
if (thing->velz == 0 &&
(!(thing->flags & MF_NOGRAVITY) ||
2016-01-20 12:48:05 +00:00
(thing->Z() == oldfloorz && !(thing->flags & MF_NOLIFTDROP))))
{
2016-01-20 12:48:05 +00:00
fixed_t oldz = thing->Z();
if ((thing->flags & MF_NOGRAVITY) || (thing->flags5 & MF5_MOVEWITHSECTOR) ||
(((cpos->sector->Flags & SECF_FLOORDROP) || cpos->moveamt < 9 * FRACUNIT)
2016-01-20 12:48:05 +00:00
&& thing->Z() - thing->floorz <= cpos->moveamt))
{
2016-01-20 12:48:05 +00:00
thing->SetZ(thing->floorz);
P_CheckFakeFloorTriggers(thing, oldz);
}
}
2016-01-20 12:48:05 +00:00
else if ((thing->Z() != oldfloorz && !(thing->flags & MF_NOLIFTDROP)))
2013-08-10 07:32:55 +00:00
{
2016-01-20 12:48:05 +00:00
fixed_t oldz = thing->Z();
2013-08-10 07:32:55 +00:00
if ((thing->flags & MF_NOGRAVITY) && (thing->flags6 & MF6_RELATIVETOFLOOR))
{
2016-01-20 12:48:05 +00:00
thing->AddZ(-oldfloorz + thing->floorz);
P_CheckFakeFloorTriggers(thing, oldz);
2013-08-10 07:32:55 +00:00
}
}
if (thing->player && thing->player->mo == thing)
{
thing->player->viewz += thing->Z() - oldz;
}
}
//=============================================================================
//
// PIT_FloorRaise
//
//=============================================================================
void PIT_FloorRaise(AActor *thing, FChangePosition *cpos)
{
fixed_t oldfloorz = thing->floorz;
2016-01-20 12:48:05 +00:00
fixed_t oldz = thing->Z();
P_AdjustFloorCeil(thing, cpos);
if (oldfloorz == thing->floorz) return;
// Move things intersecting the floor up
2016-01-20 12:48:05 +00:00
if (thing->Z() <= thing->floorz)
{
if (thing->flags4 & MF4_ACTLIKEBRIDGE)
{
cpos->nofit = true;
return; // do not move bridge things
}
intersectors.Clear();
2016-01-20 12:48:05 +00:00
thing->SetZ(thing->floorz);
2013-08-10 07:32:55 +00:00
}
else
{
if ((thing->flags & MF_NOGRAVITY) && (thing->flags6 & MF6_RELATIVETOFLOOR))
{
intersectors.Clear();
2016-01-20 12:48:05 +00:00
thing->AddZ(-oldfloorz + thing->floorz);
}
2013-08-10 07:32:55 +00:00
else return;
}
switch (P_PushUp(thing, cpos))
2013-08-10 07:32:55 +00:00
{
default:
P_CheckFakeFloorTriggers(thing, oldz);
2013-08-10 07:32:55 +00:00
break;
case 1:
P_DoCrunch(thing, cpos);
P_CheckFakeFloorTriggers(thing, oldz);
2013-08-10 07:32:55 +00:00
break;
case 2:
P_DoCrunch(thing, cpos);
2016-01-20 12:48:05 +00:00
thing->SetZ(oldz);
2013-08-10 07:32:55 +00:00
break;
}
if (thing->player && thing->player->mo == thing)
{
thing->player->viewz += thing->Z() - oldz;
}
}
//=============================================================================
//
// PIT_CeilingLower
//
//=============================================================================
void PIT_CeilingLower(AActor *thing, FChangePosition *cpos)
{
bool onfloor;
fixed_t oldz = thing->Z();
2016-01-20 12:48:05 +00:00
onfloor = thing->Z() <= thing->floorz;
P_AdjustFloorCeil(thing, cpos);
2016-01-20 12:48:05 +00:00
if (thing->Top() > thing->ceilingz)
{
if (thing->flags4 & MF4_ACTLIKEBRIDGE)
{
cpos->nofit = true;
return; // do not move bridge things
}
intersectors.Clear();
2016-01-20 12:48:05 +00:00
fixed_t oldz = thing->Z();
if (thing->ceilingz - thing->height >= thing->floorz)
{
2016-01-20 12:48:05 +00:00
thing->SetZ(thing->ceilingz - thing->height);
}
else
{
2016-01-20 12:48:05 +00:00
thing->SetZ(thing->floorz);
}
switch (P_PushDown(thing, cpos))
{
case 2:
// intentional fall-through
case 1:
if (onfloor)
2016-01-20 12:48:05 +00:00
thing->SetZ(thing->floorz);
P_DoCrunch(thing, cpos);
P_CheckFakeFloorTriggers(thing, oldz);
break;
default:
P_CheckFakeFloorTriggers(thing, oldz);
break;
}
}
if (thing->player && thing->player->mo == thing)
{
thing->player->viewz += thing->Z() - oldz;
}
}
//=============================================================================
//
// PIT_CeilingRaise
//
//=============================================================================
void PIT_CeilingRaise(AActor *thing, FChangePosition *cpos)
{
bool isgood = P_AdjustFloorCeil(thing, cpos);
fixed_t oldz = thing->Z();
if (thing->flags4 & MF4_ACTLIKEBRIDGE) return; // do not move bridge things
// 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.
2016-01-20 12:48:05 +00:00
if (thing->Z() < thing->floorz &&
thing->Top() >= thing->ceilingz - cpos->moveamt &&
!(thing->flags & MF_NOLIFTDROP))
{
2016-01-20 12:48:05 +00:00
fixed_t oldz = thing->Z();
thing->SetZ(thing->floorz);
if (thing->Top() > thing->ceilingz)
{
2016-01-20 12:48:05 +00:00
thing->SetZ(thing->ceilingz - thing->height);
}
P_CheckFakeFloorTriggers(thing, oldz);
}
2016-01-20 12:48:05 +00:00
else if ((thing->flags2 & MF2_PASSMOBJ) && !isgood && thing->Top() < thing->ceilingz)
{
AActor *onmobj;
2016-01-20 12:48:05 +00:00
if (!P_TestMobjZ(thing, true, &onmobj) && onmobj->Z() <= thing->Z())
{
2016-01-20 12:48:05 +00:00
thing->SetZ( MIN(thing->ceilingz - thing->height, onmobj->Top()));
}
}
if (thing->player && thing->player->mo == thing)
{
thing->player->viewz += thing->Z() - oldz;
}
}
//=============================================================================
//
// 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, bool isreset)
{
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
FChangePosition cpos;
void(*iterator)(AActor *, FChangePosition *);
void(*iterator2)(AActor *, FChangePosition *) = NULL;
msecnode_t *n;
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
cpos.nofit = false;
cpos.crushchange = crunch;
cpos.moveamt = abs(amt);
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
cpos.movemidtex = false;
cpos.sector = sector;
// Also process all sectors that have 3D floors transferred from the
// changed sector.
if (sector->e->XFloor.attached.Size())
{
unsigned i;
sector_t* sec;
// Use different functions for the four different types of sector movement.
// for 3D-floors the meaning of floor and ceiling is inverted!!!
if (floorOrCeil == 1)
{
iterator = (amt >= 0) ? PIT_FloorRaise : PIT_FloorDrop;
}
else
{
iterator = (amt >= 0) ? PIT_CeilingRaise : PIT_CeilingLower;
}
for (i = 0; i < sector->e->XFloor.attached.Size(); i++)
{
sec = sector->e->XFloor.attached[i];
P_Recalculate3DFloors(sec); // Must recalculate the 3d floor and light lists
// no thing checks for attached sectors because of heightsec
if (sec->heightsec == sector) continue;
for (n = sec->touching_thinglist; n; n = n->m_snext) n->visited = false;
do
{
for (n = sec->touching_thinglist; n; n = n->m_snext)
{
if (!n->visited)
{
n->visited = true;
if (!(n->m_thing->flags & MF_NOBLOCKMAP) || //jff 4/7/98 don't do these
(n->m_thing->flags5 & MF5_MOVEWITHSECTOR))
{
iterator(n->m_thing, &cpos);
}
break;
}
}
} while (n);
2016-02-14 15:26:27 +00:00
sec->CheckPortalPlane(!floorOrCeil);
}
}
P_Recalculate3DFloors(sector); // Must recalculate the 3d floor and light lists
// [RH] Use different functions for the four different types of sector
// movement.
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
switch (floorOrCeil)
{
case 0:
// floor
iterator = (amt < 0) ? PIT_FloorDrop : PIT_FloorRaise;
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
break;
case 1:
// ceiling
iterator = (amt < 0) ? PIT_CeilingLower : PIT_CeilingRaise;
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
break;
case 2:
// 3dmidtex
// This must check both floor and ceiling
iterator = (amt < 0) ? PIT_FloorDrop : PIT_FloorRaise;
iterator2 = (amt < 0) ? PIT_CeilingLower : PIT_CeilingRaise;
cpos.movemidtex = true;
break;
default:
// invalid
assert(floorOrCeil > 0 && floorOrCeil < 2);
return false;
}
// 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
(n->m_thing->flags5 & MF5_MOVEWITHSECTOR))
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
{
iterator(n->m_thing, &cpos); // process it
if (iterator2 != NULL) iterator2(n->m_thing, &cpos);
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
}
break; // exit and start over
}
}
} while (n); // repeat from scratch until all things left are marked valid
2016-02-14 15:26:27 +00:00
sector->CheckPortalPlane(floorOrCeil); // check for portal obstructions after everything is done.
if (!cpos.nofit && !isreset /* && sector->MoreFlags & (SECF_UNDERWATERMASK)*/)
{
// If this is a control sector for a deep water transfer, all actors in affected
// sectors need to have their waterlevel information updated and if applicable,
// execute appropriate sector actions.
// Only check if the sector move was successful.
TArray<sector_t *> & secs = sector->e->FakeFloor.Sectors;
for (unsigned i = 0; i < secs.Size(); i++)
{
sector_t * s = secs[i];
for (n = s->touching_thinglist; n; n = n->m_snext)
n->visited = false;
do
{
for (n = s->touching_thinglist; n; n = n->m_snext) // go through list
{
if (!n->visited && n->m_thing->Sector == s) // unprocessed thing found
{
n->visited = true; // mark thing as processed
2016-01-20 12:48:05 +00:00
n->m_thing->UpdateWaterLevel(n->m_thing->Z(), false);
P_CheckFakeFloorTriggers(n->m_thing, n->m_thing->Z() - amt);
}
}
} while (n); // repeat from scratch until all things left are marked valid
}
}
- VC++ doesn't seem to like the TArray serializer so I added a workaround to be able to save the 3dMidtex attachment info. - Fixed: The TArray serializer needs to be declared as a friend of TArray in order to be able to access its fields. - Since there are no backwards compatibility issues due to savegame version bumping I closed all gaps in the level flag set. - Bumped min. Savegame version and Netgame version for 3dMidtex related changes. - Changed Jump and Crouch DMFlags into 3-way switches: 0: map default, 1: off, 2: on. Since I needed new bits the rest of the DMFlag bit values had to be changed as a result. - fixed: PTR_SlideTraverse didn't check ML_BLOCKMONSTERS for sliding actors without MF3_NOBLOCKMONST. - Added MAPINFO commands 'checkswitchrange' and 'nocheckswitchrange' that can enable or disable switch range checking globally per map. - Changed ML_3DMIDTEX to force ML_CHECKSWITCHRANGE. - Added a ML_CHECKSWITCHRANGE flag which allows checking whether the player can actually reach the switch he wants to use. - Made DActiveButton::EWhere global so that I can use it outside thr DActiveButton class. March 17, 2008 (Changes by Graf Zahl) - Changed P_LineOpening to pass its result in a struct instead of global variables. - Added Eternity's 3DMIDTEX feature (no Eternity code used though.) It should be feature complete with the exception of the ML_BLOCKMONSTERS flag handling. That particular part of Eternity's implementation is sub-optimal because it hijacks an existing flag and doesn't seem to make much sense to me. Maybe I'll implement it as a separate flag later. SVN r810 (trunk)
2008-03-18 18:18:18 +00:00
return cpos.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->GetClass()->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
//
// 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)
{
msecnode_t *node;
// 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;
}
2016-01-20 12:48:05 +00:00
FBoundingBox box(thing->X(), thing->Y(), thing->radius);
FBlockLinesIterator it(box);
line_t *ld;
while ((ld = it.Next()))
{
if (box.Right() <= ld->bbox[BOXLEFT] ||
box.Left() >= ld->bbox[BOXRIGHT] ||
box.Top() <= ld->bbox[BOXBOTTOM] ||
box.Bottom() >= ld->bbox[BOXTOP])
continue;
if (box.BoxOnLineSide(ld) != -1)
continue;
// 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, thing, 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, thing, sector_list);
}
// 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;
}
}
}
//==========================================================================
//
//
//
//==========================================================================
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, trace.Line->sidedef[trace.Side], trace.ffloor);
}
}
//==========================================================================
//
//
//
//==========================================================================
static void SpawnDeepSplash(AActor *t1, const FTraceResults &trace, AActor *puff,
fixed_t vx, fixed_t vy, fixed_t vz, fixed_t shootz, bool ffloor)
{
const secplane_t *plane;
if (ffloor && trace.Crossed3DWater)
plane = trace.Crossed3DWater->top.plane;
else if (trace.CrossedWater && trace.CrossedWater->heightsec)
plane = &trace.CrossedWater->heightsec->floorplane;
else return;
fixed_t num, den, hitdist;
den = TMulScale16(plane->a, vx, plane->b, vy, plane->c, vz);
if (den != 0)
{
2016-01-20 12:48:05 +00:00
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)
{
2016-01-20 12:48:05 +00:00
fixed_t hitx = t1->X() + FixedMul(vx, hitdist);
fixed_t hity = t1->Y() + FixedMul(vy, hitdist);
fixed_t hitz = shootz + FixedMul(vz, hitdist);
P_HitWater(puff != NULL ? puff : t1, P_PointInSector(hitx, hity), hitx, hity, hitz);
}
}
}
//=============================================================================
//
// P_ActivateThingSpecial
//
// Handles the code for things activated by death, USESPECIAL or BUMPSPECIAL
//
//=============================================================================
bool P_ActivateThingSpecial(AActor * thing, AActor * trigger, bool death)
{
bool res = false;
// Target switching mechanism
if (thing->activationtype & THINGSPEC_ThingTargets) thing->target = trigger;
if (thing->activationtype & THINGSPEC_TriggerTargets) trigger->target = thing;
// State change mechanism. The thing needs to be not dead and to have at least one of the relevant flags
if (!death && (thing->activationtype & (THINGSPEC_Activate | THINGSPEC_Deactivate | THINGSPEC_Switch)))
{
// If a switchable thing does not know whether it should be activated
// or deactivated, the default is to activate it.
if ((thing->activationtype & THINGSPEC_Switch)
&& !(thing->activationtype & (THINGSPEC_Activate | THINGSPEC_Deactivate)))
{
thing->activationtype |= THINGSPEC_Activate;
}
// Can it be activated?
if (thing->activationtype & THINGSPEC_Activate)
{
thing->activationtype &= ~THINGSPEC_Activate; // Clear flag
if (thing->activationtype & THINGSPEC_Switch) // Set other flag if switching
thing->activationtype |= THINGSPEC_Deactivate;
thing->Activate(trigger);
res = true;
}
// If not, can it be deactivated?
else if (thing->activationtype & THINGSPEC_Deactivate)
{
thing->activationtype &= ~THINGSPEC_Deactivate; // Clear flag
if (thing->activationtype & THINGSPEC_Switch) // Set other flag if switching
thing->activationtype |= THINGSPEC_Activate;
thing->Deactivate(trigger);
res = true;
}
}
// Run the special, if any
if (thing->special)
{
res = !!P_ExecuteSpecial(thing->special, NULL,
// TriggerActs overrides the level flag, which only concerns thing activated by death
(((death && level.flags & LEVEL_ACTOWNSPECIAL && !(thing->activationtype & THINGSPEC_TriggerActs))
|| (thing->activationtype & THINGSPEC_ThingActs)) // Who triggers?
? thing : trigger),
false, thing->args[0], thing->args[1], thing->args[2], thing->args[3], thing->args[4]);
// Clears the special if it was run on thing's death or if flag is set.
if (death || (thing->activationtype & THINGSPEC_ClearSpecial && res)) thing->special = 0;
}
// Returns the result
return res;
}