mirror of
https://git.do.srb2.org/KartKrew/Kart-Public.git
synced 2024-11-15 01:01:43 +00:00
ac3de70c93
Slope fixes This branch fixes the following slope-related physics and rendering bugs: * Rings in multiplayer stages respawning inside slopes (even despite being able to spawn ABOVE them on map load) * Player starts spawning players inside slopes * Elemental flame trails not appearing if a player spindashes UP a slope; see issue #21 * Dying players "jumping" off slopes to the side if they were previously standing on one * Some issues with FOF slope rendering * Various issues with sprites displaying through walls adjacent to slopes Other features added: * Objectplace now supports slopes (this is Inuyasha's doing) * Automap in DEVMODE now supports slopes (my doing) Just making this merge request now rather than later, ~~in case I decide not to make any more fixes for the time being~~ (this branch doesn't seem to want to die lol), and so we can get these merged in as soon as the code's all been checked over. See merge request !50
7642 lines
218 KiB
C
7642 lines
218 KiB
C
// SONIC ROBO BLAST 2
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 1993-1996 by id Software, Inc.
|
|
// Copyright (C) 1998-2000 by DooM Legacy Team.
|
|
// Copyright (C) 1999-2014 by Sonic Team Junior.
|
|
//
|
|
// This program is free software distributed under the
|
|
// terms of the GNU General Public License, version 2.
|
|
// See the 'LICENSE' file for more details.
|
|
//-----------------------------------------------------------------------------
|
|
/// \file p_spec.c
|
|
/// \brief Implements special effects:
|
|
/// Texture animation, height or lighting changes
|
|
/// according to adjacent sectors, respective
|
|
/// utility functions, etc.
|
|
/// Line Tag handling. Line and Sector triggers.
|
|
|
|
#include "doomdef.h"
|
|
#include "g_game.h"
|
|
#include "p_local.h"
|
|
#include "p_setup.h" // levelflats for flat animation
|
|
#include "r_data.h"
|
|
#include "m_random.h"
|
|
#include "p_mobj.h"
|
|
#include "i_system.h"
|
|
#include "s_sound.h"
|
|
#include "w_wad.h"
|
|
#include "z_zone.h"
|
|
#include "r_main.h" //Two extra includes.
|
|
#include "r_sky.h"
|
|
#include "p_polyobj.h"
|
|
#include "p_slopes.h"
|
|
#include "hu_stuff.h"
|
|
#include "m_misc.h"
|
|
#include "m_cond.h" //unlock triggers
|
|
#include "lua_hook.h" // LUAh_LinedefExecute
|
|
|
|
#ifdef HW3SOUND
|
|
#include "hardware/hw3sound.h"
|
|
#endif
|
|
|
|
// Not sure if this is necessary, but it was in w_wad.c, so I'm putting it here too -Shadow Hog
|
|
#ifdef _WIN32_WCE
|
|
#define AVOID_ERRNO
|
|
#else
|
|
#include <errno.h>
|
|
#endif
|
|
|
|
mobj_t *skyboxmo[2];
|
|
|
|
// Amount (dx, dy) vector linedef is shifted right to get scroll amount
|
|
#define SCROLL_SHIFT 5
|
|
|
|
/** Animated texture descriptor
|
|
* This keeps track of an animated texture or an animated flat.
|
|
* \sa P_UpdateSpecials, P_InitPicAnims, animdef_t
|
|
*/
|
|
typedef struct
|
|
{
|
|
SINT8 istexture; ///< ::true for a texture, ::false for a flat
|
|
INT32 picnum; ///< The end flat number
|
|
INT32 basepic; ///< The start flat number
|
|
INT32 numpics; ///< Number of frames in the animation
|
|
tic_t speed; ///< Number of tics for which each frame is shown
|
|
} anim_t;
|
|
|
|
#if defined(_MSC_VER)
|
|
#pragma pack(1)
|
|
#endif
|
|
|
|
/** Animated texture definition.
|
|
* Used for ::harddefs and for loading an ANIMATED lump from a wad.
|
|
*
|
|
* Animations are defined by the first and last frame (i.e., flat or texture).
|
|
* The animation sequence uses all flats between the start and end entry, in
|
|
* the order found in the wad.
|
|
*
|
|
* \sa anim_t
|
|
*/
|
|
typedef struct
|
|
{
|
|
SINT8 istexture; ///< True for a texture, false for a flat.
|
|
char endname[9]; ///< Name of the last frame, null-terminated.
|
|
char startname[9]; ///< Name of the first frame, null-terminated.
|
|
INT32 speed ; ///< Number of tics for which each frame is shown.
|
|
} ATTRPACK animdef_t;
|
|
|
|
#if defined(_MSC_VER)
|
|
#pragma pack()
|
|
#endif
|
|
|
|
typedef struct
|
|
{
|
|
UINT32 count;
|
|
thinker_t **thinkers;
|
|
} thinkerlist_t;
|
|
|
|
static void P_SearchForDisableLinedefs(void);
|
|
static void P_SpawnScrollers(void);
|
|
static void P_SpawnFriction(void);
|
|
static void P_SpawnPushers(void);
|
|
static void Add_Pusher(pushertype_e type, fixed_t x_mag, fixed_t y_mag, mobj_t *source, INT32 affectee, INT32 referrer, INT32 exclusive, INT32 slider); //SoM: 3/9/2000
|
|
static void Add_MasterDisappearer(tic_t appeartime, tic_t disappeartime, tic_t offset, INT32 line, INT32 sourceline);
|
|
static void P_AddBlockThinker(sector_t *sec, line_t *sourceline);
|
|
static void P_AddFloatThinker(sector_t *sec, INT32 tag, line_t *sourceline);
|
|
//static void P_AddBridgeThinker(line_t *sourceline, sector_t *sec);
|
|
static void P_AddFakeFloorsByLine(size_t line, ffloortype_e ffloorflags, thinkerlist_t *secthinkers);
|
|
static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec);
|
|
static void Add_Friction(INT32 friction, INT32 movefactor, INT32 affectee, INT32 referrer);
|
|
static void P_AddSpikeThinker(sector_t *sec, INT32 referrer);
|
|
|
|
|
|
//SoM: 3/7/2000: New sturcture without limits.
|
|
static anim_t *lastanim;
|
|
static anim_t *anims = NULL; /// \todo free leak
|
|
static size_t maxanims;
|
|
|
|
//
|
|
// P_InitPicAnims
|
|
//
|
|
/** Hardcoded animation sequences.
|
|
* Used if no ANIMATED lump is found in a loaded wad.
|
|
*/
|
|
static animdef_t harddefs[] =
|
|
{
|
|
// flat animations.
|
|
{false, "LITEY3", "LITEY1", 4},
|
|
{false, "FWATER16", "FWATER1", 4},
|
|
{false, "BWATER16", "BWATER01", 4},
|
|
{false, "LWATER16", "LWATER1", 4},
|
|
{false, "WATER7", "WATER0", 4},
|
|
{false, "LAVA4", "LAVA1", 8},
|
|
{false, "DLAVA4", "DLAVA1", 8},
|
|
{false, "RLAVA8", "RLAVA1", 8},
|
|
{false, "LITER3", "LITER1", 8},
|
|
{false, "SURF08", "SURF01", 4},
|
|
|
|
{false, "CHEMG16", "CHEMG01", 4}, // THZ Chemical gunk
|
|
{false, "GOOP16", "GOOP01", 4}, // Green chemical gunk
|
|
{false, "OIL16", "OIL01", 4}, // Oil
|
|
{false, "THZBOXF4", "THZBOXF1", 2}, // Moved up with the flats
|
|
{false, "ALTBOXF4", "ALTBOXF1", 2},
|
|
|
|
{false, "LITEB3", "LITEB1", 4},
|
|
{false, "LITEN3", "LITEN1", 4},
|
|
{false, "ACZRFL1H", "ACZRFL1A", 4},
|
|
{false, "ACZRFL2H", "ACZRFL2A", 4},
|
|
{false, "EGRIDF3", "EGRIDF1", 4},
|
|
{false, "ERZFAN4", "ERZFAN1", 1},
|
|
{false, "ERZFANR4", "ERZFANR1", 1},
|
|
{false, "DISCO4", "DISCO1", 15},
|
|
|
|
// animated textures
|
|
{true, "GFALL4", "GFALL1", 2}, // Short waterfall
|
|
{true, "CFALL4", "CFALL1", 2}, // Long waterfall
|
|
{true, "TFALL4", "TFALL1", 2}, // THZ Chemical fall
|
|
{true, "AFALL4", "AFALL1", 2}, // Green Chemical fall
|
|
{true, "QFALL4", "QFALL1", 2}, // Quicksand fall
|
|
{true, "Q2FALL4", "Q2FALL1", 2},
|
|
{true, "Q3FALL4", "Q3FALL1", 2},
|
|
{true, "Q4FALL4", "Q4FALL1", 2},
|
|
{true, "Q5FALL4", "Q5FALL1", 2},
|
|
{true, "Q6FALL4", "Q6FALL1", 2},
|
|
{true, "Q7FALL4", "Q7FALL1", 2},
|
|
{true, "LFALL4", "LFALL1", 2},
|
|
{true, "MFALL4", "MFALL1", 2},
|
|
{true, "OFALL4", "OFALL1", 2},
|
|
{true, "DLAVA4", "DLAVA1", 8},
|
|
{true, "ERZLASA2", "ERZLASA1", 1},
|
|
{true, "ERZLASB4", "ERZLASB1", 1},
|
|
{true, "ERZLASC4", "ERZLASC1", 1},
|
|
{true, "THZBOX04", "THZBOX01", 2},
|
|
{true, "ALTBOX04", "ALTBOX01", 2},
|
|
{true, "SFALL4", "SFALL1", 4}, // Lava fall
|
|
{true, "RVZFALL8", "RVZFALL1", 4},
|
|
{true, "BFALL4", "BFALL1", 2}, // HPZ waterfall
|
|
{true, "GREYW3", "GREYW1", 4},
|
|
{true, "BLUEW3", "BLUEW1", 4},
|
|
{true, "COMP6", "COMP4", 4},
|
|
{true, "RED3", "RED1", 4},
|
|
{true, "YEL3", "YEL1", 4},
|
|
{true, "ACWRFL1D", "ACWRFL1A", 1},
|
|
{true, "ACWRFL2D", "ACWRFL2A", 1},
|
|
{true, "ACWRFL3D", "ACWRFL3A", 1},
|
|
{true, "ACWRFL4D", "ACWRFL4A", 1},
|
|
{true, "ACWRP1D", "ACWRP1A", 1},
|
|
{true, "ACWRP2D", "ACWRP2A", 1},
|
|
{true, "ACZRP1D", "ACZRP1A", 1},
|
|
{true, "ACZRP2D", "ACZRP2A", 1},
|
|
{true, "OILFALL4", "OILFALL1", 2},
|
|
{true, "SOLFALL4", "SOLFALL1", 2},
|
|
{true, "DOWN1C", "DOWN1A", 4},
|
|
{true, "DOWN2C", "DOWN2A", 4},
|
|
{true, "DOWN3D", "DOWN3A", 4},
|
|
{true, "DOWN4C", "DOWN4A", 4},
|
|
{true, "DOWN5C", "DOWN5A", 4},
|
|
{true, "UP1C", "UP1A", 4},
|
|
{true, "UP2C", "UP2A", 4},
|
|
{true, "UP3D", "UP3A", 4},
|
|
{true, "UP4C", "UP4A", 4},
|
|
{true, "UP5C", "UP5A", 4},
|
|
{true, "EGRID3", "EGRID1", 4},
|
|
{true, "ERFANW4", "ERFANW1", 1},
|
|
{true, "ERFANX4", "ERFANX1", 1},
|
|
{true, "DISCOD4", "DISCOD1", 15},
|
|
{true, "DANCE4", "DANCE1", 8},
|
|
{true, "SKY135", "SKY132", 2},
|
|
{true, "APPLMS4", "APPLMS1", 2},
|
|
{true, "APBOXW3", "APBOXW1", 2},
|
|
{true, "ERZLAZC4", "ERZLAZC1", 4},
|
|
|
|
// End of line
|
|
{ -1, "", "", 0},
|
|
};
|
|
|
|
// Animating line specials
|
|
|
|
// Init animated textures
|
|
// - now called at level loading P_SetupLevel()
|
|
|
|
static animdef_t *animdefs = NULL;
|
|
|
|
// A prototype; here instead of p_spec.h, so they're "private"
|
|
void P_ParseANIMDEFSLump(INT32 wadNum, UINT16 lumpnum, INT32 *i);
|
|
void P_ParseAnimationDefintion(SINT8 istexture, INT32 *i);
|
|
|
|
/** Sets up texture and flat animations.
|
|
*
|
|
* Converts an ::animdef_t array loaded from ::harddefs or a lump into
|
|
* ::anim_t format.
|
|
*
|
|
* Issues an error if any animation cycles are invalid.
|
|
*
|
|
* \sa P_FindAnimatedFlat, P_SetupLevelFlatAnims
|
|
* \author Steven McGranahan (original), Shadow Hog (had to rewrite it to handle multiple WADs)
|
|
*/
|
|
void P_InitPicAnims(void)
|
|
{
|
|
// Init animation
|
|
INT32 i; // Position in the animdefs array
|
|
INT32 w; // WAD
|
|
UINT8 *wadAnimdefs; // not to be confused with animdefs, the combined total of every ANIMATED lump in every WAD, or ANIMDEFS, the ZDoom lump I intend to implement later
|
|
UINT8 *currentPos;
|
|
|
|
if (W_CheckNumForName("ANIMATED") != LUMPERROR || W_CheckNumForName("ANIMDEFS") != LUMPERROR)
|
|
{
|
|
if (animdefs)
|
|
{
|
|
Z_Free(animdefs);
|
|
animdefs = NULL;
|
|
}
|
|
for (w = 0, i = 0, maxanims = 0; w < numwadfiles; w++)
|
|
{
|
|
UINT16 animatedLumpNum;
|
|
UINT16 animdefsLumpNum;
|
|
|
|
// Find ANIMATED lump in the WAD
|
|
animatedLumpNum = W_CheckNumForNamePwad("ANIMATED", w, 0);
|
|
if (animatedLumpNum != INT16_MAX)
|
|
{
|
|
wadAnimdefs = (UINT8 *)W_CacheLumpNumPwad(w, animatedLumpNum, PU_STATIC);
|
|
|
|
// Get the number of animations in the file
|
|
for (currentPos = wadAnimdefs; *currentPos != UINT8_MAX; maxanims++, currentPos+=23);
|
|
|
|
// Resize animdefs (or if it hasn't been created, create it)
|
|
animdefs = (animdef_t *)Z_Realloc(animdefs, sizeof(animdef_t)*(maxanims + 1), PU_STATIC, NULL);
|
|
// Sanity check it
|
|
if (!animdefs) {
|
|
I_Error("Not enough free memory for ANIMATED data");
|
|
}
|
|
|
|
// Populate the new array
|
|
for (currentPos = wadAnimdefs; *currentPos != UINT8_MAX; i++, currentPos+=23)
|
|
{
|
|
M_Memcpy(&(animdefs[i].istexture), currentPos, 1); // istexture, 1 byte
|
|
M_Memcpy(animdefs[i].endname, (currentPos + 1), 9); // endname, 9 bytes
|
|
M_Memcpy(animdefs[i].startname, (currentPos + 10), 9); // startname, 9 bytes
|
|
M_Memcpy(&(animdefs[i].speed), (currentPos + 19), 4); // speed, 4 bytes
|
|
}
|
|
|
|
Z_Free(wadAnimdefs);
|
|
}
|
|
|
|
// Now find ANIMDEFS
|
|
animdefsLumpNum = W_CheckNumForNamePwad("ANIMDEFS", w, 0);
|
|
if (animdefsLumpNum != INT16_MAX)
|
|
{
|
|
P_ParseANIMDEFSLump(w, animdefsLumpNum, &i);
|
|
}
|
|
}
|
|
// Define the last one
|
|
animdefs[maxanims].istexture = -1;
|
|
strncpy(animdefs[maxanims].endname, "", 9);
|
|
strncpy(animdefs[maxanims].startname, "", 9);
|
|
animdefs[maxanims].speed = 0;
|
|
}
|
|
else
|
|
{
|
|
animdefs = harddefs;
|
|
for (maxanims = 0; animdefs[maxanims].istexture != -1; maxanims++);
|
|
}
|
|
|
|
if (anims)
|
|
free(anims);
|
|
|
|
anims = (anim_t *)malloc(sizeof (*anims)*(maxanims + 1));
|
|
if (!anims)
|
|
I_Error("Not enough free memory for ANIMATED data");
|
|
|
|
lastanim = anims;
|
|
for (i = 0; animdefs[i].istexture != -1; i++)
|
|
{
|
|
if (animdefs[i].istexture)
|
|
{
|
|
if (R_CheckTextureNumForName(animdefs[i].startname) == -1)
|
|
continue;
|
|
|
|
lastanim->picnum = R_TextureNumForName(animdefs[i].endname);
|
|
lastanim->basepic = R_TextureNumForName(animdefs[i].startname);
|
|
}
|
|
else
|
|
{
|
|
if ((W_CheckNumForName(animdefs[i].startname)) == LUMPERROR)
|
|
continue;
|
|
|
|
lastanim->picnum = R_FlatNumForName(animdefs[i].endname);
|
|
lastanim->basepic = R_FlatNumForName(animdefs[i].startname);
|
|
}
|
|
|
|
lastanim->istexture = animdefs[i].istexture;
|
|
lastanim->numpics = lastanim->picnum - lastanim->basepic + 1;
|
|
|
|
if (lastanim->numpics < 2)
|
|
{
|
|
free(anims);
|
|
I_Error("P_InitPicAnims: bad cycle from %s to %s",
|
|
animdefs[i].startname, animdefs[i].endname);
|
|
}
|
|
|
|
if (animdefs == harddefs)
|
|
lastanim->speed = animdefs[i].speed;
|
|
else
|
|
lastanim->speed = LONG(animdefs[i].speed);
|
|
lastanim++;
|
|
}
|
|
lastanim->istexture = -1;
|
|
R_ClearTextureNumCache(false);
|
|
|
|
if (animdefs != harddefs)
|
|
Z_ChangeTag(animdefs, PU_CACHE);
|
|
}
|
|
|
|
void P_ParseANIMDEFSLump(INT32 wadNum, UINT16 lumpnum, INT32 *i)
|
|
{
|
|
char *animdefsLump;
|
|
size_t animdefsLumpLength;
|
|
char *animdefsText;
|
|
char *animdefsToken;
|
|
|
|
// Since lumps AREN'T \0-terminated like I'd assumed they should be, I'll
|
|
// need to make a space of memory where I can ensure that it will terminate
|
|
// correctly. Start by loading the relevant data from the WAD.
|
|
animdefsLump = (char *)W_CacheLumpNumPwad(wadNum,lumpnum,PU_STATIC);
|
|
// If that didn't exist, we have nothing to do here.
|
|
if (animdefsLump == NULL) return;
|
|
// If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly.
|
|
animdefsLumpLength = W_LumpLengthPwad(wadNum,lumpnum);
|
|
animdefsText = (char *)Z_Malloc((animdefsLumpLength+1)*sizeof(char),PU_STATIC,NULL);
|
|
// Now move the contents of the lump into this new location.
|
|
memmove(animdefsText,animdefsLump,animdefsLumpLength);
|
|
// Make damn well sure the last character in our new memory location is \0.
|
|
animdefsText[animdefsLumpLength] = '\0';
|
|
// Finally, free up the memory from the first data load, because we really
|
|
// don't need it.
|
|
Z_Free(animdefsLump);
|
|
|
|
// Now, let's start parsing this thing
|
|
animdefsToken = M_GetToken(animdefsText);
|
|
while (animdefsToken != NULL)
|
|
{
|
|
if (stricmp(animdefsToken, "TEXTURE") == 0)
|
|
{
|
|
Z_Free(animdefsToken);
|
|
P_ParseAnimationDefintion(1, i);
|
|
}
|
|
else if (stricmp(animdefsToken, "FLAT") == 0)
|
|
{
|
|
Z_Free(animdefsToken);
|
|
P_ParseAnimationDefintion(0, i);
|
|
}
|
|
else if (stricmp(animdefsToken, "OSCILLATE") == 0)
|
|
{
|
|
// This probably came off the tail of an earlier definition. It's technically legal syntax, but we don't support it.
|
|
I_Error("Error parsing ANIMDEFS lump: Animation definitions utilizing \"OSCILLATE\" (the animation plays in reverse when it reaches the end) are not supported by SRB2");
|
|
}
|
|
else
|
|
{
|
|
I_Error("Error parsing ANIMDEFS lump: Expected \"TEXTURE\" or \"FLAT\", got \"%s\"",animdefsToken);
|
|
}
|
|
animdefsToken = M_GetToken(NULL);
|
|
}
|
|
Z_Free(animdefsToken);
|
|
Z_Free((void *)animdefsText);
|
|
}
|
|
|
|
void P_ParseAnimationDefintion(SINT8 istexture, INT32 *i)
|
|
{
|
|
char *animdefsToken;
|
|
UINT8 animdefsTokenLength;
|
|
char *endPos;
|
|
INT32 animSpeed;
|
|
|
|
// Increase the size to make room for the new animation definition
|
|
maxanims++;
|
|
animdefs = (animdef_t *)Z_Realloc(animdefs, sizeof(animdef_t)*(maxanims + 1), PU_STATIC, NULL);
|
|
animdefs[*i].istexture = istexture;
|
|
|
|
// Startname
|
|
animdefsToken = M_GetToken(NULL);
|
|
if (animdefsToken == NULL)
|
|
{
|
|
I_Error("Error parsing ANIMDEFS lump: Unexpected end of file where start texture/flat name should be");
|
|
}
|
|
if (stricmp(animdefsToken, "OPTIONAL") == 0)
|
|
{
|
|
// This is meaningful to ZDoom - it tells the program NOT to bomb out
|
|
// if the textures can't be found - but it's useless in SRB2, so we'll
|
|
// just smile, nod, and carry on
|
|
Z_Free(animdefsToken);
|
|
animdefsToken = M_GetToken(NULL);
|
|
|
|
if (animdefsToken == NULL)
|
|
{
|
|
I_Error("Error parsing ANIMDEFS lump: Unexpected end of file where start texture/flat name should be");
|
|
}
|
|
else if (stricmp(animdefsToken, "RANGE") == 0)
|
|
{
|
|
// Oh. Um. Apparently "OPTIONAL" is a texture name. Naughty.
|
|
// I should probably handle this more gracefully, but right now
|
|
// I can't be bothered; especially since ZDoom doesn't handle this
|
|
// condition at all.
|
|
I_Error("Error parsing ANIMDEFS lump: \"OPTIONAL\" is a keyword; you cannot use it as the startname of an animation");
|
|
}
|
|
}
|
|
animdefsTokenLength = strlen(animdefsToken);
|
|
if (animdefsTokenLength>8)
|
|
{
|
|
I_Error("Error parsing ANIMDEFS lump: lump name \"%s\" exceeds 8 characters", animdefsToken);
|
|
}
|
|
strncpy(animdefs[*i].startname, animdefsToken, 9);
|
|
Z_Free(animdefsToken);
|
|
|
|
// "RANGE"
|
|
animdefsToken = M_GetToken(NULL);
|
|
if (animdefsToken == NULL)
|
|
{
|
|
I_Error("Error parsing ANIMDEFS lump: Unexpected end of file where \"RANGE\" after \"%s\"'s startname should be", animdefs[*i].startname);
|
|
}
|
|
if (stricmp(animdefsToken, "ALLOWDECALS") == 0)
|
|
{
|
|
// Another ZDoom keyword, ho-hum. Skip it, move on to the next token.
|
|
Z_Free(animdefsToken);
|
|
animdefsToken = M_GetToken(NULL);
|
|
}
|
|
if (stricmp(animdefsToken, "PIC") == 0)
|
|
{
|
|
// This is technically legitimate ANIMDEFS syntax, but SRB2 doesn't support it.
|
|
I_Error("Error parsing ANIMDEFS lump: Animation definitions utilizing \"PIC\" (specific frames instead of a consecutive range) are not supported by SRB2");
|
|
}
|
|
if (stricmp(animdefsToken, "RANGE") != 0)
|
|
{
|
|
I_Error("Error parsing ANIMDEFS lump: Expected \"RANGE\" after \"%s\"'s startname, got \"%s\"", animdefs[*i].startname, animdefsToken);
|
|
}
|
|
Z_Free(animdefsToken);
|
|
|
|
// Endname
|
|
animdefsToken = M_GetToken(NULL);
|
|
if (animdefsToken == NULL)
|
|
{
|
|
I_Error("Error parsing ANIMDEFS lump: Unexpected end of file where \"%s\"'s end texture/flat name should be", animdefs[*i].startname);
|
|
}
|
|
animdefsTokenLength = strlen(animdefsToken);
|
|
if (animdefsTokenLength>8)
|
|
{
|
|
I_Error("Error parsing ANIMDEFS lump: lump name \"%s\" exceeds 8 characters", animdefsToken);
|
|
}
|
|
strncpy(animdefs[*i].endname, animdefsToken, 9);
|
|
Z_Free(animdefsToken);
|
|
|
|
// "TICS"
|
|
animdefsToken = M_GetToken(NULL);
|
|
if (animdefsToken == NULL)
|
|
{
|
|
I_Error("Error parsing ANIMDEFS lump: Unexpected end of file where \"%s\"'s \"TICS\" should be", animdefs[*i].startname);
|
|
}
|
|
if (stricmp(animdefsToken, "RAND") == 0)
|
|
{
|
|
// This is technically legitimate ANIMDEFS syntax, but SRB2 doesn't support it.
|
|
I_Error("Error parsing ANIMDEFS lump: Animation definitions utilizing \"RAND\" (random duration per frame) are not supported by SRB2");
|
|
}
|
|
if (stricmp(animdefsToken, "TICS") != 0)
|
|
{
|
|
I_Error("Error parsing ANIMDEFS lump: Expected \"TICS\" in animation definition for \"%s\", got \"%s\"", animdefs[*i].startname, animdefsToken);
|
|
}
|
|
Z_Free(animdefsToken);
|
|
|
|
// Speed
|
|
animdefsToken = M_GetToken(NULL);
|
|
if (animdefsToken == NULL)
|
|
{
|
|
I_Error("Error parsing TEXTURES lump: Unexpected end of file where \"%s\"'s animation speed should be", animdefs[*i].startname);
|
|
}
|
|
endPos = NULL;
|
|
#ifndef AVOID_ERRNO
|
|
errno = 0;
|
|
#endif
|
|
animSpeed = strtol(animdefsToken,&endPos,10);
|
|
if (endPos == animdefsToken // Empty string
|
|
|| *endPos != '\0' // Not end of string
|
|
#ifndef AVOID_ERRNO
|
|
|| errno == ERANGE // Number out-of-range
|
|
#endif
|
|
|| animSpeed < 0) // Number is not positive
|
|
{
|
|
I_Error("Error parsing TEXTURES lump: Expected a positive integer for \"%s\"'s animation speed, got \"%s\"", animdefs[*i].startname, animdefsToken);
|
|
}
|
|
animdefs[*i].speed = animSpeed;
|
|
Z_Free(animdefsToken);
|
|
|
|
// Increment i before we go, so this doesn't cause issues later
|
|
(*i)++;
|
|
}
|
|
|
|
|
|
/** Checks for flats in levelflats that are part of a flat animation sequence
|
|
* and sets them up for animation.
|
|
*
|
|
* \param animnum Index into ::anims to find flats for.
|
|
* \sa P_SetupLevelFlatAnims
|
|
*/
|
|
static inline void P_FindAnimatedFlat(INT32 animnum)
|
|
{
|
|
size_t i;
|
|
lumpnum_t startflatnum, endflatnum;
|
|
levelflat_t *foundflats;
|
|
|
|
foundflats = levelflats;
|
|
startflatnum = anims[animnum].basepic;
|
|
endflatnum = anims[animnum].picnum;
|
|
|
|
// note: high word of lumpnum is the wad number
|
|
if ((startflatnum>>16) != (endflatnum>>16))
|
|
I_Error("AnimatedFlat start %s not in same wad as end %s\n",
|
|
animdefs[animnum].startname, animdefs[animnum].endname);
|
|
|
|
//
|
|
// now search through the levelflats if this anim flat sequence is used
|
|
//
|
|
for (i = 0; i < numlevelflats; i++, foundflats++)
|
|
{
|
|
// is that levelflat from the flat anim sequence ?
|
|
if (foundflats->lumpnum >= startflatnum && foundflats->lumpnum <= endflatnum)
|
|
{
|
|
foundflats->baselumpnum = startflatnum;
|
|
foundflats->animseq = foundflats->lumpnum - startflatnum;
|
|
foundflats->numpics = endflatnum - startflatnum + 1;
|
|
foundflats->speed = anims[animnum].speed;
|
|
|
|
CONS_Debug(DBG_SETUP, "animflat: #%03d name:%.8s animseq:%d numpics:%d speed:%d\n",
|
|
atoi(sizeu1(i)), foundflats->name, foundflats->animseq,
|
|
foundflats->numpics,foundflats->speed);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Sets up all flats used in a level.
|
|
*
|
|
* \sa P_InitPicAnims, P_FindAnimatedFlat
|
|
*/
|
|
void P_SetupLevelFlatAnims(void)
|
|
{
|
|
INT32 i;
|
|
|
|
// the original game flat anim sequences
|
|
for (i = 0; anims[i].istexture != -1; i++)
|
|
{
|
|
if (!anims[i].istexture)
|
|
P_FindAnimatedFlat(i);
|
|
}
|
|
}
|
|
|
|
//
|
|
// UTILITIES
|
|
//
|
|
|
|
#if 0
|
|
/** Gets a side from a sector line.
|
|
*
|
|
* \param currentSector Sector the line is in.
|
|
* \param line Index of the line within the sector.
|
|
* \param side 0 for front, 1 for back.
|
|
* \return Pointer to the side_t of the side you want.
|
|
* \sa getSector, twoSided, getNextSector
|
|
*/
|
|
static inline side_t *getSide(INT32 currentSector, INT32 line, INT32 side)
|
|
{
|
|
return &sides[(sectors[currentSector].lines[line])->sidenum[side]];
|
|
}
|
|
|
|
/** Gets a sector from a sector line.
|
|
*
|
|
* \param currentSector Sector the line is in.
|
|
* \param line Index of the line within the sector.
|
|
* \param side 0 for front, 1 for back.
|
|
* \return Pointer to the ::sector_t of the sector on that side.
|
|
* \sa getSide, twoSided, getNextSector
|
|
*/
|
|
static inline sector_t *getSector(INT32 currentSector, INT32 line, INT32 side)
|
|
{
|
|
return sides[(sectors[currentSector].lines[line])->sidenum[side]].sector;
|
|
}
|
|
|
|
/** Determines whether a sector line is two-sided.
|
|
* Uses the Boom method, checking if the line's back side is set to -1, rather
|
|
* than looking for ::ML_TWOSIDED.
|
|
*
|
|
* \param sector The sector.
|
|
* \param line Line index within the sector.
|
|
* \return 1 if the sector is two-sided, 0 otherwise.
|
|
* \sa getSide, getSector, getNextSector
|
|
*/
|
|
static inline boolean twoSided(INT32 sector, INT32 line)
|
|
{
|
|
return (sectors[sector].lines[line])->sidenum[1] != 0xffff;
|
|
}
|
|
#endif
|
|
|
|
/** Finds sector next to current.
|
|
*
|
|
* \param line Pointer to the line to cross.
|
|
* \param sec Pointer to the current sector.
|
|
* \return Pointer to a ::sector_t of the adjacent sector, or NULL if the line
|
|
* is one-sided.
|
|
* \sa getSide, getSector, twoSided
|
|
* \author Steven McGranahan
|
|
*/
|
|
static sector_t *getNextSector(line_t *line, sector_t *sec)
|
|
{
|
|
if (line->frontsector == sec)
|
|
{
|
|
if (line->backsector != sec)
|
|
return line->backsector;
|
|
else
|
|
return NULL;
|
|
}
|
|
return line->frontsector;
|
|
}
|
|
|
|
/** Finds lowest floor in adjacent sectors.
|
|
*
|
|
* \param sec Sector to start in.
|
|
* \return Lowest floor height in an adjacent sector.
|
|
* \sa P_FindHighestFloorSurrounding, P_FindNextLowestFloor,
|
|
* P_FindLowestCeilingSurrounding
|
|
*/
|
|
fixed_t P_FindLowestFloorSurrounding(sector_t *sec)
|
|
{
|
|
size_t i;
|
|
line_t *check;
|
|
sector_t *other;
|
|
fixed_t floorh;
|
|
|
|
floorh = sec->floorheight;
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
check = sec->lines[i];
|
|
other = getNextSector(check,sec);
|
|
|
|
if (!other)
|
|
continue;
|
|
|
|
if (other->floorheight < floorh)
|
|
floorh = other->floorheight;
|
|
}
|
|
return floorh;
|
|
}
|
|
|
|
/** Finds highest floor in adjacent sectors.
|
|
*
|
|
* \param sec Sector to start in.
|
|
* \return Highest floor height in an adjacent sector.
|
|
* \sa P_FindLowestFloorSurrounding, P_FindNextHighestFloor,
|
|
* P_FindHighestCeilingSurrounding
|
|
*/
|
|
fixed_t P_FindHighestFloorSurrounding(sector_t *sec)
|
|
{
|
|
size_t i;
|
|
line_t *check;
|
|
sector_t *other;
|
|
fixed_t floorh = -500*FRACUNIT;
|
|
INT32 foundsector = 0;
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
check = sec->lines[i];
|
|
other = getNextSector(check, sec);
|
|
|
|
if (!other)
|
|
continue;
|
|
|
|
if (other->floorheight > floorh || !foundsector)
|
|
floorh = other->floorheight;
|
|
|
|
if (!foundsector)
|
|
foundsector = 1;
|
|
}
|
|
return floorh;
|
|
}
|
|
|
|
/** Finds next highest floor in adjacent sectors.
|
|
*
|
|
* \param sec Sector to start in.
|
|
* \param currentheight Height to start at.
|
|
* \return Next highest floor height in an adjacent sector, or currentheight
|
|
* if there are none higher.
|
|
* \sa P_FindHighestFloorSurrounding, P_FindNextLowestFloor,
|
|
* P_FindNextHighestCeiling
|
|
* \author Lee Killough
|
|
*/
|
|
fixed_t P_FindNextHighestFloor(sector_t *sec, fixed_t currentheight)
|
|
{
|
|
sector_t *other;
|
|
size_t i;
|
|
fixed_t height;
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
other = getNextSector(sec->lines[i],sec);
|
|
if (other && other->floorheight > currentheight)
|
|
{
|
|
height = other->floorheight;
|
|
while (++i < sec->linecount)
|
|
{
|
|
other = getNextSector(sec->lines[i], sec);
|
|
if (other &&
|
|
other->floorheight < height &&
|
|
other->floorheight > currentheight)
|
|
height = other->floorheight;
|
|
}
|
|
return height;
|
|
}
|
|
}
|
|
return currentheight;
|
|
}
|
|
|
|
////////////////////////////////////////////////////
|
|
// SoM: Start new Boom functions
|
|
////////////////////////////////////////////////////
|
|
|
|
/** Finds next lowest floor in adjacent sectors.
|
|
*
|
|
* \param sec Sector to start in.
|
|
* \param currentheight Height to start at.
|
|
* \return Next lowest floor height in an adjacent sector, or currentheight
|
|
* if there are none lower.
|
|
* \sa P_FindLowestFloorSurrounding, P_FindNextHighestFloor,
|
|
* P_FindNextLowestCeiling
|
|
* \author Lee Killough
|
|
*/
|
|
fixed_t P_FindNextLowestFloor(sector_t *sec, fixed_t currentheight)
|
|
{
|
|
sector_t *other;
|
|
size_t i;
|
|
fixed_t height;
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
other = getNextSector(sec->lines[i], sec);
|
|
if (other && other->floorheight < currentheight)
|
|
{
|
|
height = other->floorheight;
|
|
while (++i < sec->linecount)
|
|
{
|
|
other = getNextSector(sec->lines[i], sec);
|
|
if (other && other->floorheight > height
|
|
&& other->floorheight < currentheight)
|
|
height = other->floorheight;
|
|
}
|
|
return height;
|
|
}
|
|
}
|
|
return currentheight;
|
|
}
|
|
|
|
#if 0
|
|
/** Finds next lowest ceiling in adjacent sectors.
|
|
*
|
|
* \param sec Sector to start in.
|
|
* \param currentheight Height to start at.
|
|
* \return Next lowest ceiling height in an adjacent sector, or currentheight
|
|
* if there are none lower.
|
|
* \sa P_FindLowestCeilingSurrounding, P_FindNextHighestCeiling,
|
|
* P_FindNextLowestFloor
|
|
* \author Lee Killough
|
|
*/
|
|
static fixed_t P_FindNextLowestCeiling(sector_t *sec, fixed_t currentheight)
|
|
{
|
|
sector_t *other;
|
|
size_t i;
|
|
fixed_t height;
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
other = getNextSector(sec->lines[i],sec);
|
|
if (other && other->ceilingheight < currentheight)
|
|
{
|
|
height = other->ceilingheight;
|
|
while (++i < sec->linecount)
|
|
{
|
|
other = getNextSector(sec->lines[i],sec);
|
|
if (other && other->ceilingheight > height
|
|
&& other->ceilingheight < currentheight)
|
|
height = other->ceilingheight;
|
|
}
|
|
return height;
|
|
}
|
|
}
|
|
return currentheight;
|
|
}
|
|
|
|
/** Finds next highest ceiling in adjacent sectors.
|
|
*
|
|
* \param sec Sector to start in.
|
|
* \param currentheight Height to start at.
|
|
* \return Next highest ceiling height in an adjacent sector, or currentheight
|
|
* if there are none higher.
|
|
* \sa P_FindHighestCeilingSurrounding, P_FindNextLowestCeiling,
|
|
* P_FindNextHighestFloor
|
|
* \author Lee Killough
|
|
*/
|
|
static fixed_t P_FindNextHighestCeiling(sector_t *sec, fixed_t currentheight)
|
|
{
|
|
sector_t *other;
|
|
size_t i;
|
|
fixed_t height;
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
other = getNextSector(sec->lines[i], sec);
|
|
if (other && other->ceilingheight > currentheight)
|
|
{
|
|
height = other->ceilingheight;
|
|
while (++i < sec->linecount)
|
|
{
|
|
other = getNextSector(sec->lines[i],sec);
|
|
if (other && other->ceilingheight < height
|
|
&& other->ceilingheight > currentheight)
|
|
height = other->ceilingheight;
|
|
}
|
|
return height;
|
|
}
|
|
}
|
|
return currentheight;
|
|
}
|
|
#endif
|
|
|
|
////////////////////////////
|
|
// End New Boom functions
|
|
////////////////////////////
|
|
|
|
/** Finds lowest ceiling in adjacent sectors.
|
|
*
|
|
* \param sec Sector to start in.
|
|
* \return Lowest ceiling height in an adjacent sector.
|
|
* \sa P_FindHighestCeilingSurrounding, P_FindNextLowestCeiling,
|
|
* P_FindLowestFloorSurrounding
|
|
*/
|
|
fixed_t P_FindLowestCeilingSurrounding(sector_t *sec)
|
|
{
|
|
size_t i;
|
|
line_t *check;
|
|
sector_t *other;
|
|
fixed_t height = 32000*FRACUNIT; //SoM: 3/7/2000: Remove ovf
|
|
INT32 foundsector = 0;
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
check = sec->lines[i];
|
|
other = getNextSector(check, sec);
|
|
|
|
if (!other)
|
|
continue;
|
|
|
|
if (other->ceilingheight < height || !foundsector)
|
|
height = other->ceilingheight;
|
|
|
|
if (!foundsector)
|
|
foundsector = 1;
|
|
}
|
|
return height;
|
|
}
|
|
|
|
/** Finds Highest ceiling in adjacent sectors.
|
|
*
|
|
* \param sec Sector to start in.
|
|
* \return Highest ceiling height in an adjacent sector.
|
|
* \sa P_FindLowestCeilingSurrounding, P_FindNextHighestCeiling,
|
|
* P_FindHighestFloorSurrounding
|
|
*/
|
|
fixed_t P_FindHighestCeilingSurrounding(sector_t *sec)
|
|
{
|
|
size_t i;
|
|
line_t *check;
|
|
sector_t *other;
|
|
fixed_t height = 0;
|
|
INT32 foundsector = 0;
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
check = sec->lines[i];
|
|
other = getNextSector(check, sec);
|
|
|
|
if (!other)
|
|
continue;
|
|
|
|
if (other->ceilingheight > height || !foundsector)
|
|
height = other->ceilingheight;
|
|
|
|
if (!foundsector)
|
|
foundsector = 1;
|
|
}
|
|
return height;
|
|
}
|
|
|
|
#if 0
|
|
//SoM: 3/7/2000: UTILS.....
|
|
//
|
|
// P_FindShortestTextureAround()
|
|
//
|
|
// Passed a sector number, returns the shortest lower texture on a
|
|
// linedef bounding the sector.
|
|
//
|
|
//
|
|
static fixed_t P_FindShortestTextureAround(INT32 secnum)
|
|
{
|
|
fixed_t minsize = 32000<<FRACBITS;
|
|
side_t *side;
|
|
size_t i;
|
|
sector_t *sec= §ors[secnum];
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
if (twoSided(secnum, i))
|
|
{
|
|
side = getSide(secnum,i,0);
|
|
if (side->bottomtexture > 0)
|
|
if (textureheight[side->bottomtexture] < minsize)
|
|
minsize = textureheight[side->bottomtexture];
|
|
side = getSide(secnum,i,1);
|
|
if (side->bottomtexture > 0)
|
|
if (textureheight[side->bottomtexture] < minsize)
|
|
minsize = textureheight[side->bottomtexture];
|
|
}
|
|
}
|
|
return minsize;
|
|
}
|
|
|
|
//SoM: 3/7/2000: Stuff.... (can you tell I'm getting tired? It's 12 : 30!)
|
|
//
|
|
// P_FindShortestUpperAround()
|
|
//
|
|
// Passed a sector number, returns the shortest upper texture on a
|
|
// linedef bounding the sector.
|
|
//
|
|
//
|
|
static fixed_t P_FindShortestUpperAround(INT32 secnum)
|
|
{
|
|
fixed_t minsize = 32000<<FRACBITS;
|
|
side_t *side;
|
|
size_t i;
|
|
sector_t *sec = §ors[secnum];
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
if (twoSided(secnum, i))
|
|
{
|
|
side = getSide(secnum,i,0);
|
|
if (side->toptexture > 0)
|
|
if (textureheight[side->toptexture] < minsize)
|
|
minsize = textureheight[side->toptexture];
|
|
side = getSide(secnum,i,1);
|
|
if (side->toptexture > 0)
|
|
if (textureheight[side->toptexture] < minsize)
|
|
minsize = textureheight[side->toptexture];
|
|
}
|
|
}
|
|
return minsize;
|
|
}
|
|
|
|
//SoM: 3/7/2000
|
|
//
|
|
// P_FindModelFloorSector()
|
|
//
|
|
// Passed a floor height and a sector number, return a pointer to a
|
|
// a sector with that floor height across the lowest numbered two sided
|
|
// line surrounding the sector.
|
|
//
|
|
// Note: If no sector at that height bounds the sector passed, return NULL
|
|
//
|
|
//
|
|
static sector_t *P_FindModelFloorSector(fixed_t floordestheight, INT32 secnum)
|
|
{
|
|
size_t i;
|
|
sector_t *sec = §ors[secnum];
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
if (twoSided(secnum, i))
|
|
{
|
|
if (getSide(secnum,i,0)->sector-sectors == secnum)
|
|
sec = getSector(secnum,i,1);
|
|
else
|
|
sec = getSector(secnum,i,0);
|
|
|
|
if (sec->floorheight == floordestheight)
|
|
return sec;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// P_FindModelCeilingSector()
|
|
//
|
|
// Passed a ceiling height and a sector number, return a pointer to a
|
|
// a sector with that ceiling height across the lowest numbered two sided
|
|
// line surrounding the sector.
|
|
//
|
|
// Note: If no sector at that height bounds the sector passed, return NULL
|
|
//
|
|
static sector_t *P_FindModelCeilingSector(fixed_t ceildestheight, INT32 secnum)
|
|
{
|
|
size_t i;
|
|
sector_t *sec = §ors[secnum];
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
if (twoSided(secnum, i))
|
|
{
|
|
if (getSide(secnum, i, 0)->sector - sectors == secnum)
|
|
sec = getSector(secnum, i, 1);
|
|
else
|
|
sec = getSector(secnum, i, 0);
|
|
|
|
if (sec->ceilingheight == ceildestheight)
|
|
return sec;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
/** Searches the tag lists for the next sector tagged to a line.
|
|
*
|
|
* \param line Tagged line used as a reference.
|
|
* \param start -1 to start at the beginning, or the result of a previous call
|
|
* to keep searching.
|
|
* \return Number of the next tagged sector found.
|
|
* \sa P_FindSectorFromTag, P_FindLineFromLineTag
|
|
*/
|
|
INT32 P_FindSectorFromLineTag(line_t *line, INT32 start)
|
|
{
|
|
if (line->tag == -1)
|
|
{
|
|
start++;
|
|
|
|
if (start >= (INT32)numsectors)
|
|
return -1;
|
|
|
|
return start;
|
|
}
|
|
else
|
|
{
|
|
start = start >= 0 ? sectors[start].nexttag :
|
|
sectors[(unsigned)line->tag % numsectors].firsttag;
|
|
while (start >= 0 && sectors[start].tag != line->tag)
|
|
start = sectors[start].nexttag;
|
|
return start;
|
|
}
|
|
}
|
|
|
|
/** Searches the tag lists for the next sector with a given tag.
|
|
*
|
|
* \param tag Tag number to look for.
|
|
* \param start -1 to start anew, or the result of a previous call to keep
|
|
* searching.
|
|
* \return Number of the next tagged sector found.
|
|
* \sa P_FindSectorFromLineTag
|
|
*/
|
|
INT32 P_FindSectorFromTag(INT16 tag, INT32 start)
|
|
{
|
|
if (tag == -1)
|
|
{
|
|
start++;
|
|
|
|
if (start >= (INT32)numsectors)
|
|
return -1;
|
|
|
|
return start;
|
|
}
|
|
else
|
|
{
|
|
start = start >= 0 ? sectors[start].nexttag :
|
|
sectors[(unsigned)tag % numsectors].firsttag;
|
|
while (start >= 0 && sectors[start].tag != tag)
|
|
start = sectors[start].nexttag;
|
|
return start;
|
|
}
|
|
}
|
|
|
|
/** Searches the tag lists for the next line tagged to a line.
|
|
*
|
|
* \param line Tagged line used as a reference.
|
|
* \param start -1 to start anew, or the result of a previous call to keep
|
|
* searching.
|
|
* \return Number of the next tagged line found.
|
|
* \sa P_FindSectorFromLineTag
|
|
*/
|
|
static INT32 P_FindLineFromLineTag(const line_t *line, INT32 start)
|
|
{
|
|
if (line->tag == -1)
|
|
{
|
|
start++;
|
|
|
|
if (start >= (INT32)numlines)
|
|
return -1;
|
|
|
|
return start;
|
|
}
|
|
else
|
|
{
|
|
start = start >= 0 ? lines[start].nexttag :
|
|
lines[(unsigned)line->tag % numlines].firsttag;
|
|
while (start >= 0 && lines[start].tag != line->tag)
|
|
start = lines[start].nexttag;
|
|
return start;
|
|
}
|
|
}
|
|
#if 0
|
|
/** Searches the tag lists for the next line with a given tag and special.
|
|
*
|
|
* \param tag Tag number.
|
|
* \param start -1 to start anew, or the result of a previous call to keep
|
|
* searching.
|
|
* \return Number of next suitable line found.
|
|
* \sa P_FindLineFromLineTag
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
static INT32 P_FindLineFromTag(INT32 tag, INT32 start)
|
|
{
|
|
if (tag == -1)
|
|
{
|
|
start++;
|
|
|
|
if (start >= numlines)
|
|
return -1;
|
|
|
|
return start;
|
|
}
|
|
else
|
|
{
|
|
start = start >= 0 ? lines[start].nexttag :
|
|
lines[(unsigned)tag % numlines].firsttag;
|
|
while (start >= 0 && lines[start].tag != tag)
|
|
start = lines[start].nexttag;
|
|
return start;
|
|
}
|
|
}
|
|
#endif
|
|
//
|
|
// P_FindSpecialLineFromTag
|
|
//
|
|
INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start)
|
|
{
|
|
if (tag == -1)
|
|
{
|
|
start++;
|
|
|
|
while (lines[start].special != special)
|
|
start++;
|
|
|
|
if (start >= (INT32)numlines)
|
|
return -1;
|
|
|
|
return start;
|
|
}
|
|
else
|
|
{
|
|
start = start >= 0 ? lines[start].nexttag :
|
|
lines[(unsigned)tag % numlines].firsttag;
|
|
while (start >= 0 && (lines[start].tag != tag || lines[start].special != special))
|
|
start = lines[start].nexttag;
|
|
return start;
|
|
}
|
|
}
|
|
|
|
// haleyjd: temporary define
|
|
#ifdef POLYOBJECTS
|
|
|
|
//
|
|
// PolyDoor
|
|
//
|
|
// Parses arguments for parameterized polyobject door types
|
|
//
|
|
static boolean PolyDoor(line_t *line)
|
|
{
|
|
polydoordata_t pdd;
|
|
|
|
pdd.polyObjNum = line->tag; // polyobject id
|
|
|
|
switch(line->special)
|
|
{
|
|
case 480: // Polyobj_DoorSlide
|
|
pdd.doorType = POLY_DOOR_SLIDE;
|
|
pdd.speed = sides[line->sidenum[0]].textureoffset / 8;
|
|
pdd.angle = R_PointToAngle2(line->v1->x, line->v1->y, line->v2->x, line->v2->y); // angle of motion
|
|
pdd.distance = sides[line->sidenum[0]].rowoffset;
|
|
|
|
if (line->sidenum[1] != 0xffff)
|
|
pdd.delay = sides[line->sidenum[1]].textureoffset >> FRACBITS; // delay in tics
|
|
else
|
|
pdd.delay = 0;
|
|
break;
|
|
case 481: // Polyobj_DoorSwing
|
|
pdd.doorType = POLY_DOOR_SWING;
|
|
pdd.speed = sides[line->sidenum[0]].textureoffset >> FRACBITS; // angular speed
|
|
pdd.distance = sides[line->sidenum[0]].rowoffset >> FRACBITS; // angular distance
|
|
|
|
if (line->sidenum[1] != 0xffff)
|
|
pdd.delay = sides[line->sidenum[1]].textureoffset >> FRACBITS; // delay in tics
|
|
else
|
|
pdd.delay = 0;
|
|
break;
|
|
default:
|
|
return 0; // ???
|
|
}
|
|
|
|
return EV_DoPolyDoor(&pdd);
|
|
}
|
|
|
|
//
|
|
// PolyMove
|
|
//
|
|
// Parses arguments for parameterized polyobject move specials
|
|
//
|
|
static boolean PolyMove(line_t *line)
|
|
{
|
|
polymovedata_t pmd;
|
|
|
|
pmd.polyObjNum = line->tag;
|
|
pmd.speed = sides[line->sidenum[0]].textureoffset / 8;
|
|
pmd.angle = R_PointToAngle2(line->v1->x, line->v1->y, line->v2->x, line->v2->y);
|
|
pmd.distance = sides[line->sidenum[0]].rowoffset;
|
|
|
|
pmd.overRide = (line->special == 483); // Polyobj_OR_Move
|
|
|
|
return EV_DoPolyObjMove(&pmd);
|
|
}
|
|
|
|
//
|
|
// PolyInvisible
|
|
//
|
|
// Makes a polyobject invisible and intangible
|
|
// If NOCLIMB is ticked, the polyobject will still be tangible, just not visible.
|
|
//
|
|
static void PolyInvisible(line_t *line)
|
|
{
|
|
INT32 polyObjNum = line->tag;
|
|
polyobj_t *po;
|
|
|
|
if (!(po = Polyobj_GetForNum(polyObjNum)))
|
|
{
|
|
CONS_Debug(DBG_POLYOBJ, "PolyInvisible: bad polyobj %d\n", polyObjNum);
|
|
return;
|
|
}
|
|
|
|
// don't allow line actions to affect bad polyobjects
|
|
if (po->isBad)
|
|
return;
|
|
|
|
if (!(line->flags & ML_NOCLIMB))
|
|
po->flags &= ~POF_SOLID;
|
|
|
|
po->flags |= POF_NOSPECIALS;
|
|
po->flags &= ~POF_RENDERALL;
|
|
}
|
|
|
|
//
|
|
// PolyVisible
|
|
//
|
|
// Makes a polyobject visible and tangible
|
|
// If NOCLIMB is ticked, the polyobject will not be tangible, just visible.
|
|
//
|
|
static void PolyVisible(line_t *line)
|
|
{
|
|
INT32 polyObjNum = line->tag;
|
|
polyobj_t *po;
|
|
|
|
if (!(po = Polyobj_GetForNum(polyObjNum)))
|
|
{
|
|
CONS_Debug(DBG_POLYOBJ, "PolyVisible: bad polyobj %d\n", polyObjNum);
|
|
return;
|
|
}
|
|
|
|
// don't allow line actions to affect bad polyobjects
|
|
if (po->isBad)
|
|
return;
|
|
|
|
if (!(line->flags & ML_NOCLIMB))
|
|
po->flags |= POF_SOLID;
|
|
|
|
po->flags &= ~POF_NOSPECIALS;
|
|
po->flags |= POF_RENDERALL;
|
|
}
|
|
|
|
//
|
|
// PolyTranslucency
|
|
//
|
|
// Sets the translucency of a polyobject
|
|
// Frontsector floor / 100 = translevel
|
|
//
|
|
static void PolyTranslucency(line_t *line)
|
|
{
|
|
INT32 polyObjNum = line->tag;
|
|
polyobj_t *po;
|
|
|
|
if (!(po = Polyobj_GetForNum(polyObjNum)))
|
|
{
|
|
CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjWaypoint: bad polyobj %d\n", polyObjNum);
|
|
return;
|
|
}
|
|
|
|
// don't allow line actions to affect bad polyobjects
|
|
if (po->isBad)
|
|
return;
|
|
|
|
po->translucency = (line->frontsector->floorheight >> FRACBITS) / 100;
|
|
}
|
|
|
|
//
|
|
// PolyWaypoint
|
|
//
|
|
// Parses arguments for parameterized polyobject waypoint movement
|
|
//
|
|
static boolean PolyWaypoint(line_t *line)
|
|
{
|
|
polywaypointdata_t pwd;
|
|
|
|
pwd.polyObjNum = line->tag;
|
|
pwd.speed = sides[line->sidenum[0]].textureoffset / 8;
|
|
pwd.sequence = sides[line->sidenum[0]].rowoffset >> FRACBITS; // Sequence #
|
|
pwd.reverse = (line->flags & ML_EFFECT1) == ML_EFFECT1; // Reverse?
|
|
pwd.comeback = (line->flags & ML_EFFECT2) == ML_EFFECT2; // Return when reaching end?
|
|
pwd.wrap = (line->flags & ML_EFFECT3) == ML_EFFECT3; // Wrap around waypoints
|
|
pwd.continuous = (line->flags & ML_EFFECT4) == ML_EFFECT4; // Continuously move - used with COMEBACK or WRAP
|
|
|
|
return EV_DoPolyObjWaypoint(&pwd);
|
|
}
|
|
|
|
//
|
|
// PolyRotate
|
|
//
|
|
// Parses arguments for parameterized polyobject rotate specials
|
|
//
|
|
static boolean PolyRotate(line_t *line)
|
|
{
|
|
polyrotdata_t prd;
|
|
|
|
prd.polyObjNum = line->tag;
|
|
prd.speed = sides[line->sidenum[0]].textureoffset >> FRACBITS; // angular speed
|
|
prd.distance = sides[line->sidenum[0]].rowoffset >> FRACBITS; // angular distance
|
|
|
|
// Polyobj_(OR_)RotateRight have dir == -1
|
|
prd.direction = (line->special == 484 || line->special == 485) ? -1 : 1;
|
|
|
|
// Polyobj_OR types have override set to true
|
|
prd.overRide = (line->special == 485 || line->special == 487);
|
|
|
|
if (line->flags & ML_NOCLIMB)
|
|
prd.turnobjs = 0;
|
|
else if (line->flags & ML_EFFECT4)
|
|
prd.turnobjs = 2;
|
|
else
|
|
prd.turnobjs = 1;
|
|
|
|
return EV_DoPolyObjRotate(&prd);
|
|
}
|
|
|
|
//
|
|
// PolyDisplace
|
|
//
|
|
// Parses arguments for parameterized polyobject move-by-sector-heights specials
|
|
//
|
|
static boolean PolyDisplace(line_t *line)
|
|
{
|
|
polydisplacedata_t pdd;
|
|
|
|
pdd.polyObjNum = line->tag;
|
|
|
|
pdd.controlSector = line->frontsector;
|
|
pdd.dx = line->dx>>8;
|
|
pdd.dy = line->dy>>8;
|
|
|
|
return EV_DoPolyObjDisplace(&pdd);
|
|
}
|
|
|
|
#endif // ifdef POLYOBJECTS
|
|
|
|
/** Changes a sector's tag.
|
|
* Used by the linedef executor tag changer and by crumblers.
|
|
*
|
|
* \param sector Sector whose tag will be changed.
|
|
* \param newtag New tag number for this sector.
|
|
* \sa P_InitTagLists, P_FindSectorFromTag
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
void P_ChangeSectorTag(UINT32 sector, INT16 newtag)
|
|
{
|
|
INT16 oldtag;
|
|
INT32 i;
|
|
|
|
I_Assert(sector < numsectors);
|
|
|
|
if ((oldtag = sectors[sector].tag) == newtag)
|
|
return;
|
|
|
|
// first you have to remove it from the old tag's taglist
|
|
i = sectors[(unsigned)oldtag % numsectors].firsttag;
|
|
|
|
if (i == -1) // shouldn't happen
|
|
I_Error("Corrupt tag list for sector %u\n", sector);
|
|
else if ((UINT32)i == sector)
|
|
sectors[(unsigned)oldtag % numsectors].firsttag = sectors[sector].nexttag;
|
|
else
|
|
{
|
|
while (sectors[i].nexttag != -1 && (UINT32)sectors[i].nexttag < sector )
|
|
i = sectors[i].nexttag;
|
|
|
|
sectors[i].nexttag = sectors[sector].nexttag;
|
|
}
|
|
|
|
sectors[sector].tag = newtag;
|
|
|
|
// now add it to the new tag's taglist
|
|
if ((UINT32)sectors[(unsigned)newtag % numsectors].firsttag > sector)
|
|
{
|
|
sectors[sector].nexttag = sectors[(unsigned)newtag % numsectors].firsttag;
|
|
sectors[(unsigned)newtag % numsectors].firsttag = sector;
|
|
}
|
|
else
|
|
{
|
|
i = sectors[(unsigned)newtag % numsectors].firsttag;
|
|
|
|
if (i == -1)
|
|
{
|
|
sectors[(unsigned)newtag % numsectors].firsttag = sector;
|
|
sectors[sector].nexttag = -1;
|
|
}
|
|
else
|
|
{
|
|
while (sectors[i].nexttag != -1 && (UINT32)sectors[i].nexttag < sector )
|
|
i = sectors[i].nexttag;
|
|
|
|
sectors[sector].nexttag = sectors[i].nexttag;
|
|
sectors[i].nexttag = sector;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Hashes the sector tags across the sectors and linedefs.
|
|
*
|
|
* \sa P_FindSectorFromTag, P_ChangeSectorTag
|
|
* \author Lee Killough
|
|
*/
|
|
static inline void P_InitTagLists(void)
|
|
{
|
|
register size_t i;
|
|
|
|
for (i = numsectors - 1; i != (size_t)-1; i--)
|
|
{
|
|
size_t j = (unsigned)sectors[i].tag % numsectors;
|
|
sectors[i].nexttag = sectors[j].firsttag;
|
|
sectors[j].firsttag = (INT32)i;
|
|
}
|
|
|
|
for (i = numlines - 1; i != (size_t)-1; i--)
|
|
{
|
|
size_t j = (unsigned)lines[i].tag % numlines;
|
|
lines[i].nexttag = lines[j].firsttag;
|
|
lines[j].firsttag = (INT32)i;
|
|
}
|
|
}
|
|
|
|
/** Finds minimum light from an adjacent sector.
|
|
*
|
|
* \param sector Sector to start in.
|
|
* \param max Maximum value to return.
|
|
* \return Minimum light value from an adjacent sector, or max if the minimum
|
|
* light value is greater than max.
|
|
*/
|
|
INT32 P_FindMinSurroundingLight(sector_t *sector, INT32 max)
|
|
{
|
|
size_t i;
|
|
INT32 min = max;
|
|
line_t *line;
|
|
sector_t *check;
|
|
|
|
for (i = 0; i < sector->linecount; i++)
|
|
{
|
|
line = sector->lines[i];
|
|
check = getNextSector(line,sector);
|
|
|
|
if (!check)
|
|
continue;
|
|
|
|
if (check->lightlevel < min)
|
|
min = check->lightlevel;
|
|
}
|
|
return min;
|
|
}
|
|
|
|
void T_ExecutorDelay(executor_t *e)
|
|
{
|
|
if (--e->timer <= 0)
|
|
{
|
|
if (e->caller && P_MobjWasRemoved(e->caller)) // If the mobj died while we were delaying
|
|
P_SetTarget(&e->caller, NULL); // Call with no mobj!
|
|
P_ProcessLineSpecial(e->line, e->caller, e->sector);
|
|
P_SetTarget(&e->caller, NULL); // Let the mobj know it can be removed now.
|
|
P_RemoveThinker(&e->thinker);
|
|
}
|
|
}
|
|
|
|
static void P_AddExecutorDelay(line_t *line, mobj_t *mobj, sector_t *sector)
|
|
{
|
|
executor_t *e;
|
|
|
|
if (!line->backsector)
|
|
I_Error("P_AddExecutorDelay: Line has no backsector!\n");
|
|
|
|
e = Z_Calloc(sizeof (*e), PU_LEVSPEC, NULL);
|
|
|
|
e->thinker.function.acp1 = (actionf_p1)T_ExecutorDelay;
|
|
e->line = line;
|
|
e->sector = sector;
|
|
e->timer = (line->backsector->ceilingheight>>FRACBITS)+(line->backsector->floorheight>>FRACBITS);
|
|
P_SetTarget(&e->caller, mobj); // Use P_SetTarget to make sure the mobj doesn't get freed while we're delaying.
|
|
P_AddThinker(&e->thinker);
|
|
}
|
|
|
|
/** Used by P_LinedefExecute to check a trigger linedef's conditions
|
|
* The linedef executor specials in the trigger linedef's sector are run if all conditions are met.
|
|
* Return false cancels P_LinedefExecute, this happens if a condition is not met.
|
|
*
|
|
* \param triggerline Trigger linedef to check conditions for; should NEVER be NULL.
|
|
* \param actor Object initiating the action; should not be NULL.
|
|
* \param caller Sector in which the action was started. May be NULL.
|
|
* \sa P_ProcessLineSpecial, P_LinedefExecute
|
|
*/
|
|
boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller)
|
|
{
|
|
sector_t *ctlsector;
|
|
fixed_t dist = P_AproxDistance(triggerline->dx, triggerline->dy)>>FRACBITS;
|
|
size_t i, linecnt, sectori;
|
|
INT16 specialtype = triggerline->special;
|
|
|
|
/////////////////////////////////////////////////
|
|
// Distance-checking/sector trigger conditions //
|
|
/////////////////////////////////////////////////
|
|
|
|
// Linetypes 303 and 304 require a specific
|
|
// number, or minimum or maximum, of rings.
|
|
if (specialtype == 303 || specialtype == 304)
|
|
{
|
|
fixed_t rings = 0;
|
|
|
|
// With the passuse flag, count all player's
|
|
// rings.
|
|
if (triggerline->flags & ML_EFFECT4)
|
|
{
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i] || players[i].spectator)
|
|
continue;
|
|
|
|
if (!players[i].mo || players[i].mo->health < 1)
|
|
continue;
|
|
|
|
rings += players[i].mo->health-1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!(actor && actor->player))
|
|
return false; // no player to count rings from here, sorry
|
|
|
|
rings = actor->health-1;
|
|
}
|
|
|
|
if (triggerline->flags & ML_NOCLIMB)
|
|
{
|
|
if (rings > dist)
|
|
return false;
|
|
}
|
|
else if (triggerline->flags & ML_BLOCKMONSTERS)
|
|
{
|
|
if (rings < dist)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (rings != dist)
|
|
return false;
|
|
}
|
|
}
|
|
else if (specialtype >= 314 && specialtype <= 315)
|
|
{
|
|
msecnode_t *node;
|
|
mobj_t *mo;
|
|
INT32 numpush = 0;
|
|
INT32 numneeded = dist;
|
|
|
|
if (!caller)
|
|
return false; // we need a calling sector to find pushables in, silly!
|
|
|
|
// Count the pushables in this sector
|
|
node = caller->touching_thinglist; // things touching this sector
|
|
while (node)
|
|
{
|
|
mo = node->m_thing;
|
|
if (mo->flags & MF_PUSHABLE)
|
|
numpush++;
|
|
node = node->m_snext;
|
|
}
|
|
|
|
if (triggerline->flags & ML_NOCLIMB) // Need at least or more
|
|
{
|
|
if (numpush < numneeded)
|
|
return false;
|
|
}
|
|
else if (triggerline->flags & ML_EFFECT4) // Need less than
|
|
{
|
|
if (numpush >= numneeded)
|
|
return false;
|
|
}
|
|
else // Need exact
|
|
{
|
|
if (numpush != numneeded)
|
|
return false;
|
|
}
|
|
}
|
|
else if (caller)
|
|
{
|
|
if (GETSECSPECIAL(caller->special, 2) == 6)
|
|
{
|
|
if (!(ALL7EMERALDS(emeralds)))
|
|
return false;
|
|
}
|
|
else if (GETSECSPECIAL(caller->special, 2) == 7)
|
|
{
|
|
UINT8 mare;
|
|
|
|
if (!(maptol & TOL_NIGHTS))
|
|
return false;
|
|
|
|
mare = P_FindLowestMare();
|
|
|
|
if (triggerline->flags & ML_NOCLIMB)
|
|
{
|
|
if (!(mare <= dist))
|
|
return false;
|
|
}
|
|
else if (triggerline->flags & ML_BLOCKMONSTERS)
|
|
{
|
|
if (!(mare >= dist))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (!(mare == dist))
|
|
return false;
|
|
}
|
|
}
|
|
// If we were not triggered by a sector type especially for the purpose,
|
|
// a Linedef Executor linedef trigger is not handling sector triggers properly, return.
|
|
|
|
else if ((!GETSECSPECIAL(caller->special, 2) || GETSECSPECIAL(caller->special, 2) > 7) && (specialtype > 322))
|
|
{
|
|
CONS_Alert(CONS_WARNING,
|
|
M_GetText("Linedef executor trigger isn't handling sector triggers properly!\nspecialtype = %d, if you are not a dev, report this warning instance\nalong with the wad that caused it!\n"),
|
|
specialtype);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////
|
|
// Miscellaneous trigger conditions //
|
|
//////////////////////////////////////
|
|
|
|
switch (specialtype)
|
|
{
|
|
case 305: // continuous
|
|
case 306: // each time
|
|
case 307: // once
|
|
if (!(actor && actor->player && actor->player->charability != dist/10))
|
|
return false;
|
|
break;
|
|
case 309: // continuous
|
|
case 310: // each time
|
|
// Only red team members can activate this.
|
|
if (!(actor && actor->player && actor->player->ctfteam == 1))
|
|
return false;
|
|
break;
|
|
case 311: // continuous
|
|
case 312: // each time
|
|
// Only blue team members can activate this.
|
|
if (!(actor && actor->player && actor->player->ctfteam == 2))
|
|
return false;
|
|
break;
|
|
case 317: // continuous
|
|
case 318: // once
|
|
{ // Unlockable triggers required
|
|
INT32 trigid = (INT32)(sides[triggerline->sidenum[0]].textureoffset>>FRACBITS);
|
|
|
|
if ((modifiedgame && !savemoddata) || (netgame || multiplayer))
|
|
return false;
|
|
else if (trigid < 0 || trigid > 31) // limited by 32 bit variable
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %hu): bad trigger ID %d\n", triggerline->sidenum[0], trigid);
|
|
return false;
|
|
}
|
|
else if (!(unlocktriggers & (1 << trigid)))
|
|
return false;
|
|
}
|
|
break;
|
|
case 319: // continuous
|
|
case 320: // once
|
|
{ // An unlockable itself must be unlocked!
|
|
INT32 unlockid = (INT32)(sides[triggerline->sidenum[0]].textureoffset>>FRACBITS);
|
|
|
|
if ((modifiedgame && !savemoddata) || (netgame || multiplayer))
|
|
return false;
|
|
else if (unlockid < 0 || unlockid >= MAXUNLOCKABLES) // limited by unlockable count
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Unlockable check (sidedef %hu): bad unlockable ID %d\n", triggerline->sidenum[0], unlockid);
|
|
return false;
|
|
}
|
|
else if (!(unlockables[unlockid-1].unlocked))
|
|
return false;
|
|
}
|
|
break;
|
|
case 321: // continuous
|
|
case 322: // each time
|
|
// decrement calls left before triggering
|
|
if (triggerline->callcount > 0)
|
|
{
|
|
if (--triggerline->callcount > 0)
|
|
return false;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/////////////////////////////////
|
|
// Processing linedef specials //
|
|
/////////////////////////////////
|
|
|
|
ctlsector = triggerline->frontsector;
|
|
sectori = (size_t)(ctlsector - sectors);
|
|
linecnt = ctlsector->linecount;
|
|
|
|
if (triggerline->flags & ML_EFFECT5) // disregard order for efficiency
|
|
{
|
|
for (i = 0; i < linecnt; i++)
|
|
if (ctlsector->lines[i]->special >= 400
|
|
&& ctlsector->lines[i]->special < 500)
|
|
{
|
|
if (ctlsector->lines[i]->flags & ML_DONTPEGTOP)
|
|
P_AddExecutorDelay(ctlsector->lines[i], actor, caller);
|
|
else
|
|
P_ProcessLineSpecial(ctlsector->lines[i], actor, caller);
|
|
}
|
|
}
|
|
else // walk around the sector in a defined order
|
|
{
|
|
boolean backwards = false;
|
|
size_t j, masterlineindex = (size_t)-1;
|
|
|
|
for (i = 0; i < linecnt; i++)
|
|
if (ctlsector->lines[i] == triggerline)
|
|
{
|
|
masterlineindex = i;
|
|
break;
|
|
}
|
|
|
|
#ifdef PARANOIA
|
|
if (masterlineindex == (size_t)-1)
|
|
{
|
|
const size_t li = (size_t)(ctlsector->lines[i] - lines);
|
|
I_Error("Line %s isn't linked into its front sector", sizeu1(li));
|
|
}
|
|
#endif
|
|
|
|
// i == masterlineindex
|
|
for (;;)
|
|
{
|
|
if (backwards) // v2 to v1
|
|
{
|
|
for (j = 0; j < linecnt; j++)
|
|
{
|
|
if (i == j)
|
|
continue;
|
|
if (ctlsector->lines[i]->v1 == ctlsector->lines[j]->v2)
|
|
{
|
|
i = j;
|
|
break;
|
|
}
|
|
if (ctlsector->lines[i]->v1 == ctlsector->lines[j]->v1)
|
|
{
|
|
i = j;
|
|
backwards = false;
|
|
break;
|
|
}
|
|
}
|
|
if (j == linecnt)
|
|
{
|
|
const size_t vertexei = (size_t)(ctlsector->lines[i]->v1 - vertexes);
|
|
CONS_Debug(DBG_GAMELOGIC, "Warning: Sector %s is not closed at vertex %s (%d, %d)\n",
|
|
sizeu1(sectori), sizeu2(vertexei), ctlsector->lines[i]->v1->x, ctlsector->lines[i]->v1->y);
|
|
return false; // abort
|
|
}
|
|
}
|
|
else // v1 to v2
|
|
{
|
|
for (j = 0; j < linecnt; j++)
|
|
{
|
|
if (i == j)
|
|
continue;
|
|
if (ctlsector->lines[i]->v2 == ctlsector->lines[j]->v1)
|
|
{
|
|
i = j;
|
|
break;
|
|
}
|
|
if (ctlsector->lines[i]->v2 == ctlsector->lines[j]->v2)
|
|
{
|
|
i = j;
|
|
backwards = true;
|
|
break;
|
|
}
|
|
}
|
|
if (j == linecnt)
|
|
{
|
|
const size_t vertexei = (size_t)(ctlsector->lines[i]->v1 - vertexes);
|
|
CONS_Debug(DBG_GAMELOGIC, "Warning: Sector %s is not closed at vertex %s (%d, %d)\n",
|
|
sizeu1(sectori), sizeu2(vertexei), ctlsector->lines[i]->v2->x, ctlsector->lines[i]->v2->y);
|
|
return false; // abort
|
|
}
|
|
}
|
|
|
|
if (i == masterlineindex)
|
|
break;
|
|
|
|
if (ctlsector->lines[i]->special >= 400
|
|
&& ctlsector->lines[i]->special < 500)
|
|
{
|
|
if (ctlsector->lines[i]->flags & ML_DONTPEGTOP)
|
|
P_AddExecutorDelay(ctlsector->lines[i], actor, caller);
|
|
else
|
|
P_ProcessLineSpecial(ctlsector->lines[i], actor, caller);
|
|
}
|
|
}
|
|
}
|
|
|
|
// "Trigger on X calls" linedefs reset if noclimb is set
|
|
if ((specialtype == 321 || specialtype == 322) && triggerline->flags & ML_NOCLIMB)
|
|
triggerline->callcount = sides[triggerline->sidenum[0]].textureoffset>>FRACBITS;
|
|
else
|
|
// These special types work only once
|
|
if (specialtype == 302 // Once
|
|
|| specialtype == 304 // Ring count - Once
|
|
|| specialtype == 307 // Character ability - Once
|
|
|| specialtype == 308 // Race only - Once
|
|
|| specialtype == 315 // No of pushables - Once
|
|
|| specialtype == 318 // Unlockable trigger - Once
|
|
|| specialtype == 320 // Unlockable - Once
|
|
|| specialtype == 321 || specialtype == 322 // Trigger on X calls - Continuous + Each Time
|
|
|| specialtype == 399) // Level Load
|
|
triggerline->special = 0; // Clear it out
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Runs a linedef executor.
|
|
* Can be called by:
|
|
* - a player moving into a special sector or FOF.
|
|
* - a pushable object moving into a special sector or FOF.
|
|
* - a ceiling or floor movement from a previous linedef executor finishing.
|
|
* - any object in a state with the A_LinedefExecute() action.
|
|
*
|
|
* \param tag Tag of the linedef executor to run.
|
|
* \param actor Object initiating the action; should not be NULL.
|
|
* \param caller Sector in which the action was started. May be NULL.
|
|
* \sa P_ProcessLineSpecial, P_RunTriggerLinedef
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller)
|
|
{
|
|
size_t masterline;
|
|
|
|
CONS_Debug(DBG_GAMELOGIC, "P_LinedefExecute: Executing trigger linedefs of tag %d\n", tag);
|
|
|
|
I_Assert(!actor || !P_MobjWasRemoved(actor)); // If actor is there, it must be valid.
|
|
|
|
for (masterline = 0; masterline < numlines; masterline++)
|
|
{
|
|
if (lines[masterline].tag != tag)
|
|
continue;
|
|
|
|
// "No More Enemies" and "Level Load" take care of themselves.
|
|
if (lines[masterline].special == 313
|
|
|| lines[masterline].special == 399
|
|
// Each-time executors handle themselves, too
|
|
|| lines[masterline].special == 301 // Each time
|
|
|| lines[masterline].special == 306 // Character ability - Each time
|
|
|| lines[masterline].special == 310 // CTF Red team - Each time
|
|
|| lines[masterline].special == 312 // CTF Blue team - Each time
|
|
|| lines[masterline].special == 322) // Trigger on X calls - Each Time
|
|
continue;
|
|
|
|
if (lines[masterline].special < 300
|
|
|| lines[masterline].special > 399)
|
|
continue;
|
|
|
|
if (!P_RunTriggerLinedef(&lines[masterline], actor, caller))
|
|
return; // cancel P_LinedefExecute if function returns false
|
|
}
|
|
}
|
|
|
|
//
|
|
// P_SwitchWeather
|
|
//
|
|
// Switches the weather!
|
|
//
|
|
void P_SwitchWeather(INT32 weathernum)
|
|
{
|
|
boolean purge = false;
|
|
INT32 swap = 0;
|
|
|
|
switch (weathernum)
|
|
{
|
|
case PRECIP_NONE: // None
|
|
if (curWeather == PRECIP_NONE)
|
|
return; // Nothing to do.
|
|
purge = true;
|
|
break;
|
|
case PRECIP_STORM: // Storm
|
|
case PRECIP_STORM_NOSTRIKES: // Storm w/ no lightning
|
|
case PRECIP_RAIN: // Rain
|
|
if (curWeather == PRECIP_SNOW || curWeather == PRECIP_BLANK || curWeather == PRECIP_STORM_NORAIN)
|
|
swap = PRECIP_RAIN;
|
|
break;
|
|
case PRECIP_SNOW: // Snow
|
|
if (curWeather == PRECIP_SNOW)
|
|
return; // Nothing to do.
|
|
if (curWeather == PRECIP_RAIN || curWeather == PRECIP_STORM || curWeather == PRECIP_STORM_NOSTRIKES || curWeather == PRECIP_BLANK || curWeather == PRECIP_STORM_NORAIN)
|
|
swap = PRECIP_SNOW; // Need to delete the other precips.
|
|
break;
|
|
case PRECIP_STORM_NORAIN: // Storm w/o rain
|
|
if (curWeather == PRECIP_SNOW
|
|
|| curWeather == PRECIP_STORM
|
|
|| curWeather == PRECIP_STORM_NOSTRIKES
|
|
|| curWeather == PRECIP_RAIN
|
|
|| curWeather == PRECIP_BLANK)
|
|
swap = PRECIP_STORM_NORAIN;
|
|
else if (curWeather == PRECIP_STORM_NORAIN)
|
|
return;
|
|
break;
|
|
case PRECIP_BLANK:
|
|
if (curWeather == PRECIP_SNOW
|
|
|| curWeather == PRECIP_STORM
|
|
|| curWeather == PRECIP_STORM_NOSTRIKES
|
|
|| curWeather == PRECIP_RAIN)
|
|
swap = PRECIP_BLANK;
|
|
else if (curWeather == PRECIP_STORM_NORAIN)
|
|
swap = PRECIP_BLANK;
|
|
else if (curWeather == PRECIP_BLANK)
|
|
return;
|
|
break;
|
|
default:
|
|
CONS_Debug(DBG_GAMELOGIC, "P_SwitchWeather: Unknown weather type %d.\n", weathernum);
|
|
break;
|
|
}
|
|
|
|
if (purge)
|
|
{
|
|
thinker_t *think;
|
|
precipmobj_t *precipmobj;
|
|
|
|
for (think = thinkercap.next; think != &thinkercap; think = think->next)
|
|
{
|
|
if ((think->function.acp1 != (actionf_p1)P_SnowThinker)
|
|
&& (think->function.acp1 != (actionf_p1)P_RainThinker))
|
|
continue; // not a precipmobj thinker
|
|
|
|
precipmobj = (precipmobj_t *)think;
|
|
|
|
P_RemovePrecipMobj(precipmobj);
|
|
}
|
|
}
|
|
else if (swap && !((swap == PRECIP_BLANK && curWeather == PRECIP_STORM_NORAIN) || (swap == PRECIP_STORM_NORAIN && curWeather == PRECIP_BLANK))) // Rather than respawn all that crap, reuse it!
|
|
{
|
|
thinker_t *think;
|
|
precipmobj_t *precipmobj;
|
|
state_t *st;
|
|
|
|
for (think = thinkercap.next; think != &thinkercap; think = think->next)
|
|
{
|
|
if (swap == PRECIP_RAIN) // Snow To Rain
|
|
{
|
|
if (!(think->function.acp1 == (actionf_p1)P_SnowThinker
|
|
|| think->function.acp1 == (actionf_p1)P_NullPrecipThinker))
|
|
continue; // not a precipmobj thinker
|
|
|
|
precipmobj = (precipmobj_t *)think;
|
|
|
|
precipmobj->flags = mobjinfo[MT_RAIN].flags;
|
|
st = &states[mobjinfo[MT_RAIN].spawnstate];
|
|
precipmobj->state = st;
|
|
precipmobj->tics = st->tics;
|
|
precipmobj->sprite = st->sprite;
|
|
precipmobj->frame = st->frame;
|
|
precipmobj->momz = mobjinfo[MT_RAIN].speed;
|
|
|
|
precipmobj->precipflags &= ~PCF_INVISIBLE;
|
|
|
|
think->function.acp1 = (actionf_p1)P_RainThinker;
|
|
}
|
|
else if (swap == PRECIP_SNOW) // Rain To Snow
|
|
{
|
|
INT32 z;
|
|
|
|
if (!(think->function.acp1 == (actionf_p1)P_RainThinker
|
|
|| think->function.acp1 == (actionf_p1)P_NullPrecipThinker))
|
|
continue; // not a precipmobj thinker
|
|
|
|
precipmobj = (precipmobj_t *)think;
|
|
|
|
precipmobj->flags = mobjinfo[MT_SNOWFLAKE].flags;
|
|
z = M_RandomByte();
|
|
|
|
if (z < 64)
|
|
z = 2;
|
|
else if (z < 144)
|
|
z = 1;
|
|
else
|
|
z = 0;
|
|
|
|
st = &states[mobjinfo[MT_SNOWFLAKE].spawnstate+z];
|
|
precipmobj->state = st;
|
|
precipmobj->tics = st->tics;
|
|
precipmobj->sprite = st->sprite;
|
|
precipmobj->frame = st->frame;
|
|
precipmobj->momz = mobjinfo[MT_SNOWFLAKE].speed;
|
|
|
|
precipmobj->precipflags &= ~PCF_INVISIBLE;
|
|
|
|
think->function.acp1 = (actionf_p1)P_SnowThinker;
|
|
}
|
|
else if (swap == PRECIP_BLANK || swap == PRECIP_STORM_NORAIN) // Remove precip, but keep it around for reuse.
|
|
{
|
|
if (!(think->function.acp1 == (actionf_p1)P_RainThinker
|
|
|| think->function.acp1 == (actionf_p1)P_SnowThinker))
|
|
continue;
|
|
|
|
precipmobj = (precipmobj_t *)think;
|
|
|
|
think->function.acp1 = (actionf_p1)P_NullPrecipThinker;
|
|
|
|
precipmobj->precipflags |= PCF_INVISIBLE;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (weathernum)
|
|
{
|
|
case PRECIP_SNOW: // snow
|
|
curWeather = PRECIP_SNOW;
|
|
|
|
if (!swap)
|
|
P_SpawnPrecipitation();
|
|
|
|
break;
|
|
case PRECIP_RAIN: // rain
|
|
{
|
|
boolean dontspawn = false;
|
|
|
|
if (curWeather == PRECIP_RAIN || curWeather == PRECIP_STORM || curWeather == PRECIP_STORM_NOSTRIKES)
|
|
dontspawn = true;
|
|
|
|
curWeather = PRECIP_RAIN;
|
|
|
|
if (!dontspawn && !swap)
|
|
P_SpawnPrecipitation();
|
|
|
|
break;
|
|
}
|
|
case PRECIP_STORM: // storm
|
|
{
|
|
boolean dontspawn = false;
|
|
|
|
if (curWeather == PRECIP_RAIN || curWeather == PRECIP_STORM || curWeather == PRECIP_STORM_NOSTRIKES)
|
|
dontspawn = true;
|
|
|
|
curWeather = PRECIP_STORM;
|
|
|
|
if (!dontspawn && !swap)
|
|
P_SpawnPrecipitation();
|
|
|
|
break;
|
|
}
|
|
case PRECIP_STORM_NOSTRIKES: // storm w/o lightning
|
|
{
|
|
boolean dontspawn = false;
|
|
|
|
if (curWeather == PRECIP_RAIN || curWeather == PRECIP_STORM || curWeather == PRECIP_STORM_NOSTRIKES)
|
|
dontspawn = true;
|
|
|
|
curWeather = PRECIP_STORM_NOSTRIKES;
|
|
|
|
if (!dontspawn && !swap)
|
|
P_SpawnPrecipitation();
|
|
|
|
break;
|
|
}
|
|
case PRECIP_STORM_NORAIN: // storm w/o rain
|
|
curWeather = PRECIP_STORM_NORAIN;
|
|
|
|
if (!swap)
|
|
P_SpawnPrecipitation();
|
|
|
|
break;
|
|
case PRECIP_BLANK:
|
|
curWeather = PRECIP_BLANK;
|
|
|
|
if (!swap)
|
|
P_SpawnPrecipitation();
|
|
|
|
break;
|
|
default:
|
|
curWeather = PRECIP_NONE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** Gets an object.
|
|
*
|
|
* \param type Object type to look for.
|
|
* \param s Sector number to look in.
|
|
* \return Pointer to the first ::type found in the sector.
|
|
* \sa P_GetPushThing
|
|
*/
|
|
static mobj_t *P_GetObjectTypeInSectorNum(mobjtype_t type, size_t s)
|
|
{
|
|
sector_t *sec = sectors + s;
|
|
mobj_t *thing = sec->thinglist;
|
|
|
|
while (thing)
|
|
{
|
|
if (thing->type == type)
|
|
return thing;
|
|
thing = thing->snext;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/** Processes the line special triggered by an object.
|
|
*
|
|
* \param line Line with the special command on it.
|
|
* \param mo mobj that triggered the line. Must be valid and non-NULL.
|
|
* \param callsec sector in which action was initiated; this can be NULL.
|
|
* Because of the A_LinedefExecute() action, even if non-NULL,
|
|
* this sector might not have the same tag as the linedef executor,
|
|
* and it might not have the linedef executor sector type.
|
|
* \todo Handle mo being NULL gracefully. T_MoveFloor() and T_MoveCeiling()
|
|
* don't have an object to pass.
|
|
* \todo Split up into multiple functions.
|
|
* \sa P_LinedefExecute
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
|
|
{
|
|
INT32 secnum = -1;
|
|
mobj_t *bot = NULL;
|
|
|
|
I_Assert(!mo || !P_MobjWasRemoved(mo)); // If mo is there, mo must be valid!
|
|
|
|
if (mo && mo->player && botingame)
|
|
bot = players[secondarydisplayplayer].mo;
|
|
|
|
// note: only commands with linedef types >= 400 && < 500 can be used
|
|
switch (line->special)
|
|
{
|
|
case 400: // Set tagged sector's floor height/pic
|
|
EV_DoFloor(line, instantMoveFloorByFrontSector);
|
|
break;
|
|
|
|
case 401: // Set tagged sector's ceiling height/pic
|
|
EV_DoCeiling(line, instantMoveCeilingByFrontSector);
|
|
break;
|
|
|
|
case 402: // Set tagged sector's light level
|
|
{
|
|
INT16 newlightlevel;
|
|
INT32 newfloorlightsec, newceilinglightsec;
|
|
|
|
newlightlevel = line->frontsector->lightlevel;
|
|
newfloorlightsec = line->frontsector->floorlightsec;
|
|
newceilinglightsec = line->frontsector->ceilinglightsec;
|
|
|
|
// act on all sectors with the same tag as the triggering linedef
|
|
while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
|
|
{
|
|
if (sectors[secnum].lightingdata)
|
|
{
|
|
// Stop the lighting madness going on in this sector!
|
|
P_RemoveThinker(&((elevator_t *)sectors[secnum].lightingdata)->thinker);
|
|
sectors[secnum].lightingdata = NULL;
|
|
|
|
// No, it's not an elevator_t, but any struct with a thinker_t named
|
|
// 'thinker' at the beginning will do here. (We don't know what it
|
|
// actually is: could be lightlevel_t, fireflicker_t, glow_t, etc.)
|
|
}
|
|
|
|
sectors[secnum].lightlevel = newlightlevel;
|
|
sectors[secnum].floorlightsec = newfloorlightsec;
|
|
sectors[secnum].ceilinglightsec = newceilinglightsec;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 403: // Move floor, linelen = speed, frontsector floor = dest height
|
|
EV_DoFloor(line, moveFloorByFrontSector);
|
|
break;
|
|
|
|
case 404: // Move ceiling, linelen = speed, frontsector ceiling = dest height
|
|
EV_DoCeiling(line, moveCeilingByFrontSector);
|
|
break;
|
|
|
|
case 405: // Move floor by front side texture offsets, offset x = speed, offset y = amount to raise/lower
|
|
EV_DoFloor(line, moveFloorByFrontTexture);
|
|
break;
|
|
|
|
case 407: // Move ceiling by front side texture offsets, offset x = speed, offset y = amount to raise/lower
|
|
EV_DoCeiling(line, moveCeilingByFrontTexture);
|
|
break;
|
|
|
|
/* case 405: // Lower floor by line, dx = speed, dy = amount to lower
|
|
EV_DoFloor(line, lowerFloorByLine);
|
|
break;
|
|
|
|
case 406: // Raise floor by line, dx = speed, dy = amount to raise
|
|
EV_DoFloor(line, raiseFloorByLine);
|
|
break;
|
|
|
|
case 407: // Lower ceiling by line, dx = speed, dy = amount to lower
|
|
EV_DoCeiling(line, lowerCeilingByLine);
|
|
break;
|
|
|
|
case 408: // Raise ceiling by line, dx = speed, dy = amount to raise
|
|
EV_DoCeiling(line, raiseCeilingByLine);
|
|
break;*/
|
|
|
|
case 409: // Change tagged sectors' tag
|
|
// (formerly "Change calling sectors' tag", but behavior was changed)
|
|
{
|
|
while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
|
|
P_ChangeSectorTag(secnum,(INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
|
|
break;
|
|
}
|
|
|
|
case 410: // Change front sector's tag
|
|
P_ChangeSectorTag((UINT32)(line->frontsector - sectors), (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
|
|
break;
|
|
|
|
case 411: // Stop floor/ceiling movement in tagged sector(s)
|
|
while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
|
|
{
|
|
if (sectors[secnum].floordata)
|
|
{
|
|
if (sectors[secnum].floordata == sectors[secnum].ceilingdata) // elevator
|
|
{
|
|
P_RemoveThinker(&((elevator_t *)sectors[secnum].floordata)->thinker);
|
|
sectors[secnum].floordata = sectors[secnum].ceilingdata = NULL;
|
|
sectors[secnum].floorspeed = sectors[secnum].ceilspeed = 0;
|
|
}
|
|
else // floormove
|
|
{
|
|
P_RemoveThinker(&((floormove_t *)sectors[secnum].floordata)->thinker);
|
|
sectors[secnum].floordata = NULL;
|
|
sectors[secnum].floorspeed = 0;
|
|
}
|
|
}
|
|
|
|
if (sectors[secnum].ceilingdata) // ceiling
|
|
{
|
|
P_RemoveThinker(&((ceiling_t *)sectors[secnum].ceilingdata)->thinker);
|
|
sectors[secnum].ceilingdata = NULL;
|
|
sectors[secnum].ceilspeed = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 412: // Teleport the player or thing
|
|
{
|
|
mobj_t *dest;
|
|
|
|
if (!mo) // nothing to teleport
|
|
return;
|
|
|
|
if (line->flags & ML_EFFECT3) // Relative silent teleport
|
|
{
|
|
fixed_t x, y, z;
|
|
|
|
x = sides[line->sidenum[0]].textureoffset;
|
|
y = sides[line->sidenum[0]].rowoffset;
|
|
z = line->frontsector->ceilingheight;
|
|
|
|
P_UnsetThingPosition(mo);
|
|
mo->x += x;
|
|
mo->y += y;
|
|
mo->z += z;
|
|
P_SetThingPosition(mo);
|
|
|
|
if (mo->player)
|
|
{
|
|
if (bot) // This might put poor Tails in a wall if he's too far behind! D: But okay, whatever! >:3
|
|
P_TeleportMove(bot, bot->x + x, bot->y + y, bot->z + z);
|
|
if (splitscreen && mo->player == &players[secondarydisplayplayer] && camera2.chase)
|
|
{
|
|
camera2.x += x;
|
|
camera2.y += y;
|
|
camera2.z += z;
|
|
camera2.subsector = R_PointInSubsector(camera2.x, camera2.y);
|
|
}
|
|
else if (camera.chase && mo->player == &players[displayplayer])
|
|
{
|
|
camera.x += x;
|
|
camera.y += y;
|
|
camera.z += z;
|
|
camera.subsector = R_PointInSubsector(camera.x, camera.y);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((secnum = P_FindSectorFromLineTag(line, -1)) < 0)
|
|
return;
|
|
|
|
dest = P_GetObjectTypeInSectorNum(MT_TELEPORTMAN, secnum);
|
|
if (!dest)
|
|
return;
|
|
|
|
if (bot)
|
|
P_Teleport(bot, dest->x, dest->y, dest->z, (line->flags & ML_NOCLIMB) ? mo->angle : dest->angle, (line->flags & ML_BLOCKMONSTERS) == 0, (line->flags & ML_EFFECT4) == ML_EFFECT4);
|
|
if (line->flags & ML_BLOCKMONSTERS)
|
|
P_Teleport(mo, dest->x, dest->y, dest->z, (line->flags & ML_NOCLIMB) ? mo->angle : dest->angle, false, (line->flags & ML_EFFECT4) == ML_EFFECT4);
|
|
else
|
|
{
|
|
P_Teleport(mo, dest->x, dest->y, dest->z, (line->flags & ML_NOCLIMB) ? mo->angle : dest->angle, true, (line->flags & ML_EFFECT4) == ML_EFFECT4);
|
|
// Play the 'bowrwoosh!' sound
|
|
S_StartSound(dest, sfx_mixup);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 413: // Change music
|
|
// console player only unless NOCLIMB is set
|
|
if ((line->flags & ML_NOCLIMB) || (mo && mo->player && P_IsLocalPlayer(mo->player)))
|
|
{
|
|
UINT16 tracknum = (UINT16)sides[line->sidenum[0]].bottomtexture;
|
|
|
|
strncpy(mapmusname, sides[line->sidenum[0]].text, 7);
|
|
mapmusname[6] = 0;
|
|
|
|
mapmusflags = tracknum & MUSIC_TRACKMASK;
|
|
if (!(line->flags & ML_BLOCKMONSTERS))
|
|
mapmusflags |= MUSIC_RELOADRESET;
|
|
|
|
S_ChangeMusic(mapmusname, mapmusflags, !(line->flags & ML_EFFECT4));
|
|
|
|
// Except, you can use the ML_BLOCKMONSTERS flag to change this behavior.
|
|
// if (mapmusflags & MUSIC_RELOADRESET) then it will reset the music in G_PlayerReborn.
|
|
}
|
|
break;
|
|
|
|
case 414: // Play SFX
|
|
{
|
|
fixed_t sfxnum;
|
|
|
|
sfxnum = sides[line->sidenum[0]].toptexture; //P_AproxDistance(line->dx, line->dy)>>FRACBITS;
|
|
|
|
if (line->tag != 0 && line->flags & ML_EFFECT5)
|
|
{
|
|
sector_t *sec;
|
|
|
|
while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
|
|
{
|
|
sec = §ors[secnum];
|
|
S_StartSound(&sec->soundorg, sfxnum);
|
|
}
|
|
}
|
|
else if (line->tag != 0 && mo)
|
|
{
|
|
// Only trigger if mobj is touching the tag
|
|
ffloor_t *rover;
|
|
boolean foundit = false;
|
|
|
|
for(rover = mo->subsector->sector->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (rover->master->frontsector->tag != line->tag)
|
|
continue;
|
|
|
|
if (mo->z > *rover->topheight)
|
|
continue;
|
|
|
|
if (mo->z + mo->height < *rover->bottomheight)
|
|
continue;
|
|
|
|
foundit = true;
|
|
}
|
|
|
|
if (mo->subsector->sector->tag == line->tag)
|
|
foundit = true;
|
|
|
|
if (!foundit)
|
|
return;
|
|
}
|
|
|
|
if (sfxnum < NUMSFX && sfxnum > sfx_None)
|
|
{
|
|
if (line->flags & ML_NOCLIMB)
|
|
{
|
|
// play the sound from nowhere, but only if display player triggered it
|
|
if (mo && mo->player && (mo->player == &players[displayplayer] || mo->player == &players[secondarydisplayplayer]))
|
|
S_StartSound(NULL, sfxnum);
|
|
}
|
|
else if (line->flags & ML_EFFECT4)
|
|
{
|
|
// play the sound from nowhere
|
|
S_StartSound(NULL, sfxnum);
|
|
}
|
|
else if (line->flags & ML_BLOCKMONSTERS)
|
|
{
|
|
// play the sound from calling sector's soundorg
|
|
if (callsec)
|
|
S_StartSound(&callsec->soundorg, sfxnum);
|
|
else if (mo)
|
|
S_StartSound(&mo->subsector->sector->soundorg, sfxnum);
|
|
}
|
|
else if (mo)
|
|
{
|
|
// play the sound from mobj that triggered it
|
|
S_StartSound(mo, sfxnum);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 415: // Run a script
|
|
if (cv_runscripts.value)
|
|
{
|
|
INT32 scrnum;
|
|
lumpnum_t lumpnum;
|
|
char newname[9];
|
|
|
|
strcpy(newname, G_BuildMapName(gamemap));
|
|
newname[0] = 'S';
|
|
newname[1] = 'C';
|
|
newname[2] = 'R';
|
|
|
|
scrnum = sides[line->sidenum[0]].textureoffset>>FRACBITS;
|
|
if (scrnum < 0 || scrnum > 999)
|
|
{
|
|
scrnum = 0;
|
|
newname[5] = newname[6] = newname[7] = '0';
|
|
}
|
|
else
|
|
{
|
|
newname[5] = (char)('0' + (char)((scrnum/100)));
|
|
newname[6] = (char)('0' + (char)((scrnum%100)/10));
|
|
newname[7] = (char)('0' + (char)(scrnum%10));
|
|
}
|
|
newname[8] = '\0';
|
|
|
|
lumpnum = W_CheckNumForName(newname);
|
|
|
|
if (lumpnum == LUMPERROR || W_LumpLength(lumpnum) == 0)
|
|
{
|
|
CONS_Debug(DBG_SETUP, "SOC Error: script lump %s not found/not valid.\n", newname);
|
|
}
|
|
else
|
|
COM_BufInsertText(W_CacheLumpNum(lumpnum, PU_CACHE));
|
|
}
|
|
break;
|
|
|
|
case 416: // Spawn adjustable fire flicker
|
|
while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
|
|
{
|
|
if (line->flags & ML_NOCLIMB && line->backsector)
|
|
{
|
|
// Use front sector for min light level, back sector for max.
|
|
// This is tricky because P_SpawnAdjustableFireFlicker expects
|
|
// the maxsector (second argument) to also be the target
|
|
// sector, so we have to do some light level twiddling.
|
|
fireflicker_t *flick;
|
|
INT16 reallightlevel = sectors[secnum].lightlevel;
|
|
sectors[secnum].lightlevel = line->backsector->lightlevel;
|
|
|
|
flick = P_SpawnAdjustableFireFlicker(line->frontsector, §ors[secnum],
|
|
P_AproxDistance(line->dx, line->dy)>>FRACBITS);
|
|
|
|
// Make sure the starting light level is in range.
|
|
if (reallightlevel < flick->minlight)
|
|
reallightlevel = (INT16)flick->minlight;
|
|
else if (reallightlevel > flick->maxlight)
|
|
reallightlevel = (INT16)flick->maxlight;
|
|
|
|
sectors[secnum].lightlevel = reallightlevel;
|
|
}
|
|
else
|
|
{
|
|
// Use front sector for min, target sector for max,
|
|
// the same way linetype 61 does it.
|
|
P_SpawnAdjustableFireFlicker(line->frontsector, §ors[secnum],
|
|
P_AproxDistance(line->dx, line->dy)>>FRACBITS);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 417: // Spawn adjustable glowing light
|
|
while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
|
|
{
|
|
if (line->flags & ML_NOCLIMB && line->backsector)
|
|
{
|
|
// Use front sector for min light level, back sector for max.
|
|
// This is tricky because P_SpawnAdjustableGlowingLight expects
|
|
// the maxsector (second argument) to also be the target
|
|
// sector, so we have to do some light level twiddling.
|
|
glow_t *glow;
|
|
INT16 reallightlevel = sectors[secnum].lightlevel;
|
|
sectors[secnum].lightlevel = line->backsector->lightlevel;
|
|
|
|
glow = P_SpawnAdjustableGlowingLight(line->frontsector, §ors[secnum],
|
|
P_AproxDistance(line->dx, line->dy)>>FRACBITS);
|
|
|
|
// Make sure the starting light level is in range.
|
|
if (reallightlevel < glow->minlight)
|
|
reallightlevel = (INT16)glow->minlight;
|
|
else if (reallightlevel > glow->maxlight)
|
|
reallightlevel = (INT16)glow->maxlight;
|
|
|
|
sectors[secnum].lightlevel = reallightlevel;
|
|
}
|
|
else
|
|
{
|
|
// Use front sector for min, target sector for max,
|
|
// the same way linetype 602 does it.
|
|
P_SpawnAdjustableGlowingLight(line->frontsector, §ors[secnum],
|
|
P_AproxDistance(line->dx, line->dy)>>FRACBITS);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 418: // Spawn adjustable strobe flash (unsynchronized)
|
|
while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
|
|
{
|
|
if (line->flags & ML_NOCLIMB && line->backsector)
|
|
{
|
|
// Use front sector for min light level, back sector for max.
|
|
// This is tricky because P_SpawnAdjustableGlowingLight expects
|
|
// the maxsector (second argument) to also be the target
|
|
// sector, so we have to do some light level twiddling.
|
|
strobe_t *flash;
|
|
INT16 reallightlevel = sectors[secnum].lightlevel;
|
|
sectors[secnum].lightlevel = line->backsector->lightlevel;
|
|
|
|
flash = P_SpawnAdjustableStrobeFlash(line->frontsector, §ors[secnum],
|
|
abs(line->dx)>>FRACBITS, abs(line->dy)>>FRACBITS, false);
|
|
|
|
// Make sure the starting light level is in range.
|
|
if (reallightlevel < flash->minlight)
|
|
reallightlevel = (INT16)flash->minlight;
|
|
else if (reallightlevel > flash->maxlight)
|
|
reallightlevel = (INT16)flash->maxlight;
|
|
|
|
sectors[secnum].lightlevel = reallightlevel;
|
|
}
|
|
else
|
|
{
|
|
// Use front sector for min, target sector for max,
|
|
// the same way linetype 602 does it.
|
|
P_SpawnAdjustableStrobeFlash(line->frontsector, §ors[secnum],
|
|
abs(line->dx)>>FRACBITS, abs(line->dy)>>FRACBITS, false);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 419: // Spawn adjustable strobe flash (synchronized)
|
|
while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
|
|
{
|
|
if (line->flags & ML_NOCLIMB && line->backsector)
|
|
{
|
|
// Use front sector for min light level, back sector for max.
|
|
// This is tricky because P_SpawnAdjustableGlowingLight expects
|
|
// the maxsector (second argument) to also be the target
|
|
// sector, so we have to do some light level twiddling.
|
|
strobe_t *flash;
|
|
INT16 reallightlevel = sectors[secnum].lightlevel;
|
|
sectors[secnum].lightlevel = line->backsector->lightlevel;
|
|
|
|
flash = P_SpawnAdjustableStrobeFlash(line->frontsector, §ors[secnum],
|
|
abs(line->dx)>>FRACBITS, abs(line->dy)>>FRACBITS, true);
|
|
|
|
// Make sure the starting light level is in range.
|
|
if (reallightlevel < flash->minlight)
|
|
reallightlevel = (INT16)flash->minlight;
|
|
else if (reallightlevel > flash->maxlight)
|
|
reallightlevel = (INT16)flash->maxlight;
|
|
|
|
sectors[secnum].lightlevel = reallightlevel;
|
|
}
|
|
else
|
|
{
|
|
// Use front sector for min, target sector for max,
|
|
// the same way linetype 602 does it.
|
|
P_SpawnAdjustableStrobeFlash(line->frontsector, §ors[secnum],
|
|
abs(line->dx)>>FRACBITS, abs(line->dy)>>FRACBITS, true);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 420: // Fade light levels in tagged sectors to new value
|
|
P_FadeLight(line->tag, line->frontsector->lightlevel, P_AproxDistance(line->dx, line->dy)>>FRACBITS);
|
|
break;
|
|
|
|
case 421: // Stop lighting effect in tagged sectors
|
|
while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
|
|
if (sectors[secnum].lightingdata)
|
|
{
|
|
P_RemoveThinker(&((elevator_t *)sectors[secnum].lightingdata)->thinker);
|
|
sectors[secnum].lightingdata = NULL;
|
|
}
|
|
break;
|
|
|
|
case 422: // Cut away to another view
|
|
{
|
|
mobj_t *altview;
|
|
|
|
if (!mo || !mo->player) // only players have views
|
|
return;
|
|
|
|
if ((secnum = P_FindSectorFromLineTag(line, -1)) < 0)
|
|
return;
|
|
|
|
altview = P_GetObjectTypeInSectorNum(MT_ALTVIEWMAN, secnum);
|
|
if (!altview)
|
|
return;
|
|
|
|
P_SetTarget(&mo->player->awayviewmobj, altview);
|
|
mo->player->awayviewtics = P_AproxDistance(line->dx, line->dy)>>FRACBITS;
|
|
|
|
if (line->flags & ML_NOCLIMB) // lets you specify a vertical angle
|
|
{
|
|
INT32 aim;
|
|
|
|
aim = sides[line->sidenum[0]].textureoffset>>FRACBITS;
|
|
while (aim < 0)
|
|
aim += 360;
|
|
while (aim >= 360)
|
|
aim -= 360;
|
|
aim *= (ANGLE_90>>8);
|
|
aim /= 90;
|
|
aim <<= 8;
|
|
mo->player->awayviewaiming = (angle_t)aim;
|
|
}
|
|
else
|
|
mo->player->awayviewaiming = 0; // straight ahead
|
|
}
|
|
break;
|
|
|
|
case 423: // Change Sky
|
|
if ((mo && mo->player && P_IsLocalPlayer(mo->player)) || (line->flags & ML_NOCLIMB))
|
|
P_SetupLevelSky(sides[line->sidenum[0]].textureoffset>>FRACBITS, (line->flags & ML_NOCLIMB));
|
|
break;
|
|
|
|
case 424: // Change Weather
|
|
if (line->flags & ML_NOCLIMB)
|
|
{
|
|
globalweather = (UINT8)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
|
|
P_SwitchWeather(globalweather);
|
|
}
|
|
else if (mo && mo->player && P_IsLocalPlayer(mo->player))
|
|
P_SwitchWeather(sides[line->sidenum[0]].textureoffset>>FRACBITS);
|
|
break;
|
|
|
|
case 425: // Calls P_SetMobjState on calling mobj
|
|
if (mo && !mo->player)
|
|
P_SetMobjState(mo, sides[line->sidenum[0]].toptexture); //P_AproxDistance(line->dx, line->dy)>>FRACBITS);
|
|
break;
|
|
|
|
case 426: // Moves the mobj to its sector's soundorg and on the floor, and stops it
|
|
if (!mo)
|
|
return;
|
|
|
|
if (line->flags & ML_NOCLIMB)
|
|
{
|
|
P_UnsetThingPosition(mo);
|
|
mo->x = mo->subsector->sector->soundorg.x;
|
|
mo->y = mo->subsector->sector->soundorg.y;
|
|
mo->z = mo->floorz;
|
|
P_SetThingPosition(mo);
|
|
}
|
|
|
|
mo->momx = mo->momy = mo->momz = 1;
|
|
mo->pmomz = 0;
|
|
|
|
if (mo->player)
|
|
{
|
|
mo->player->rmomx = mo->player->rmomy = 1;
|
|
mo->player->cmomx = mo->player->cmomy = 0;
|
|
P_ResetPlayer(mo->player);
|
|
P_SetPlayerMobjState(mo, S_PLAY_STND);
|
|
|
|
// Reset bot too.
|
|
if (bot) {
|
|
if (line->flags & ML_NOCLIMB)
|
|
P_TeleportMove(bot, mo->x, mo->y, mo->z);
|
|
bot->momx = bot->momy = bot->momz = 1;
|
|
bot->pmomz = 0;
|
|
bot->player->rmomx = bot->player->rmomy = 1;
|
|
bot->player->cmomx = bot->player->cmomy = 0;
|
|
P_ResetPlayer(bot->player);
|
|
P_SetPlayerMobjState(bot, S_PLAY_STND);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 427: // Awards points if the mobj is a player
|
|
if (mo && mo->player)
|
|
P_AddPlayerScore(mo->player, sides[line->sidenum[0]].textureoffset>>FRACBITS);
|
|
break;
|
|
|
|
case 428: // Start floating platform movement
|
|
EV_DoElevator(line, elevateContinuous, true);
|
|
break;
|
|
|
|
case 429: // Crush Ceiling Down Once
|
|
EV_DoCrush(line, crushCeilOnce);
|
|
break;
|
|
|
|
case 430: // Crush Floor Up Once
|
|
EV_DoFloor(line, crushFloorOnce);
|
|
break;
|
|
|
|
case 431: // Crush Floor & Ceiling to middle Once
|
|
EV_DoCrush(line, crushBothOnce);
|
|
break;
|
|
|
|
case 432: // Enable 2D Mode (Disable if noclimb)
|
|
if (mo->player)
|
|
{
|
|
if (line->flags & ML_NOCLIMB)
|
|
mo->flags2 &= ~MF2_TWOD;
|
|
else
|
|
mo->flags2 |= MF2_TWOD;
|
|
|
|
// Copy effect to bot if necessary
|
|
// (Teleport them to you so they don't break it.)
|
|
if (bot && (bot->flags2 & MF2_TWOD) != (mo->flags2 & MF2_TWOD)) {
|
|
bot->flags2 = (bot->flags2 & ~MF2_TWOD) | (mo->flags2 & MF2_TWOD);
|
|
P_TeleportMove(bot, mo->x, mo->y, mo->z);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 433: // Flip gravity (Flop gravity if noclimb) Works on pushables, too!
|
|
if (line->flags & ML_NOCLIMB)
|
|
mo->flags2 &= ~MF2_OBJECTFLIP;
|
|
else
|
|
mo->flags2 |= MF2_OBJECTFLIP;
|
|
if (bot)
|
|
bot->flags2 = (bot->flags2 & ~MF2_OBJECTFLIP) | (mo->flags2 & MF2_OBJECTFLIP);
|
|
break;
|
|
|
|
case 434: // Custom Power
|
|
if (mo->player)
|
|
{
|
|
mobj_t *dummy = P_SpawnMobj(mo->x, mo->y, mo->z, MT_NULL);
|
|
|
|
var1 = sides[line->sidenum[0]].toptexture; //(line->dx>>FRACBITS)-1;
|
|
|
|
if (line->sidenum[1] != 0xffff && line->flags & ML_BLOCKMONSTERS) // read power from back sidedef
|
|
var2 = sides[line->sidenum[1]].toptexture;
|
|
else if (line->flags & ML_NOCLIMB) // 'Infinite'
|
|
var2 = UINT16_MAX;
|
|
else
|
|
var2 = sides[line->sidenum[0]].textureoffset>>FRACBITS;
|
|
|
|
P_SetTarget(&dummy->target, mo);
|
|
A_CustomPower(dummy);
|
|
|
|
if (bot) {
|
|
P_SetTarget(&dummy->target, bot);
|
|
A_CustomPower(dummy);
|
|
}
|
|
P_RemoveMobj(dummy);
|
|
}
|
|
break;
|
|
|
|
case 435: // Change scroller direction
|
|
{
|
|
scroll_t *scroller;
|
|
thinker_t *th;
|
|
|
|
for (th = thinkercap.next; th != &thinkercap; th = th->next)
|
|
{
|
|
if (th->function.acp1 != (actionf_p1)T_Scroll)
|
|
continue;
|
|
|
|
scroller = (scroll_t *)th;
|
|
|
|
if (sectors[scroller->affectee].tag != line->tag)
|
|
continue;
|
|
|
|
scroller->dx = FixedMul(line->dx>>SCROLL_SHIFT, CARRYFACTOR);
|
|
scroller->dy = FixedMul(line->dy>>SCROLL_SHIFT, CARRYFACTOR);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 436: // Shatter block remotely
|
|
{
|
|
INT16 sectag = (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
|
|
INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
|
|
sector_t *sec; // Sector that the FOF is visible in
|
|
ffloor_t *rover; // FOF that we are going to crumble
|
|
|
|
for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
|
|
{
|
|
sec = sectors + secnum;
|
|
|
|
if (!sec->ffloors)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 436 Executor: Target sector #%d has no FOFs.\n", secnum);
|
|
return;
|
|
}
|
|
|
|
for (rover = sec->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (rover->master->frontsector->tag == foftag)
|
|
break;
|
|
}
|
|
|
|
if (!rover)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 436 Executor: Can't find a FOF control sector with tag %d\n", foftag);
|
|
return;
|
|
}
|
|
|
|
EV_CrumbleChain(sec, rover);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 437: // Disable Player Controls
|
|
if (mo->player)
|
|
{
|
|
UINT16 fractime = (UINT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
|
|
if (fractime < 1)
|
|
fractime = 1; //instantly wears off upon leaving
|
|
if (line->flags & ML_NOCLIMB)
|
|
fractime |= 1<<15; //more crazy &ing, as if music stuff wasn't enough
|
|
mo->player->powers[pw_nocontrol] = fractime;
|
|
if (bot)
|
|
bot->player->powers[pw_nocontrol] = fractime;
|
|
}
|
|
break;
|
|
|
|
case 438: // Set player scale
|
|
if (mo)
|
|
{
|
|
mo->destscale = FixedDiv(P_AproxDistance(line->dx, line->dy), 100<<FRACBITS);
|
|
if (mo->destscale < FRACUNIT/100)
|
|
mo->destscale = FRACUNIT/100;
|
|
if (mo->player && bot)
|
|
bot->destscale = mo->destscale;
|
|
}
|
|
break;
|
|
|
|
case 439: // Set texture
|
|
{
|
|
size_t linenum;
|
|
side_t *set = &sides[line->sidenum[0]], *this;
|
|
boolean always = !(line->flags & ML_NOCLIMB); // If noclimb: Only change mid texture if mid texture already exists on tagged lines, etc.
|
|
for (linenum = 0; linenum < numlines; linenum++)
|
|
{
|
|
if (lines[linenum].special == 439)
|
|
continue; // Don't override other set texture lines!
|
|
|
|
if (lines[linenum].tag != line->tag)
|
|
continue; // Find tagged lines
|
|
|
|
// Front side
|
|
this = &sides[lines[linenum].sidenum[0]];
|
|
if (always || this->toptexture) this->toptexture = set->toptexture;
|
|
if (always || this->midtexture) this->midtexture = set->midtexture;
|
|
if (always || this->bottomtexture) this->bottomtexture = set->bottomtexture;
|
|
|
|
if (lines[linenum].sidenum[1] == 0xffff)
|
|
continue; // One-sided stops here.
|
|
|
|
// Back side
|
|
this = &sides[lines[linenum].sidenum[1]];
|
|
if (always || this->toptexture) this->toptexture = set->toptexture;
|
|
if (always || this->midtexture) this->midtexture = set->midtexture;
|
|
if (always || this->bottomtexture) this->bottomtexture = set->bottomtexture;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 440: // Play race countdown and start Metal Sonic
|
|
if (!metalrecording && !metalplayback)
|
|
G_DoPlayMetal();
|
|
break;
|
|
|
|
case 441: // Trigger unlockable
|
|
if ((!modifiedgame || savemoddata) && !(netgame || multiplayer))
|
|
{
|
|
INT32 trigid = (INT32)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
|
|
|
|
if (trigid < 0 || trigid > 31) // limited by 32 bit variable
|
|
CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %hu): bad trigger ID %d\n", line->sidenum[0], trigid);
|
|
else
|
|
{
|
|
unlocktriggers |= 1 << trigid;
|
|
|
|
// Unlocked something?
|
|
if (M_UpdateUnlockablesAndExtraEmblems())
|
|
{
|
|
S_StartSound(NULL, sfx_ncitem);
|
|
G_SaveGameData(); // only save if unlocked something
|
|
}
|
|
}
|
|
}
|
|
|
|
// Execute one time only
|
|
line->special = 0;
|
|
break;
|
|
|
|
case 442: // Calls P_SetMobjState on mobjs of a given type in the tagged sectors
|
|
{
|
|
const mobjtype_t type = (mobjtype_t)sides[line->sidenum[0]].toptexture;
|
|
statenum_t state = NUMSTATES;
|
|
sector_t *sec;
|
|
mobj_t *thing;
|
|
|
|
if (line->sidenum[1] != 0xffff)
|
|
state = (statenum_t)sides[line->sidenum[1]].toptexture;
|
|
|
|
while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
|
|
{
|
|
boolean tryagain;
|
|
sec = sectors + secnum;
|
|
do {
|
|
tryagain = false;
|
|
for (thing = sec->thinglist; thing; thing = thing->snext)
|
|
if (thing->type == type)
|
|
{
|
|
if (state != NUMSTATES)
|
|
{
|
|
if (!P_SetMobjState(thing, state)) // set state to specific state
|
|
{ // mobj was removed
|
|
tryagain = true; // snext is corrupt, we'll have to start over.
|
|
break;
|
|
}
|
|
}
|
|
else if (!P_SetMobjState(thing, thing->state->nextstate)) // set state to nextstate
|
|
{ // mobj was removed
|
|
tryagain = true; // snext is corrupt, we'll have to start over.
|
|
break;
|
|
}
|
|
}
|
|
} while (tryagain);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 443: // Calls a named Lua function
|
|
#ifdef HAVE_BLUA
|
|
LUAh_LinedefExecute(line, mo, callsec);
|
|
#else
|
|
CONS_Alert(CONS_ERROR, "The map is trying to run a Lua script, but this exe was not compiled with Lua support!\n");
|
|
#endif
|
|
break;
|
|
|
|
case 444: // Earthquake camera
|
|
{
|
|
quake.intensity = sides[line->sidenum[0]].textureoffset;
|
|
quake.radius = sides[line->sidenum[0]].rowoffset;
|
|
quake.time = P_AproxDistance(line->dx, line->dy)>>FRACBITS;
|
|
|
|
quake.epicenter = NULL; /// \todo
|
|
|
|
// reasonable defaults.
|
|
if (!quake.intensity)
|
|
quake.intensity = 8<<FRACBITS;
|
|
if (!quake.radius)
|
|
quake.radius = 512<<FRACBITS;
|
|
break;
|
|
}
|
|
|
|
case 445: // Force block disappear remotely (reappear if noclimb)
|
|
{
|
|
INT16 sectag = (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
|
|
INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
|
|
sector_t *sec; // Sector that the FOF is visible (or not visible) in
|
|
ffloor_t *rover; // FOF to vanish/un-vanish
|
|
ffloortype_e oldflags; // store FOF's old flags
|
|
|
|
for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
|
|
{
|
|
sec = sectors + secnum;
|
|
|
|
if (!sec->ffloors)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 445 Executor: Target sector #%d has no FOFs.\n", secnum);
|
|
return;
|
|
}
|
|
|
|
for (rover = sec->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (rover->master->frontsector->tag == foftag)
|
|
break;
|
|
}
|
|
|
|
if (!rover)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 445 Executor: Can't find a FOF control sector with tag %d\n", foftag);
|
|
return;
|
|
}
|
|
|
|
oldflags = rover->flags;
|
|
|
|
// Abracadabra!
|
|
if (line->flags & ML_NOCLIMB)
|
|
rover->flags |= FF_EXISTS;
|
|
else
|
|
rover->flags &= ~FF_EXISTS;
|
|
|
|
// if flags changed, reset sector's light list
|
|
if (rover->flags != oldflags)
|
|
sec->moved = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 450: // Execute Linedef Executor - for recursion
|
|
P_LinedefExecute(line->tag, mo, NULL);
|
|
break;
|
|
|
|
case 451: // Execute Random Linedef Executor
|
|
{
|
|
INT32 rvalue1 = sides[line->sidenum[0]].textureoffset>>FRACBITS;
|
|
INT32 rvalue2 = sides[line->sidenum[0]].rowoffset>>FRACBITS;
|
|
INT32 result;
|
|
|
|
if (rvalue1 <= rvalue2)
|
|
result = P_RandomRange(rvalue1, rvalue2);
|
|
else
|
|
result = P_RandomRange(rvalue2, rvalue1);
|
|
|
|
P_LinedefExecute((INT16)result, mo, NULL);
|
|
break;
|
|
}
|
|
|
|
#ifdef POLYOBJECTS
|
|
case 480: // Polyobj_DoorSlide
|
|
case 481: // Polyobj_DoorSwing
|
|
PolyDoor(line);
|
|
break;
|
|
case 482: // Polyobj_Move
|
|
case 483: // Polyobj_OR_Move
|
|
PolyMove(line);
|
|
break;
|
|
case 484: // Polyobj_RotateRight
|
|
case 485: // Polyobj_OR_RotateRight
|
|
case 486: // Polyobj_RotateLeft
|
|
case 487: // Polyobj_OR_RotateLeft
|
|
PolyRotate(line);
|
|
break;
|
|
case 488: // Polyobj_Waypoint
|
|
PolyWaypoint(line);
|
|
break;
|
|
case 489:
|
|
PolyInvisible(line);
|
|
break;
|
|
case 490:
|
|
PolyVisible(line);
|
|
break;
|
|
case 491:
|
|
PolyTranslucency(line);
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// P_SetupSignExit
|
|
//
|
|
// Finds the exit sign in the current sector and
|
|
// sets its target to the player who passed the map.
|
|
//
|
|
void P_SetupSignExit(player_t *player)
|
|
{
|
|
mobj_t *thing;
|
|
msecnode_t *node = player->mo->subsector->sector->touching_thinglist; // things touching this sector
|
|
thinker_t *think;
|
|
INT32 numfound = 0;
|
|
|
|
for (; node; node = node->m_snext)
|
|
{
|
|
thing = node->m_thing;
|
|
if (thing->type != MT_SIGN)
|
|
continue;
|
|
|
|
if (thing->state != &states[thing->info->spawnstate])
|
|
continue;
|
|
|
|
P_SetTarget(&thing->target, player->mo);
|
|
P_SetMobjState(thing, S_SIGN1);
|
|
if (thing->info->seesound)
|
|
S_StartSound(thing, thing->info->seesound);
|
|
|
|
++numfound;
|
|
}
|
|
|
|
if (numfound)
|
|
return;
|
|
|
|
// didn't find any signposts in the exit sector.
|
|
// spin all signposts in the level then.
|
|
for (think = thinkercap.next; think != &thinkercap; think = think->next)
|
|
{
|
|
if (think->function.acp1 != (actionf_p1)P_MobjThinker)
|
|
continue; // not a mobj thinker
|
|
|
|
thing = (mobj_t *)think;
|
|
if (thing->type != MT_SIGN)
|
|
continue;
|
|
|
|
if (thing->state != &states[thing->info->spawnstate])
|
|
continue;
|
|
|
|
P_SetTarget(&thing->target, player->mo);
|
|
P_SetMobjState(thing, S_SIGN1);
|
|
if (thing->info->seesound)
|
|
S_StartSound(thing, thing->info->seesound);
|
|
|
|
++numfound;
|
|
}
|
|
}
|
|
|
|
//
|
|
// P_IsFlagAtBase
|
|
//
|
|
// Checks to see if a flag is at its base.
|
|
//
|
|
boolean P_IsFlagAtBase(mobjtype_t flag)
|
|
{
|
|
thinker_t *think;
|
|
mobj_t *mo;
|
|
INT32 specialnum = 0;
|
|
|
|
for (think = thinkercap.next; think != &thinkercap; think = think->next)
|
|
{
|
|
if (think->function.acp1 != (actionf_p1)P_MobjThinker)
|
|
continue; // not a mobj thinker
|
|
|
|
mo = (mobj_t *)think;
|
|
|
|
if (mo->type != flag)
|
|
continue;
|
|
|
|
if (mo->type == MT_REDFLAG)
|
|
specialnum = 3;
|
|
else if (mo->type == MT_BLUEFLAG)
|
|
specialnum = 4;
|
|
|
|
if (GETSECSPECIAL(mo->subsector->sector->special, 4) == specialnum)
|
|
return true;
|
|
else if (mo->subsector->sector->ffloors) // Check the 3D floors
|
|
{
|
|
ffloor_t *rover;
|
|
|
|
for (rover = mo->subsector->sector->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (!(rover->flags & FF_EXISTS))
|
|
continue;
|
|
|
|
if (GETSECSPECIAL(rover->master->frontsector->special, 4) != specialnum)
|
|
continue;
|
|
|
|
if (mo->z <= *rover->topheight
|
|
&& mo->z >= *rover->bottomheight)
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// P_PlayerTouchingSectorSpecial
|
|
//
|
|
// Replaces the old player->specialsector.
|
|
// This allows a player to touch more than
|
|
// one sector at a time, if necessary.
|
|
//
|
|
// Returns a pointer to the first sector of
|
|
// the particular type that it finds.
|
|
// Returns NULL if it doesn't find it.
|
|
//
|
|
sector_t *P_PlayerTouchingSectorSpecial(player_t *player, INT32 section, INT32 number)
|
|
{
|
|
msecnode_t *node;
|
|
ffloor_t *rover;
|
|
|
|
if (!player->mo)
|
|
return NULL;
|
|
|
|
// Check default case first
|
|
if (GETSECSPECIAL(player->mo->subsector->sector->special, section) == number)
|
|
return player->mo->subsector->sector;
|
|
|
|
// Hmm.. maybe there's a FOF that has it...
|
|
for (rover = player->mo->subsector->sector->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (GETSECSPECIAL(rover->master->frontsector->special, section) != number)
|
|
continue;
|
|
|
|
if (!(rover->flags & FF_EXISTS))
|
|
continue;
|
|
|
|
// Check the 3D floor's type...
|
|
if (rover->flags & FF_BLOCKPLAYER)
|
|
{
|
|
// Thing must be on top of the floor to be affected...
|
|
if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR)
|
|
&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING))
|
|
{
|
|
if ((player->mo->eflags & MFE_VERTICALFLIP) || player->mo->z != *rover->topheight)
|
|
continue;
|
|
}
|
|
else if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING)
|
|
&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR))
|
|
{
|
|
if (!(player->mo->eflags & MFE_VERTICALFLIP)
|
|
|| player->mo->z + player->mo->height != *rover->bottomheight)
|
|
continue;
|
|
}
|
|
else if (rover->master->frontsector->flags & SF_FLIPSPECIAL_BOTH)
|
|
{
|
|
if (!((player->mo->eflags & MFE_VERTICALFLIP && player->mo->z + player->mo->height == *rover->bottomheight)
|
|
|| (!(player->mo->eflags & MFE_VERTICALFLIP) && player->mo->z == *rover->topheight)))
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Water and DEATH FOG!!! heh
|
|
if (player->mo->z > *rover->topheight || (player->mo->z + player->mo->height) < *rover->bottomheight)
|
|
continue;
|
|
}
|
|
|
|
// This FOF has the special we're looking for!
|
|
return rover->master->frontsector;
|
|
}
|
|
|
|
for (node = player->mo->touching_sectorlist; node; node = node->m_snext)
|
|
{
|
|
if (GETSECSPECIAL(node->m_sector->special, section) == number)
|
|
{
|
|
// This sector has the special we're looking for, but
|
|
// are we allowed to touch it?
|
|
if (node->m_sector == player->mo->subsector->sector
|
|
|| (node->m_sector->flags & SF_TRIGGERSPECIAL_TOUCH))
|
|
return node->m_sector;
|
|
}
|
|
|
|
// Hmm.. maybe there's a FOF that has it...
|
|
for (rover = node->m_sector->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (GETSECSPECIAL(rover->master->frontsector->special, section) != number)
|
|
continue;
|
|
|
|
if (!(rover->flags & FF_EXISTS))
|
|
continue;
|
|
|
|
// Check the 3D floor's type...
|
|
if (rover->flags & FF_BLOCKPLAYER)
|
|
{
|
|
// Thing must be on top of the floor to be affected...
|
|
if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR)
|
|
&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING))
|
|
{
|
|
if ((player->mo->eflags & MFE_VERTICALFLIP) || player->mo->z != *rover->topheight)
|
|
continue;
|
|
}
|
|
else if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING)
|
|
&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR))
|
|
{
|
|
if (!(player->mo->eflags & MFE_VERTICALFLIP)
|
|
|| player->mo->z + player->mo->height != *rover->bottomheight)
|
|
continue;
|
|
}
|
|
else if (rover->master->frontsector->flags & SF_FLIPSPECIAL_BOTH)
|
|
{
|
|
if (!((player->mo->eflags & MFE_VERTICALFLIP && player->mo->z + player->mo->height == *rover->bottomheight)
|
|
|| (!(player->mo->eflags & MFE_VERTICALFLIP) && player->mo->z == *rover->topheight)))
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Water and DEATH FOG!!! heh
|
|
if (player->mo->z > *rover->topheight || (player->mo->z + player->mo->height) < *rover->bottomheight)
|
|
continue;
|
|
}
|
|
|
|
// This FOF has the special we're looking for, but are we allowed to touch it?
|
|
if (node->m_sector == player->mo->subsector->sector
|
|
|| (rover->master->frontsector->flags & SF_TRIGGERSPECIAL_TOUCH))
|
|
return rover->master->frontsector;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// P_ThingIsOnThe3DFloor
|
|
//
|
|
// This checks whether the mobj is on/in the FOF we want it to be at
|
|
// Needed for the "All players" trigger sector specials only
|
|
//
|
|
static boolean P_ThingIsOnThe3DFloor(mobj_t *mo, sector_t *sector, sector_t *targetsec)
|
|
{
|
|
ffloor_t *rover;
|
|
fixed_t top, bottom;
|
|
|
|
if (!mo->player) // should NEVER happen
|
|
return false;
|
|
|
|
if (!targetsec->ffloors) // also should NEVER happen
|
|
return false;
|
|
|
|
for (rover = targetsec->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (rover->master->frontsector != sector)
|
|
continue;
|
|
|
|
// we're assuming the FOF existed when the first player touched it
|
|
//if (!(rover->flags & FF_EXISTS))
|
|
// return false;
|
|
|
|
top = P_GetSpecialTopZ(mo, sector, targetsec);
|
|
bottom = P_GetSpecialBottomZ(mo, sector, targetsec);
|
|
|
|
// Check the 3D floor's type...
|
|
if (rover->flags & FF_BLOCKPLAYER)
|
|
{
|
|
// Thing must be on top of the floor to be affected...
|
|
if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR)
|
|
&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING))
|
|
{
|
|
if ((mo->eflags & MFE_VERTICALFLIP) || mo->z != top)
|
|
return false;
|
|
}
|
|
else if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING)
|
|
&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR))
|
|
{
|
|
if (!(mo->eflags & MFE_VERTICALFLIP)
|
|
|| mo->z + mo->height != bottom)
|
|
return false;
|
|
}
|
|
else if (rover->master->frontsector->flags & SF_FLIPSPECIAL_BOTH)
|
|
{
|
|
if (!((mo->eflags & MFE_VERTICALFLIP && mo->z + mo->height == bottom)
|
|
|| (!(mo->eflags & MFE_VERTICALFLIP) && mo->z == top)))
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Water and intangible FOFs
|
|
if (mo->z > top || (mo->z + mo->height) < bottom)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// P_MobjReadyToTrigger
|
|
//
|
|
// Is player standing on the sector's "ground"?
|
|
//
|
|
static inline boolean P_MobjReadyToTrigger(mobj_t *mo, sector_t *sec)
|
|
{
|
|
if (mo->eflags & MFE_VERTICALFLIP)
|
|
return (mo->z+mo->height == P_GetSpecialTopZ(mo, sec, sec) && sec->flags & SF_FLIPSPECIAL_CEILING);
|
|
else
|
|
return (mo->z == P_GetSpecialBottomZ(mo, sec, sec) && sec->flags & SF_FLIPSPECIAL_FLOOR);
|
|
}
|
|
|
|
/** Applies a sector special to a player.
|
|
*
|
|
* \param player Player in the sector.
|
|
* \param sector Sector with the special.
|
|
* \param roversector If !NULL, sector is actually an FOF; otherwise, sector
|
|
* is being physically contacted by the player.
|
|
* \todo Split up into multiple functions.
|
|
* \sa P_PlayerInSpecialSector, P_PlayerOnSpecial3DFloor
|
|
*/
|
|
void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *roversector)
|
|
{
|
|
INT32 i = 0;
|
|
INT32 section1, section2, section3, section4;
|
|
INT32 special;
|
|
|
|
section1 = GETSECSPECIAL(sector->special, 1);
|
|
section2 = GETSECSPECIAL(sector->special, 2);
|
|
section3 = GETSECSPECIAL(sector->special, 3);
|
|
section4 = GETSECSPECIAL(sector->special, 4);
|
|
|
|
// Ignore spectators
|
|
if (player->spectator)
|
|
return;
|
|
|
|
// Ignore dead players.
|
|
// If this strange phenomenon could be potentially used in levels,
|
|
// TODO: modify this to accommodate for it.
|
|
if (player->playerstate == PST_DEAD)
|
|
return;
|
|
|
|
// Conveyor stuff
|
|
if (section3 == 2 || section3 == 4)
|
|
player->onconveyor = section3;
|
|
|
|
special = section1;
|
|
|
|
// Process Section 1
|
|
switch (special)
|
|
{
|
|
case 1: // Damage (Generic)
|
|
if (roversector || P_MobjReadyToTrigger(player->mo, sector))
|
|
P_DamageMobj(player->mo, NULL, NULL, 1);
|
|
break;
|
|
case 2: // Damage (Water)
|
|
if ((roversector || P_MobjReadyToTrigger(player->mo, sector)) && (player->powers[pw_underwater] || player->pflags & PF_NIGHTSMODE) && (player->powers[pw_shield] & SH_NOSTACK) != SH_ELEMENTAL)
|
|
P_DamageMobj(player->mo, NULL, NULL, 1);
|
|
break;
|
|
case 3: // Damage (Fire)
|
|
if ((roversector || P_MobjReadyToTrigger(player->mo, sector)) && (player->powers[pw_shield] & SH_NOSTACK) != SH_ELEMENTAL)
|
|
P_DamageMobj(player->mo, NULL, NULL, 1);
|
|
break;
|
|
case 4: // Damage (Electrical)
|
|
if ((roversector || P_MobjReadyToTrigger(player->mo, sector)) && (player->powers[pw_shield] & SH_NOSTACK) != SH_ATTRACT)
|
|
P_DamageMobj(player->mo, NULL, NULL, 1);
|
|
break;
|
|
case 5: // Spikes
|
|
// Don't do anything. In Soviet Russia, spikes find you.
|
|
break;
|
|
case 6: // Death Pit (Camera Mod)
|
|
case 7: // Death Pit (No Camera Mod)
|
|
if (roversector || P_MobjReadyToTrigger(player->mo, sector))
|
|
P_DamageMobj(player->mo, NULL, NULL, 10000);
|
|
break;
|
|
case 8: // Instant Kill
|
|
P_DamageMobj(player->mo, NULL, NULL, 10000);
|
|
break;
|
|
case 9: // Ring Drainer (Floor Touch)
|
|
case 10: // Ring Drainer (No Floor Touch)
|
|
if (leveltime % (TICRATE/2) == 0 && player->mo->health > 1)
|
|
{
|
|
player->mo->health--;
|
|
player->health--;
|
|
S_StartSound(player->mo, sfx_itemup);
|
|
}
|
|
break;
|
|
case 11: // Special Stage Damage - Kind of like a mini-P_DamageMobj()
|
|
if (player->powers[pw_invulnerability] || player->powers[pw_flashing] || player->powers[pw_super] || player->exiting || player->bot)
|
|
break;
|
|
|
|
if (!(player->powers[pw_shield] || player->mo->health > 1)) // Don't do anything if no shield or rings anyway
|
|
break;
|
|
|
|
if (player->powers[pw_shield])
|
|
{
|
|
P_RemoveShield(player);
|
|
S_StartSound(player->mo, sfx_shldls); // Ba-Dum! Shield loss.
|
|
}
|
|
else if (player->mo->health > 1)
|
|
{
|
|
P_PlayRinglossSound(player->mo);
|
|
if (player->mo->health > 10)
|
|
player->mo->health -= 10;
|
|
else
|
|
player->mo->health = 1;
|
|
player->health = player->mo->health;
|
|
}
|
|
|
|
P_DoPlayerPain(player, NULL, NULL); // this does basically everything that was here before
|
|
|
|
if (gametype == GT_CTF && player->gotflag & (GF_REDFLAG|GF_BLUEFLAG))
|
|
P_PlayerFlagBurst(player, false);
|
|
break;
|
|
case 12: // Space Countdown
|
|
if ((player->powers[pw_shield] & SH_NOSTACK) != SH_ELEMENTAL && !player->powers[pw_spacetime])
|
|
player->powers[pw_spacetime] = spacetimetics + 1;
|
|
break;
|
|
case 13: // Ramp Sector (Increase step-up/down)
|
|
case 14: // Non-Ramp Sector (Don't step-down)
|
|
case 15: // Bouncy Sector (FOF Control Only)
|
|
break;
|
|
}
|
|
|
|
special = section2;
|
|
|
|
// Process Section 2
|
|
switch (special)
|
|
{
|
|
case 1: // Trigger Linedef Exec (Pushable Objects)
|
|
break;
|
|
case 2: // Linedef executor requires all players present+doesn't require touching floor
|
|
case 3: // Linedef executor requires all players present
|
|
/// \todo check continues for proper splitscreen support?
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
if (playeringame[i] && !players[i].bot && players[i].mo && (gametype != GT_COOP || players[i].lives > 0))
|
|
{
|
|
if (roversector)
|
|
{
|
|
if (players[i].mo->subsector->sector != roversector)
|
|
goto DoneSection2;
|
|
if (!P_ThingIsOnThe3DFloor(players[i].mo, sector, roversector))
|
|
goto DoneSection2;
|
|
}
|
|
else
|
|
{
|
|
if (players[i].mo->subsector->sector != sector)
|
|
goto DoneSection2;
|
|
|
|
if (special == 3 && !P_MobjReadyToTrigger(players[i].mo, sector))
|
|
goto DoneSection2;
|
|
}
|
|
}
|
|
case 4: // Linedef executor that doesn't require touching floor
|
|
case 5: // Linedef executor
|
|
case 6: // Linedef executor (7 Emeralds)
|
|
case 7: // Linedef executor (NiGHTS Mare)
|
|
if (!player->bot)
|
|
P_LinedefExecute(sector->tag, player->mo, sector);
|
|
break;
|
|
case 8: // Tells pushable things to check FOFs
|
|
break;
|
|
case 9: // Egg trap capsule
|
|
{
|
|
thinker_t *th;
|
|
mobj_t *mo2;
|
|
line_t junk;
|
|
|
|
if (player->bot || sector->ceilingdata || sector->floordata)
|
|
return;
|
|
|
|
// Find the center of the Eggtrap and release all the pretty animals!
|
|
// The chimps are my friends.. heeheeheheehehee..... - LouisJM
|
|
for (th = thinkercap.next; th != &thinkercap; th = th->next)
|
|
{
|
|
if (th->function.acp1 != (actionf_p1)P_MobjThinker)
|
|
continue;
|
|
|
|
mo2 = (mobj_t *)th;
|
|
if (mo2->type == MT_EGGTRAP)
|
|
P_KillMobj(mo2, NULL, player->mo);
|
|
}
|
|
|
|
// clear the special so you can't push the button twice.
|
|
sector->special = 0;
|
|
|
|
// Move the button down
|
|
junk.tag = 680;
|
|
EV_DoElevator(&junk, elevateDown, false);
|
|
|
|
// Open the top FOF
|
|
junk.tag = 681;
|
|
EV_DoFloor(&junk, raiseFloorToNearestFast);
|
|
// Open the bottom FOF
|
|
junk.tag = 682;
|
|
EV_DoCeiling(&junk, lowerToLowestFast);
|
|
|
|
// Mark all players with the time to exit thingy!
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
P_DoPlayerExit(&players[i]);
|
|
break;
|
|
}
|
|
case 10: // Special Stage Time/Rings
|
|
case 11: // Custom Gravity
|
|
break;
|
|
case 12: // Lua sector special
|
|
break;
|
|
}
|
|
DoneSection2:
|
|
|
|
special = section3;
|
|
|
|
// Process Section 3
|
|
switch (special)
|
|
{
|
|
case 1: // Ice/Sludge
|
|
case 2: // Wind/Current
|
|
case 3: // Ice/Sludge and Wind/Current
|
|
case 4: // Conveyor Belt
|
|
break;
|
|
|
|
case 5: // Speed pad w/o spin
|
|
case 6: // Speed pad w/ spin
|
|
if (player->powers[pw_flashing] != 0 && player->powers[pw_flashing] < TICRATE/2)
|
|
break;
|
|
|
|
i = P_FindSpecialLineFromTag(4, sector->tag, -1);
|
|
|
|
if (i != -1)
|
|
{
|
|
angle_t lineangle;
|
|
fixed_t linespeed;
|
|
|
|
lineangle = R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y);
|
|
linespeed = P_AproxDistance(lines[i].v2->x-lines[i].v1->x, lines[i].v2->y-lines[i].v1->y);
|
|
|
|
player->mo->angle = lineangle;
|
|
|
|
if (player == &players[consoleplayer])
|
|
localangle = player->mo->angle;
|
|
else if (player == &players[secondarydisplayplayer])
|
|
localangle2 = player->mo->angle;
|
|
|
|
if (!(lines[i].flags & ML_EFFECT4))
|
|
{
|
|
P_UnsetThingPosition(player->mo);
|
|
if (roversector) // make FOF speed pads work
|
|
{
|
|
player->mo->x = roversector->soundorg.x;
|
|
player->mo->y = roversector->soundorg.y;
|
|
}
|
|
else
|
|
{
|
|
player->mo->x = sector->soundorg.x;
|
|
player->mo->y = sector->soundorg.y;
|
|
}
|
|
P_SetThingPosition(player->mo);
|
|
}
|
|
|
|
P_InstaThrust(player->mo, player->mo->angle, linespeed);
|
|
|
|
if (GETSECSPECIAL(sector->special, 3) == 6 && (player->charability2 == CA2_SPINDASH))
|
|
{
|
|
if (!(player->pflags & PF_SPINNING))
|
|
player->pflags |= PF_SPINNING;
|
|
|
|
P_SetPlayerMobjState(player->mo, S_PLAY_ATK1);
|
|
}
|
|
|
|
player->powers[pw_flashing] = TICRATE/3;
|
|
S_StartSound(player->mo, sfx_spdpad);
|
|
}
|
|
break;
|
|
|
|
case 7: // Bustable block sprite parameter
|
|
case 8:
|
|
case 9:
|
|
case 10:
|
|
case 11:
|
|
case 12:
|
|
case 13:
|
|
case 14:
|
|
case 15:
|
|
break;
|
|
}
|
|
|
|
special = section4;
|
|
|
|
// Process Section 4
|
|
switch (special)
|
|
{
|
|
case 1: // Starpost Activator
|
|
{
|
|
mobj_t *post = P_GetObjectTypeInSectorNum(MT_STARPOST, sector - sectors);
|
|
|
|
if (!post)
|
|
break;
|
|
|
|
P_TouchSpecialThing(post, player->mo, false);
|
|
break;
|
|
}
|
|
|
|
case 2: // Special stage GOAL sector / Exit Sector / CTF Flag Return
|
|
if (player->bot)
|
|
break;
|
|
if (!useNightsSS && G_IsSpecialStage(gamemap) && sstimer > 6)
|
|
sstimer = 6; // Just let P_Ticker take care of the rest.
|
|
|
|
// Exit (for FOF exits; others are handled in P_PlayerThink in p_user.c)
|
|
{
|
|
INT32 lineindex;
|
|
|
|
P_DoPlayerExit(player);
|
|
|
|
P_SetupSignExit(player);
|
|
// important: use sector->tag on next line instead of player->mo->subsector->tag
|
|
// this part is different from in P_PlayerThink, this is what was causing
|
|
// FOF custom exits not to work.
|
|
lineindex = P_FindSpecialLineFromTag(2, sector->tag, -1);
|
|
|
|
if (gametype == GT_COOP && lineindex != -1) // Custom exit!
|
|
{
|
|
// Special goodies with the block monsters flag depending on emeralds collected
|
|
if ((lines[lineindex].flags & ML_BLOCKMONSTERS) && ALL7EMERALDS(emeralds))
|
|
nextmapoverride = (INT16)(lines[lineindex].frontsector->ceilingheight>>FRACBITS);
|
|
else
|
|
nextmapoverride = (INT16)(lines[lineindex].frontsector->floorheight>>FRACBITS);
|
|
|
|
if (lines[lineindex].flags & ML_NOCLIMB)
|
|
skipstats = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 3: // Red Team's Base
|
|
if (gametype == GT_CTF && P_IsObjectOnGround(player->mo))
|
|
{
|
|
if (player->ctfteam == 1 && (player->gotflag & GF_BLUEFLAG))
|
|
{
|
|
mobj_t *mo;
|
|
|
|
// Make sure the red team still has their own
|
|
// flag at their base so they can score.
|
|
if (!P_IsFlagAtBase(MT_REDFLAG))
|
|
break;
|
|
|
|
HU_SetCEchoFlags(0);
|
|
HU_SetCEchoDuration(5);
|
|
HU_DoCEcho(va(M_GetText("%s\\captured the blue flag.\\\\\\\\"), player_names[player-players]));
|
|
|
|
if (splitscreen || players[consoleplayer].ctfteam == 1)
|
|
S_StartSound(NULL, sfx_flgcap);
|
|
else if (players[consoleplayer].ctfteam == 2)
|
|
S_StartSound(NULL, sfx_lose);
|
|
|
|
mo = P_SpawnMobj(player->mo->x,player->mo->y,player->mo->z,MT_BLUEFLAG);
|
|
player->gotflag &= ~GF_BLUEFLAG;
|
|
mo->flags &= ~MF_SPECIAL;
|
|
mo->fuse = TICRATE;
|
|
mo->spawnpoint = bflagpoint;
|
|
mo->flags2 |= MF2_JUSTATTACKED;
|
|
redscore += 1;
|
|
P_AddPlayerScore(player, 250);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 4: // Blue Team's Base
|
|
if (gametype == GT_CTF && P_IsObjectOnGround(player->mo))
|
|
{
|
|
if (player->ctfteam == 2 && (player->gotflag & GF_REDFLAG))
|
|
{
|
|
mobj_t *mo;
|
|
|
|
// Make sure the blue team still has their own
|
|
// flag at their base so they can score.
|
|
if (!P_IsFlagAtBase(MT_BLUEFLAG))
|
|
break;
|
|
|
|
HU_SetCEchoFlags(0);
|
|
HU_SetCEchoDuration(5);
|
|
HU_DoCEcho(va(M_GetText("%s\\captured the red flag.\\\\\\\\"), player_names[player-players]));
|
|
|
|
if (splitscreen || players[consoleplayer].ctfteam == 2)
|
|
S_StartSound(NULL, sfx_flgcap);
|
|
else if (players[consoleplayer].ctfteam == 1)
|
|
S_StartSound(NULL, sfx_lose);
|
|
|
|
mo = P_SpawnMobj(player->mo->x,player->mo->y,player->mo->z,MT_REDFLAG);
|
|
player->gotflag &= ~GF_REDFLAG;
|
|
mo->flags &= ~MF_SPECIAL;
|
|
mo->fuse = TICRATE;
|
|
mo->spawnpoint = rflagpoint;
|
|
mo->flags2 |= MF2_JUSTATTACKED;
|
|
bluescore += 1;
|
|
P_AddPlayerScore(player, 250);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 5: // Fan sector
|
|
player->mo->momz += mobjinfo[MT_FAN].mass/4;
|
|
|
|
if (player->mo->momz > mobjinfo[MT_FAN].mass)
|
|
player->mo->momz = mobjinfo[MT_FAN].mass;
|
|
|
|
P_ResetPlayer(player);
|
|
if (player->panim != PA_FALL)
|
|
P_SetPlayerMobjState(player->mo, S_PLAY_FALL1);
|
|
break;
|
|
|
|
case 6: // Super Sonic transformer
|
|
if (player->mo->health > 0 && !player->bot && (player->charflags & SF_SUPER) && !player->powers[pw_super] && ALL7EMERALDS(emeralds))
|
|
P_DoSuperTransformation(player, true);
|
|
break;
|
|
|
|
case 7: // Make player spin
|
|
if (!(player->pflags & PF_SPINNING) && P_IsObjectOnGround(player->mo) && (player->charability2 == CA2_SPINDASH))
|
|
{
|
|
player->pflags |= PF_SPINNING;
|
|
P_SetPlayerMobjState(player->mo, S_PLAY_ATK1);
|
|
S_StartAttackSound(player->mo, sfx_spin);
|
|
|
|
if (abs(player->rmomx) < FixedMul(5*FRACUNIT, player->mo->scale)
|
|
&& abs(player->rmomy) < FixedMul(5*FRACUNIT, player->mo->scale))
|
|
P_InstaThrust(player->mo, player->mo->angle, FixedMul(10*FRACUNIT, player->mo->scale));
|
|
}
|
|
break;
|
|
|
|
case 8: // Zoom Tube Start
|
|
{
|
|
INT32 sequence;
|
|
fixed_t speed;
|
|
INT32 lineindex;
|
|
thinker_t *th;
|
|
mobj_t *waypoint = NULL;
|
|
mobj_t *mo2;
|
|
angle_t an;
|
|
|
|
if (player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT)
|
|
break;
|
|
|
|
// Find line #3 tagged to this sector
|
|
lineindex = P_FindSpecialLineFromTag(3, sector->tag, -1);
|
|
|
|
if (lineindex == -1)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "ERROR: Sector special %d missing line special #3.\n", sector->special);
|
|
break;
|
|
}
|
|
|
|
// Grab speed and sequence values
|
|
speed = abs(lines[lineindex].dx)/8;
|
|
sequence = abs(lines[lineindex].dy)>>FRACBITS;
|
|
|
|
// scan the thinkers
|
|
// to find the first waypoint
|
|
for (th = thinkercap.next; th != &thinkercap; th = th->next)
|
|
{
|
|
if (th->function.acp1 != (actionf_p1)P_MobjThinker)
|
|
continue;
|
|
|
|
mo2 = (mobj_t *)th;
|
|
|
|
if (mo2->type == MT_TUBEWAYPOINT && mo2->threshold == sequence
|
|
&& mo2->health == 0)
|
|
{
|
|
waypoint = mo2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!waypoint)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "ERROR: FIRST WAYPOINT IN SEQUENCE %d NOT FOUND.\n", sequence);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Waypoint %d found in sequence %d - speed = %d\n", waypoint->health, sequence, speed);
|
|
}
|
|
|
|
an = R_PointToAngle2(player->mo->x, player->mo->y, waypoint->x, waypoint->y) - player->mo->angle;
|
|
|
|
if (an > ANGLE_90 && an < ANGLE_270 && !(lines[lineindex].flags & ML_EFFECT4))
|
|
break; // behind back
|
|
|
|
P_SetTarget(&player->mo->tracer, waypoint);
|
|
player->speed = speed;
|
|
player->pflags |= PF_SPINNING;
|
|
player->pflags &= ~PF_JUMPED;
|
|
player->pflags &= ~PF_GLIDING;
|
|
player->climbing = 0;
|
|
|
|
if (!(player->mo->state >= &states[S_PLAY_ATK1] && player->mo->state <= &states[S_PLAY_ATK4]))
|
|
{
|
|
P_SetPlayerMobjState(player->mo, S_PLAY_ATK1);
|
|
S_StartSound(player->mo, sfx_spin);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 9: // Zoom Tube End
|
|
{
|
|
INT32 sequence;
|
|
fixed_t speed;
|
|
INT32 lineindex;
|
|
thinker_t *th;
|
|
mobj_t *waypoint = NULL;
|
|
mobj_t *mo2;
|
|
angle_t an;
|
|
|
|
if (player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT)
|
|
break;
|
|
|
|
// Find line #3 tagged to this sector
|
|
lineindex = P_FindSpecialLineFromTag(3, sector->tag, -1);
|
|
|
|
if (lineindex == -1)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "ERROR: Sector special %d missing line special #3.\n", sector->special);
|
|
break;
|
|
}
|
|
|
|
// Grab speed and sequence values
|
|
speed = -(abs(lines[lineindex].dx)/8); // Negative means reverse
|
|
sequence = abs(lines[lineindex].dy)>>FRACBITS;
|
|
|
|
// scan the thinkers
|
|
// to find the last waypoint
|
|
for (th = thinkercap.next; th != &thinkercap; th = th->next)
|
|
{
|
|
if (th->function.acp1 != (actionf_p1)P_MobjThinker)
|
|
continue;
|
|
|
|
mo2 = (mobj_t *)th;
|
|
|
|
if (mo2->type == MT_TUBEWAYPOINT && mo2->threshold == sequence)
|
|
{
|
|
if (!waypoint)
|
|
waypoint = mo2;
|
|
else if (mo2->health > waypoint->health)
|
|
waypoint = mo2;
|
|
}
|
|
}
|
|
|
|
if (!waypoint)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "ERROR: LAST WAYPOINT IN SEQUENCE %d NOT FOUND.\n", sequence);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Waypoint %d found in sequence %d - speed = %d\n", waypoint->health, sequence, speed);
|
|
}
|
|
|
|
an = R_PointToAngle2(player->mo->x, player->mo->y, waypoint->x, waypoint->y) - player->mo->angle;
|
|
|
|
if (an > ANGLE_90 && an < ANGLE_270 && !(lines[lineindex].flags & ML_EFFECT4))
|
|
break; // behind back
|
|
|
|
P_SetTarget(&player->mo->tracer, waypoint);
|
|
player->speed = speed;
|
|
player->pflags |= PF_SPINNING;
|
|
player->pflags &= ~PF_JUMPED;
|
|
|
|
if (!(player->mo->state >= &states[S_PLAY_ATK1] && player->mo->state <= &states[S_PLAY_ATK4]))
|
|
{
|
|
P_SetPlayerMobjState(player->mo, S_PLAY_ATK1);
|
|
S_StartSound(player->mo, sfx_spin);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 10: // Finish Line
|
|
if (gametype == GT_RACE && !player->exiting)
|
|
{
|
|
if (player->starpostnum == numstarposts) // Must have touched all the starposts
|
|
{
|
|
player->laps++;
|
|
|
|
if (player->pflags & PF_NIGHTSMODE)
|
|
player->drillmeter += 48*20;
|
|
|
|
if (player->laps >= (UINT8)cv_numlaps.value)
|
|
CONS_Printf(M_GetText("%s has finished the race.\n"), player_names[player-players]);
|
|
else
|
|
CONS_Printf(M_GetText("%s started lap %u\n"), player_names[player-players], (UINT32)player->laps+1);
|
|
|
|
// Reset starposts (checkpoints) info
|
|
player->starpostangle = player->starposttime = player->starpostnum = 0;
|
|
player->starpostx = player->starposty = player->starpostz = 0;
|
|
P_ResetStarposts();
|
|
|
|
// Play the starpost sound for 'consistency'
|
|
S_StartSound(player->mo, sfx_strpst);
|
|
}
|
|
else if (player->starpostnum)
|
|
{
|
|
// blatant reuse of a variable that's normally unused in circuit
|
|
if (!player->tossdelay)
|
|
S_StartSound(player->mo, sfx_lose);
|
|
player->tossdelay = 3;
|
|
}
|
|
|
|
if (player->laps >= (unsigned)cv_numlaps.value)
|
|
{
|
|
if (P_IsLocalPlayer(player))
|
|
{
|
|
HU_SetCEchoFlags(0);
|
|
HU_SetCEchoDuration(5);
|
|
HU_DoCEcho("FINISHED!");
|
|
}
|
|
|
|
P_DoPlayerExit(player);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 11: // Rope hang
|
|
{
|
|
INT32 sequence;
|
|
fixed_t speed;
|
|
INT32 lineindex;
|
|
thinker_t *th;
|
|
mobj_t *waypointmid = NULL;
|
|
mobj_t *waypointhigh = NULL;
|
|
mobj_t *waypointlow = NULL;
|
|
mobj_t *mo2;
|
|
mobj_t *closest = NULL;
|
|
line_t junk;
|
|
vertex_t v1, v2, resulthigh, resultlow;
|
|
mobj_t *highest = NULL;
|
|
|
|
if (player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT)
|
|
break;
|
|
|
|
if (player->mo->momz > 0)
|
|
break;
|
|
|
|
if (player->cmd.buttons & BT_USE)
|
|
break;
|
|
|
|
if (!(player->pflags & PF_SLIDING) && player->mo->state == &states[player->mo->info->painstate])
|
|
break;
|
|
|
|
if (player->exiting)
|
|
break;
|
|
|
|
//initialize resulthigh and resultlow with 0
|
|
memset(&resultlow, 0x00, sizeof(resultlow));
|
|
memset(&resulthigh, 0x00, sizeof(resulthigh));
|
|
|
|
// Find line #11 tagged to this sector
|
|
lineindex = P_FindSpecialLineFromTag(11, sector->tag, -1);
|
|
|
|
if (lineindex == -1)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "ERROR: Sector special %d missing line special #11.\n", sector->special);
|
|
break;
|
|
}
|
|
|
|
// Grab speed and sequence values
|
|
speed = abs(lines[lineindex].dx)/8;
|
|
sequence = abs(lines[lineindex].dy)>>FRACBITS;
|
|
|
|
// Find the closest waypoint
|
|
// Find the preceding waypoint
|
|
// Find the proceeding waypoint
|
|
// Determine the closest spot on the line between the three waypoints
|
|
// Put player at that location.
|
|
|
|
// scan the thinkers
|
|
// to find the first waypoint
|
|
for (th = thinkercap.next; th != &thinkercap; th = th->next)
|
|
{
|
|
if (th->function.acp1 != (actionf_p1)P_MobjThinker)
|
|
continue;
|
|
|
|
mo2 = (mobj_t *)th;
|
|
|
|
if (mo2->type != MT_TUBEWAYPOINT)
|
|
continue;
|
|
|
|
if (mo2->threshold != sequence)
|
|
continue;
|
|
|
|
if (!highest)
|
|
highest = mo2;
|
|
else if (mo2->health > highest->health) // Find the highest waypoint # in case we wrap
|
|
highest = mo2;
|
|
|
|
if (closest && P_AproxDistance(P_AproxDistance(player->mo->x-mo2->x, player->mo->y-mo2->y),
|
|
player->mo->z-mo2->z) > P_AproxDistance(P_AproxDistance(player->mo->x-closest->x,
|
|
player->mo->y-closest->y), player->mo->z-closest->z))
|
|
continue;
|
|
|
|
// Found a target
|
|
closest = mo2;
|
|
}
|
|
|
|
waypointmid = closest;
|
|
|
|
closest = NULL;
|
|
|
|
if (waypointmid == NULL)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "ERROR: WAYPOINT(S) IN SEQUENCE %d NOT FOUND.\n", sequence);
|
|
break;
|
|
}
|
|
|
|
// Find waypoint before this one (waypointlow)
|
|
for (th = thinkercap.next; th != &thinkercap; th = th->next)
|
|
{
|
|
if (th->function.acp1 != (actionf_p1)P_MobjThinker)
|
|
continue;
|
|
|
|
mo2 = (mobj_t *)th;
|
|
|
|
if (mo2->type != MT_TUBEWAYPOINT)
|
|
continue;
|
|
|
|
if (mo2->threshold != sequence)
|
|
continue;
|
|
|
|
if (waypointmid->health == 0)
|
|
{
|
|
if (mo2->health != highest->health)
|
|
continue;
|
|
}
|
|
else if (mo2->health != waypointmid->health - 1)
|
|
continue;
|
|
|
|
// Found a target
|
|
waypointlow = mo2;
|
|
break;
|
|
}
|
|
|
|
// Find waypoint after this one (waypointhigh)
|
|
for (th = thinkercap.next; th != &thinkercap; th = th->next)
|
|
{
|
|
if (th->function.acp1 != (actionf_p1)P_MobjThinker)
|
|
continue;
|
|
|
|
mo2 = (mobj_t *)th;
|
|
|
|
if (mo2->type != MT_TUBEWAYPOINT)
|
|
continue;
|
|
|
|
if (mo2->threshold != sequence)
|
|
continue;
|
|
|
|
if (waypointmid->health == highest->health)
|
|
{
|
|
if (mo2->health != 0)
|
|
continue;
|
|
}
|
|
else if (mo2->health != waypointmid->health + 1)
|
|
continue;
|
|
|
|
// Found a target
|
|
waypointhigh = mo2;
|
|
break;
|
|
}
|
|
|
|
CONS_Debug(DBG_GAMELOGIC, "WaypointMid: %d; WaypointLow: %d; WaypointHigh: %d\n",
|
|
waypointmid->health, waypointlow ? waypointlow->health : -1, waypointhigh ? waypointhigh->health : -1);
|
|
|
|
// Now we have three waypoints... the closest one we're near, and the one that comes before, and after.
|
|
// Next, we need to find the closest point on the line between each set, and determine which one we're
|
|
// closest to.
|
|
|
|
// Waypointmid and Waypointlow:
|
|
if (waypointlow)
|
|
{
|
|
v1.x = waypointmid->x;
|
|
v1.y = waypointmid->y;
|
|
v1.z = waypointmid->z;
|
|
v2.x = waypointlow->x;
|
|
v2.y = waypointlow->y;
|
|
v2.z = waypointlow->z;
|
|
junk.v1 = &v1;
|
|
junk.v2 = &v2;
|
|
junk.dx = v2.x - v1.x;
|
|
junk.dy = v2.y - v1.y;
|
|
|
|
P_ClosestPointOnLine3D(player->mo->x, player->mo->y, player->mo->z, &junk, &resultlow);
|
|
}
|
|
|
|
// Waypointmid and Waypointhigh:
|
|
if (waypointhigh)
|
|
{
|
|
v1.x = waypointmid->x;
|
|
v1.y = waypointmid->y;
|
|
v1.z = waypointmid->z;
|
|
v2.x = waypointhigh->x;
|
|
v2.y = waypointhigh->y;
|
|
v2.z = waypointhigh->z;
|
|
junk.v1 = &v1;
|
|
junk.v2 = &v2;
|
|
junk.dx = v2.x - v1.x;
|
|
junk.dy = v2.y - v1.y;
|
|
|
|
P_ClosestPointOnLine3D(player->mo->x, player->mo->y, player->mo->z, &junk, &resulthigh);
|
|
}
|
|
|
|
// 3D support now available. Disregard the previous notice here. -Red
|
|
|
|
P_UnsetThingPosition(player->mo);
|
|
P_ResetPlayer(player);
|
|
player->mo->momx = player->mo->momy = player->mo->momz = 0;
|
|
|
|
if (lines[lineindex].flags & ML_EFFECT1) // Don't wrap
|
|
{
|
|
highest->flags |= MF_SLIDEME;
|
|
}
|
|
|
|
// Changing the conditions on these ifs to fix issues with snapping to the wrong spot -Red
|
|
if ((lines[lineindex].flags & ML_EFFECT1) && waypointmid->health == 0)
|
|
{
|
|
closest = waypointhigh;
|
|
player->mo->x = resulthigh.x;
|
|
player->mo->y = resulthigh.y;
|
|
player->mo->z = resulthigh.z - P_GetPlayerHeight(player);
|
|
}
|
|
else if ((lines[lineindex].flags & ML_EFFECT1) && waypointmid->health == highest->health)
|
|
{
|
|
closest = waypointmid;
|
|
player->mo->x = resultlow.x;
|
|
player->mo->y = resultlow.y;
|
|
player->mo->z = resultlow.z - P_GetPlayerHeight(player);
|
|
}
|
|
else
|
|
{
|
|
if (P_AproxDistance(P_AproxDistance(player->mo->x-resultlow.x, player->mo->y-resultlow.y),
|
|
player->mo->z-resultlow.z) < P_AproxDistance(P_AproxDistance(player->mo->x-resulthigh.x,
|
|
player->mo->y-resulthigh.y), player->mo->z-resulthigh.z))
|
|
{
|
|
// Line between Mid and Low is closer
|
|
closest = waypointmid;
|
|
player->mo->x = resultlow.x;
|
|
player->mo->y = resultlow.y;
|
|
player->mo->z = resultlow.z - P_GetPlayerHeight(player);
|
|
}
|
|
else
|
|
{
|
|
// Line between Mid and High is closer
|
|
closest = waypointhigh;
|
|
player->mo->x = resulthigh.x;
|
|
player->mo->y = resulthigh.y;
|
|
player->mo->z = resulthigh.z - P_GetPlayerHeight(player);
|
|
}
|
|
}
|
|
|
|
P_SetTarget(&player->mo->tracer, closest);
|
|
|
|
// Option for static ropes.
|
|
if (lines[lineindex].flags & ML_NOCLIMB)
|
|
player->speed = 0;
|
|
else
|
|
player->speed = speed;
|
|
|
|
player->pflags |= PF_ROPEHANG;
|
|
|
|
S_StartSound(player->mo, sfx_s3k4a);
|
|
|
|
player->pflags &= ~PF_JUMPED;
|
|
player->pflags &= ~PF_GLIDING;
|
|
player->pflags &= ~PF_SLIDING;
|
|
player->climbing = 0;
|
|
P_SetThingPosition(player->mo);
|
|
P_SetPlayerMobjState(player->mo, S_PLAY_CARRY);
|
|
}
|
|
break;
|
|
case 12: // Camera noclip
|
|
case 13: // Unused
|
|
case 14: // Unused
|
|
case 15: // Unused
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** Checks if an object is standing on or is inside a special 3D floor.
|
|
* If so, the sector is returned.
|
|
*
|
|
* \param mo Object to check.
|
|
* \return Pointer to the sector with a special type, or NULL if no special 3D
|
|
* floors are being contacted.
|
|
* \sa P_PlayerOnSpecial3DFloor
|
|
*/
|
|
sector_t *P_ThingOnSpecial3DFloor(mobj_t *mo)
|
|
{
|
|
sector_t *sector;
|
|
ffloor_t *rover;
|
|
|
|
sector = mo->subsector->sector;
|
|
if (!sector->ffloors)
|
|
return NULL;
|
|
|
|
for (rover = sector->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (!rover->master->frontsector->special)
|
|
continue;
|
|
|
|
if (!(rover->flags & FF_EXISTS))
|
|
continue;
|
|
|
|
// Check the 3D floor's type...
|
|
if (((rover->flags & FF_BLOCKPLAYER) && mo->player)
|
|
|| ((rover->flags & FF_BLOCKOTHERS) && !mo->player))
|
|
{
|
|
// Thing must be on top of the floor to be affected...
|
|
if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR)
|
|
&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING))
|
|
{
|
|
if ((mo->eflags & MFE_VERTICALFLIP) || mo->z != *rover->topheight)
|
|
continue;
|
|
}
|
|
else if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING)
|
|
&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR))
|
|
{
|
|
if (!(mo->eflags & MFE_VERTICALFLIP)
|
|
|| mo->z + mo->height != *rover->bottomheight)
|
|
continue;
|
|
}
|
|
else if (rover->master->frontsector->flags & SF_FLIPSPECIAL_BOTH)
|
|
{
|
|
if (!((mo->eflags & MFE_VERTICALFLIP && mo->z + mo->height == *rover->bottomheight)
|
|
|| (!(mo->eflags & MFE_VERTICALFLIP) && mo->z == *rover->topheight)))
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Water and intangible FOFs
|
|
if (mo->z > *rover->topheight || (mo->z + mo->height) < *rover->bottomheight)
|
|
continue;
|
|
}
|
|
|
|
return rover->master->frontsector;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/** Checks if a player is standing on or is inside a 3D floor (e.g. water) and
|
|
* applies any specials.
|
|
*
|
|
* \param player Player to check.
|
|
* \sa P_ThingOnSpecial3DFloor, P_PlayerInSpecialSector
|
|
*/
|
|
static void P_PlayerOnSpecial3DFloor(player_t *player, sector_t *sector)
|
|
{
|
|
ffloor_t *rover;
|
|
|
|
for (rover = sector->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (!rover->master->frontsector->special)
|
|
continue;
|
|
|
|
if (!(rover->flags & FF_EXISTS))
|
|
continue;
|
|
|
|
// Check the 3D floor's type...
|
|
if (rover->flags & FF_BLOCKPLAYER)
|
|
{
|
|
// Thing must be on top of the floor to be affected...
|
|
if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR)
|
|
&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING))
|
|
{
|
|
if ((player->mo->eflags & MFE_VERTICALFLIP) || player->mo->z != P_GetSpecialTopZ(player->mo, sectors + rover->secnum, sector))
|
|
continue;
|
|
}
|
|
else if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING)
|
|
&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR))
|
|
{
|
|
if (!(player->mo->eflags & MFE_VERTICALFLIP)
|
|
|| player->mo->z + player->mo->height != P_GetSpecialBottomZ(player->mo, sectors + rover->secnum, sector))
|
|
continue;
|
|
}
|
|
else if (rover->master->frontsector->flags & SF_FLIPSPECIAL_BOTH)
|
|
{
|
|
if (!((player->mo->eflags & MFE_VERTICALFLIP && player->mo->z + player->mo->height == P_GetSpecialBottomZ(player->mo, sectors + rover->secnum, sector))
|
|
|| (!(player->mo->eflags & MFE_VERTICALFLIP) && player->mo->z == P_GetSpecialTopZ(player->mo, sectors + rover->secnum, sector))))
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Water and DEATH FOG!!! heh
|
|
if (player->mo->z > P_GetSpecialTopZ(player->mo, sectors + rover->secnum, sector) || (player->mo->z + player->mo->height) < P_GetSpecialBottomZ(player->mo, sectors + rover->secnum, sector))
|
|
continue;
|
|
}
|
|
|
|
// This FOF has the special we're looking for, but are we allowed to touch it?
|
|
if (sector == player->mo->subsector->sector
|
|
|| (rover->master->frontsector->flags & SF_TRIGGERSPECIAL_TOUCH))
|
|
P_ProcessSpecialSector(player, rover->master->frontsector, sector);
|
|
}
|
|
|
|
// Allow sector specials to be applied to polyobjects!
|
|
if (player->mo->subsector->polyList)
|
|
{
|
|
polyobj_t *po = player->mo->subsector->polyList;
|
|
sector_t *polysec;
|
|
boolean touching = false;
|
|
boolean inside = false;
|
|
|
|
while(po)
|
|
{
|
|
if (po->flags & POF_NOSPECIALS)
|
|
{
|
|
po = (polyobj_t *)(po->link.next);
|
|
continue;
|
|
}
|
|
|
|
polysec = po->lines[0]->backsector;
|
|
|
|
if ((polysec->flags & SF_TRIGGERSPECIAL_TOUCH))
|
|
touching = P_MobjTouchingPolyobj(po, player->mo);
|
|
else
|
|
touching = false;
|
|
|
|
inside = P_MobjInsidePolyobj(po, player->mo);
|
|
|
|
if (!(inside || touching))
|
|
{
|
|
po = (polyobj_t *)(po->link.next);
|
|
continue;
|
|
}
|
|
|
|
// We're inside it! Yess...
|
|
if (!polysec->special)
|
|
{
|
|
po = (polyobj_t *)(po->link.next);
|
|
continue;
|
|
}
|
|
|
|
if (!(po->flags & POF_TESTHEIGHT)) // Don't do height checking
|
|
{
|
|
}
|
|
else if (po->flags & POF_SOLID)
|
|
{
|
|
// Thing must be on top of the floor to be affected...
|
|
if ((polysec->flags & SF_FLIPSPECIAL_FLOOR)
|
|
&& !(polysec->flags & SF_FLIPSPECIAL_CEILING))
|
|
{
|
|
if ((player->mo->eflags & MFE_VERTICALFLIP) || player->mo->z != polysec->ceilingheight)
|
|
{
|
|
po = (polyobj_t *)(po->link.next);
|
|
continue;
|
|
}
|
|
}
|
|
else if ((polysec->flags & SF_FLIPSPECIAL_CEILING)
|
|
&& !(polysec->flags & SF_FLIPSPECIAL_FLOOR))
|
|
{
|
|
if (!(player->mo->eflags & MFE_VERTICALFLIP)
|
|
|| player->mo->z + player->mo->height != polysec->floorheight)
|
|
{
|
|
po = (polyobj_t *)(po->link.next);
|
|
continue;
|
|
}
|
|
}
|
|
else if (polysec->flags & SF_FLIPSPECIAL_BOTH)
|
|
{
|
|
if (!((player->mo->eflags & MFE_VERTICALFLIP && player->mo->z + player->mo->height == polysec->floorheight)
|
|
|| (!(player->mo->eflags & MFE_VERTICALFLIP) && player->mo->z == polysec->ceilingheight)))
|
|
{
|
|
po = (polyobj_t *)(po->link.next);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Water and DEATH FOG!!! heh
|
|
if (player->mo->z > polysec->ceilingheight || (player->mo->z + player->mo->height) < polysec->floorheight)
|
|
{
|
|
po = (polyobj_t *)(po->link.next);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
P_ProcessSpecialSector(player, polysec, sector);
|
|
|
|
po = (polyobj_t *)(po->link.next);
|
|
}
|
|
}
|
|
}
|
|
|
|
#define VDOORSPEED (FRACUNIT*2)
|
|
|
|
//
|
|
// P_RunSpecialSectorCheck
|
|
//
|
|
// Helper function to P_PlayerInSpecialSector
|
|
//
|
|
static void P_RunSpecialSectorCheck(player_t *player, sector_t *sector)
|
|
{
|
|
boolean nofloorneeded = false;
|
|
fixed_t f_affectpoint, c_affectpoint;
|
|
|
|
if (!sector->special) // nothing special, exit
|
|
return;
|
|
|
|
if (GETSECSPECIAL(sector->special, 2) == 9) // Egg trap capsule -- should only be for 3dFloors!
|
|
return;
|
|
|
|
// The list of specials that activate without floor touch
|
|
// Check Section 1
|
|
switch(GETSECSPECIAL(sector->special, 1))
|
|
{
|
|
case 2: // Damage (water)
|
|
case 8: // Instant kill
|
|
case 10: // Ring drainer that doesn't require floor touch
|
|
case 12: // Space countdown
|
|
nofloorneeded = true;
|
|
break;
|
|
}
|
|
|
|
// Check Section 2
|
|
switch(GETSECSPECIAL(sector->special, 2))
|
|
{
|
|
case 2: // Linedef executor (All players needed)
|
|
case 4: // Linedef executor
|
|
case 6: // Linedef executor (7 Emeralds)
|
|
case 7: // Linedef executor (NiGHTS Mare)
|
|
nofloorneeded = true;
|
|
break;
|
|
}
|
|
|
|
// Check Section 3
|
|
/* switch(GETSECSPECIAL(sector->special, 3))
|
|
{
|
|
|
|
}*/
|
|
|
|
// Check Section 4
|
|
switch(GETSECSPECIAL(sector->special, 4))
|
|
{
|
|
case 2: // Level Exit / GOAL Sector / Flag Return
|
|
if (!useNightsSS && G_IsSpecialStage(gamemap))
|
|
{
|
|
// Special stage GOAL sector
|
|
// requires touching floor.
|
|
break;
|
|
}
|
|
case 1: // Starpost activator
|
|
case 5: // Fan sector
|
|
case 6: // Super Sonic Transform
|
|
case 8: // Zoom Tube Start
|
|
case 9: // Zoom Tube End
|
|
case 10: // Finish line
|
|
nofloorneeded = true;
|
|
break;
|
|
}
|
|
|
|
if (nofloorneeded)
|
|
{
|
|
P_ProcessSpecialSector(player, sector, NULL);
|
|
return;
|
|
}
|
|
|
|
f_affectpoint = P_GetSpecialBottomZ(player->mo, sector, sector);
|
|
c_affectpoint = P_GetSpecialTopZ(player->mo, sector, sector);
|
|
|
|
// Only go further if on the ground
|
|
if ((sector->flags & SF_FLIPSPECIAL_FLOOR) && !(sector->flags & SF_FLIPSPECIAL_CEILING) && player->mo->z != f_affectpoint)
|
|
return;
|
|
|
|
if ((sector->flags & SF_FLIPSPECIAL_CEILING) && !(sector->flags & SF_FLIPSPECIAL_FLOOR) && player->mo->z + player->mo->height != c_affectpoint)
|
|
return;
|
|
|
|
if ((sector->flags & SF_FLIPSPECIAL_BOTH)
|
|
&& player->mo->z != f_affectpoint
|
|
&& player->mo->z + player->mo->height != c_affectpoint)
|
|
return;
|
|
|
|
P_ProcessSpecialSector(player, sector, NULL);
|
|
}
|
|
|
|
/** Checks if the player is in a special sector or FOF and apply any specials.
|
|
*
|
|
* \param player Player to check.
|
|
* \sa P_PlayerOnSpecial3DFloor, P_ProcessSpecialSector
|
|
*/
|
|
void P_PlayerInSpecialSector(player_t *player)
|
|
{
|
|
sector_t *sector;
|
|
msecnode_t *node;
|
|
|
|
if (!player->mo)
|
|
return;
|
|
|
|
// Do your ->subsector->sector first
|
|
sector = player->mo->subsector->sector;
|
|
P_PlayerOnSpecial3DFloor(player, sector);
|
|
// After P_PlayerOnSpecial3DFloor, recheck if the player is in that sector,
|
|
// because the player can be teleported in between these times.
|
|
if (sector == player->mo->subsector->sector)
|
|
P_RunSpecialSectorCheck(player, sector);
|
|
|
|
// Iterate through touching_sectorlist
|
|
for (node = player->mo->touching_sectorlist; node; node = node->m_snext)
|
|
{
|
|
sector = node->m_sector;
|
|
|
|
if (sector == player->mo->subsector->sector) // Don't duplicate
|
|
continue;
|
|
|
|
// Check 3D floors...
|
|
P_PlayerOnSpecial3DFloor(player, sector);
|
|
|
|
if (!(sector->flags & SF_TRIGGERSPECIAL_TOUCH))
|
|
return;
|
|
// After P_PlayerOnSpecial3DFloor, recheck if the player is in that sector,
|
|
// because the player can be teleported in between these times.
|
|
if (sector == player->mo->subsector->sector)
|
|
P_RunSpecialSectorCheck(player, sector);
|
|
}
|
|
}
|
|
|
|
/** Animate planes, scroll walls, etc. and keeps track of level timelimit and exits if time is up.
|
|
*
|
|
* \sa P_CheckTimeLimit, P_CheckPointLimit
|
|
*/
|
|
void P_UpdateSpecials(void)
|
|
{
|
|
anim_t *anim;
|
|
INT32 i;
|
|
INT32 pic;
|
|
size_t j;
|
|
|
|
levelflat_t *foundflats; // for flat animation
|
|
|
|
// LEVEL TIMER
|
|
P_CheckTimeLimit();
|
|
|
|
// POINT LIMIT
|
|
P_CheckPointLimit();
|
|
|
|
#ifdef ESLOPE
|
|
// Dynamic slopeness
|
|
P_RunDynamicSlopes();
|
|
#endif
|
|
|
|
// ANIMATE TEXTURES
|
|
for (anim = anims; anim < lastanim; anim++)
|
|
{
|
|
for (i = 0; i < anim->numpics; i++)
|
|
{
|
|
pic = anim->basepic + ((leveltime/anim->speed + i) % anim->numpics);
|
|
if (anim->istexture)
|
|
texturetranslation[anim->basepic+i] = pic;
|
|
}
|
|
}
|
|
|
|
// ANIMATE FLATS
|
|
/// \todo do not check the non-animate flat.. link the animated ones?
|
|
/// \note its faster than the original anywaysince it animates only
|
|
/// flats used in the level, and there's usually very few of them
|
|
foundflats = levelflats;
|
|
for (j = 0; j < numlevelflats; j++, foundflats++)
|
|
{
|
|
if (foundflats->speed) // it is an animated flat
|
|
{
|
|
// update the levelflat lump number
|
|
foundflats->lumpnum = foundflats->baselumpnum +
|
|
((leveltime/foundflats->speed + foundflats->animseq) % foundflats->numpics);
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline ffloor_t *P_GetFFloorBySec(sector_t *sec, sector_t *sec2)
|
|
{
|
|
ffloor_t *rover;
|
|
|
|
if (!sec->ffloors)
|
|
return NULL;
|
|
for (rover = sec->ffloors; rover; rover = rover->next)
|
|
if (rover->secnum == (size_t)(sec2 - sectors))
|
|
return rover;
|
|
return NULL;
|
|
}
|
|
|
|
/** Adds a newly formed 3Dfloor structure to a sector's ffloors list.
|
|
*
|
|
* \param sec Target sector.
|
|
* \param ffloor Newly formed 3Dfloor structure.
|
|
* \sa P_AddFakeFloor
|
|
*/
|
|
static inline void P_AddFFloorToList(sector_t *sec, ffloor_t *ffloor)
|
|
{
|
|
ffloor_t *rover;
|
|
|
|
if (!sec->ffloors)
|
|
{
|
|
sec->ffloors = ffloor;
|
|
ffloor->next = 0;
|
|
ffloor->prev = 0;
|
|
return;
|
|
}
|
|
|
|
for (rover = sec->ffloors; rover->next; rover = rover->next);
|
|
|
|
rover->next = ffloor;
|
|
ffloor->prev = rover;
|
|
ffloor->next = 0;
|
|
}
|
|
|
|
/** Adds a 3Dfloor.
|
|
*
|
|
* \param sec Target sector.
|
|
* \param sec2 Control sector.
|
|
* \param master Control linedef.
|
|
* \param flags Options affecting this 3Dfloor.
|
|
* \param secthinkers List of relevant thinkers sorted by sector. May be NULL.
|
|
* \return Pointer to the new 3Dfloor.
|
|
* \sa P_AddFFloor, P_AddFakeFloorsByLine, P_SpawnSpecials
|
|
*/
|
|
static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, ffloortype_e flags, thinkerlist_t *secthinkers)
|
|
{
|
|
ffloor_t *ffloor;
|
|
thinker_t *th;
|
|
friction_t *f;
|
|
pusher_t *p;
|
|
levelspecthink_t *lst;
|
|
size_t sec2num;
|
|
size_t i;
|
|
|
|
if (sec == sec2)
|
|
return NULL; //Don't need a fake floor on a control sector.
|
|
if ((ffloor = (P_GetFFloorBySec(sec, sec2))))
|
|
return ffloor; // If this ffloor already exists, return it
|
|
|
|
if (sec2->ceilingheight < sec2->floorheight)
|
|
{
|
|
fixed_t tempceiling = sec2->ceilingheight;
|
|
//flip the sector around and print an error instead of crashing 12.1.08 -Inuyasha
|
|
CONS_Alert(CONS_ERROR, M_GetText("A FOF tagged %d has a top height below its bottom.\n"), master->tag);
|
|
sec2->ceilingheight = sec2->floorheight;
|
|
sec2->floorheight = tempceiling;
|
|
}
|
|
|
|
sec2->tagline = master;
|
|
|
|
if (sec2->numattached == 0)
|
|
{
|
|
sec2->attached = Z_Malloc(sizeof (*sec2->attached) * sec2->maxattached, PU_STATIC, NULL);
|
|
sec2->attachedsolid = Z_Malloc(sizeof (*sec2->attachedsolid) * sec2->maxattached, PU_STATIC, NULL);
|
|
sec2->attached[0] = sec - sectors;
|
|
sec2->numattached = 1;
|
|
sec2->attachedsolid[0] = (flags & FF_SOLID);
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < sec2->numattached; i++)
|
|
if (sec2->attached[i] == (size_t)(sec - sectors))
|
|
return NULL;
|
|
|
|
if (sec2->numattached >= sec2->maxattached)
|
|
{
|
|
sec2->maxattached *= 2;
|
|
sec2->attached = Z_Realloc(sec2->attached, sizeof (*sec2->attached) * sec2->maxattached, PU_STATIC, NULL);
|
|
sec2->attachedsolid = Z_Realloc(sec2->attachedsolid, sizeof (*sec2->attachedsolid) * sec2->maxattached, PU_STATIC, NULL);
|
|
}
|
|
sec2->attached[sec2->numattached] = sec - sectors;
|
|
sec2->attachedsolid[sec2->numattached] = (flags & FF_SOLID);
|
|
sec2->numattached++;
|
|
}
|
|
|
|
// Add the floor
|
|
ffloor = Z_Calloc(sizeof (*ffloor), PU_LEVEL, NULL);
|
|
ffloor->secnum = sec2 - sectors;
|
|
ffloor->target = sec;
|
|
ffloor->bottomheight = &sec2->floorheight;
|
|
ffloor->bottompic = &sec2->floorpic;
|
|
ffloor->bottomxoffs = &sec2->floor_xoffs;
|
|
ffloor->bottomyoffs = &sec2->floor_yoffs;
|
|
ffloor->bottomangle = &sec2->floorpic_angle;
|
|
|
|
// Add the ceiling
|
|
ffloor->topheight = &sec2->ceilingheight;
|
|
ffloor->toppic = &sec2->ceilingpic;
|
|
ffloor->toplightlevel = &sec2->lightlevel;
|
|
ffloor->topxoffs = &sec2->ceiling_xoffs;
|
|
ffloor->topyoffs = &sec2->ceiling_yoffs;
|
|
ffloor->topangle = &sec2->ceilingpic_angle;
|
|
|
|
#ifdef ESLOPE
|
|
// Add slopes
|
|
ffloor->t_slope = &sec2->c_slope;
|
|
ffloor->b_slope = &sec2->f_slope;
|
|
#endif
|
|
|
|
if ((flags & FF_SOLID) && (master->flags & ML_EFFECT1)) // Block player only
|
|
flags &= ~FF_BLOCKOTHERS;
|
|
|
|
if ((flags & FF_SOLID) && (master->flags & ML_EFFECT2)) // Block all BUT player
|
|
flags &= ~FF_BLOCKPLAYER;
|
|
|
|
ffloor->spawnflags = ffloor->flags = flags;
|
|
ffloor->master = master;
|
|
ffloor->norender = INFTICS;
|
|
|
|
|
|
// Scan the thinkers to check for special conditions applying to this FOF.
|
|
// If we have thinkers sorted by sector, just check the relevant ones;
|
|
// otherwise, check them all. Apologies for the ugly loop...
|
|
|
|
sec2num = sec2 - sectors;
|
|
|
|
// Just initialise both of these to placate the compiler.
|
|
i = 0;
|
|
th = thinkercap.next;
|
|
|
|
for(;;)
|
|
{
|
|
if(secthinkers)
|
|
{
|
|
if(i < secthinkers[sec2num].count)
|
|
th = secthinkers[sec2num].thinkers[i];
|
|
else break;
|
|
}
|
|
else if (th == &thinkercap)
|
|
break;
|
|
|
|
// Should this FOF have spikeness?
|
|
if (th->function.acp1 == (actionf_p1)T_SpikeSector)
|
|
{
|
|
lst = (levelspecthink_t *)th;
|
|
|
|
if (lst->sector == sec2)
|
|
P_AddSpikeThinker(sec, (INT32)sec2num);
|
|
}
|
|
// Should this FOF have friction?
|
|
else if(th->function.acp1 == (actionf_p1)T_Friction)
|
|
{
|
|
f = (friction_t *)th;
|
|
|
|
if (f->affectee == (INT32)sec2num)
|
|
Add_Friction(f->friction, f->movefactor, (INT32)(sec-sectors), f->affectee);
|
|
}
|
|
// Should this FOF have wind/current/pusher?
|
|
else if(th->function.acp1 == (actionf_p1)T_Pusher)
|
|
{
|
|
p = (pusher_t *)th;
|
|
|
|
if (p->affectee == (INT32)sec2num)
|
|
Add_Pusher(p->type, p->x_mag<<FRACBITS, p->y_mag<<FRACBITS, p->source, (INT32)(sec-sectors), p->affectee, p->exclusive, p->slider);
|
|
}
|
|
|
|
if(secthinkers) i++;
|
|
else th = th->next;
|
|
}
|
|
|
|
|
|
if (flags & FF_TRANSLUCENT)
|
|
{
|
|
if (sides[master->sidenum[0]].toptexture > 0)
|
|
ffloor->alpha = sides[master->sidenum[0]].toptexture; // for future reference, "#0" is 1, and "#255" is 256. Be warned
|
|
else
|
|
ffloor->alpha = 0x80;
|
|
}
|
|
else
|
|
ffloor->alpha = 0xff;
|
|
|
|
ffloor->spawnalpha = ffloor->alpha; // save for netgames
|
|
|
|
if (flags & FF_QUICKSAND)
|
|
CheckForQuicksand = true;
|
|
|
|
if ((flags & FF_BUSTUP) || (flags & FF_SHATTER) || (flags & FF_SPINBUST))
|
|
CheckForBustableBlocks = true;
|
|
|
|
if ((flags & FF_MARIO))
|
|
{
|
|
P_AddBlockThinker(sec2, master);
|
|
CheckForMarioBlocks = true;
|
|
}
|
|
|
|
if ((flags & FF_CRUMBLE))
|
|
sec2->crumblestate = 1;
|
|
|
|
if ((flags & FF_FLOATBOB))
|
|
{
|
|
P_AddFloatThinker(sec2, sec->tag, master);
|
|
CheckForFloatBob = true;
|
|
}
|
|
|
|
P_AddFFloorToList(sec, ffloor);
|
|
|
|
return ffloor;
|
|
}
|
|
|
|
//
|
|
// SPECIAL SPAWNING
|
|
//
|
|
|
|
/** Adds a spike thinker.
|
|
* Sector type Section1:5 will result in this effect.
|
|
*
|
|
* \param sec Sector in which to add the thinker.
|
|
* \param referrer If != sec, then we're dealing with a FOF
|
|
* \sa P_SpawnSpecials, T_SpikeSector
|
|
* \author SSNTails <http://www.ssntails.org>
|
|
*/
|
|
static void P_AddSpikeThinker(sector_t *sec, INT32 referrer)
|
|
{
|
|
levelspecthink_t *spikes;
|
|
|
|
// create and initialize new thinker
|
|
spikes = Z_Calloc(sizeof (*spikes), PU_LEVSPEC, NULL);
|
|
P_AddThinker(&spikes->thinker);
|
|
|
|
spikes->thinker.function.acp1 = (actionf_p1)T_SpikeSector;
|
|
|
|
spikes->sector = sec;
|
|
spikes->vars[0] = referrer;
|
|
}
|
|
|
|
/** Adds a float thinker.
|
|
* Float thinkers cause solid 3Dfloors to float on water.
|
|
*
|
|
* \param sec Control sector.
|
|
* \param actionsector Target sector.
|
|
* \sa P_SpawnSpecials, T_FloatSector
|
|
* \author SSNTails <http://www.ssntails.org>
|
|
*/
|
|
static void P_AddFloatThinker(sector_t *sec, INT32 tag, line_t *sourceline)
|
|
{
|
|
levelspecthink_t *floater;
|
|
|
|
// create and initialize new thinker
|
|
floater = Z_Calloc(sizeof (*floater), PU_LEVSPEC, NULL);
|
|
P_AddThinker(&floater->thinker);
|
|
|
|
floater->thinker.function.acp1 = (actionf_p1)T_FloatSector;
|
|
|
|
floater->sector = sec;
|
|
floater->vars[0] = tag;
|
|
floater->sourceline = sourceline;
|
|
}
|
|
|
|
/** Adds a bridge thinker.
|
|
* Bridge thinkers cause a group of FOFs to behave like
|
|
* a bridge made up of pieces, that bows under weight.
|
|
*
|
|
* \param sec Control sector.
|
|
* \sa P_SpawnSpecials, T_BridgeThinker
|
|
* \author SSNTails <http://www.ssntails.org>
|
|
*/
|
|
/*
|
|
static inline void P_AddBridgeThinker(line_t *sourceline, sector_t *sec)
|
|
{
|
|
levelspecthink_t *bridge;
|
|
|
|
// create an initialize new thinker
|
|
bridge = Z_Calloc(sizeof (*bridge), PU_LEVSPEC, NULL);
|
|
P_AddThinker(&bridge->thinker);
|
|
|
|
bridge->thinker.function.acp1 = (actionf_p1)T_BridgeThinker;
|
|
|
|
bridge->sector = sec;
|
|
bridge->vars[0] = sourceline->frontsector->floorheight;
|
|
bridge->vars[1] = sourceline->frontsector->ceilingheight;
|
|
bridge->vars[2] = P_AproxDistance(sourceline->dx, sourceline->dy); // Speed
|
|
bridge->vars[2] = FixedDiv(bridge->vars[2], 16*FRACUNIT);
|
|
bridge->vars[3] = bridge->vars[2];
|
|
|
|
// Start tag and end tag are TARGET SECTORS, not CONTROL SECTORS
|
|
// Control sector tags should be End_Tag + (End_Tag - Start_Tag)
|
|
bridge->vars[4] = sourceline->tag; // Start tag
|
|
bridge->vars[5] = (sides[sourceline->sidenum[0]].textureoffset>>FRACBITS); // End tag
|
|
}
|
|
*/
|
|
|
|
/** Adds a Mario block thinker, which changes the block's texture between blank
|
|
* and ? depending on whether it has contents.
|
|
* Needed in case objects respawn inside.
|
|
*
|
|
* \param sec Control sector.
|
|
* \param actionsector Target sector.
|
|
* \param sourceline Control linedef.
|
|
* \sa P_SpawnSpecials, T_MarioBlockChecker
|
|
* \author SSNTails <http://www.ssntails.org>
|
|
*/
|
|
static void P_AddBlockThinker(sector_t *sec, line_t *sourceline)
|
|
{
|
|
levelspecthink_t *block;
|
|
|
|
// create and initialize new elevator thinker
|
|
block = Z_Calloc(sizeof (*block), PU_LEVSPEC, NULL);
|
|
P_AddThinker(&block->thinker);
|
|
|
|
block->thinker.function.acp1 = (actionf_p1)T_MarioBlockChecker;
|
|
block->sourceline = sourceline;
|
|
|
|
block->sector = sec;
|
|
}
|
|
|
|
/** Adds a raise thinker.
|
|
* A raise thinker checks to see if the
|
|
* player is standing on its 3D Floor,
|
|
* and if so, raises the platform towards
|
|
* it's destination. Otherwise, it lowers
|
|
* to the lowest nearby height if not
|
|
* there already.
|
|
*
|
|
* Replaces the old "AirBob".
|
|
*
|
|
* \param sec Control sector.
|
|
* \param actionsector Target sector.
|
|
* \param sourceline Control linedef.
|
|
* \sa P_SpawnSpecials, T_RaiseSector
|
|
* \author SSNTails <http://www.ssntails.org>
|
|
*/
|
|
static void P_AddRaiseThinker(sector_t *sec, line_t *sourceline)
|
|
{
|
|
levelspecthink_t *raise;
|
|
|
|
raise = Z_Calloc(sizeof (*raise), PU_LEVSPEC, NULL);
|
|
P_AddThinker(&raise->thinker);
|
|
|
|
raise->thinker.function.acp1 = (actionf_p1)T_RaiseSector;
|
|
|
|
if (sourceline->flags & ML_BLOCKMONSTERS)
|
|
raise->vars[0] = 1;
|
|
else
|
|
raise->vars[0] = 0;
|
|
|
|
// set up the fields
|
|
raise->sector = sec;
|
|
|
|
// Require a spindash to activate
|
|
if (sourceline->flags & ML_NOCLIMB)
|
|
raise->vars[1] = 1;
|
|
else
|
|
raise->vars[1] = 0;
|
|
|
|
raise->vars[2] = P_AproxDistance(sourceline->dx, sourceline->dy);
|
|
raise->vars[2] = FixedDiv(raise->vars[2], 4*FRACUNIT);
|
|
raise->vars[3] = raise->vars[2];
|
|
|
|
raise->vars[5] = P_FindHighestCeilingSurrounding(sec);
|
|
raise->vars[4] = raise->vars[5]
|
|
- (sec->ceilingheight - sec->floorheight);
|
|
|
|
raise->vars[7] = P_FindLowestCeilingSurrounding(sec);
|
|
raise->vars[6] = raise->vars[7]
|
|
- (sec->ceilingheight - sec->floorheight);
|
|
|
|
raise->sourceline = sourceline;
|
|
}
|
|
|
|
// Function to maintain backwards compatibility
|
|
static void P_AddOldAirbob(sector_t *sec, line_t *sourceline, boolean noadjust)
|
|
{
|
|
levelspecthink_t *airbob;
|
|
|
|
airbob = Z_Calloc(sizeof (*airbob), PU_LEVSPEC, NULL);
|
|
P_AddThinker(&airbob->thinker);
|
|
|
|
airbob->thinker.function.acp1 = (actionf_p1)T_RaiseSector;
|
|
|
|
// set up the fields
|
|
airbob->sector = sec;
|
|
|
|
// Require a spindash to activate
|
|
if (sourceline->flags & ML_NOCLIMB)
|
|
airbob->vars[1] = 1;
|
|
else
|
|
airbob->vars[1] = 0;
|
|
|
|
airbob->vars[2] = FRACUNIT;
|
|
|
|
if (noadjust)
|
|
{
|
|
airbob->vars[7] = airbob->sector->ceilingheight-16*FRACUNIT;
|
|
airbob->vars[6] = airbob->vars[7]
|
|
- (sec->ceilingheight - sec->floorheight);
|
|
}
|
|
else
|
|
airbob->vars[7] = airbob->sector->ceilingheight - P_AproxDistance(sourceline->dx, sourceline->dy);
|
|
|
|
airbob->vars[3] = airbob->vars[2];
|
|
|
|
if (sourceline->flags & ML_BLOCKMONSTERS)
|
|
airbob->vars[0] = 1;
|
|
else
|
|
airbob->vars[0] = 0;
|
|
|
|
airbob->vars[5] = sec->ceilingheight;
|
|
airbob->vars[4] = airbob->vars[5]
|
|
- (sec->ceilingheight - sec->floorheight);
|
|
|
|
airbob->sourceline = sourceline;
|
|
}
|
|
|
|
/** Adds a thwomp thinker.
|
|
* Even thwomps need to think!
|
|
*
|
|
* \param sec Control sector.
|
|
* \param actionsector Target sector.
|
|
* \param sourceline Control linedef.
|
|
* \sa P_SpawnSpecials, T_ThwompSector
|
|
* \author SSNTails <http://www.ssntails.org>
|
|
*/
|
|
static inline void P_AddThwompThinker(sector_t *sec, sector_t *actionsector, line_t *sourceline)
|
|
{
|
|
#define speed vars[1]
|
|
#define direction vars[2]
|
|
#define distance vars[3]
|
|
#define floorwasheight vars[4]
|
|
#define ceilingwasheight vars[5]
|
|
levelspecthink_t *thwomp;
|
|
|
|
// You *probably* already have a thwomp in this sector. If you've combined it with something
|
|
// else that uses the floordata/ceilingdata, you must be weird.
|
|
if (sec->floordata || sec->ceilingdata)
|
|
return;
|
|
|
|
// create and initialize new elevator thinker
|
|
thwomp = Z_Calloc(sizeof (*thwomp), PU_LEVSPEC, NULL);
|
|
P_AddThinker(&thwomp->thinker);
|
|
|
|
thwomp->thinker.function.acp1 = (actionf_p1)T_ThwompSector;
|
|
|
|
// set up the fields according to the type of elevator action
|
|
thwomp->sector = sec;
|
|
thwomp->vars[0] = actionsector->tag;
|
|
thwomp->floorwasheight = thwomp->sector->floorheight;
|
|
thwomp->ceilingwasheight = thwomp->sector->ceilingheight;
|
|
thwomp->direction = 0;
|
|
thwomp->distance = 1;
|
|
thwomp->sourceline = sourceline;
|
|
thwomp->sector->floordata = thwomp;
|
|
thwomp->sector->ceilingdata = thwomp;
|
|
return;
|
|
#undef speed
|
|
#undef direction
|
|
#undef distance
|
|
#undef floorwasheight
|
|
#undef ceilingwasheight
|
|
}
|
|
|
|
/** Adds a thinker which checks if any MF_ENEMY objects with health are in the defined area.
|
|
* If not, a linedef executor is run once.
|
|
*
|
|
* \param sec Control sector.
|
|
* \param sourceline Control linedef.
|
|
* \sa P_SpawnSpecials, T_NoEnemiesSector
|
|
* \author SSNTails <http://www.ssntails.org>
|
|
*/
|
|
static inline void P_AddNoEnemiesThinker(sector_t *sec, line_t *sourceline)
|
|
{
|
|
levelspecthink_t *nobaddies;
|
|
|
|
// create and initialize new thinker
|
|
nobaddies = Z_Calloc(sizeof (*nobaddies), PU_LEVSPEC, NULL);
|
|
P_AddThinker(&nobaddies->thinker);
|
|
|
|
nobaddies->thinker.function.acp1 = (actionf_p1)T_NoEnemiesSector;
|
|
|
|
nobaddies->sector = sec;
|
|
nobaddies->sourceline = sourceline;
|
|
}
|
|
|
|
/** Adds a thinker for Each-Time linedef executors. A linedef executor is run
|
|
* only when a player enters the area and doesn't run again until they re-enter.
|
|
*
|
|
* \param sec Control sector that contains the lines of executors we will want to run.
|
|
* \param sourceline Control linedef.
|
|
* \sa P_SpawnSpecials, T_EachTimeThinker
|
|
* \author SSNTails <http://www.ssntails.org>
|
|
*/
|
|
static inline void P_AddEachTimeThinker(sector_t *sec, line_t *sourceline)
|
|
{
|
|
levelspecthink_t *eachtime;
|
|
|
|
// create and initialize new thinker
|
|
eachtime = Z_Calloc(sizeof (*eachtime), PU_LEVSPEC, NULL);
|
|
P_AddThinker(&eachtime->thinker);
|
|
|
|
eachtime->thinker.function.acp1 = (actionf_p1)T_EachTimeThinker;
|
|
|
|
eachtime->sector = sec;
|
|
eachtime->sourceline = sourceline;
|
|
}
|
|
|
|
/** Adds a camera scanner.
|
|
*
|
|
* \param sourcesec Control sector.
|
|
* \param actionsector Target sector.
|
|
* \param angle Angle of the source line.
|
|
* \sa P_SpawnSpecials, T_CameraScanner
|
|
* \author SSNTails <http://www.ssntails.org>
|
|
*/
|
|
static inline void P_AddCameraScanner(sector_t *sourcesec, sector_t *actionsector, angle_t angle)
|
|
{
|
|
elevator_t *elevator; // Why not? LOL
|
|
|
|
// create and initialize new elevator thinker
|
|
elevator = Z_Calloc(sizeof (*elevator), PU_LEVSPEC, NULL);
|
|
P_AddThinker(&elevator->thinker);
|
|
|
|
elevator->thinker.function.acp1 = (actionf_p1)T_CameraScanner;
|
|
elevator->type = elevateBounce;
|
|
|
|
// set up the fields according to the type of elevator action
|
|
elevator->sector = sourcesec;
|
|
elevator->actionsector = actionsector;
|
|
elevator->distance = FixedInt(AngleFixed(angle));
|
|
}
|
|
|
|
static const ffloortype_e laserflags = FF_EXISTS|FF_RENDERALL|FF_NOSHADE|FF_EXTRA|FF_CUTEXTRA;
|
|
|
|
/** Flashes a laser block.
|
|
*
|
|
* \param flash Thinker structure for this laser.
|
|
* \sa EV_AddLaserThinker
|
|
* \author SSNTails <http://www.ssntails.org>
|
|
*/
|
|
void T_LaserFlash(laserthink_t *flash)
|
|
{
|
|
msecnode_t *node;
|
|
mobj_t *thing;
|
|
sector_t *sourcesec;
|
|
ffloor_t *ffloor = flash->ffloor;
|
|
sector_t *sector = flash->sector;
|
|
fixed_t top, bottom;
|
|
|
|
if (!ffloor || !(ffloor->flags & FF_EXISTS))
|
|
return;
|
|
|
|
if (leveltime & 1)
|
|
ffloor->flags |= FF_RENDERALL;
|
|
else
|
|
ffloor->flags &= ~FF_RENDERALL;
|
|
|
|
sourcesec = ffloor->master->frontsector; // Less to type!
|
|
|
|
sector->soundorg.z = (*ffloor->topheight + *ffloor->bottomheight)/2;
|
|
S_StartSound(§or->soundorg, sfx_laser);
|
|
|
|
// Seek out objects to DESTROY! MUAHAHHAHAHAA!!!*cough*
|
|
for (node = sector->touching_thinglist; node && node->m_thing; node = node->m_snext)
|
|
{
|
|
thing = node->m_thing;
|
|
|
|
if ((ffloor->master->flags & ML_EFFECT1)
|
|
&& thing->flags & MF_BOSS)
|
|
continue; // Don't hurt bosses
|
|
|
|
top = P_GetSpecialTopZ(thing, sourcesec, sector);
|
|
bottom = P_GetSpecialBottomZ(thing, sourcesec, sector);
|
|
|
|
if (thing->z >= top
|
|
|| thing->z + thing->height <= bottom)
|
|
continue;
|
|
|
|
if (thing->flags & MF_SHOOTABLE)
|
|
P_DamageMobj(thing, NULL, NULL, 1);
|
|
else if (thing->type == MT_EGGSHIELD)
|
|
P_KillMobj(thing, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
/** Adds a laser thinker to a 3Dfloor.
|
|
*
|
|
* \param ffloor 3Dfloor to turn into a laser block.
|
|
* \param sector Target sector.
|
|
* \param secthkiners Lists of thinkers sorted by sector. May be NULL.
|
|
* \sa T_LaserFlash
|
|
* \author SSNTails <http://www.ssntails.org>
|
|
*/
|
|
static inline void EV_AddLaserThinker(sector_t *sec, sector_t *sec2, line_t *line, thinkerlist_t *secthinkers)
|
|
{
|
|
laserthink_t *flash;
|
|
ffloor_t *ffloor = P_AddFakeFloor(sec, sec2, line, laserflags, secthinkers);
|
|
|
|
if (!ffloor)
|
|
return;
|
|
|
|
flash = Z_Calloc(sizeof (*flash), PU_LEVSPEC, NULL);
|
|
|
|
P_AddThinker(&flash->thinker);
|
|
|
|
flash->thinker.function.acp1 = (actionf_p1)T_LaserFlash;
|
|
flash->ffloor = ffloor;
|
|
flash->sector = sec; // For finding mobjs
|
|
flash->sec = sec2;
|
|
flash->sourceline = line;
|
|
}
|
|
|
|
//
|
|
// P_RunLevelLoadExecutors
|
|
//
|
|
// After loading/spawning all other specials
|
|
// and items, execute these.
|
|
//
|
|
static void P_RunLevelLoadExecutors(void)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < numlines; i++)
|
|
{
|
|
if (lines[i].special == 399)
|
|
P_RunTriggerLinedef(&lines[i], NULL, NULL);
|
|
}
|
|
}
|
|
|
|
/** After the map has loaded, scans for specials that spawn 3Dfloors and
|
|
* thinkers.
|
|
*
|
|
* \todo Split up into multiple functions.
|
|
* \todo Get rid of all the magic numbers.
|
|
* \todo Potentially use 'fromnetsave' to stop any new thinkers from being created
|
|
* as they'll just be erased by UnArchiveThinkers.
|
|
* \sa P_SpawnPrecipitation, P_SpawnFriction, P_SpawnPushers, P_SpawnScrollers
|
|
*/
|
|
void P_SpawnSpecials(INT32 fromnetsave)
|
|
{
|
|
sector_t *sector;
|
|
size_t i;
|
|
INT32 j;
|
|
thinkerlist_t *secthinkers;
|
|
thinker_t *th;
|
|
|
|
// This used to be used, and *should* be used in the future,
|
|
// but currently isn't.
|
|
(void)fromnetsave;
|
|
|
|
// Set the default gravity. Custom gravity overrides this setting.
|
|
gravity = FRACUNIT/2;
|
|
|
|
// Defaults in case levels don't have them set.
|
|
sstimer = 90*TICRATE + 6;
|
|
totalrings = 1;
|
|
|
|
CheckForBustableBlocks = CheckForBouncySector = CheckForQuicksand = CheckForMarioBlocks = CheckForFloatBob = CheckForReverseGravity = false;
|
|
|
|
// Init special SECTORs.
|
|
sector = sectors;
|
|
for (i = 0; i < numsectors; i++, sector++)
|
|
{
|
|
if (!sector->special)
|
|
continue;
|
|
|
|
// Process Section 1
|
|
switch(GETSECSPECIAL(sector->special, 1))
|
|
{
|
|
case 5: // Spikes
|
|
P_AddSpikeThinker(sector, (INT32)(sector-sectors));
|
|
break;
|
|
|
|
case 15: // Bouncy sector
|
|
CheckForBouncySector = true;
|
|
break;
|
|
}
|
|
|
|
// Process Section 2
|
|
switch(GETSECSPECIAL(sector->special, 2))
|
|
{
|
|
case 10: // Time for special stage
|
|
sstimer = (sector->floorheight>>FRACBITS) * TICRATE + 6; // Time to finish
|
|
totalrings = sector->ceilingheight>>FRACBITS; // Ring count for special stage
|
|
break;
|
|
|
|
case 11: // Custom global gravity!
|
|
gravity = sector->floorheight/1000;
|
|
break;
|
|
}
|
|
|
|
// Process Section 3
|
|
/* switch(GETSECSPECIAL(player->specialsector, 3))
|
|
{
|
|
|
|
}*/
|
|
|
|
// Process Section 4
|
|
switch(GETSECSPECIAL(sector->special, 4))
|
|
{
|
|
case 10: // Circuit finish line
|
|
if (gametype == GT_RACE)
|
|
circuitmap = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (mapheaderinfo[gamemap-1]->weather == 2) // snow
|
|
curWeather = PRECIP_SNOW;
|
|
else if (mapheaderinfo[gamemap-1]->weather == 3) // rain
|
|
curWeather = PRECIP_RAIN;
|
|
else if (mapheaderinfo[gamemap-1]->weather == 1) // storm
|
|
curWeather = PRECIP_STORM;
|
|
else if (mapheaderinfo[gamemap-1]->weather == 5) // storm w/o rain
|
|
curWeather = PRECIP_STORM_NORAIN;
|
|
else if (mapheaderinfo[gamemap-1]->weather == 6) // storm w/o lightning
|
|
curWeather = PRECIP_STORM_NOSTRIKES;
|
|
else
|
|
curWeather = PRECIP_NONE;
|
|
|
|
P_InitTagLists(); // Create xref tables for tags
|
|
P_SearchForDisableLinedefs(); // Disable linedefs are now allowed to disable *any* line
|
|
|
|
P_SpawnScrollers(); // Add generalized scrollers
|
|
P_SpawnFriction(); // Friction model using linedefs
|
|
P_SpawnPushers(); // Pusher model using linedefs
|
|
|
|
// Look for thinkers that affect FOFs, and sort them by sector
|
|
|
|
secthinkers = Z_Calloc(numsectors * sizeof(thinkerlist_t), PU_STATIC, NULL);
|
|
|
|
// Firstly, find out how many there are in each sector
|
|
for (th = thinkercap.next; th != &thinkercap; th = th->next)
|
|
{
|
|
if (th->function.acp1 == (actionf_p1)T_SpikeSector)
|
|
secthinkers[((levelspecthink_t *)th)->sector - sectors].count++;
|
|
else if (th->function.acp1 == (actionf_p1)T_Friction)
|
|
secthinkers[((friction_t *)th)->affectee].count++;
|
|
else if (th->function.acp1 == (actionf_p1)T_Pusher)
|
|
secthinkers[((pusher_t *)th)->affectee].count++;
|
|
}
|
|
|
|
// Allocate each list, and then zero the count so we can use it to track
|
|
// the end of the list as we add the thinkers
|
|
for (i = 0; i < numsectors; i++)
|
|
if(secthinkers[i].count > 0)
|
|
{
|
|
secthinkers[i].thinkers = Z_Malloc(secthinkers[i].count * sizeof(thinker_t *), PU_STATIC, NULL);
|
|
secthinkers[i].count = 0;
|
|
}
|
|
|
|
// Finally, populate the lists.
|
|
for (th = thinkercap.next; th != &thinkercap; th = th->next)
|
|
{
|
|
size_t secnum = (size_t)-1;
|
|
|
|
if (th->function.acp1 == (actionf_p1)T_SpikeSector)
|
|
secnum = ((levelspecthink_t *)th)->sector - sectors;
|
|
else if (th->function.acp1 == (actionf_p1)T_Friction)
|
|
secnum = ((friction_t *)th)->affectee;
|
|
else if (th->function.acp1 == (actionf_p1)T_Pusher)
|
|
secnum = ((pusher_t *)th)->affectee;
|
|
|
|
if (secnum != (size_t)-1)
|
|
secthinkers[secnum].thinkers[secthinkers[secnum].count++] = th;
|
|
}
|
|
|
|
|
|
// Init line EFFECTs
|
|
for (i = 0; i < numlines; i++)
|
|
{
|
|
// set line specials to 0 here too, same reason as above
|
|
if (netgame || multiplayer)
|
|
{
|
|
// future: nonet flag?
|
|
}
|
|
else if ((lines[i].flags & ML_NETONLY) == ML_NETONLY)
|
|
{
|
|
lines[i].special = 0;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (players[consoleplayer].charability == CA_THOK && (lines[i].flags & ML_NOSONIC))
|
|
{
|
|
lines[i].special = 0;
|
|
continue;
|
|
}
|
|
if (players[consoleplayer].charability == CA_FLY && (lines[i].flags & ML_NOTAILS))
|
|
{
|
|
lines[i].special = 0;
|
|
continue;
|
|
}
|
|
if (players[consoleplayer].charability == CA_GLIDEANDCLIMB && (lines[i].flags & ML_NOKNUX))
|
|
{
|
|
lines[i].special = 0;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
switch (lines[i].special)
|
|
{
|
|
INT32 s;
|
|
size_t sec;
|
|
ffloortype_e ffloorflags;
|
|
|
|
case 1: // Definable gravity per sector
|
|
sec = sides[*lines[i].sidenum].sector - sectors;
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
{
|
|
sectors[s].gravity = §ors[sec].floorheight; // This allows it to change in realtime!
|
|
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
sectors[s].verticalflip = true;
|
|
else
|
|
sectors[s].verticalflip = false;
|
|
|
|
CheckForReverseGravity = sectors[s].verticalflip;
|
|
}
|
|
break;
|
|
|
|
case 2: // Custom exit
|
|
break;
|
|
|
|
case 3: // Zoom Tube Parameters
|
|
break;
|
|
|
|
case 4: // Speed pad (combines with sector special Section3:5 or Section3:6)
|
|
break;
|
|
|
|
case 5: // Change camera info
|
|
sec = sides[*lines[i].sidenum].sector - sectors;
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
P_AddCameraScanner(§ors[sec], §ors[s], R_PointToAngle2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y));
|
|
break;
|
|
|
|
#ifdef PARANOIA
|
|
case 6: // Disable tags if level not cleared
|
|
I_Error("Failed to catch a disable linedef");
|
|
break;
|
|
#endif
|
|
|
|
case 7: // Flat alignment
|
|
if (lines[i].flags & ML_EFFECT4) // Align angle
|
|
{
|
|
if (!(lines[i].flags & ML_EFFECT5)) // Align floor unless ALLTRIGGER flag is set
|
|
{
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
sectors[s].spawn_flrpic_angle = sectors[s].floorpic_angle = R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y);
|
|
}
|
|
|
|
if (!(lines[i].flags & ML_BOUNCY)) // Align ceiling unless BOUNCY flag is set
|
|
{
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
sectors[s].spawn_ceilpic_angle = sectors[s].ceilingpic_angle = R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y);
|
|
}
|
|
}
|
|
else // Do offsets
|
|
{
|
|
if (!(lines[i].flags & ML_BLOCKMONSTERS)) // Align floor unless BLOCKMONSTERS flag is set
|
|
{
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
{
|
|
sectors[s].floor_xoffs += lines[i].dx;
|
|
sectors[s].floor_yoffs += lines[i].dy;
|
|
// saved for netgames
|
|
sectors[s].spawn_flr_xoffs = sectors[s].floor_xoffs;
|
|
sectors[s].spawn_flr_yoffs = sectors[s].floor_yoffs;
|
|
}
|
|
}
|
|
|
|
if (!(lines[i].flags & ML_NOCLIMB)) // Align ceiling unless NOCLIMB flag is set
|
|
{
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
{
|
|
sectors[s].ceiling_xoffs += lines[i].dx;
|
|
sectors[s].ceiling_yoffs += lines[i].dy;
|
|
// saved for netgames
|
|
sectors[s].spawn_ceil_xoffs = sectors[s].ceiling_xoffs;
|
|
sectors[s].spawn_ceil_yoffs = sectors[s].ceiling_yoffs;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 8: // Sector Parameters
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
{
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
{
|
|
sectors[s].flags &= ~SF_FLIPSPECIAL_FLOOR;
|
|
sectors[s].flags |= SF_FLIPSPECIAL_CEILING;
|
|
}
|
|
else if (lines[i].flags & ML_EFFECT4)
|
|
sectors[s].flags |= SF_FLIPSPECIAL_BOTH;
|
|
|
|
if (lines[i].flags & ML_EFFECT3)
|
|
sectors[s].flags |= SF_TRIGGERSPECIAL_TOUCH;
|
|
|
|
if (lines[i].frontsector && GETSECSPECIAL(lines[i].frontsector->special, 4) == 12)
|
|
sectors[s].camsec = sides[*lines[i].sidenum].sector-sectors;
|
|
}
|
|
break;
|
|
|
|
case 9: // Chain Parameters
|
|
break;
|
|
|
|
case 10: // Vertical culling plane for sprites and FOFs
|
|
sec = sides[*lines[i].sidenum].sector - sectors;
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
sectors[s].cullheight = &lines[i]; // This allows it to change in realtime!
|
|
break;
|
|
|
|
case 50: // Insta-Lower Sector
|
|
EV_DoFloor(&lines[i], instantLower);
|
|
break;
|
|
|
|
case 51: // Instant raise for ceilings
|
|
EV_DoCeiling(&lines[i], instantRaise);
|
|
break;
|
|
|
|
case 52: // Continuously Falling sector
|
|
EV_DoContinuousFall(lines[i].frontsector, lines[i].backsector, P_AproxDistance(lines[i].dx, lines[i].dy), (lines[i].flags & ML_NOCLIMB));
|
|
break;
|
|
|
|
case 53: // New super cool and awesome moving floor and ceiling type
|
|
case 54: // New super cool and awesome moving floor type
|
|
if (lines[i].backsector)
|
|
EV_DoFloor(&lines[i], bounceFloor);
|
|
if (lines[i].special == 54)
|
|
break;
|
|
case 55: // New super cool and awesome moving ceiling type
|
|
if (lines[i].backsector)
|
|
EV_DoCeiling(&lines[i], bounceCeiling);
|
|
break;
|
|
|
|
case 56: // New super cool and awesome moving floor and ceiling crush type
|
|
case 57: // New super cool and awesome moving floor crush type
|
|
if (lines[i].backsector)
|
|
EV_DoFloor(&lines[i], bounceFloorCrush);
|
|
|
|
if (lines[i].special == 57)
|
|
break; //only move the floor
|
|
|
|
case 58: // New super cool and awesome moving ceiling crush type
|
|
if (lines[i].backsector)
|
|
EV_DoCeiling(&lines[i], bounceCeilingCrush);
|
|
break;
|
|
|
|
case 59: // Activate floating platform
|
|
EV_DoElevator(&lines[i], elevateContinuous, false);
|
|
break;
|
|
|
|
case 60: // Floating platform with adjustable speed
|
|
EV_DoElevator(&lines[i], elevateContinuous, true);
|
|
break;
|
|
|
|
case 61: // Crusher!
|
|
EV_DoCrush(&lines[i], crushAndRaise);
|
|
break;
|
|
|
|
case 62: // Crusher (up and then down)!
|
|
EV_DoCrush(&lines[i], fastCrushAndRaise);
|
|
break;
|
|
|
|
case 63: // support for drawn heights coming from different sector
|
|
sec = sides[*lines[i].sidenum].sector-sectors;
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
sectors[s].heightsec = (INT32)sec;
|
|
break;
|
|
|
|
case 64: // Appearing/Disappearing FOF option
|
|
if (lines[i].flags & ML_BLOCKMONSTERS) { // Find FOFs by control sector tag
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
for (j = 0; (unsigned)j < sectors[s].linecount; j++)
|
|
if (sectors[s].lines[j]->special >= 100 && sectors[s].lines[j]->special < 300)
|
|
Add_MasterDisappearer(abs(lines[i].dx>>FRACBITS), abs(lines[i].dy>>FRACBITS), abs(sides[lines[i].sidenum[0]].sector->floorheight>>FRACBITS), (INT32)(sectors[s].lines[j]-lines), (INT32)i);
|
|
} else // Find FOFs by effect sector tag
|
|
for (s = -1; (s = P_FindLineFromLineTag(lines + i, s)) >= 0 ;)
|
|
{
|
|
if ((size_t)s == i)
|
|
continue;
|
|
if (sides[lines[s].sidenum[0]].sector->tag == sides[lines[i].sidenum[0]].sector->tag)
|
|
Add_MasterDisappearer(abs(lines[i].dx>>FRACBITS), abs(lines[i].dy>>FRACBITS), abs(sides[lines[i].sidenum[0]].sector->floorheight>>FRACBITS), s, (INT32)i);
|
|
}
|
|
break;
|
|
|
|
case 65: // Bridge Thinker
|
|
/*
|
|
// Disable this until it's working right!
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
P_AddBridgeThinker(&lines[i], §ors[s]);*/
|
|
break;
|
|
|
|
case 100: // FOF (solid, opaque, shadows)
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
|
|
break;
|
|
|
|
case 101: // FOF (solid, opaque, no shadows)
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_NOSHADE|FF_CUTLEVEL, secthinkers);
|
|
break;
|
|
|
|
case 102: // TL block: FOF (solid, translucent)
|
|
ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_NOSHADE|FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA;
|
|
|
|
// Draw the 'insides' of the block too
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
{
|
|
ffloorflags |= FF_CUTLEVEL;
|
|
ffloorflags |= FF_BOTHPLANES;
|
|
ffloorflags |= FF_ALLSIDES;
|
|
ffloorflags &= ~FF_EXTRA;
|
|
ffloorflags &= ~FF_CUTEXTRA;
|
|
}
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 103: // Solid FOF with no floor/ceiling (quite possibly useless)
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERSIDES|FF_NOSHADE|FF_CUTLEVEL, secthinkers);
|
|
break;
|
|
|
|
case 104: // 3D Floor type that doesn't draw sides
|
|
// If line has no-climb set, give it shadows, otherwise don't
|
|
ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERPLANES|FF_CUTLEVEL;
|
|
if (!(lines[i].flags & ML_NOCLIMB))
|
|
ffloorflags |= FF_NOSHADE;
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 105: // FOF (solid, invisible)
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_NOSHADE, secthinkers);
|
|
break;
|
|
|
|
case 120: // Opaque water
|
|
ffloorflags = FF_EXISTS|FF_RENDERALL|FF_SWIMMABLE|FF_BOTHPLANES|FF_ALLSIDES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
ffloorflags |= FF_DOUBLESHADOW;
|
|
if (lines[i].flags & ML_EFFECT4)
|
|
ffloorflags |= FF_COLORMAPONLY;
|
|
if (lines[i].flags & ML_EFFECT5)
|
|
ffloorflags |= FF_RIPPLE;
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 121: // TL water
|
|
ffloorflags = FF_EXISTS|FF_RENDERALL|FF_TRANSLUCENT|FF_SWIMMABLE|FF_BOTHPLANES|FF_ALLSIDES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
ffloorflags |= FF_DOUBLESHADOW;
|
|
if (lines[i].flags & ML_EFFECT4)
|
|
ffloorflags |= FF_COLORMAPONLY;
|
|
if (lines[i].flags & ML_EFFECT5)
|
|
ffloorflags |= FF_RIPPLE;
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 122: // Opaque water, no sides
|
|
ffloorflags = FF_EXISTS|FF_RENDERPLANES|FF_SWIMMABLE|FF_BOTHPLANES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
ffloorflags |= FF_DOUBLESHADOW;
|
|
if (lines[i].flags & ML_EFFECT4)
|
|
ffloorflags |= FF_COLORMAPONLY;
|
|
if (lines[i].flags & ML_EFFECT5)
|
|
ffloorflags |= FF_RIPPLE;
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 123: // TL water, no sides
|
|
ffloorflags = FF_EXISTS|FF_RENDERPLANES|FF_TRANSLUCENT|FF_SWIMMABLE|FF_BOTHPLANES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
ffloorflags |= FF_DOUBLESHADOW;
|
|
if (lines[i].flags & ML_EFFECT4)
|
|
ffloorflags |= FF_COLORMAPONLY;
|
|
if (lines[i].flags & ML_EFFECT5)
|
|
ffloorflags |= FF_RIPPLE;
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 124: // goo water
|
|
ffloorflags = FF_EXISTS|FF_RENDERALL|FF_TRANSLUCENT|FF_SWIMMABLE|FF_GOOWATER|FF_BOTHPLANES|FF_ALLSIDES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
ffloorflags |= FF_DOUBLESHADOW;
|
|
if (lines[i].flags & ML_EFFECT4)
|
|
ffloorflags |= FF_COLORMAPONLY;
|
|
if (lines[i].flags & ML_EFFECT5)
|
|
ffloorflags |= FF_RIPPLE;
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 125: // goo water, no sides
|
|
ffloorflags = FF_EXISTS|FF_RENDERPLANES|FF_TRANSLUCENT|FF_SWIMMABLE|FF_GOOWATER|FF_BOTHPLANES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
ffloorflags |= FF_DOUBLESHADOW;
|
|
if (lines[i].flags & ML_EFFECT4)
|
|
ffloorflags |= FF_COLORMAPONLY;
|
|
if (lines[i].flags & ML_EFFECT5)
|
|
ffloorflags |= FF_RIPPLE;
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 140: // 'Platform' - You can jump up through it
|
|
// If line has no-climb set, don't give it shadows, otherwise do
|
|
ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_PLATFORM|FF_BOTHPLANES|FF_ALLSIDES;
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
ffloorflags |= FF_NOSHADE;
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 141: // Translucent "platform"
|
|
// If line has no-climb set, don't give it shadows, otherwise do
|
|
ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_PLATFORM|FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA;
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
ffloorflags |= FF_NOSHADE;
|
|
|
|
// Draw the 'insides' of the block too
|
|
if (lines[i].flags & ML_EFFECT2)
|
|
{
|
|
ffloorflags |= FF_CUTLEVEL;
|
|
ffloorflags |= FF_BOTHPLANES;
|
|
ffloorflags |= FF_ALLSIDES;
|
|
ffloorflags &= ~FF_EXTRA;
|
|
ffloorflags &= ~FF_CUTEXTRA;
|
|
}
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 142: // Translucent "platform" with no sides
|
|
ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERPLANES|FF_TRANSLUCENT|FF_PLATFORM|FF_EXTRA|FF_CUTEXTRA;
|
|
if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
|
|
ffloorflags |= FF_NOSHADE;
|
|
|
|
// Draw the 'insides' of the block too
|
|
if (lines[i].flags & ML_EFFECT2)
|
|
{
|
|
ffloorflags |= FF_CUTLEVEL;
|
|
ffloorflags |= FF_BOTHPLANES;
|
|
ffloorflags |= FF_ALLSIDES;
|
|
ffloorflags &= ~FF_EXTRA;
|
|
ffloorflags &= ~FF_CUTEXTRA;
|
|
}
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 143: // 'Reverse platform' - You fall through it
|
|
// If line has no-climb set, don't give it shadows, otherwise do
|
|
ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_REVERSEPLATFORM|FF_BOTHPLANES|FF_ALLSIDES;
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
ffloorflags |= FF_NOSHADE;
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 144: // Translucent "reverse platform"
|
|
// If line has no-climb set, don't give it shadows, otherwise do
|
|
ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_REVERSEPLATFORM|FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA;
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
ffloorflags |= FF_NOSHADE;
|
|
|
|
// Draw the 'insides' of the block too
|
|
if (lines[i].flags & ML_EFFECT2)
|
|
{
|
|
ffloorflags |= FF_CUTLEVEL;
|
|
ffloorflags |= FF_BOTHPLANES;
|
|
ffloorflags |= FF_ALLSIDES;
|
|
ffloorflags &= ~FF_EXTRA;
|
|
ffloorflags &= ~FF_CUTEXTRA;
|
|
}
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 145: // Translucent "reverse platform" with no sides
|
|
ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERPLANES|FF_TRANSLUCENT|FF_REVERSEPLATFORM|FF_EXTRA|FF_CUTEXTRA;
|
|
if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
|
|
ffloorflags |= FF_NOSHADE;
|
|
|
|
// Draw the 'insides' of the block too
|
|
if (lines[i].flags & ML_EFFECT2)
|
|
{
|
|
ffloorflags |= FF_CUTLEVEL;
|
|
ffloorflags |= FF_BOTHPLANES;
|
|
ffloorflags |= FF_ALLSIDES;
|
|
ffloorflags &= ~FF_EXTRA;
|
|
ffloorflags &= ~FF_CUTEXTRA;
|
|
}
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 146: // Intangible floor/ceiling with solid sides (fences/hoops maybe?)
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERSIDES|FF_ALLSIDES|FF_INTANGABLEFLATS, secthinkers);
|
|
break;
|
|
|
|
case 150: // Air bobbing platform
|
|
case 151: // Adjustable air bobbing platform
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
|
|
lines[i].flags |= ML_BLOCKMONSTERS;
|
|
P_AddOldAirbob(lines[i].frontsector, lines + i, (lines[i].special != 151));
|
|
break;
|
|
case 152: // Adjustable air bobbing platform in reverse
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
|
|
P_AddOldAirbob(lines[i].frontsector, lines + i, true);
|
|
break;
|
|
|
|
case 160: // Float/bob platform
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_FLOATBOB, secthinkers);
|
|
break;
|
|
|
|
case 170: // Crumbling platform
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE, secthinkers);
|
|
break;
|
|
|
|
case 171: // Crumbling platform that will not return
|
|
P_AddFakeFloorsByLine(i,
|
|
FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE|FF_NORETURN, secthinkers);
|
|
break;
|
|
|
|
case 172: // "Platform" that crumbles and returns
|
|
ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_PLATFORM|FF_CRUMBLE|FF_BOTHPLANES|FF_ALLSIDES;
|
|
if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
|
|
ffloorflags |= FF_NOSHADE;
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 173: // "Platform" that crumbles and doesn't return
|
|
ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_PLATFORM|FF_CRUMBLE|FF_NORETURN|FF_BOTHPLANES|FF_ALLSIDES;
|
|
if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
|
|
ffloorflags |= FF_NOSHADE;
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 174: // Translucent "platform" that crumbles and returns
|
|
ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_PLATFORM|FF_CRUMBLE|FF_TRANSLUCENT|FF_BOTHPLANES|FF_ALLSIDES;
|
|
if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
|
|
ffloorflags |= FF_NOSHADE;
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 175: // Translucent "platform" that crumbles and doesn't return
|
|
ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_PLATFORM|FF_CRUMBLE|FF_NORETURN|FF_TRANSLUCENT|FF_BOTHPLANES|FF_ALLSIDES;
|
|
if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
|
|
ffloorflags |= FF_NOSHADE;
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 176: // Air bobbing platform that will crumble and bob on the water when it falls and hits
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_FLOATBOB|FF_CRUMBLE, secthinkers);
|
|
lines[i].flags |= ML_BLOCKMONSTERS;
|
|
P_AddOldAirbob(lines[i].frontsector, lines + i, true);
|
|
break;
|
|
|
|
case 177: // Air bobbing platform that will crumble and bob on
|
|
// the water when it falls and hits, then never return
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_FLOATBOB|FF_CRUMBLE|FF_NORETURN, secthinkers);
|
|
lines[i].flags |= ML_BLOCKMONSTERS;
|
|
P_AddOldAirbob(lines[i].frontsector, lines + i, true);
|
|
break;
|
|
|
|
case 178: // Crumbling platform that will float when it hits water
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CRUMBLE|FF_FLOATBOB, secthinkers);
|
|
break;
|
|
|
|
case 179: // Crumbling platform that will float when it hits water, but not return
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE|FF_FLOATBOB|FF_NORETURN, secthinkers);
|
|
break;
|
|
|
|
case 180: // Air bobbing platform that will crumble
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE, secthinkers);
|
|
lines[i].flags |= ML_BLOCKMONSTERS;
|
|
P_AddOldAirbob(lines[i].frontsector, lines + i, true);
|
|
break;
|
|
|
|
case 190: // Rising Platform FOF (solid, opaque, shadows)
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
|
|
P_AddRaiseThinker(lines[i].frontsector, &lines[i]);
|
|
break;
|
|
|
|
case 191: // Rising Platform FOF (solid, opaque, no shadows)
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_NOSHADE|FF_CUTLEVEL, secthinkers);
|
|
P_AddRaiseThinker(lines[i].frontsector, &lines[i]);
|
|
break;
|
|
|
|
case 192: // Rising Platform TL block: FOF (solid, translucent)
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_NOSHADE|FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA, secthinkers);
|
|
P_AddRaiseThinker(lines[i].frontsector, &lines[i]);
|
|
break;
|
|
|
|
case 193: // Rising Platform FOF (solid, invisible)
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_NOSHADE, secthinkers);
|
|
P_AddRaiseThinker(lines[i].frontsector, &lines[i]);
|
|
break;
|
|
|
|
case 194: // Rising Platform 'Platform' - You can jump up through it
|
|
// If line has no-climb set, don't give it shadows, otherwise do
|
|
ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_PLATFORM|FF_BOTHPLANES|FF_ALLSIDES;
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
ffloorflags |= FF_NOSHADE;
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
P_AddRaiseThinker(lines[i].frontsector, &lines[i]);
|
|
break;
|
|
|
|
case 195: // Rising Platform Translucent "platform"
|
|
// If line has no-climb set, don't give it shadows, otherwise do
|
|
ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_PLATFORM|FF_TRANSLUCENT|FF_BOTHPLANES|FF_ALLSIDES|FF_EXTRA|FF_CUTEXTRA;
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
ffloorflags |= FF_NOSHADE;
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
P_AddRaiseThinker(lines[i].frontsector, &lines[i]);
|
|
break;
|
|
|
|
case 200: // Double light effect
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_CUTSPRITES|FF_DOUBLESHADOW, secthinkers);
|
|
break;
|
|
|
|
case 201: // Light effect
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_CUTSPRITES, secthinkers);
|
|
break;
|
|
|
|
case 202: // Fog
|
|
ffloorflags = FF_EXISTS|FF_RENDERALL|FF_FOG|FF_BOTHPLANES|FF_INVERTPLANES|FF_ALLSIDES|FF_INVERTSIDES|FF_CUTEXTRA|FF_EXTRA|FF_DOUBLESHADOW|FF_CUTSPRITES;
|
|
sec = sides[*lines[i].sidenum].sector - sectors;
|
|
// SoM: Because it's fog, check for an extra colormap and set
|
|
// the fog flag...
|
|
if (sectors[sec].extra_colormap)
|
|
sectors[sec].extra_colormap->fog = 1;
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 220: // Like opaque water, but not swimmable. (Good for snow effect on FOFs)
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_RENDERALL|FF_BOTHPLANES|FF_ALLSIDES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES, secthinkers);
|
|
break;
|
|
|
|
case 221: // FOF (intangible, translucent)
|
|
// If line has no-climb set, give it shadows, otherwise don't
|
|
ffloorflags = FF_EXISTS|FF_RENDERALL|FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA|FF_CUTSPRITES;
|
|
if (!(lines[i].flags & ML_NOCLIMB))
|
|
ffloorflags |= FF_NOSHADE;
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 222: // FOF with no floor/ceiling (good for GFZGRASS effect on FOFs)
|
|
// If line has no-climb set, give it shadows, otherwise don't
|
|
ffloorflags = FF_EXISTS|FF_RENDERSIDES|FF_ALLSIDES;
|
|
if (!(lines[i].flags & ML_NOCLIMB))
|
|
ffloorflags |= FF_NOSHADE|FF_CUTSPRITES;
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
|
|
break;
|
|
|
|
case 223: // FOF (intangible, invisible) - for combining specials in a sector
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_NOSHADE, secthinkers);
|
|
break;
|
|
|
|
case 250: // Mario Block
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_MARIO, secthinkers);
|
|
break;
|
|
|
|
case 251: // A THWOMP!
|
|
sec = sides[*lines[i].sidenum].sector - sectors;
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
{
|
|
P_AddThwompThinker(§ors[sec], §ors[s], &lines[i]);
|
|
P_AddFakeFloor(§ors[s], §ors[sec], lines + i,
|
|
FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
|
|
}
|
|
break;
|
|
|
|
case 252: // Shatter block (breaks when touched)
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_BUSTUP|FF_SHATTER|FF_SHATTERBOTTOM, secthinkers);
|
|
else
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_RENDERALL|FF_BUSTUP|FF_SHATTER, secthinkers);
|
|
break;
|
|
|
|
case 253: // Translucent shatter block (see 76)
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_RENDERALL|FF_BUSTUP|FF_SHATTER|FF_TRANSLUCENT, secthinkers);
|
|
break;
|
|
|
|
case 254: // Bustable block
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_BUSTUP|FF_ONLYKNUX, secthinkers);
|
|
else
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_BUSTUP, secthinkers);
|
|
break;
|
|
|
|
case 255: // Spin bust block (breaks when jumped or spun downwards onto)
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_BUSTUP|FF_SPINBUST, secthinkers);
|
|
break;
|
|
|
|
case 256: // Translucent spin bust block (see 78)
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_BUSTUP|FF_SPINBUST|FF_TRANSLUCENT, secthinkers);
|
|
break;
|
|
|
|
case 257: // Quicksand
|
|
if (lines[i].flags & ML_EFFECT5)
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_QUICKSAND|FF_RENDERALL|FF_ALLSIDES|FF_CUTSPRITES|FF_RIPPLE, secthinkers);
|
|
else
|
|
P_AddFakeFloorsByLine(i, FF_EXISTS|FF_QUICKSAND|FF_RENDERALL|FF_ALLSIDES|FF_CUTSPRITES, secthinkers);
|
|
break;
|
|
|
|
case 258: // Laser block
|
|
sec = sides[*lines[i].sidenum].sector - sectors;
|
|
|
|
// No longer totally disrupts netgames
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
EV_AddLaserThinker(§ors[s], §ors[sec], lines + i, secthinkers);
|
|
break;
|
|
|
|
case 259: // Make-Your-Own FOF!
|
|
if (lines[i].sidenum[1] != 0xffff)
|
|
{
|
|
UINT8 *data = W_CacheLumpNum(lastloadedmaplumpnum + ML_SIDEDEFS,PU_STATIC);
|
|
UINT16 b;
|
|
|
|
for (b = 0; b < (INT16)numsides; b++)
|
|
{
|
|
register mapsidedef_t *msd = (mapsidedef_t *)data + b;
|
|
|
|
if (b == lines[i].sidenum[1])
|
|
{
|
|
if ((msd->toptexture[0] >= '0' && msd->toptexture[0] <= '9')
|
|
|| (msd->toptexture[0] >= 'A' && msd->toptexture[0] <= 'F'))
|
|
{
|
|
ffloortype_e FOF_Flags = axtoi(msd->toptexture);
|
|
|
|
P_AddFakeFloorsByLine(i, FOF_Flags, secthinkers);
|
|
break;
|
|
}
|
|
else
|
|
I_Error("Make-Your-Own-FOF (tag %d) needs a value in the linedef's second side upper texture field.", lines[i].tag);
|
|
}
|
|
}
|
|
Z_Free(data);
|
|
}
|
|
else
|
|
I_Error("Make-Your-Own FOF (tag %d) found without a 2nd linedef side!", lines[i].tag);
|
|
break;
|
|
|
|
case 300: // Linedef executor (combines with sector special 974/975) and commands
|
|
case 302:
|
|
case 303:
|
|
case 304:
|
|
|
|
// Charability linedef executors
|
|
case 305:
|
|
case 307:
|
|
break;
|
|
|
|
case 308: // Race-only linedef executor. Triggers once.
|
|
if (gametype != GT_RACE && gametype != GT_COMPETITION)
|
|
lines[i].special = 0;
|
|
break;
|
|
|
|
// Linedef executor triggers for CTF teams.
|
|
case 309:
|
|
case 311:
|
|
if (gametype != GT_CTF)
|
|
lines[i].special = 0;
|
|
break;
|
|
|
|
// Each time executors
|
|
case 306:
|
|
case 301:
|
|
case 310:
|
|
case 312:
|
|
sec = sides[*lines[i].sidenum].sector - sectors;
|
|
P_AddEachTimeThinker(§ors[sec], &lines[i]);
|
|
break;
|
|
|
|
// No More Enemies Linedef Exec
|
|
case 313:
|
|
sec = sides[*lines[i].sidenum].sector - sectors;
|
|
P_AddNoEnemiesThinker(§ors[sec], &lines[i]);
|
|
break;
|
|
|
|
// Pushable linedef executors (count # of pushables)
|
|
case 314:
|
|
case 315:
|
|
break;
|
|
|
|
// Unlock trigger executors
|
|
case 317:
|
|
case 318:
|
|
break;
|
|
case 319:
|
|
case 320:
|
|
break;
|
|
|
|
// Trigger on X calls
|
|
case 321:
|
|
case 322:
|
|
if (lines[i].flags & ML_NOCLIMB && sides[lines[i].sidenum[0]].rowoffset > 0) // optional "starting" count
|
|
lines[i].callcount = sides[lines[i].sidenum[0]].rowoffset>>FRACBITS;
|
|
else
|
|
lines[i].callcount = sides[lines[i].sidenum[0]].textureoffset>>FRACBITS;
|
|
if (lines[i].special == 322) // Each time
|
|
{
|
|
sec = sides[*lines[i].sidenum].sector - sectors;
|
|
P_AddEachTimeThinker(§ors[sec], &lines[i]);
|
|
}
|
|
break;
|
|
|
|
case 399: // Linedef execute on map load
|
|
// This is handled in P_RunLevelLoadExecutors.
|
|
break;
|
|
|
|
case 400:
|
|
case 401:
|
|
case 402:
|
|
case 403:
|
|
case 404:
|
|
case 405:
|
|
case 406:
|
|
case 407:
|
|
case 408:
|
|
case 409:
|
|
case 410:
|
|
case 411:
|
|
case 412:
|
|
case 413:
|
|
case 414:
|
|
case 415:
|
|
case 416:
|
|
case 417:
|
|
case 418:
|
|
case 419:
|
|
case 420:
|
|
case 421:
|
|
case 422:
|
|
case 423:
|
|
case 424:
|
|
case 425:
|
|
case 426:
|
|
case 427:
|
|
case 428:
|
|
case 429:
|
|
case 430:
|
|
case 431:
|
|
break;
|
|
|
|
// 500 is used for a scroller
|
|
// 501 is used for a scroller
|
|
// 502 is used for a scroller
|
|
// 503 is used for a scroller
|
|
// 504 is used for a scroller
|
|
// 505 is used for a scroller
|
|
// 510 is used for a scroller
|
|
// 511 is used for a scroller
|
|
// 512 is used for a scroller
|
|
// 513 is used for a scroller
|
|
// 514 is used for a scroller
|
|
// 515 is used for a scroller
|
|
// 520 is used for a scroller
|
|
// 521 is used for a scroller
|
|
// 522 is used for a scroller
|
|
// 523 is used for a scroller
|
|
// 524 is used for a scroller
|
|
// 525 is used for a scroller
|
|
// 530 is used for a scroller
|
|
// 531 is used for a scroller
|
|
// 532 is used for a scroller
|
|
// 533 is used for a scroller
|
|
// 534 is used for a scroller
|
|
// 535 is used for a scroller
|
|
// 540 is used for friction
|
|
// 541 is used for wind
|
|
// 542 is used for upwards wind
|
|
// 543 is used for downwards wind
|
|
// 544 is used for current
|
|
// 545 is used for upwards current
|
|
// 546 is used for downwards current
|
|
// 547 is used for push/pull
|
|
|
|
case 600: // floor lighting independently (e.g. lava)
|
|
sec = sides[*lines[i].sidenum].sector-sectors;
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
sectors[s].floorlightsec = (INT32)sec;
|
|
break;
|
|
|
|
case 601: // ceiling lighting independently
|
|
sec = sides[*lines[i].sidenum].sector-sectors;
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
sectors[s].ceilinglightsec = (INT32)sec;
|
|
break;
|
|
|
|
case 602: // Adjustable pulsating light
|
|
sec = sides[*lines[i].sidenum].sector - sectors;
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
P_SpawnAdjustableGlowingLight(§ors[sec], §ors[s],
|
|
P_AproxDistance(lines[i].dx, lines[i].dy)>>FRACBITS);
|
|
break;
|
|
|
|
case 603: // Adjustable flickering light
|
|
sec = sides[*lines[i].sidenum].sector - sectors;
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
P_SpawnAdjustableFireFlicker(§ors[sec], §ors[s],
|
|
P_AproxDistance(lines[i].dx, lines[i].dy)>>FRACBITS);
|
|
break;
|
|
|
|
case 604: // Adjustable Blinking Light (unsynchronized)
|
|
sec = sides[*lines[i].sidenum].sector - sectors;
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
P_SpawnAdjustableStrobeFlash(§ors[sec], §ors[s],
|
|
abs(lines[i].dx)>>FRACBITS, abs(lines[i].dy)>>FRACBITS, false);
|
|
break;
|
|
|
|
case 605: // Adjustable Blinking Light (synchronized)
|
|
sec = sides[*lines[i].sidenum].sector - sectors;
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
P_SpawnAdjustableStrobeFlash(§ors[sec], §ors[s],
|
|
abs(lines[i].dx)>>FRACBITS, abs(lines[i].dy)>>FRACBITS, true);
|
|
break;
|
|
|
|
case 606: // HACK! Copy colormaps. Just plain colormaps.
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
sectors[s].midmap = lines[i].frontsector->midmap;
|
|
break;
|
|
|
|
#ifdef ESLOPE // Slope copy specials. Handled here for sanity.
|
|
case 720:
|
|
case 721:
|
|
case 722:
|
|
P_CopySectorSlope(&lines[i]);
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Allocate each list
|
|
for (i = 0; i < numsectors; i++)
|
|
if(secthinkers[i].thinkers)
|
|
Z_Free(secthinkers[i].thinkers);
|
|
|
|
Z_Free(secthinkers);
|
|
|
|
#ifdef POLYOBJECTS
|
|
// haleyjd 02/20/06: spawn polyobjects
|
|
Polyobj_InitLevel();
|
|
|
|
for (i = 0; i < numlines; i++)
|
|
{
|
|
switch (lines[i].special)
|
|
{
|
|
case 30: // Polyobj_Flag
|
|
EV_DoPolyObjFlag(&lines[i]);
|
|
break;
|
|
|
|
case 31: // Polyobj_Displace
|
|
PolyDisplace(&lines[i]);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
P_RunLevelLoadExecutors();
|
|
}
|
|
|
|
/** Adds 3Dfloors as appropriate based on a common control linedef.
|
|
*
|
|
* \param line Control linedef to use.
|
|
* \param ffloorflags 3Dfloor flags to use.
|
|
* \param secthkiners Lists of thinkers sorted by sector. May be NULL.
|
|
* \sa P_SpawnSpecials, P_AddFakeFloor
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
static void P_AddFakeFloorsByLine(size_t line, ffloortype_e ffloorflags, thinkerlist_t *secthinkers)
|
|
{
|
|
INT32 s;
|
|
size_t sec = sides[*lines[line].sidenum].sector-sectors;
|
|
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines+line, s)) >= 0 ;)
|
|
P_AddFakeFloor(§ors[s], §ors[sec], lines+line, ffloorflags, secthinkers);
|
|
}
|
|
|
|
/*
|
|
SoM: 3/8/2000: General scrolling functions.
|
|
T_Scroll,
|
|
Add_Scroller,
|
|
Add_WallScroller,
|
|
P_SpawnScrollers
|
|
*/
|
|
|
|
// helper function for T_Scroll
|
|
static void P_DoScrollMove(mobj_t *thing, fixed_t dx, fixed_t dy, INT32 exclusive)
|
|
{
|
|
fixed_t fuckaj = 0; // Nov 05 14:12:08 <+MonsterIestyn> I've heard of explicitly defined variables but this is ridiculous
|
|
if (thing->player)
|
|
{
|
|
if (!(dx | dy))
|
|
{
|
|
thing->player->cmomx = 0;
|
|
thing->player->cmomy = 0;
|
|
}
|
|
else
|
|
{
|
|
thing->player->cmomx += dx;
|
|
thing->player->cmomy += dy;
|
|
thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
|
|
thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
|
|
}
|
|
}
|
|
|
|
if (thing->player && (thing->player->pflags & PF_SPINNING) && (thing->player->rmomx || thing->player->rmomy) && !(thing->player->pflags & PF_STARTDASH))
|
|
fuckaj = FixedDiv(549*ORIG_FRICTION,500*FRACUNIT);
|
|
else if (thing->friction != ORIG_FRICTION)
|
|
fuckaj = thing->friction;
|
|
|
|
if (fuckaj) {
|
|
// refactor thrust for new friction
|
|
dx = FixedDiv(dx, CARRYFACTOR);
|
|
dy = FixedDiv(dy, CARRYFACTOR);
|
|
|
|
dx = FixedMul(dx, FRACUNIT-fuckaj);
|
|
dy = FixedMul(dy, FRACUNIT-fuckaj);
|
|
}
|
|
|
|
thing->momx += dx;
|
|
thing->momy += dy;
|
|
|
|
if (exclusive)
|
|
thing->flags2 |= MF2_PUSHED;
|
|
}
|
|
|
|
/** Processes an active scroller.
|
|
* This function, with the help of r_plane.c and r_bsp.c, supports generalized
|
|
* scrolling floors and walls, with optional mobj-carrying properties, e.g.
|
|
* conveyor belts, rivers, etc. A linedef with a special type affects all
|
|
* tagged sectors the same way, by creating scrolling and/or object-carrying
|
|
* properties. Multiple linedefs may be used on the same sector and are
|
|
* cumulative, although the special case of scrolling a floor and carrying
|
|
* things on it requires only one linedef.
|
|
*
|
|
* The linedef's direction determines the scrolling direction, and the
|
|
* linedef's length determines the scrolling speed. This was designed so an
|
|
* edge around a sector can be used to control the direction of the sector's
|
|
* scrolling, which is usually what is desired.
|
|
*
|
|
* \param s Thinker for the scroller to process.
|
|
* \todo Split up into multiple functions.
|
|
* \todo Use attached lists to make ::sc_carry_ceiling case faster and
|
|
* cleaner.
|
|
* \sa Add_Scroller, Add_WallScroller, P_SpawnScrollers
|
|
* \author Steven McGranahan
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
void T_Scroll(scroll_t *s)
|
|
{
|
|
fixed_t dx = s->dx, dy = s->dy;
|
|
boolean is3dblock = false;
|
|
|
|
if (s->control != -1)
|
|
{ // compute scroll amounts based on a sector's height changes
|
|
fixed_t height = sectors[s->control].floorheight +
|
|
sectors[s->control].ceilingheight;
|
|
fixed_t delta = height - s->last_height;
|
|
s->last_height = height;
|
|
dx = FixedMul(dx, delta);
|
|
dy = FixedMul(dy, delta);
|
|
}
|
|
|
|
if (s->accel)
|
|
{
|
|
s->vdx = dx += s->vdx;
|
|
s->vdy = dy += s->vdy;
|
|
}
|
|
|
|
// if (!(dx | dy)) // no-op if both (x,y) offsets 0
|
|
// return;
|
|
|
|
switch (s->type)
|
|
{
|
|
side_t *side;
|
|
sector_t *sec;
|
|
fixed_t height;
|
|
msecnode_t *node;
|
|
mobj_t *thing;
|
|
line_t *line;
|
|
size_t i;
|
|
INT32 sect;
|
|
|
|
case sc_side: // scroll wall texture
|
|
side = sides + s->affectee;
|
|
side->textureoffset += dx;
|
|
side->rowoffset += dy;
|
|
break;
|
|
|
|
case sc_floor: // scroll floor texture
|
|
sec = sectors + s->affectee;
|
|
sec->floor_xoffs += dx;
|
|
sec->floor_yoffs += dy;
|
|
break;
|
|
|
|
case sc_ceiling: // scroll ceiling texture
|
|
sec = sectors + s->affectee;
|
|
sec->ceiling_xoffs += dx;
|
|
sec->ceiling_yoffs += dy;
|
|
break;
|
|
|
|
case sc_carry:
|
|
sec = sectors + s->affectee;
|
|
height = sec->floorheight;
|
|
|
|
// sec is the control sector, find the real sector(s) to use
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
line = sec->lines[i];
|
|
|
|
if (line->special < 100 || line->special >= 300)
|
|
is3dblock = false;
|
|
else
|
|
is3dblock = true;
|
|
|
|
if (!is3dblock)
|
|
continue;
|
|
|
|
for (sect = -1; (sect = P_FindSectorFromTag(line->tag, sect)) >= 0 ;)
|
|
{
|
|
sector_t *psec;
|
|
psec = sectors + sect;
|
|
|
|
for (node = psec->touching_thinglist; node; node = node->m_snext)
|
|
{
|
|
thing = node->m_thing;
|
|
|
|
if (thing->flags2 & MF2_PUSHED) // Already pushed this tic by an exclusive pusher.
|
|
continue;
|
|
|
|
height = P_GetSpecialBottomZ(thing, sec, psec);
|
|
|
|
if (!(thing->flags & MF_NOCLIP)) // Thing must be clipped
|
|
if (!(thing->flags & MF_NOGRAVITY || thing->z+thing->height != height)) // Thing must a) be non-floating and have z+height == height
|
|
{
|
|
// Move objects only if on floor
|
|
// non-floating, and clipped.
|
|
P_DoScrollMove(thing, dx, dy, s->exclusive);
|
|
}
|
|
} // end of for loop through touching_thinglist
|
|
} // end of loop through sectors
|
|
}
|
|
|
|
if (!is3dblock)
|
|
{
|
|
for (node = sec->touching_thinglist; node; node = node->m_snext)
|
|
{
|
|
thing = node->m_thing;
|
|
|
|
if (thing->flags2 & MF2_PUSHED)
|
|
continue;
|
|
|
|
height = P_GetSpecialBottomZ(thing, sec, sec);
|
|
|
|
if (!(thing->flags & MF_NOCLIP) &&
|
|
(!(thing->flags & MF_NOGRAVITY || thing->z > height)))
|
|
{
|
|
// Move objects only if on floor or underwater,
|
|
// non-floating, and clipped.
|
|
P_DoScrollMove(thing, dx, dy, s->exclusive);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case sc_carry_ceiling: // carry on ceiling (FOF scrolling)
|
|
sec = sectors + s->affectee;
|
|
height = sec->ceilingheight;
|
|
|
|
// sec is the control sector, find the real sector(s) to use
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
line = sec->lines[i];
|
|
if (line->special < 100 || line->special >= 300)
|
|
is3dblock = false;
|
|
else
|
|
is3dblock = true;
|
|
|
|
if (!is3dblock)
|
|
continue;
|
|
|
|
for (sect = -1; (sect = P_FindSectorFromTag(line->tag, sect)) >= 0 ;)
|
|
{
|
|
sector_t *psec;
|
|
psec = sectors + sect;
|
|
|
|
for (node = psec->touching_thinglist; node; node = node->m_snext)
|
|
{
|
|
thing = node->m_thing;
|
|
|
|
if (thing->flags2 & MF2_PUSHED)
|
|
continue;
|
|
|
|
height = P_GetSpecialTopZ(thing, sec, psec);
|
|
|
|
if (!(thing->flags & MF_NOCLIP)) // Thing must be clipped
|
|
if (!(thing->flags & MF_NOGRAVITY || thing->z != height))// Thing must a) be non-floating and have z == height
|
|
{
|
|
// Move objects only if on floor or underwater,
|
|
// non-floating, and clipped.
|
|
P_DoScrollMove(thing, dx, dy, s->exclusive);
|
|
}
|
|
} // end of for loop through touching_thinglist
|
|
} // end of loop through sectors
|
|
}
|
|
|
|
if (!is3dblock)
|
|
{
|
|
for (node = sec->touching_thinglist; node; node = node->m_snext)
|
|
{
|
|
thing = node->m_thing;
|
|
|
|
if (thing->flags2 & MF2_PUSHED)
|
|
continue;
|
|
|
|
height = P_GetSpecialTopZ(thing, sec, sec);
|
|
|
|
if (!(thing->flags & MF_NOCLIP) &&
|
|
(!(thing->flags & MF_NOGRAVITY || thing->z+thing->height < height)))
|
|
{
|
|
// Move objects only if on floor or underwater,
|
|
// non-floating, and clipped.
|
|
P_DoScrollMove(thing, dx, dy, s->exclusive);
|
|
}
|
|
}
|
|
}
|
|
break; // end of sc_carry_ceiling
|
|
} // end of switch
|
|
}
|
|
|
|
/** Adds a generalized scroller to the thinker list.
|
|
*
|
|
* \param type The enumerated type of scrolling.
|
|
* \param dx x speed of scrolling or its acceleration.
|
|
* \param dy y speed of scrolling or its acceleration.
|
|
* \param control Sector whose heights control this scroller's effect
|
|
* remotely, or -1 if there is no control sector.
|
|
* \param affectee Index of the affected object, sector or sidedef.
|
|
* \param accel Nonzero for an accelerative effect.
|
|
* \sa Add_WallScroller, P_SpawnScrollers, T_Scroll
|
|
*/
|
|
static void Add_Scroller(INT32 type, fixed_t dx, fixed_t dy, INT32 control, INT32 affectee, INT32 accel, INT32 exclusive)
|
|
{
|
|
scroll_t *s = Z_Calloc(sizeof *s, PU_LEVSPEC, NULL);
|
|
s->thinker.function.acp1 = (actionf_p1)T_Scroll;
|
|
s->type = type;
|
|
s->dx = dx;
|
|
s->dy = dy;
|
|
s->accel = accel;
|
|
s->exclusive = exclusive;
|
|
s->vdx = s->vdy = 0;
|
|
if ((s->control = control) != -1)
|
|
s->last_height = sectors[control].floorheight + sectors[control].ceilingheight;
|
|
s->affectee = affectee;
|
|
P_AddThinker(&s->thinker);
|
|
}
|
|
|
|
/** Adds a wall scroller.
|
|
* Scroll amount is rotated with respect to wall's linedef first, so that
|
|
* scrolling towards the wall in a perpendicular direction is translated into
|
|
* vertical motion, while scrolling along the wall in a parallel direction is
|
|
* translated into horizontal motion.
|
|
*
|
|
* \param dx x speed of scrolling or its acceleration.
|
|
* \param dy y speed of scrolling or its acceleration.
|
|
* \param l Line whose front side will scroll.
|
|
* \param control Sector whose heights control this scroller's effect
|
|
* remotely, or -1 if there is no control sector.
|
|
* \param accel Nonzero for an accelerative effect.
|
|
* \sa Add_Scroller, P_SpawnScrollers
|
|
*/
|
|
static void Add_WallScroller(fixed_t dx, fixed_t dy, const line_t *l, INT32 control, INT32 accel)
|
|
{
|
|
fixed_t x = abs(l->dx), y = abs(l->dy), d;
|
|
if (y > x)
|
|
d = x, x = y, y = d;
|
|
d = FixedDiv(x, FINESINE((tantoangle[FixedDiv(y, x) >> DBITS] + ANGLE_90) >> ANGLETOFINESHIFT));
|
|
x = -FixedDiv(FixedMul(dy, l->dy) + FixedMul(dx, l->dx), d);
|
|
y = -FixedDiv(FixedMul(dx, l->dy) - FixedMul(dy, l->dx), d);
|
|
Add_Scroller(sc_side, x, y, control, *l->sidenum, accel, 0);
|
|
}
|
|
|
|
/** Initializes the scrollers.
|
|
*
|
|
* \todo Get rid of all the magic numbers.
|
|
* \sa P_SpawnSpecials, Add_Scroller, Add_WallScroller
|
|
*/
|
|
static void P_SpawnScrollers(void)
|
|
{
|
|
size_t i;
|
|
line_t *l = lines;
|
|
|
|
for (i = 0; i < numlines; i++, l++)
|
|
{
|
|
fixed_t dx = l->dx >> SCROLL_SHIFT; // direction and speed of scrolling
|
|
fixed_t dy = l->dy >> SCROLL_SHIFT;
|
|
INT32 control = -1, accel = 0; // no control sector or acceleration
|
|
INT32 special = l->special;
|
|
|
|
// These types are same as the ones they get set to except that the
|
|
// first side's sector's heights cause scrolling when they change, and
|
|
// this linedef controls the direction and speed of the scrolling. The
|
|
// most complicated linedef since donuts, but powerful :)
|
|
|
|
if (special == 515 || special == 512 || special == 522 || special == 532 || special == 504) // displacement scrollers
|
|
{
|
|
special -= 2;
|
|
control = (INT32)(sides[*l->sidenum].sector - sectors);
|
|
}
|
|
else if (special == 514 || special == 511 || special == 521 || special == 531 || special == 503) // accelerative scrollers
|
|
{
|
|
special--;
|
|
accel = 1;
|
|
control = (INT32)(sides[*l->sidenum].sector - sectors);
|
|
}
|
|
else if (special == 535 || special == 525) // displacement scrollers
|
|
{
|
|
special -= 2;
|
|
control = (INT32)(sides[*l->sidenum].sector - sectors);
|
|
}
|
|
else if (special == 534 || special == 524) // accelerative scrollers
|
|
{
|
|
accel = 1;
|
|
special--;
|
|
control = (INT32)(sides[*l->sidenum].sector - sectors);
|
|
}
|
|
|
|
switch (special)
|
|
{
|
|
register INT32 s;
|
|
|
|
case 513: // scroll effect ceiling
|
|
case 533: // scroll and carry objects on ceiling
|
|
for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
|
|
Add_Scroller(sc_ceiling, -dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
|
|
if (special != 533)
|
|
break;
|
|
|
|
case 523: // carry objects on ceiling
|
|
dx = FixedMul(dx, CARRYFACTOR);
|
|
dy = FixedMul(dy, CARRYFACTOR);
|
|
for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
|
|
Add_Scroller(sc_carry_ceiling, dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
|
|
break;
|
|
|
|
case 510: // scroll effect floor
|
|
case 530: // scroll and carry objects on floor
|
|
for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
|
|
Add_Scroller(sc_floor, -dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
|
|
if (special != 530)
|
|
break;
|
|
|
|
case 520: // carry objects on floor
|
|
dx = FixedMul(dx, CARRYFACTOR);
|
|
dy = FixedMul(dy, CARRYFACTOR);
|
|
for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
|
|
Add_Scroller(sc_carry, dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
|
|
break;
|
|
|
|
// scroll wall according to linedef
|
|
// (same direction and speed as scrolling floors)
|
|
case 502:
|
|
for (s = -1; (s = P_FindLineFromLineTag(l, s)) >= 0 ;)
|
|
if (s != (INT32)i)
|
|
Add_WallScroller(dx, dy, lines+s, control, accel);
|
|
break;
|
|
|
|
case 505:
|
|
s = lines[i].sidenum[0];
|
|
Add_Scroller(sc_side, -sides[s].textureoffset, sides[s].rowoffset, -1, s, accel, 0);
|
|
break;
|
|
|
|
case 506:
|
|
s = lines[i].sidenum[1];
|
|
|
|
if (s != 0xffff)
|
|
Add_Scroller(sc_side, -sides[s].textureoffset, sides[s].rowoffset, -1, lines[i].sidenum[0], accel, 0);
|
|
else
|
|
CONS_Debug(DBG_GAMELOGIC, "Line special 506 (line #%s) missing 2nd side!\n", sizeu1(i));
|
|
break;
|
|
|
|
case 500: // scroll first side
|
|
Add_Scroller(sc_side, FRACUNIT, 0, -1, lines[i].sidenum[0], accel, 0);
|
|
break;
|
|
|
|
case 501: // jff 1/30/98 2-way scroll
|
|
Add_Scroller(sc_side, -FRACUNIT, 0, -1, lines[i].sidenum[0], accel, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Adds master appear/disappear thinker.
|
|
*
|
|
* \param appeartime tics to be existent
|
|
* \param disappeartime tics to be nonexistent
|
|
* \param sector pointer to control sector
|
|
*/
|
|
static void Add_MasterDisappearer(tic_t appeartime, tic_t disappeartime, tic_t offset, INT32 line, INT32 sourceline)
|
|
{
|
|
disappear_t *d = Z_Malloc(sizeof *d, PU_LEVSPEC, NULL);
|
|
|
|
d->thinker.function.acp1 = (actionf_p1)T_Disappear;
|
|
d->appeartime = appeartime;
|
|
d->disappeartime = disappeartime;
|
|
d->offset = offset;
|
|
d->affectee = line;
|
|
d->sourceline = sourceline;
|
|
d->exists = true;
|
|
d->timer = 1;
|
|
|
|
P_AddThinker(&d->thinker);
|
|
}
|
|
|
|
/** Makes a FOF appear/disappear
|
|
*
|
|
* \param d Disappear thinker.
|
|
* \sa Add_MasterDisappearer
|
|
*/
|
|
void T_Disappear(disappear_t *d)
|
|
{
|
|
if (d->offset && !d->exists)
|
|
{
|
|
d->offset--;
|
|
return;
|
|
}
|
|
|
|
if (--d->timer <= 0)
|
|
{
|
|
ffloor_t *rover;
|
|
register INT32 s;
|
|
|
|
for (s = -1; (s = P_FindSectorFromLineTag(&lines[d->affectee], s)) >= 0 ;)
|
|
{
|
|
for (rover = sectors[s].ffloors; rover; rover = rover->next)
|
|
{
|
|
if (rover->master != &lines[d->affectee])
|
|
continue;
|
|
|
|
if (d->exists)
|
|
rover->flags &= ~FF_EXISTS;
|
|
else
|
|
{
|
|
rover->flags |= FF_EXISTS;
|
|
|
|
if (!(lines[d->sourceline].flags & ML_NOCLIMB))
|
|
{
|
|
sectors[s].soundorg.z = *rover->topheight;
|
|
S_StartSound(§ors[s].soundorg, sfx_appear);
|
|
}
|
|
}
|
|
}
|
|
sectors[s].moved = true;
|
|
}
|
|
|
|
if (d->exists)
|
|
{
|
|
d->timer = d->disappeartime;
|
|
d->exists = false;
|
|
}
|
|
else
|
|
{
|
|
d->timer = d->appeartime;
|
|
d->exists = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
SoM: 3/8/2000: Friction functions start.
|
|
Add_Friction,
|
|
T_Friction,
|
|
P_SpawnFriction
|
|
*/
|
|
|
|
/** Adds friction thinker.
|
|
*
|
|
* \param friction Friction value, 0xe800 is normal.
|
|
* \param movefactor Inertia factor.
|
|
* \param affectee Target sector.
|
|
* \param roverfriction FOF or not
|
|
* \sa T_Friction, P_SpawnFriction
|
|
*/
|
|
static void Add_Friction(INT32 friction, INT32 movefactor, INT32 affectee, INT32 referrer)
|
|
{
|
|
friction_t *f = Z_Calloc(sizeof *f, PU_LEVSPEC, NULL);
|
|
|
|
f->thinker.function.acp1 = (actionf_p1)T_Friction;
|
|
f->friction = friction;
|
|
f->movefactor = movefactor;
|
|
f->affectee = affectee;
|
|
|
|
if (referrer != -1)
|
|
{
|
|
f->roverfriction = true;
|
|
f->referrer = referrer;
|
|
}
|
|
else
|
|
f->roverfriction = false;
|
|
|
|
P_AddThinker(&f->thinker);
|
|
}
|
|
|
|
/** Applies friction to all things in a sector.
|
|
*
|
|
* \param f Friction thinker.
|
|
* \sa Add_Friction
|
|
*/
|
|
void T_Friction(friction_t *f)
|
|
{
|
|
sector_t *sec, *referrer = NULL;
|
|
mobj_t *thing;
|
|
msecnode_t *node;
|
|
|
|
sec = sectors + f->affectee;
|
|
|
|
// Make sure the sector type hasn't changed
|
|
if (f->roverfriction)
|
|
{
|
|
referrer = sectors + f->referrer;
|
|
|
|
if (!(GETSECSPECIAL(referrer->special, 3) == 1
|
|
|| GETSECSPECIAL(referrer->special, 3) == 3))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (!(GETSECSPECIAL(sec->special, 3) == 1
|
|
|| GETSECSPECIAL(sec->special, 3) == 3))
|
|
return;
|
|
}
|
|
|
|
// Assign the friction value to players on the floor, non-floating,
|
|
// and clipped. Normally the object's friction value is kept at
|
|
// ORIG_FRICTION and this thinker changes it for icy or muddy floors.
|
|
|
|
// When the object is straddling sectors with the same
|
|
// floorheight that have different frictions, use the lowest
|
|
// friction value (muddy has precedence over icy).
|
|
|
|
node = sec->touching_thinglist; // things touching this sector
|
|
while (node)
|
|
{
|
|
thing = node->m_thing;
|
|
// apparently, all I had to do was comment out part of the next line and
|
|
// friction works for all mobj's
|
|
// (or at least MF_PUSHABLEs, which is all I care about anyway)
|
|
if (!(thing->flags & (MF_NOGRAVITY | MF_NOCLIP)) && thing->z == thing->floorz)
|
|
{
|
|
if (f->roverfriction)
|
|
{
|
|
if (thing->floorz != P_GetSpecialTopZ(thing, referrer, sec))
|
|
{
|
|
node = node->m_snext;
|
|
continue;
|
|
}
|
|
|
|
if ((thing->friction == ORIG_FRICTION) // normal friction?
|
|
|| (f->friction < thing->friction))
|
|
{
|
|
thing->friction = f->friction;
|
|
thing->movefactor = f->movefactor;
|
|
}
|
|
}
|
|
else if (P_GetSpecialBottomZ(thing, sec, sec) == thing->floorz && (thing->friction == ORIG_FRICTION // normal friction?
|
|
|| f->friction < thing->friction))
|
|
{
|
|
thing->friction = f->friction;
|
|
thing->movefactor = f->movefactor;
|
|
}
|
|
}
|
|
node = node->m_snext;
|
|
}
|
|
}
|
|
|
|
/** Spawns all friction effects.
|
|
*
|
|
* \sa P_SpawnSpecials, Add_Friction
|
|
*/
|
|
static void P_SpawnFriction(void)
|
|
{
|
|
size_t i;
|
|
line_t *l = lines;
|
|
register INT32 s;
|
|
fixed_t length; // line length controls magnitude
|
|
fixed_t friction; // friction value to be applied during movement
|
|
INT32 movefactor; // applied to each player move to simulate inertia
|
|
|
|
for (i = 0; i < numlines; i++, l++)
|
|
if (l->special == 540)
|
|
{
|
|
length = P_AproxDistance(l->dx, l->dy)>>FRACBITS;
|
|
friction = (0x1EB8*length)/0x80 + 0xD000;
|
|
|
|
if (friction > FRACUNIT)
|
|
friction = FRACUNIT;
|
|
if (friction < 0)
|
|
friction = 0;
|
|
|
|
// The following check might seem odd. At the time of movement,
|
|
// the move distance is multiplied by 'friction/0x10000', so a
|
|
// higher friction value actually means 'less friction'.
|
|
|
|
if (friction > ORIG_FRICTION) // ice
|
|
movefactor = ((0x10092 - friction)*(0x70))/0x158;
|
|
else
|
|
movefactor = ((friction - 0xDB34)*(0xA))/0x80;
|
|
|
|
// killough 8/28/98: prevent odd situations
|
|
if (movefactor < 32)
|
|
movefactor = 32;
|
|
|
|
for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
|
|
Add_Friction(friction, movefactor, s, -1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
SoM: 3/8/2000: Push/Pull/Wind/Current functions.
|
|
Add_Pusher,
|
|
PIT_PushThing,
|
|
T_Pusher,
|
|
P_GetPushThing,
|
|
P_SpawnPushers
|
|
*/
|
|
|
|
#define PUSH_FACTOR 7
|
|
|
|
/** Adds a pusher.
|
|
*
|
|
* \param type Type of push/pull effect.
|
|
* \param x_mag X magnitude.
|
|
* \param y_mag Y magnitude.
|
|
* \param source For a point pusher/puller, the source object.
|
|
* \param affectee Target sector.
|
|
* \param referrer What sector set it
|
|
* \sa T_Pusher, P_GetPushThing, P_SpawnPushers
|
|
*/
|
|
static void Add_Pusher(pushertype_e type, fixed_t x_mag, fixed_t y_mag, mobj_t *source, INT32 affectee, INT32 referrer, INT32 exclusive, INT32 slider)
|
|
{
|
|
pusher_t *p = Z_Calloc(sizeof *p, PU_LEVSPEC, NULL);
|
|
|
|
p->thinker.function.acp1 = (actionf_p1)T_Pusher;
|
|
p->source = source;
|
|
p->type = type;
|
|
p->x_mag = x_mag>>FRACBITS;
|
|
p->y_mag = y_mag>>FRACBITS;
|
|
p->exclusive = exclusive;
|
|
p->slider = slider;
|
|
|
|
if (referrer != -1)
|
|
{
|
|
p->roverpusher = true;
|
|
p->referrer = referrer;
|
|
}
|
|
else
|
|
p->roverpusher = false;
|
|
|
|
// "The right triangle of the square of the length of the hypotenuse is equal to the sum of the squares of the lengths of the other two sides."
|
|
// "Bah! Stupid brains! Don't you know anything besides the Pythagorean Theorem?" - Earthworm Jim
|
|
if (type == p_downcurrent || type == p_upcurrent || type == p_upwind || type == p_downwind)
|
|
p->magnitude = P_AproxDistance(p->x_mag,p->y_mag)<<(FRACBITS-PUSH_FACTOR);
|
|
else
|
|
p->magnitude = P_AproxDistance(p->x_mag,p->y_mag);
|
|
if (source) // point source exist?
|
|
{
|
|
// where force goes to zero
|
|
if (type == p_push)
|
|
p->radius = AngleFixed(source->angle);
|
|
else
|
|
p->radius = (p->magnitude)<<(FRACBITS+1);
|
|
|
|
p->x = p->source->x;
|
|
p->y = p->source->y;
|
|
p->z = p->source->z;
|
|
}
|
|
p->affectee = affectee;
|
|
P_AddThinker(&p->thinker);
|
|
}
|
|
|
|
|
|
// PIT_PushThing determines the angle and magnitude of the effect.
|
|
// The object's x and y momentum values are changed.
|
|
static pusher_t *tmpusher; // pusher structure for blockmap searches
|
|
|
|
/** Applies a point pusher/puller to a thing.
|
|
*
|
|
* \param thing Thing to be pushed.
|
|
* \return True if the thing was pushed.
|
|
* \todo Make a more robust P_BlockThingsIterator() so the hidden parameter
|
|
* ::tmpusher won't need to be used.
|
|
* \sa T_Pusher
|
|
*/
|
|
static inline boolean PIT_PushThing(mobj_t *thing)
|
|
{
|
|
if (thing->flags2 & MF2_PUSHED)
|
|
return false;
|
|
|
|
if (thing->player && thing->player->pflags & PF_ROPEHANG)
|
|
return false;
|
|
|
|
// Allow this to affect pushable objects at some point?
|
|
if (thing->player && (!(thing->flags & (MF_NOGRAVITY | MF_NOCLIP)) || thing->player->pflags & PF_NIGHTSMODE))
|
|
{
|
|
INT32 dist;
|
|
INT32 speed;
|
|
INT32 sx, sy, sz;
|
|
|
|
sx = tmpusher->x;
|
|
sy = tmpusher->y;
|
|
sz = tmpusher->z;
|
|
|
|
// don't fade wrt Z if health & 2 (mapthing has multi flag)
|
|
if (tmpusher->source->health & 2)
|
|
dist = P_AproxDistance(thing->x - sx,thing->y - sy);
|
|
else
|
|
{
|
|
// Make sure the Z is in range
|
|
if (thing->z < sz - tmpusher->radius || thing->z > sz + tmpusher->radius)
|
|
return false;
|
|
|
|
dist = P_AproxDistance(P_AproxDistance(thing->x - sx, thing->y - sy),
|
|
thing->z - sz);
|
|
}
|
|
|
|
speed = (tmpusher->magnitude - ((dist>>FRACBITS)>>1))<<(FRACBITS - PUSH_FACTOR - 1);
|
|
|
|
// If speed <= 0, you're outside the effective radius. You also have
|
|
// to be able to see the push/pull source point.
|
|
|
|
// Written with bits and pieces of P_HomingAttack
|
|
if ((speed > 0) && (P_CheckSight(thing, tmpusher->source)))
|
|
{
|
|
if (!(thing->player->pflags & PF_NIGHTSMODE))
|
|
{
|
|
// only push wrt Z if health & 1 (mapthing has ambush flag)
|
|
if (tmpusher->source->health & 1)
|
|
{
|
|
fixed_t tmpmomx, tmpmomy, tmpmomz;
|
|
|
|
tmpmomx = FixedMul(FixedDiv(sx - thing->x, dist), speed);
|
|
tmpmomy = FixedMul(FixedDiv(sy - thing->y, dist), speed);
|
|
tmpmomz = FixedMul(FixedDiv(sz - thing->z, dist), speed);
|
|
if (tmpusher->source->type == MT_PUSH) // away!
|
|
{
|
|
tmpmomx *= -1;
|
|
tmpmomy *= -1;
|
|
tmpmomz *= -1;
|
|
}
|
|
|
|
thing->momx += tmpmomx;
|
|
thing->momy += tmpmomy;
|
|
thing->momz += tmpmomz;
|
|
|
|
if (thing->player)
|
|
{
|
|
thing->player->cmomx += tmpmomx;
|
|
thing->player->cmomy += tmpmomy;
|
|
thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
|
|
thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
angle_t pushangle;
|
|
|
|
pushangle = R_PointToAngle2(thing->x, thing->y, sx, sy);
|
|
if (tmpusher->source->type == MT_PUSH)
|
|
pushangle += ANGLE_180; // away
|
|
pushangle >>= ANGLETOFINESHIFT;
|
|
thing->momx += FixedMul(speed, FINECOSINE(pushangle));
|
|
thing->momy += FixedMul(speed, FINESINE(pushangle));
|
|
|
|
if (thing->player)
|
|
{
|
|
thing->player->cmomx += FixedMul(speed, FINECOSINE(pushangle));
|
|
thing->player->cmomy += FixedMul(speed, FINESINE(pushangle));
|
|
thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
|
|
thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//NiGHTS-specific handling.
|
|
//By default, pushes and pulls only affect the Z-axis.
|
|
//By having the ambush flag, it affects the X-axis.
|
|
//By having the object special flag, it affects the Y-axis.
|
|
fixed_t tmpmomx, tmpmomy, tmpmomz;
|
|
|
|
if (tmpusher->source->health & 1)
|
|
tmpmomx = FixedMul(FixedDiv(sx - thing->x, dist), speed);
|
|
else
|
|
tmpmomx = 0;
|
|
|
|
if (tmpusher->source->health & 2)
|
|
tmpmomy = FixedMul(FixedDiv(sy - thing->y, dist), speed);
|
|
else
|
|
tmpmomy = 0;
|
|
|
|
tmpmomz = FixedMul(FixedDiv(sz - thing->z, dist), speed);
|
|
|
|
if (tmpusher->source->type == MT_PUSH) // away!
|
|
{
|
|
tmpmomx *= -1;
|
|
tmpmomy *= -1;
|
|
tmpmomz *= -1;
|
|
}
|
|
|
|
thing->momx += tmpmomx;
|
|
thing->momy += tmpmomy;
|
|
thing->momz += tmpmomz;
|
|
|
|
if (thing->player)
|
|
{
|
|
thing->player->cmomx += tmpmomx;
|
|
thing->player->cmomy += tmpmomy;
|
|
thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
|
|
thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tmpusher->exclusive)
|
|
thing->flags2 |= MF2_PUSHED;
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Applies a pusher to all affected objects.
|
|
*
|
|
* \param p Thinker for the pusher effect.
|
|
* \todo Split up into multiple functions.
|
|
* \sa Add_Pusher, PIT_PushThing
|
|
*/
|
|
void T_Pusher(pusher_t *p)
|
|
{
|
|
sector_t *sec, *referrer = NULL;
|
|
mobj_t *thing;
|
|
msecnode_t *node;
|
|
INT32 xspeed = 0,yspeed = 0;
|
|
INT32 xl, xh, yl, yh, bx, by;
|
|
INT32 radius;
|
|
//INT32 ht = 0;
|
|
boolean inFOF;
|
|
boolean touching;
|
|
boolean moved;
|
|
|
|
xspeed = yspeed = 0;
|
|
|
|
sec = sectors + p->affectee;
|
|
|
|
// Be sure the special sector type is still turned on. If so, proceed.
|
|
// Else, bail out; the sector type has been changed on us.
|
|
|
|
if (p->roverpusher)
|
|
{
|
|
referrer = §ors[p->referrer];
|
|
|
|
if (!(GETSECSPECIAL(referrer->special, 3) == 2
|
|
|| GETSECSPECIAL(referrer->special, 3) == 3))
|
|
return;
|
|
}
|
|
else if (!(GETSECSPECIAL(sec->special, 3) == 2
|
|
|| GETSECSPECIAL(sec->special, 3) == 3))
|
|
return;
|
|
|
|
// For constant pushers (wind/current) there are 3 situations:
|
|
//
|
|
// 1) Affected Thing is above the floor.
|
|
//
|
|
// Apply the full force if wind, no force if current.
|
|
//
|
|
// 2) Affected Thing is on the ground.
|
|
//
|
|
// Apply half force if wind, full force if current.
|
|
//
|
|
// 3) Affected Thing is below the ground (underwater effect).
|
|
//
|
|
// Apply no force if wind, full force if current.
|
|
//
|
|
// Apply the effect to clipped players only for now.
|
|
//
|
|
// In Phase II, you can apply these effects to Things other than players.
|
|
|
|
if (p->type == p_push)
|
|
{
|
|
|
|
// Seek out all pushable things within the force radius of this
|
|
// point pusher. Crosses sectors, so use blockmap.
|
|
|
|
tmpusher = p; // MT_PUSH/MT_PULL point source
|
|
radius = p->radius; // where force goes to zero
|
|
tmbbox[BOXTOP] = p->y + radius;
|
|
tmbbox[BOXBOTTOM] = p->y - radius;
|
|
tmbbox[BOXRIGHT] = p->x + radius;
|
|
tmbbox[BOXLEFT] = p->x - radius;
|
|
|
|
xl = (unsigned)(tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
xh = (unsigned)(tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
yl = (unsigned)(tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
yh = (unsigned)(tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
for (bx = xl; bx <= xh; bx++)
|
|
for (by = yl; by <= yh; by++)
|
|
P_BlockThingsIterator(bx,by, PIT_PushThing);
|
|
return;
|
|
}
|
|
|
|
// constant pushers p_wind and p_current
|
|
node = sec->touching_thinglist; // things touching this sector
|
|
for (; node; node = node->m_snext)
|
|
{
|
|
thing = node->m_thing;
|
|
if (thing->flags & (MF_NOGRAVITY | MF_NOCLIP)
|
|
&& !(thing->type == MT_SMALLBUBBLE
|
|
|| thing->type == MT_MEDIUMBUBBLE
|
|
|| thing->type == MT_EXTRALARGEBUBBLE))
|
|
continue;
|
|
|
|
if (!(thing->flags & MF_PUSHABLE) && !(thing->type == MT_PLAYER
|
|
|| thing->type == MT_SMALLBUBBLE
|
|
|| thing->type == MT_MEDIUMBUBBLE
|
|
|| thing->type == MT_EXTRALARGEBUBBLE
|
|
|| thing->type == MT_LITTLETUMBLEWEED
|
|
|| thing->type == MT_BIGTUMBLEWEED))
|
|
continue;
|
|
|
|
if (thing->flags2 & MF2_PUSHED)
|
|
continue;
|
|
|
|
if (thing->player && thing->player->pflags & PF_ROPEHANG)
|
|
continue;
|
|
|
|
if (thing->player && (thing->state == &states[thing->info->painstate]) && (thing->player->powers[pw_flashing] > (flashingtics/4)*3 && thing->player->powers[pw_flashing] <= flashingtics))
|
|
continue;
|
|
|
|
inFOF = touching = moved = false;
|
|
|
|
// Find the area that the 'thing' is in
|
|
if (p->roverpusher)
|
|
{
|
|
fixed_t top, bottom;
|
|
|
|
top = P_GetSpecialTopZ(thing, referrer, sec);
|
|
bottom = P_GetSpecialBottomZ(thing, referrer, sec);
|
|
|
|
if (thing->eflags & MFE_VERTICALFLIP)
|
|
{
|
|
if (bottom > thing->z + thing->height
|
|
|| top < (thing->z + (thing->height >> 1)))
|
|
continue;
|
|
|
|
if (thing->z < bottom)
|
|
touching = true;
|
|
|
|
if (thing->z + (thing->height >> 1) > bottom)
|
|
inFOF = true;
|
|
|
|
}
|
|
else
|
|
{
|
|
if (top < thing->z || referrer->floorheight > (thing->z + (thing->height >> 1)))
|
|
continue;
|
|
if (thing->z + thing->height > top)
|
|
touching = true;
|
|
|
|
if (thing->z + (thing->height >> 1) < top)
|
|
inFOF = true;
|
|
}
|
|
}
|
|
else // Treat the entire sector as one big FOF
|
|
{
|
|
if (thing->z == P_GetSpecialBottomZ(thing, sec, sec))
|
|
touching = true;
|
|
else if (p->type != p_current)
|
|
inFOF = true;
|
|
}
|
|
|
|
if (!touching && !inFOF) // Object is out of range of effect
|
|
continue;
|
|
|
|
if (p->type == p_wind)
|
|
{
|
|
if (touching) // on ground
|
|
{
|
|
xspeed = (p->x_mag)>>1; // half force
|
|
yspeed = (p->y_mag)>>1;
|
|
moved = true;
|
|
}
|
|
else if (inFOF)
|
|
{
|
|
xspeed = (p->x_mag); // full force
|
|
yspeed = (p->y_mag);
|
|
moved = true;
|
|
}
|
|
}
|
|
else if (p->type == p_upwind)
|
|
{
|
|
if (touching) // on ground
|
|
{
|
|
thing->momz += (p->magnitude)>>1;
|
|
moved = true;
|
|
}
|
|
else if (inFOF)
|
|
{
|
|
thing->momz += p->magnitude;
|
|
moved = true;
|
|
}
|
|
}
|
|
else if (p->type == p_downwind)
|
|
{
|
|
if (touching) // on ground
|
|
{
|
|
thing->momz -= (p->magnitude)>>1;
|
|
moved = true;
|
|
}
|
|
else if (inFOF)
|
|
{
|
|
thing->momz -= p->magnitude;
|
|
moved = true;
|
|
}
|
|
}
|
|
else // p_current
|
|
{
|
|
if (!touching && !inFOF) // Not in water at all
|
|
xspeed = yspeed = 0; // no force
|
|
else // underwater / touching water
|
|
{
|
|
if (p->type == p_upcurrent)
|
|
thing->momz += p->magnitude;
|
|
else if (p->type == p_downcurrent)
|
|
thing->momz -= p->magnitude;
|
|
else
|
|
{
|
|
xspeed = p->x_mag; // full force
|
|
yspeed = p->y_mag;
|
|
}
|
|
moved = true;
|
|
}
|
|
}
|
|
|
|
if (p->type != p_downcurrent && p->type != p_upcurrent
|
|
&& p->type != p_upwind && p->type != p_downwind)
|
|
{
|
|
thing->momx += xspeed<<(FRACBITS-PUSH_FACTOR);
|
|
thing->momy += yspeed<<(FRACBITS-PUSH_FACTOR);
|
|
if (thing->player)
|
|
{
|
|
thing->player->cmomx += xspeed<<(FRACBITS-PUSH_FACTOR);
|
|
thing->player->cmomy += yspeed<<(FRACBITS-PUSH_FACTOR);
|
|
thing->player->cmomx = FixedMul(thing->player->cmomx, ORIG_FRICTION);
|
|
thing->player->cmomy = FixedMul(thing->player->cmomy, ORIG_FRICTION);
|
|
}
|
|
|
|
// Tumbleweeds bounce a bit...
|
|
if (thing->type == MT_LITTLETUMBLEWEED || thing->type == MT_BIGTUMBLEWEED)
|
|
thing->momz += P_AproxDistance(xspeed<<(FRACBITS-PUSH_FACTOR), yspeed<<(FRACBITS-PUSH_FACTOR)) >> 2;
|
|
}
|
|
|
|
if (moved)
|
|
{
|
|
if (p->slider && thing->player)
|
|
{
|
|
boolean jumped = (thing->player->pflags & PF_JUMPED);
|
|
P_ResetPlayer (thing->player);
|
|
|
|
if (jumped)
|
|
thing->player->pflags |= PF_JUMPED;
|
|
|
|
thing->player->pflags |= PF_SLIDING;
|
|
P_SetPlayerMobjState (thing, thing->info->painstate); // Whee!
|
|
thing->angle = R_PointToAngle2 (0, 0, xspeed<<(FRACBITS-PUSH_FACTOR), yspeed<<(FRACBITS-PUSH_FACTOR));
|
|
|
|
if (thing->player == &players[consoleplayer])
|
|
{
|
|
if (thing->angle - localangle > ANGLE_180)
|
|
localangle -= (localangle - thing->angle) / 8;
|
|
else
|
|
localangle += (thing->angle - localangle) / 8;
|
|
}
|
|
else if (thing->player == &players[secondarydisplayplayer])
|
|
{
|
|
if (thing->angle - localangle2 > ANGLE_180)
|
|
localangle2 -= (localangle2 - thing->angle) / 8;
|
|
else
|
|
localangle2 += (thing->angle - localangle2) / 8;
|
|
}
|
|
/*if (thing->player == &players[consoleplayer])
|
|
localangle = thing->angle;
|
|
else if (thing->player == &players[secondarydisplayplayer])
|
|
localangle2 = thing->angle;*/
|
|
}
|
|
|
|
if (p->exclusive)
|
|
thing->flags2 |= MF2_PUSHED;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/** Gets a push/pull object.
|
|
*
|
|
* \param s Sector number to look in.
|
|
* \return Pointer to the first ::MT_PUSH or ::MT_PULL object found in the
|
|
* sector.
|
|
* \sa P_GetTeleportDestThing, P_GetStarpostThing, P_GetAltViewThing
|
|
*/
|
|
mobj_t *P_GetPushThing(UINT32 s)
|
|
{
|
|
mobj_t *thing;
|
|
sector_t *sec;
|
|
|
|
sec = sectors + s;
|
|
thing = sec->thinglist;
|
|
while (thing)
|
|
{
|
|
switch (thing->type)
|
|
{
|
|
case MT_PUSH:
|
|
case MT_PULL:
|
|
return thing;
|
|
default:
|
|
break;
|
|
}
|
|
thing = thing->snext;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/** Spawns pushers.
|
|
*
|
|
* \todo Remove magic numbers.
|
|
* \sa P_SpawnSpecials, Add_Pusher
|
|
*/
|
|
static void P_SpawnPushers(void)
|
|
{
|
|
size_t i;
|
|
line_t *l = lines;
|
|
register INT32 s;
|
|
mobj_t *thing;
|
|
|
|
for (i = 0; i < numlines; i++, l++)
|
|
switch (l->special)
|
|
{
|
|
case 541: // wind
|
|
for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
|
|
Add_Pusher(p_wind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
|
|
break;
|
|
case 544: // current
|
|
for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
|
|
Add_Pusher(p_current, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
|
|
break;
|
|
case 547: // push/pull
|
|
for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
|
|
{
|
|
thing = P_GetPushThing(s);
|
|
if (thing) // No MT_P* means no effect
|
|
Add_Pusher(p_push, l->dx, l->dy, thing, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
|
|
}
|
|
break;
|
|
case 545: // current up
|
|
for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
|
|
Add_Pusher(p_upcurrent, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
|
|
break;
|
|
case 546: // current down
|
|
for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
|
|
Add_Pusher(p_downcurrent, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
|
|
break;
|
|
case 542: // wind up
|
|
for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
|
|
Add_Pusher(p_upwind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
|
|
break;
|
|
case 543: // wind down
|
|
for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
|
|
Add_Pusher(p_downwind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void P_SearchForDisableLinedefs(void)
|
|
{
|
|
size_t i;
|
|
INT32 j;
|
|
|
|
// Look for disable linedefs
|
|
for (i = 0; i < numlines; i++)
|
|
{
|
|
if (lines[i].special == 6)
|
|
{
|
|
// Remove special
|
|
// Do *not* remove tag. That would mess with the tag lists
|
|
// that P_InitTagLists literally just created!
|
|
lines[i].special = 0;
|
|
|
|
// Ability flags can disable disable linedefs now, lol
|
|
if (netgame || multiplayer)
|
|
{
|
|
// future: nonet flag?
|
|
}
|
|
else if ((lines[i].flags & ML_NETONLY) == ML_NETONLY)
|
|
continue; // Net-only never triggers in single player
|
|
else if (players[consoleplayer].charability == CA_THOK && (lines[i].flags & ML_NOSONIC))
|
|
continue;
|
|
else if (players[consoleplayer].charability == CA_FLY && (lines[i].flags & ML_NOTAILS))
|
|
continue;
|
|
else if (players[consoleplayer].charability == CA_GLIDEANDCLIMB && (lines[i].flags & ML_NOKNUX))
|
|
continue;
|
|
|
|
// Disable any linedef specials with our tag.
|
|
for (j = -1; (j = P_FindLineFromLineTag(&lines[i], j)) >= 0;)
|
|
lines[j].special = 0;
|
|
}
|
|
}
|
|
}
|