mirror of
https://git.do.srb2.org/STJr/SRB2.git
synced 2024-12-21 18:30:40 +00:00
a9110c0645
Mobjs got their own thinker list after all, and disappearing thinkers are automatically purged from their lists and sent to the limbo list. So it's safe to assume all thinkers inside the mobj list must be mobjs. Signed-off-by: Nev3r <apophycens@gmail.com>
9273 lines
274 KiB
C
9273 lines
274 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-2018 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 "v_video.h" // V_AUTOFADEOUT|V_ALLOWLOWERCASE
|
|
#include "m_misc.h"
|
|
#include "m_cond.h" //unlock triggers
|
|
#include "lua_hook.h" // LUAh_LinedefExecute
|
|
#include "f_finale.h" // control text prompt
|
|
|
|
#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
|
|
#include <errno.h>
|
|
|
|
mobj_t *skyboxmo[2]; // current skybox mobjs: 0 = viewpoint, 1 = centerpoint
|
|
mobj_t *skyboxviewpnts[16]; // array of MT_SKYBOX viewpoint mobjs
|
|
mobj_t *skyboxcenterpnts[16]; // array of MT_SKYBOX centerpoint mobjs
|
|
|
|
// Amount (dx, dy) vector linedef is shifted right to get scroll amount
|
|
#define SCROLL_SHIFT 5
|
|
|
|
// This must be updated whenever we up the max flat size - quicker to assume rather than figuring out the sqrt of the specific flat's filesize.
|
|
#define MAXFLATSIZE (2048<<FRACBITS)
|
|
|
|
/** 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 loading an ANIMDEFS 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_ResetFakeFloorFader(ffloor_t *rover, fade_t *data, boolean finalize);
|
|
#define P_RemoveFakeFloorFader(l) P_ResetFakeFloorFader(l, NULL, false);
|
|
static boolean P_FadeFakeFloor(ffloor_t *rover, INT16 sourcevalue, INT16 destvalue, INT16 speed, boolean ticbased, INT32 *timer,
|
|
boolean doexists, boolean dotranslucent, boolean dolighting, boolean docolormap,
|
|
boolean docollision, boolean doghostfade, boolean exactalpha);
|
|
static void P_AddFakeFloorFader(ffloor_t *rover, size_t sectornum, size_t ffloornum,
|
|
INT16 destvalue, INT16 speed, boolean ticbased, boolean relative,
|
|
boolean doexists, boolean dotranslucent, boolean dolighting, boolean docolormap,
|
|
boolean docollision, boolean doghostfade, boolean exactalpha);
|
|
static void P_ResetColormapFader(sector_t *sector);
|
|
static void Add_ColormapFader(sector_t *sector, extracolormap_t *source_exc, extracolormap_t *dest_exc,
|
|
boolean ticbased, INT32 duration);
|
|
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);
|
|
static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control, INT32 affectee, UINT8 reverse);
|
|
|
|
|
|
//SoM: 3/7/2000: New sturcture without limits.
|
|
static anim_t *lastanim;
|
|
static anim_t *anims = NULL; /// \todo free leak
|
|
static size_t maxanims;
|
|
|
|
// 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);
|
|
void P_ParseAnimationDefintion(SINT8 istexture);
|
|
|
|
/** Sets up texture and flat animations.
|
|
*
|
|
* Converts an ::animdef_t array loaded from 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), JTE (had to rewrite it to handle multiple WADs _correctly_)
|
|
*/
|
|
void P_InitPicAnims(void)
|
|
{
|
|
// Init animation
|
|
INT32 w; // WAD
|
|
size_t i;
|
|
|
|
I_Assert(animdefs == NULL);
|
|
|
|
maxanims = 0;
|
|
|
|
for (w = numwadfiles-1; w >= 0; w--)
|
|
{
|
|
UINT16 animdefsLumpNum;
|
|
|
|
// Find ANIMDEFS lump in the WAD
|
|
animdefsLumpNum = W_CheckNumForNamePwad("ANIMDEFS", w, 0);
|
|
|
|
while (animdefsLumpNum != INT16_MAX)
|
|
{
|
|
P_ParseANIMDEFSLump(w, animdefsLumpNum);
|
|
animdefsLumpNum = W_CheckNumForNamePwad("ANIMDEFS", (UINT16)w, animdefsLumpNum + 1);
|
|
}
|
|
}
|
|
|
|
// Define the last one
|
|
animdefs[maxanims].istexture = -1;
|
|
strncpy(animdefs[maxanims].endname, "", 9);
|
|
strncpy(animdefs[maxanims].startname, "", 9);
|
|
animdefs[maxanims].speed = 0;
|
|
|
|
if (anims)
|
|
free(anims);
|
|
|
|
anims = (anim_t *)malloc(sizeof (*anims)*(maxanims + 1));
|
|
if (!anims)
|
|
I_Error("Not enough free memory for ANIMDEFS 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);
|
|
}
|
|
|
|
lastanim->speed = LONG(animdefs[i].speed);
|
|
lastanim++;
|
|
}
|
|
lastanim->istexture = -1;
|
|
R_ClearTextureNumCache(false);
|
|
|
|
// Clear animdefs now that we're done with it.
|
|
// We'll only be using anims from now on.
|
|
Z_Free(animdefs);
|
|
animdefs = NULL;
|
|
}
|
|
|
|
void P_ParseANIMDEFSLump(INT32 wadNum, UINT16 lumpnum)
|
|
{
|
|
char *animdefsLump;
|
|
size_t animdefsLumpLength;
|
|
char *animdefsText;
|
|
char *animdefsToken;
|
|
char *p;
|
|
|
|
// 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
|
|
p = animdefsText;
|
|
animdefsToken = M_GetToken(p);
|
|
while (animdefsToken != NULL)
|
|
{
|
|
if (stricmp(animdefsToken, "TEXTURE") == 0)
|
|
{
|
|
Z_Free(animdefsToken);
|
|
P_ParseAnimationDefintion(1);
|
|
}
|
|
else if (stricmp(animdefsToken, "FLAT") == 0)
|
|
{
|
|
Z_Free(animdefsToken);
|
|
P_ParseAnimationDefintion(0);
|
|
}
|
|
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);
|
|
}
|
|
// parse next line
|
|
while (*p != '\0' && *p != '\n') ++p;
|
|
if (*p == '\n') ++p;
|
|
animdefsToken = M_GetToken(p);
|
|
}
|
|
Z_Free(animdefsToken);
|
|
Z_Free((void *)animdefsText);
|
|
}
|
|
|
|
void P_ParseAnimationDefintion(SINT8 istexture)
|
|
{
|
|
char *animdefsToken;
|
|
size_t animdefsTokenLength;
|
|
char *endPos;
|
|
INT32 animSpeed;
|
|
size_t i;
|
|
|
|
// 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);
|
|
}
|
|
|
|
// Search for existing animdef
|
|
for (i = 0; i < maxanims; i++)
|
|
if (animdefs[i].istexture == istexture // Check if it's the same type!
|
|
&& stricmp(animdefsToken, animdefs[i].startname) == 0)
|
|
{
|
|
//CONS_Alert(CONS_NOTICE, "Duplicate animation: %s\n", animdefsToken);
|
|
|
|
// If we weren't parsing in reverse order, we would `break` here and parse the new data into the existing slot we found.
|
|
// Instead, we're just going to skip parsing the rest of this line entirely.
|
|
Z_Free(animdefsToken);
|
|
return;
|
|
}
|
|
|
|
// Not found
|
|
if (i == maxanims)
|
|
{
|
|
// 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);
|
|
strncpy(animdefs[i].startname, animdefsToken, 9);
|
|
}
|
|
|
|
// animdefs[i].startname is now set to animdefsToken either way.
|
|
Z_Free(animdefsToken);
|
|
|
|
// set texture type
|
|
animdefs[i].istexture = istexture;
|
|
|
|
// "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 ANIMDEFS 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 ANIMDEFS lump: Expected a positive integer for \"%s\"'s animation speed, got \"%s\"", animdefs[i].startname, animdefsToken);
|
|
}
|
|
animdefs[i].speed = animSpeed;
|
|
Z_Free(animdefsToken);
|
|
}
|
|
|
|
|
|
/** 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++;
|
|
|
|
// This redundant check stops the compiler from complaining about function expansion
|
|
// elsewhere for some reason and everything is awful
|
|
if (start >= (INT32)numlines)
|
|
return -1;
|
|
|
|
while (start < (INT32)numlines && 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 |= (po->spawnflags & 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;
|
|
|
|
// if DONTPEGBOTTOM, specify raw translucency value in Front X Offset
|
|
// else, take it out of 1000. If Front X Offset is specified, use that. Else, use floorheight.
|
|
if (line->flags & ML_EFFECT3) // relative calc
|
|
po->translucency = max(min(po->translucency + ((line->flags & ML_DONTPEGBOTTOM) ?
|
|
(sides[line->sidenum[0]].textureoffset ?
|
|
max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, NUMTRANSMAPS), -NUMTRANSMAPS)
|
|
: max(min(line->frontsector->floorheight>>FRACBITS, NUMTRANSMAPS), -NUMTRANSMAPS))
|
|
: (sides[line->sidenum[0]].textureoffset ?
|
|
max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, 1000), -1000) / 100
|
|
: max(min(line->frontsector->floorheight>>FRACBITS, 1000), -1000) / 100)),
|
|
NUMTRANSMAPS), 0);
|
|
else
|
|
po->translucency = (line->flags & ML_DONTPEGBOTTOM) ?
|
|
(sides[line->sidenum[0]].textureoffset ?
|
|
max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, NUMTRANSMAPS), 0)
|
|
: max(min(line->frontsector->floorheight>>FRACBITS, NUMTRANSMAPS), 0))
|
|
: (sides[line->sidenum[0]].textureoffset ?
|
|
max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, 1000), 0) / 100
|
|
: max(min(line->frontsector->floorheight>>FRACBITS, 1000), 0) / 100);
|
|
}
|
|
|
|
//
|
|
// PolyFade
|
|
//
|
|
// Makes a polyobject translucency fade and applies tangibility
|
|
//
|
|
static boolean PolyFade(line_t *line)
|
|
{
|
|
INT32 polyObjNum = line->tag;
|
|
polyobj_t *po;
|
|
polyfadedata_t pfd;
|
|
|
|
if (!(po = Polyobj_GetForNum(polyObjNum)))
|
|
{
|
|
CONS_Debug(DBG_POLYOBJ, "PolyFade: bad polyobj %d\n", polyObjNum);
|
|
return 0;
|
|
}
|
|
|
|
// don't allow line actions to affect bad polyobjects
|
|
if (po->isBad)
|
|
return 0;
|
|
|
|
// Prevent continuous execs from interfering on an existing fade
|
|
if (!(line->flags & ML_EFFECT5)
|
|
&& po->thinker
|
|
&& po->thinker->function.acp1 == (actionf_p1)T_PolyObjFade)
|
|
{
|
|
CONS_Debug(DBG_POLYOBJ, "Line type 492 Executor: Fade PolyObject thinker already exists\n");
|
|
return 0;
|
|
}
|
|
|
|
pfd.polyObjNum = polyObjNum;
|
|
|
|
// if DONTPEGBOTTOM, specify raw translucency value in Front X Offset
|
|
// else, take it out of 1000. If Front X Offset is specified, use that. Else, use floorheight.
|
|
if (line->flags & ML_EFFECT3) // relative calc
|
|
pfd.destvalue = max(min(po->translucency + ((line->flags & ML_DONTPEGBOTTOM) ?
|
|
(sides[line->sidenum[0]].textureoffset ?
|
|
max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, NUMTRANSMAPS), -NUMTRANSMAPS)
|
|
: max(min(line->frontsector->floorheight>>FRACBITS, NUMTRANSMAPS), -NUMTRANSMAPS))
|
|
: (sides[line->sidenum[0]].textureoffset ?
|
|
max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, 1000), -1000) / 100
|
|
: max(min(line->frontsector->floorheight>>FRACBITS, 1000), -1000) / 100)),
|
|
NUMTRANSMAPS), 0);
|
|
else
|
|
pfd.destvalue = (line->flags & ML_DONTPEGBOTTOM) ?
|
|
(sides[line->sidenum[0]].textureoffset ?
|
|
max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, NUMTRANSMAPS), 0)
|
|
: max(min(line->frontsector->floorheight>>FRACBITS, NUMTRANSMAPS), 0))
|
|
: (sides[line->sidenum[0]].textureoffset ?
|
|
max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, 1000), 0) / 100
|
|
: max(min(line->frontsector->floorheight>>FRACBITS, 1000), 0) / 100);
|
|
|
|
// already equal, nothing to do
|
|
if (po->translucency == pfd.destvalue)
|
|
return 1;
|
|
|
|
pfd.docollision = !(line->flags & ML_BOUNCY); // do not handle collision flags
|
|
pfd.doghostfade = (line->flags & ML_EFFECT1); // do ghost fade (no collision flags during fade)
|
|
pfd.ticbased = (line->flags & ML_EFFECT4); // Speed = Tic Duration
|
|
|
|
// allow Back Y Offset to be consistent with other fade specials
|
|
pfd.speed = (line->sidenum[1] != 0xFFFF && !sides[line->sidenum[0]].rowoffset) ?
|
|
abs(sides[line->sidenum[1]].rowoffset>>FRACBITS)
|
|
: abs(sides[line->sidenum[0]].rowoffset>>FRACBITS);
|
|
|
|
|
|
return EV_DoPolyObjFade(&pfd);
|
|
}
|
|
|
|
//
|
|
// 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);
|
|
}
|
|
|
|
|
|
/** Similar to PolyDisplace().
|
|
*/
|
|
static boolean PolyRotDisplace(line_t *line)
|
|
{
|
|
polyrotdisplacedata_t pdd;
|
|
fixed_t anginter, distinter;
|
|
|
|
pdd.polyObjNum = line->tag;
|
|
pdd.controlSector = line->frontsector;
|
|
|
|
// Rotate 'anginter' interval for each 'distinter' interval from the control sector.
|
|
// Use default values if not provided as fallback.
|
|
anginter = sides[line->sidenum[0]].rowoffset ? sides[line->sidenum[0]].rowoffset : 90*FRACUNIT;
|
|
distinter = sides[line->sidenum[0]].textureoffset ? sides[line->sidenum[0]].textureoffset : 128*FRACUNIT;
|
|
pdd.rotscale = FixedDiv(anginter, distinter);
|
|
|
|
// Same behavior as other rotators when carrying things.
|
|
if (line->flags & ML_NOCLIMB)
|
|
pdd.turnobjs = 0;
|
|
else if (line->flags & ML_EFFECT4)
|
|
pdd.turnobjs = 2;
|
|
else
|
|
pdd.turnobjs = 1;
|
|
|
|
return EV_DoPolyObjRotDisplace(&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;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// P_RunNightserizeExecutors
|
|
//
|
|
void P_RunNightserizeExecutors(mobj_t *actor)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < numlines; i++)
|
|
{
|
|
if (lines[i].special == 323 || lines[i].special == 324)
|
|
P_RunTriggerLinedef(&lines[i], actor, NULL);
|
|
}
|
|
}
|
|
|
|
//
|
|
// P_RunDeNightserizeExecutors
|
|
//
|
|
void P_RunDeNightserizeExecutors(mobj_t *actor)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < numlines; i++)
|
|
{
|
|
if (lines[i].special == 325 || lines[i].special == 326)
|
|
P_RunTriggerLinedef(&lines[i], actor, NULL);
|
|
}
|
|
}
|
|
|
|
//
|
|
// P_RunNightsLapExecutors
|
|
//
|
|
void P_RunNightsLapExecutors(mobj_t *actor)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < numlines; i++)
|
|
{
|
|
if (lines[i].special == 327 || lines[i].special == 328)
|
|
P_RunTriggerLinedef(&lines[i], actor, NULL);
|
|
}
|
|
}
|
|
|
|
//
|
|
// P_RunNightsCapsuleTouchExecutors
|
|
//
|
|
void P_RunNightsCapsuleTouchExecutors(mobj_t *actor, boolean entering, boolean enoughspheres)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < numlines; i++)
|
|
{
|
|
if ((lines[i].special == 329 || lines[i].special == 330)
|
|
&& ((entering && (lines[i].flags & ML_TFERLINE))
|
|
|| (!entering && !(lines[i].flags & ML_TFERLINE)))
|
|
&& ((lines[i].flags & ML_DONTPEGTOP)
|
|
|| (enoughspheres && !(lines[i].flags & ML_BOUNCY))
|
|
|| (!enoughspheres && (lines[i].flags & ML_BOUNCY))))
|
|
P_RunTriggerLinedef(&lines[i], actor, NULL);
|
|
}
|
|
}
|
|
|
|
/** 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;
|
|
sectors[i].spawn_nexttag = sectors[i].nexttag;
|
|
sectors[j].spawn_firsttag = sectors[j].firsttag;
|
|
}
|
|
|
|
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(THINK_MAIN, &e->thinker);
|
|
}
|
|
|
|
/** Used by P_RunTriggerLinedef to check a NiGHTS trigger linedef's conditions
|
|
*
|
|
* \param triggerline Trigger linedef to check conditions for; should NEVER be NULL.
|
|
* \param actor Object initiating the action; should not be NULL.
|
|
* \sa P_RunTriggerLinedef
|
|
*/
|
|
static boolean P_CheckNightsTriggerLine(line_t *triggerline, mobj_t *actor)
|
|
{
|
|
INT16 specialtype = triggerline->special;
|
|
size_t i;
|
|
|
|
UINT8 inputmare = max(0, min(255, sides[triggerline->sidenum[0]].textureoffset>>FRACBITS));
|
|
UINT8 inputlap = max(0, min(255, sides[triggerline->sidenum[0]].rowoffset>>FRACBITS));
|
|
|
|
boolean ltemare = triggerline->flags & ML_NOCLIMB;
|
|
boolean gtemare = triggerline->flags & ML_BLOCKMONSTERS;
|
|
boolean ltelap = triggerline->flags & ML_EFFECT1;
|
|
boolean gtelap = triggerline->flags & ML_EFFECT2;
|
|
|
|
boolean lapfrombonustime = triggerline->flags & ML_EFFECT3;
|
|
boolean perglobalinverse = triggerline->flags & ML_DONTPEGBOTTOM;
|
|
boolean perglobal = !(triggerline->flags & ML_EFFECT4) && !perglobalinverse;
|
|
|
|
boolean donomares = triggerline->flags & ML_BOUNCY; // nightserize: run at end of level (no mares)
|
|
boolean fromnonights = triggerline->flags & ML_TFERLINE; // nightserize: from non-nights // denightserize: all players no nights
|
|
boolean fromnights = triggerline->flags & ML_DONTPEGTOP; // nightserize: from nights // denightserize: >0 players are nights
|
|
|
|
UINT8 currentmare = UINT8_MAX;
|
|
UINT8 currentlap = UINT8_MAX;
|
|
|
|
// Do early returns for Nightserize
|
|
if (specialtype >= 323 && specialtype <= 324)
|
|
{
|
|
// run only when no mares are found
|
|
if (donomares && P_FindLowestMare() != UINT8_MAX)
|
|
return false;
|
|
|
|
// run only if there is a mare present
|
|
if (!donomares && P_FindLowestMare() == UINT8_MAX)
|
|
return false;
|
|
|
|
// run only if player is nightserizing from non-nights
|
|
if (fromnonights)
|
|
{
|
|
if (!actor->player)
|
|
return false;
|
|
else if (actor->player->powers[pw_carry] == CR_NIGHTSMODE)
|
|
return false;
|
|
}
|
|
// run only if player is nightserizing from nights
|
|
else if (fromnights)
|
|
{
|
|
if (!actor->player)
|
|
return false;
|
|
else if (actor->player->powers[pw_carry] != CR_NIGHTSMODE)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Get current mare and lap (and check early return for DeNightserize)
|
|
if (perglobal || perglobalinverse
|
|
|| (specialtype >= 325 && specialtype <= 326 && (fromnonights || fromnights)))
|
|
{
|
|
UINT8 playersarenights = 0;
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
UINT8 lap;
|
|
if (!playeringame[i] || players[i].spectator)
|
|
continue;
|
|
|
|
// denightserize: run only if all players are not nights
|
|
if (specialtype >= 325 && specialtype <= 326 && fromnonights
|
|
&& players[i].powers[pw_carry] == CR_NIGHTSMODE)
|
|
return false;
|
|
|
|
// count number of nights players for denightserize return
|
|
if (specialtype >= 325 && specialtype <= 326 && fromnights
|
|
&& players[i].powers[pw_carry] == CR_NIGHTSMODE)
|
|
playersarenights++;
|
|
|
|
lap = lapfrombonustime ? players[i].marebonuslap : players[i].marelap;
|
|
|
|
// get highest mare/lap of players
|
|
if (perglobal)
|
|
{
|
|
if (players[i].mare > currentmare || currentmare == UINT8_MAX)
|
|
{
|
|
currentmare = players[i].mare;
|
|
currentlap = UINT8_MAX;
|
|
}
|
|
if (players[i].mare == currentmare
|
|
&& (lap > currentlap || currentlap == UINT8_MAX))
|
|
currentlap = lap;
|
|
}
|
|
// get lowest mare/lap of players
|
|
else if (perglobalinverse)
|
|
{
|
|
if (players[i].mare < currentmare || currentmare == UINT8_MAX)
|
|
{
|
|
currentmare = players[i].mare;
|
|
currentlap = UINT8_MAX;
|
|
}
|
|
if (players[i].mare == currentmare
|
|
&& (lap < currentlap || currentlap == UINT8_MAX))
|
|
currentlap = lap;
|
|
}
|
|
}
|
|
|
|
// denightserize: run only if >0 players are nights
|
|
if (specialtype >= 325 && specialtype <= 326 && fromnights
|
|
&& playersarenights < 1)
|
|
return false;
|
|
}
|
|
// get current mare/lap from triggering player
|
|
else if (!perglobal && !perglobalinverse)
|
|
{
|
|
if (!actor->player)
|
|
return false;
|
|
currentmare = actor->player->mare;
|
|
currentlap = lapfrombonustime ? actor->player->marebonuslap : actor->player->marelap;
|
|
}
|
|
|
|
if (lapfrombonustime && !currentlap)
|
|
return false; // special case: player->marebonuslap is 0 until passing through on bonus time. Don't trigger lines looking for inputlap 0.
|
|
|
|
// Compare current mare/lap to input mare/lap based on rules
|
|
if (!(specialtype >= 323 && specialtype <= 324 && donomares) // don't return false if donomares and we got this far
|
|
&& ((ltemare && currentmare > inputmare)
|
|
|| (gtemare && currentmare < inputmare)
|
|
|| (!ltemare && !gtemare && currentmare != inputmare)
|
|
|| (ltelap && currentlap > inputlap)
|
|
|| (gtelap && currentlap < inputlap)
|
|
|| (!ltelap && !gtelap && currentlap != inputlap))
|
|
)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/** 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 || ((maptol & TOL_NIGHTS) ? players[i].spheres : players[i].rings) <= 0)
|
|
continue;
|
|
|
|
rings += (maptol & TOL_NIGHTS) ? players[i].spheres : players[i].rings;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!(actor && actor->player))
|
|
return false; // no player to count rings from here, sorry
|
|
|
|
rings = (maptol & TOL_NIGHTS) ? actor->player->spheres : actor->player->rings;
|
|
}
|
|
|
|
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_thinglist_next;
|
|
}
|
|
|
|
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;
|
|
case 323: // nightserize - each time
|
|
case 324: // nightserize - once
|
|
case 325: // denightserize - each time
|
|
case 326: // denightserize - once
|
|
case 327: // nights lap - each time
|
|
case 328: // nights lap - once
|
|
case 329: // nights egg capsule touch - each time
|
|
case 330: // nights egg capsule touch - once
|
|
if (!P_CheckNightsTriggerLine(triggerline, actor))
|
|
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 == 313 // No More Enemies - 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 == 324 // Nightserize - Once
|
|
|| specialtype == 326 // DeNightserize - Once
|
|
|| specialtype == 328 // Nights lap - Once
|
|
|| specialtype == 330 // Nights Bonus Time - Once
|
|
|| 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 = thlist[THINK_MAIN].next; think != &thlist[THINK_MAIN]; think = think->next)
|
|
{
|
|
if (think->function.acp1 != (actionf_p1)P_NullPrecipThinker)
|
|
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 = thlist[THINK_MAIN].next; think != &thlist[THINK_MAIN]; think = think->next)
|
|
{
|
|
if (think->function.acp1 != (actionf_p1)P_NullPrecipThinker)
|
|
continue; // not a precipmobj thinker
|
|
precipmobj = (precipmobj_t *)think;
|
|
|
|
if (swap == PRECIP_RAIN) // Snow To Rain
|
|
{
|
|
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;
|
|
|
|
precipmobj->precipflags |= PCF_RAIN;
|
|
//think->function.acp1 = (actionf_p1)P_RainThinker;
|
|
}
|
|
else if (swap == PRECIP_SNOW) // Rain To Snow
|
|
{
|
|
INT32 z;
|
|
|
|
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|PCF_RAIN);
|
|
|
|
//think->function.acp1 = (actionf_p1)P_SnowThinker;
|
|
}
|
|
else if (swap == PRECIP_BLANK || swap == PRECIP_STORM_NORAIN) // Remove precip, but keep it around for reuse.
|
|
{
|
|
//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)))
|
|
{
|
|
boolean musicsame = (!sides[line->sidenum[0]].text[0] || !strnicmp(sides[line->sidenum[0]].text, S_MusicName(), 7));
|
|
UINT16 tracknum = (UINT16)max(sides[line->sidenum[0]].bottomtexture, 0);
|
|
INT32 position = (INT32)max(sides[line->sidenum[0]].midtexture, 0);
|
|
UINT32 prefadems = (UINT32)max(sides[line->sidenum[0]].textureoffset >> FRACBITS, 0);
|
|
UINT32 postfadems = (UINT32)max(sides[line->sidenum[0]].rowoffset >> FRACBITS, 0);
|
|
UINT8 fadetarget = (UINT8)max((line->sidenum[1] != 0xffff) ? sides[line->sidenum[1]].textureoffset >> FRACBITS : 0, 0);
|
|
INT16 fadesource = (INT16)max((line->sidenum[1] != 0xffff) ? sides[line->sidenum[1]].rowoffset >> FRACBITS : -1, -1);
|
|
|
|
// Seek offset from current song position
|
|
if (line->flags & ML_EFFECT1)
|
|
{
|
|
// adjust for loop point if subtracting
|
|
if (position < 0 && S_GetMusicLength() &&
|
|
S_GetMusicPosition() > S_GetMusicLoopPoint() &&
|
|
S_GetMusicPosition() + position < S_GetMusicLoopPoint())
|
|
position = max(S_GetMusicLength() - (S_GetMusicLoopPoint() - (S_GetMusicPosition() + position)), 0);
|
|
else
|
|
position = max(S_GetMusicPosition() + position, 0);
|
|
}
|
|
|
|
// Fade current music to target volume (if music won't be changed)
|
|
if ((line->flags & ML_EFFECT2) && fadetarget && musicsame)
|
|
{
|
|
// 0 fadesource means fade from current volume.
|
|
// meaning that we can't specify volume 0 as the source volume -- this starts at 1.
|
|
if (!fadesource)
|
|
fadesource = -1;
|
|
|
|
if (!postfadems)
|
|
S_SetInternalMusicVolume(fadetarget);
|
|
else
|
|
S_FadeMusicFromVolume(fadetarget, fadesource, postfadems);
|
|
|
|
if (position)
|
|
S_SetMusicPosition(position);
|
|
}
|
|
// Change the music and apply position/fade operations
|
|
else
|
|
{
|
|
strncpy(mapmusname, sides[line->sidenum[0]].text, 7);
|
|
mapmusname[6] = 0;
|
|
|
|
mapmusflags = tracknum & MUSIC_TRACKMASK;
|
|
if (!(line->flags & ML_BLOCKMONSTERS))
|
|
mapmusflags |= MUSIC_RELOADRESET;
|
|
if (line->flags & ML_BOUNCY)
|
|
mapmusflags |= MUSIC_FORCERESET;
|
|
|
|
mapmusposition = position;
|
|
|
|
S_ChangeMusicEx(mapmusname, mapmusflags, !(line->flags & ML_EFFECT4), position,
|
|
!(line->flags & ML_EFFECT2) ? prefadems : 0,
|
|
!(line->flags & ML_EFFECT2) ? postfadems : 0);
|
|
|
|
if ((line->flags & ML_EFFECT2) && fadetarget)
|
|
{
|
|
if (!postfadems)
|
|
S_SetInternalMusicVolume(fadetarget);
|
|
else
|
|
S_FadeMusicFromVolume(fadetarget, fadesource, postfadems);
|
|
}
|
|
}
|
|
|
|
// 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
|
|
{
|
|
INT32 sfxnum;
|
|
|
|
sfxnum = sides[line->sidenum[0]].toptexture;
|
|
|
|
if (sfxnum == sfx_None)
|
|
return; // Do nothing!
|
|
if (sfxnum < sfx_None || sfxnum >= NUMSFX)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 414 Executor: sfx number %d is invalid!\n", sfxnum);
|
|
return;
|
|
}
|
|
if (line->tag != 0) // Do special stuff only if a non-zero linedef tag is set
|
|
{
|
|
if (line->flags & ML_EFFECT5) // Repeat Midtexture
|
|
{
|
|
// Additionally play the sound from tagged sectors' soundorgs
|
|
sector_t *sec;
|
|
|
|
while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
|
|
{
|
|
sec = §ors[secnum];
|
|
S_StartSound(&sec->soundorg, sfxnum);
|
|
}
|
|
}
|
|
else if (mo) // A mobj must have triggered the executor
|
|
{
|
|
// 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 > P_GetSpecialTopZ(mo, sectors + rover->secnum, mo->subsector->sector))
|
|
continue;
|
|
|
|
if (mo->z + mo->height < P_GetSpecialBottomZ(mo, sectors + rover->secnum, mo->subsector->sector))
|
|
continue;
|
|
|
|
foundit = true;
|
|
}
|
|
|
|
if (mo->subsector->sector->tag == line->tag)
|
|
foundit = true;
|
|
|
|
if (!foundit)
|
|
return;
|
|
}
|
|
}
|
|
|
|
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->flags & ML_DONTPEGBOTTOM) ? max(sides[line->sidenum[0]].textureoffset>>FRACBITS, 0) : line->frontsector->lightlevel,
|
|
// failsafe: if user specifies Back Y Offset and NOT Front Y Offset, use the Back Offset
|
|
// to be consistent with other light and fade specials
|
|
(line->flags & ML_DONTPEGBOTTOM) ?
|
|
((line->sidenum[1] != 0xFFFF && !(sides[line->sidenum[0]].rowoffset>>FRACBITS)) ?
|
|
max(min(sides[line->sidenum[1]].rowoffset>>FRACBITS, 255), 0)
|
|
: max(min(sides[line->sidenum[0]].rowoffset>>FRACBITS, 255), 0))
|
|
: abs(P_AproxDistance(line->dx, line->dy))>>FRACBITS,
|
|
(line->flags & ML_EFFECT4),
|
|
(line->flags & ML_EFFECT5));
|
|
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 = thlist[THINK_MAIN].next; th != &thlist[THINK_MAIN]; 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
|
|
boolean foundrover = false; // for debug, "Can't find a FOF" message
|
|
|
|
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)
|
|
{
|
|
foundrover = true;
|
|
|
|
EV_CrumbleChain(sec, rover);
|
|
}
|
|
}
|
|
|
|
if (!foundrover)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 436 Executor: Can't find a FOF control sector with tag %d\n", foftag);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
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_s3k68);
|
|
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
|
|
if (line->text)
|
|
LUAh_LinedefExecute(line, mo, callsec);
|
|
else
|
|
CONS_Alert(CONS_WARNING, "Linedef %s is missing the hook name of the Lua function to call! (This should be given in the front texture fields)\n", sizeu1(line-lines));
|
|
#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
|
|
boolean foundrover = false; // for debug, "Can't find a FOF" message
|
|
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)
|
|
{
|
|
foundrover = true;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (!foundrover)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 445 Executor: Can't find a FOF control sector with tag %d\n", foftag);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 446: // Make block fall remotely (acts like FF_CRUMBLE)
|
|
{
|
|
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 make fall down
|
|
boolean foundrover = false; // for debug, "Can't find a FOF" message
|
|
player_t *player = NULL; // player that caused FOF to fall
|
|
boolean respawn = true; // should the fallen FOF respawn?
|
|
|
|
if (mo) // NULL check
|
|
player = mo->player;
|
|
|
|
if (line->flags & ML_NOCLIMB) // don't respawn!
|
|
respawn = false;
|
|
|
|
for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
|
|
{
|
|
sec = sectors + secnum;
|
|
|
|
if (!sec->ffloors)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 446 Executor: Target sector #%d has no FOFs.\n", secnum);
|
|
return;
|
|
}
|
|
|
|
for (rover = sec->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (rover->master->frontsector->tag == foftag)
|
|
{
|
|
foundrover = true;
|
|
|
|
if (line->flags & ML_BLOCKMONSTERS) // FOF flags determine respawn ability instead?
|
|
respawn = !(rover->flags & FF_NORETURN) ^ !!(line->flags & ML_NOCLIMB); // no climb inverts
|
|
|
|
EV_StartCrumble(rover->master->frontsector, rover, (rover->flags & FF_FLOATBOB), player, rover->alpha, respawn);
|
|
}
|
|
}
|
|
|
|
if (!foundrover)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 446 Executor: Can't find a FOF control sector with tag %d\n", foftag);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 447: // Change colormap of tagged sectors!
|
|
// Basically this special applies a colormap to the tagged sectors, just like 606 (the colormap linedef)
|
|
// Except it is activated by linedef executor, not level load
|
|
// This could even override existing colormaps I believe
|
|
// -- Monster Iestyn 14/06/18
|
|
for (secnum = -1; (secnum = P_FindSectorFromLineTag(line, secnum)) >= 0 ;)
|
|
{
|
|
P_ResetColormapFader(§ors[secnum]);
|
|
|
|
if (line->flags & ML_EFFECT3) // relative calc
|
|
{
|
|
extracolormap_t *exc = R_AddColormaps(
|
|
(line->flags & ML_TFERLINE) && line->sidenum[1] != 0xFFFF ?
|
|
sides[line->sidenum[1]].colormap_data : sectors[secnum].extra_colormap, // use back colormap instead of target sector
|
|
sides[line->sidenum[0]].colormap_data,
|
|
line->flags & ML_EFFECT1, // subtract R
|
|
line->flags & ML_NOCLIMB, // subtract G
|
|
line->flags & ML_EFFECT2, // subtract B
|
|
false, // subtract A (no flag for this, just pass negative alpha)
|
|
line->flags & ML_EFFECT1, // subtract FadeR
|
|
line->flags & ML_NOCLIMB, // subtract FadeG
|
|
line->flags & ML_EFFECT2, // subtract FadeB
|
|
false, // subtract FadeA (no flag for this, just pass negative alpha)
|
|
false, // subtract FadeStart (we ran out of flags)
|
|
false, // subtract FadeEnd (we ran out of flags)
|
|
false, // ignore Fog (we ran out of flags)
|
|
line->flags & ML_DONTPEGBOTTOM,
|
|
(line->flags & ML_DONTPEGBOTTOM) ? (sides[line->sidenum[0]].textureoffset >> FRACBITS) : 0,
|
|
(line->flags & ML_DONTPEGBOTTOM) ? (sides[line->sidenum[0]].rowoffset >> FRACBITS) : 0,
|
|
false);
|
|
|
|
if (!(sectors[secnum].extra_colormap = R_GetColormapFromList(exc)))
|
|
{
|
|
exc->colormap = R_CreateLightTable(exc);
|
|
R_AddColormapToList(exc);
|
|
sectors[secnum].extra_colormap = exc;
|
|
}
|
|
else
|
|
Z_Free(exc);
|
|
}
|
|
else if (line->flags & ML_DONTPEGBOTTOM) // alternate alpha (by texture offsets)
|
|
{
|
|
extracolormap_t *exc = R_CopyColormap(sides[line->sidenum[0]].colormap_data, false);
|
|
exc->rgba = R_GetRgbaRGB(exc->rgba) + R_PutRgbaA(max(min(sides[line->sidenum[0]].textureoffset >> FRACBITS, 25), 0));
|
|
exc->fadergba = R_GetRgbaRGB(exc->fadergba) + R_PutRgbaA(max(min(sides[line->sidenum[0]].rowoffset >> FRACBITS, 25), 0));
|
|
|
|
if (!(sectors[secnum].extra_colormap = R_GetColormapFromList(exc)))
|
|
{
|
|
exc->colormap = R_CreateLightTable(exc);
|
|
R_AddColormapToList(exc);
|
|
sectors[secnum].extra_colormap = exc;
|
|
}
|
|
else
|
|
Z_Free(exc);
|
|
}
|
|
else
|
|
sectors[secnum].extra_colormap = sides[line->sidenum[0]].colormap_data;
|
|
}
|
|
break;
|
|
|
|
case 448: // Change skybox viewpoint/centerpoint
|
|
if ((mo && mo->player && P_IsLocalPlayer(mo->player)) || (line->flags & ML_NOCLIMB))
|
|
{
|
|
INT32 viewid = sides[line->sidenum[0]].textureoffset>>FRACBITS;
|
|
INT32 centerid = sides[line->sidenum[0]].rowoffset>>FRACBITS;
|
|
|
|
if ((line->flags & (ML_EFFECT4|ML_BLOCKMONSTERS)) == ML_EFFECT4) // Solid Midtexture is on but Block Enemies is off?
|
|
{
|
|
CONS_Alert(CONS_WARNING,
|
|
M_GetText("Skybox switch linedef (tag %d) doesn't have anything to do.\nConsider changing the linedef's flag configuration or removing it entirely.\n"),
|
|
line->tag);
|
|
}
|
|
else
|
|
{
|
|
// set viewpoint mobj
|
|
if (!(line->flags & ML_EFFECT4)) // Solid Midtexture turns off viewpoint setting
|
|
{
|
|
if (viewid >= 0 && viewid < 16)
|
|
skyboxmo[0] = skyboxviewpnts[viewid];
|
|
else
|
|
skyboxmo[0] = NULL;
|
|
}
|
|
|
|
// set centerpoint mobj
|
|
if (line->flags & ML_BLOCKMONSTERS) // Block Enemies turns ON centerpoint setting
|
|
{
|
|
if (centerid >= 0 && centerid < 16)
|
|
skyboxmo[1] = skyboxcenterpnts[centerid];
|
|
else
|
|
skyboxmo[1] = NULL;
|
|
}
|
|
}
|
|
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 448 Executor: viewid = %d, centerid = %d, viewpoint? = %s, centerpoint? = %s\n",
|
|
viewid,
|
|
centerid,
|
|
((line->flags & ML_EFFECT4) ? "no" : "yes"),
|
|
((line->flags & ML_BLOCKMONSTERS) ? "yes" : "no"));
|
|
}
|
|
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;
|
|
}
|
|
|
|
case 452: // Set FOF alpha
|
|
{
|
|
INT16 destvalue = line->sidenum[1] != 0xffff ?
|
|
(INT16)(sides[line->sidenum[1]].textureoffset>>FRACBITS) : (INT16)(P_AproxDistance(line->dx, line->dy)>>FRACBITS);
|
|
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 operate
|
|
boolean foundrover = false; // for debug, "Can't find a FOF" message
|
|
|
|
for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
|
|
{
|
|
sec = sectors + secnum;
|
|
|
|
if (!sec->ffloors)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 452 Executor: Target sector #%d has no FOFs.\n", secnum);
|
|
return;
|
|
}
|
|
|
|
for (rover = sec->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (rover->master->frontsector->tag == foftag)
|
|
{
|
|
foundrover = true;
|
|
|
|
// If fading an invisible FOF whose render flags we did not yet set,
|
|
// initialize its alpha to 1
|
|
// for relative alpha calc
|
|
if (!(line->flags & ML_NOCLIMB) && // do translucent
|
|
(rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
|
|
!(rover->spawnflags & FF_RENDERSIDES) &&
|
|
!(rover->spawnflags & FF_RENDERPLANES) &&
|
|
!(rover->flags & FF_RENDERALL))
|
|
rover->alpha = 1;
|
|
|
|
P_RemoveFakeFloorFader(rover);
|
|
P_FadeFakeFloor(rover,
|
|
rover->alpha,
|
|
max(1, min(256, (line->flags & ML_EFFECT3) ? rover->alpha + destvalue : destvalue)),
|
|
0, // set alpha immediately
|
|
false, NULL, // tic-based logic
|
|
false, // do not handle FF_EXISTS
|
|
!(line->flags & ML_NOCLIMB), // handle FF_TRANSLUCENT
|
|
false, // do not handle lighting
|
|
false, // do not handle colormap
|
|
false, // do not handle collision
|
|
false, // do not do ghost fade (no collision during fade)
|
|
true); // use exact alpha values (for opengl)
|
|
}
|
|
}
|
|
|
|
if (!foundrover)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 452 Executor: Can't find a FOF control sector with tag %d\n", foftag);
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 453: // Fade FOF
|
|
{
|
|
INT16 destvalue = line->sidenum[1] != 0xffff ?
|
|
(INT16)(sides[line->sidenum[1]].textureoffset>>FRACBITS) : (INT16)(line->dx>>FRACBITS);
|
|
INT16 speed = line->sidenum[1] != 0xffff ?
|
|
(INT16)(abs(sides[line->sidenum[1]].rowoffset>>FRACBITS)) : (INT16)(abs(line->dy)>>FRACBITS);
|
|
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 operate
|
|
boolean foundrover = false; // for debug, "Can't find a FOF" message
|
|
size_t j = 0; // sec->ffloors is saved as ffloor #0, ss->ffloors->next is #1, etc
|
|
|
|
for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
|
|
{
|
|
sec = sectors + secnum;
|
|
|
|
if (!sec->ffloors)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 453 Executor: Target sector #%d has no FOFs.\n", secnum);
|
|
return;
|
|
}
|
|
|
|
for (rover = sec->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (rover->master->frontsector->tag == foftag)
|
|
{
|
|
foundrover = true;
|
|
|
|
// Prevent continuous execs from interfering on an existing fade
|
|
if (!(line->flags & ML_EFFECT5)
|
|
&& rover->fadingdata)
|
|
//&& ((fade_t*)rover->fadingdata)->timer > (ticbased ? 2 : speed*2))
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 453 Executor: Fade FOF thinker already exists, timer: %d\n", ((fade_t*)rover->fadingdata)->timer);
|
|
continue;
|
|
}
|
|
|
|
if (speed > 0)
|
|
P_AddFakeFloorFader(rover, secnum, j,
|
|
destvalue,
|
|
speed,
|
|
(line->flags & ML_EFFECT4), // tic-based logic
|
|
(line->flags & ML_EFFECT3), // Relative destvalue
|
|
!(line->flags & ML_BLOCKMONSTERS), // do not handle FF_EXISTS
|
|
!(line->flags & ML_NOCLIMB), // do not handle FF_TRANSLUCENT
|
|
!(line->flags & ML_EFFECT2), // do not handle lighting
|
|
!(line->flags & ML_EFFECT2), // do not handle colormap (ran out of flags)
|
|
!(line->flags & ML_BOUNCY), // do not handle collision
|
|
(line->flags & ML_EFFECT1), // do ghost fade (no collision during fade)
|
|
(line->flags & ML_TFERLINE)); // use exact alpha values (for opengl)
|
|
else
|
|
{
|
|
// If fading an invisible FOF whose render flags we did not yet set,
|
|
// initialize its alpha to 1
|
|
// for relative alpha calc
|
|
if (!(line->flags & ML_NOCLIMB) && // do translucent
|
|
(rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
|
|
!(rover->spawnflags & FF_RENDERSIDES) &&
|
|
!(rover->spawnflags & FF_RENDERPLANES) &&
|
|
!(rover->flags & FF_RENDERALL))
|
|
rover->alpha = 1;
|
|
|
|
P_RemoveFakeFloorFader(rover);
|
|
P_FadeFakeFloor(rover,
|
|
rover->alpha,
|
|
max(1, min(256, (line->flags & ML_EFFECT3) ? rover->alpha + destvalue : destvalue)),
|
|
0, // set alpha immediately
|
|
false, NULL, // tic-based logic
|
|
!(line->flags & ML_BLOCKMONSTERS), // do not handle FF_EXISTS
|
|
!(line->flags & ML_NOCLIMB), // do not handle FF_TRANSLUCENT
|
|
!(line->flags & ML_EFFECT2), // do not handle lighting
|
|
!(line->flags & ML_EFFECT2), // do not handle colormap (ran out of flags)
|
|
!(line->flags & ML_BOUNCY), // do not handle collision
|
|
(line->flags & ML_EFFECT1), // do ghost fade (no collision during fade)
|
|
(line->flags & ML_TFERLINE)); // use exact alpha values (for opengl)
|
|
}
|
|
}
|
|
j++;
|
|
}
|
|
|
|
if (!foundrover)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 453 Executor: Can't find a FOF control sector with tag %d\n", foftag);
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 454: // Stop fading FOF
|
|
{
|
|
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 operate
|
|
boolean foundrover = false; // for debug, "Can't find a FOF" message
|
|
|
|
for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
|
|
{
|
|
sec = sectors + secnum;
|
|
|
|
if (!sec->ffloors)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 454 Executor: Target sector #%d has no FOFs.\n", secnum);
|
|
return;
|
|
}
|
|
|
|
for (rover = sec->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (rover->master->frontsector->tag == foftag)
|
|
{
|
|
foundrover = true;
|
|
|
|
P_ResetFakeFloorFader(rover, NULL,
|
|
!(line->flags & ML_BLOCKMONSTERS)); // do not finalize collision flags
|
|
}
|
|
}
|
|
|
|
if (!foundrover)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 454 Executor: Can't find a FOF control sector with tag %d\n", foftag);
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 455: // Fade colormap
|
|
for (secnum = -1; (secnum = P_FindSectorFromLineTag(line, secnum)) >= 0 ;)
|
|
{
|
|
extracolormap_t *source_exc, *dest_exc, *exc;
|
|
INT32 speed = (INT32)((line->flags & ML_DONTPEGBOTTOM) || !sides[line->sidenum[0]].rowoffset) && line->sidenum[1] != 0xFFFF ?
|
|
abs(sides[line->sidenum[1]].rowoffset >> FRACBITS)
|
|
: abs(sides[line->sidenum[0]].rowoffset >> FRACBITS);
|
|
|
|
// Prevent continuous execs from interfering on an existing fade
|
|
if (!(line->flags & ML_EFFECT5)
|
|
&& sectors[secnum].fadecolormapdata)
|
|
//&& ((fadecolormap_t*)sectors[secnum].fadecolormapdata)->timer > (ticbased ? 2 : speed*2))
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Line type 455 Executor: Fade color thinker already exists, timer: %d\n", ((fadecolormap_t*)sectors[secnum].fadecolormapdata)->timer);
|
|
continue;
|
|
}
|
|
|
|
if (line->flags & ML_TFERLINE) // use back colormap instead of target sector
|
|
sectors[secnum].extra_colormap = (line->sidenum[1] != 0xFFFF) ?
|
|
sides[line->sidenum[1]].colormap_data : NULL;
|
|
|
|
exc = sectors[secnum].extra_colormap;
|
|
|
|
if (!(line->flags & ML_BOUNCY) // BOUNCY: Do not override fade from default rgba
|
|
&& !R_CheckDefaultColormap(sides[line->sidenum[0]].colormap_data, true, false, false)
|
|
&& R_CheckDefaultColormap(exc, true, false, false))
|
|
{
|
|
exc = R_CopyColormap(exc, false);
|
|
exc->rgba = R_GetRgbaRGB(sides[line->sidenum[0]].colormap_data->rgba) + R_PutRgbaA(R_GetRgbaA(exc->rgba));
|
|
//exc->fadergba = R_GetRgbaRGB(sides[line->sidenum[0]].colormap_data->rgba) + R_PutRgbaA(R_GetRgbaA(exc->fadergba));
|
|
|
|
if (!(source_exc = R_GetColormapFromList(exc)))
|
|
{
|
|
exc->colormap = R_CreateLightTable(exc);
|
|
R_AddColormapToList(exc);
|
|
source_exc = exc;
|
|
}
|
|
else
|
|
Z_Free(exc);
|
|
|
|
sectors[secnum].extra_colormap = source_exc;
|
|
}
|
|
else
|
|
source_exc = exc ? exc : R_GetDefaultColormap();
|
|
|
|
if (line->flags & ML_EFFECT3) // relative calc
|
|
{
|
|
exc = R_AddColormaps(
|
|
source_exc,
|
|
sides[line->sidenum[0]].colormap_data,
|
|
line->flags & ML_EFFECT1, // subtract R
|
|
line->flags & ML_NOCLIMB, // subtract G
|
|
line->flags & ML_EFFECT2, // subtract B
|
|
false, // subtract A (no flag for this, just pass negative alpha)
|
|
line->flags & ML_EFFECT1, // subtract FadeR
|
|
line->flags & ML_NOCLIMB, // subtract FadeG
|
|
line->flags & ML_EFFECT2, // subtract FadeB
|
|
false, // subtract FadeA (no flag for this, just pass negative alpha)
|
|
false, // subtract FadeStart (we ran out of flags)
|
|
false, // subtract FadeEnd (we ran out of flags)
|
|
false, // ignore Fog (we ran out of flags)
|
|
line->flags & ML_DONTPEGBOTTOM,
|
|
(line->flags & ML_DONTPEGBOTTOM) ? (sides[line->sidenum[0]].textureoffset >> FRACBITS) : 0,
|
|
(line->flags & ML_DONTPEGBOTTOM) ? (sides[line->sidenum[0]].rowoffset >> FRACBITS) : 0,
|
|
false);
|
|
}
|
|
else if (line->flags & ML_DONTPEGBOTTOM) // alternate alpha (by texture offsets)
|
|
{
|
|
exc = R_CopyColormap(sides[line->sidenum[0]].colormap_data, false);
|
|
exc->rgba = R_GetRgbaRGB(exc->rgba) + R_PutRgbaA(max(min(sides[line->sidenum[0]].textureoffset >> FRACBITS, 25), 0));
|
|
exc->fadergba = R_GetRgbaRGB(exc->fadergba) + R_PutRgbaA(max(min(sides[line->sidenum[0]].rowoffset >> FRACBITS, 25), 0));
|
|
}
|
|
else
|
|
exc = R_CopyColormap(sides[line->sidenum[0]].colormap_data, false);
|
|
|
|
if (!(dest_exc = R_GetColormapFromList(exc)))
|
|
{
|
|
exc->colormap = R_CreateLightTable(exc);
|
|
R_AddColormapToList(exc);
|
|
dest_exc = exc;
|
|
}
|
|
else
|
|
Z_Free(exc);
|
|
|
|
Add_ColormapFader(§ors[secnum], source_exc, dest_exc, (line->flags & ML_EFFECT4), // tic-based timing
|
|
speed);
|
|
}
|
|
break;
|
|
|
|
case 456: // Stop fade colormap
|
|
for (secnum = -1; (secnum = P_FindSectorFromLineTag(line, secnum)) >= 0 ;)
|
|
P_ResetColormapFader(§ors[secnum]);
|
|
break;
|
|
|
|
case 457: // Track mobj angle to point
|
|
if (mo)
|
|
{
|
|
INT32 failureangle = FixedAngle((min(max(abs(sides[line->sidenum[0]].textureoffset>>FRACBITS), 0), 360))*FRACUNIT);
|
|
INT32 failuredelay = abs(sides[line->sidenum[0]].rowoffset>>FRACBITS);
|
|
INT32 failureexectag = line->sidenum[1] != 0xffff ?
|
|
(INT32)(sides[line->sidenum[1]].textureoffset>>FRACBITS) : 0;
|
|
boolean persist = (line->flags & ML_EFFECT2);
|
|
mobj_t *anchormo;
|
|
|
|
if ((secnum = P_FindSectorFromLineTag(line, -1)) < 0)
|
|
return;
|
|
|
|
anchormo = P_GetObjectTypeInSectorNum(MT_ANGLEMAN, secnum);
|
|
if (!anchormo)
|
|
return;
|
|
|
|
mo->eflags |= MFE_TRACERANGLE;
|
|
P_SetTarget(&mo->tracer, anchormo);
|
|
mo->lastlook = persist; // don't disable behavior after first failure
|
|
mo->extravalue1 = failureangle; // angle to exceed for failure state
|
|
mo->extravalue2 = failureexectag; // exec tag for failure state (angle is not within range)
|
|
mo->cusval = mo->cvmem = failuredelay; // cusval = tics to allow failure before line trigger; cvmem = decrement timer
|
|
}
|
|
break;
|
|
|
|
case 458: // Stop tracking mobj angle to point
|
|
if (mo && (mo->eflags & MFE_TRACERANGLE))
|
|
{
|
|
mo->eflags &= ~MFE_TRACERANGLE;
|
|
P_SetTarget(&mo->tracer, NULL);
|
|
mo->lastlook = mo->cvmem = mo->cusval = mo->extravalue1 = mo->extravalue2 = 0;
|
|
}
|
|
break;
|
|
|
|
case 459: // Control Text Prompt
|
|
// console player only unless NOCLIMB is set
|
|
if (mo && mo->player && P_IsLocalPlayer(mo->player) && (!bot || bot != mo))
|
|
{
|
|
INT32 promptnum = max(0, (sides[line->sidenum[0]].textureoffset>>FRACBITS)-1);
|
|
INT32 pagenum = max(0, (sides[line->sidenum[0]].rowoffset>>FRACBITS)-1);
|
|
INT32 postexectag = abs((line->sidenum[1] != 0xFFFF) ? sides[line->sidenum[1]].textureoffset>>FRACBITS : line->tag);
|
|
|
|
boolean closetextprompt = (line->flags & ML_BLOCKMONSTERS);
|
|
//boolean allplayers = (line->flags & ML_NOCLIMB);
|
|
boolean runpostexec = (line->flags & ML_EFFECT1);
|
|
boolean blockcontrols = !(line->flags & ML_EFFECT2);
|
|
boolean freezerealtime = !(line->flags & ML_EFFECT3);
|
|
//boolean freezethinkers = (line->flags & ML_EFFECT4);
|
|
boolean callbynamedtag = (line->flags & ML_TFERLINE);
|
|
|
|
if (closetextprompt)
|
|
F_EndTextPrompt(false, false);
|
|
else
|
|
{
|
|
if (callbynamedtag && sides[line->sidenum[0]].text && sides[line->sidenum[0]].text[0])
|
|
F_GetPromptPageByNamedTag(sides[line->sidenum[0]].text, &promptnum, &pagenum);
|
|
F_StartTextPrompt(promptnum, pagenum, mo, runpostexec ? postexectag : 0, blockcontrols, freezerealtime);
|
|
}
|
|
}
|
|
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;
|
|
case 492:
|
|
PolyFade(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_thinglist_next)
|
|
{
|
|
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 = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
|
|
{
|
|
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 = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
|
|
{
|
|
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 <= P_GetSpecialTopZ(mo, sectors + rover->secnum, mo->subsector->sector)
|
|
&& mo->z >= P_GetSpecialBottomZ(mo, sectors + rover->secnum, mo->subsector->sector))
|
|
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)
|
|
{
|
|
fixed_t topheight, bottomheight;
|
|
|
|
if (GETSECSPECIAL(rover->master->frontsector->special, section) != number)
|
|
continue;
|
|
|
|
if (!(rover->flags & FF_EXISTS))
|
|
continue;
|
|
|
|
topheight = P_GetSpecialTopZ(player->mo, sectors + rover->secnum, player->mo->subsector->sector);
|
|
bottomheight = P_GetSpecialBottomZ(player->mo, sectors + rover->secnum, player->mo->subsector->sector);
|
|
|
|
// 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 != 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 != bottomheight)
|
|
continue;
|
|
}
|
|
else if (rover->master->frontsector->flags & SF_FLIPSPECIAL_BOTH)
|
|
{
|
|
if (!((player->mo->eflags & MFE_VERTICALFLIP && player->mo->z + player->mo->height == bottomheight)
|
|
|| (!(player->mo->eflags & MFE_VERTICALFLIP) && player->mo->z == topheight)))
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Water and DEATH FOG!!! heh
|
|
if (player->mo->z > topheight || (player->mo->z + player->mo->height) < 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_sectorlist_next)
|
|
{
|
|
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)
|
|
{
|
|
fixed_t topheight, bottomheight;
|
|
|
|
if (GETSECSPECIAL(rover->master->frontsector->special, section) != number)
|
|
continue;
|
|
|
|
if (!(rover->flags & FF_EXISTS))
|
|
continue;
|
|
|
|
topheight = P_GetSpecialTopZ(player->mo, sectors + rover->secnum, player->mo->subsector->sector);
|
|
bottomheight = P_GetSpecialBottomZ(player->mo, sectors + rover->secnum, player->mo->subsector->sector);
|
|
|
|
// 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 != 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 != bottomheight)
|
|
continue;
|
|
}
|
|
else if (rover->master->frontsector->flags & SF_FLIPSPECIAL_BOTH)
|
|
{
|
|
if (!((player->mo->eflags & MFE_VERTICALFLIP && player->mo->z + player->mo->height == bottomheight)
|
|
|| (!(player->mo->eflags & MFE_VERTICALFLIP) && player->mo->z == topheight)))
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Water and DEATH FOG!!! heh
|
|
if (player->mo->z > topheight || (player->mo->z + player->mo->height) < 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 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, 0);
|
|
break;
|
|
case 2: // Damage (Water)
|
|
if ((roversector || P_MobjReadyToTrigger(player->mo, sector)) && (player->powers[pw_underwater] || player->powers[pw_carry] == CR_NIGHTSMODE))
|
|
P_DamageMobj(player->mo, NULL, NULL, 1, DMG_WATER);
|
|
break;
|
|
case 3: // Damage (Fire)
|
|
if (roversector || P_MobjReadyToTrigger(player->mo, sector))
|
|
P_DamageMobj(player->mo, NULL, NULL, 1, DMG_FIRE);
|
|
break;
|
|
case 4: // Damage (Electrical)
|
|
if (roversector || P_MobjReadyToTrigger(player->mo, sector))
|
|
P_DamageMobj(player->mo, NULL, NULL, 1, DMG_ELECTRIC);
|
|
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, 1, DMG_DEATHPIT);
|
|
break;
|
|
case 8: // Instant Kill
|
|
P_DamageMobj(player->mo, NULL, NULL, 1, DMG_INSTAKILL);
|
|
break;
|
|
case 9: // Ring Drainer (Floor Touch)
|
|
case 10: // Ring Drainer (No Floor Touch)
|
|
if (leveltime % (TICRATE/2) == 0 && player->rings > 0)
|
|
{
|
|
player->rings--;
|
|
S_StartSound(player->mo, sfx_itemup);
|
|
}
|
|
break;
|
|
case 11: // Special Stage Damage
|
|
if (player->exiting || player->bot) // Don't do anything for bots or players who have just finished
|
|
break;
|
|
|
|
if (!(player->powers[pw_shield] || player->spheres > 0)) // Don't do anything if no shield or spheres anyway
|
|
break;
|
|
|
|
P_SpecialStageDamage(player, NULL, NULL);
|
|
break;
|
|
case 12: // Space Countdown
|
|
if (!(player->powers[pw_shield] & SH_PROTECTWATER) && !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)
|
|
;
|
|
else if (sector->flags & SF_TRIGGERSPECIAL_TOUCH)
|
|
{
|
|
boolean insector = false;
|
|
msecnode_t *node;
|
|
for (node = players[i].mo->touching_sectorlist; node; node = node->m_sectorlist_next)
|
|
{
|
|
if (node->m_sector == roversector)
|
|
{
|
|
insector = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!insector)
|
|
goto DoneSection2;
|
|
}
|
|
else
|
|
goto DoneSection2;
|
|
|
|
if (!P_ThingIsOnThe3DFloor(players[i].mo, sector, roversector))
|
|
goto DoneSection2;
|
|
}
|
|
else
|
|
{
|
|
if (players[i].mo->subsector->sector == sector)
|
|
;
|
|
else if (sector->flags & SF_TRIGGERSPECIAL_TOUCH)
|
|
{
|
|
boolean insector = false;
|
|
msecnode_t *node;
|
|
for (node = players[i].mo->touching_sectorlist; node; node = node->m_sectorlist_next)
|
|
{
|
|
if (node->m_sector == sector)
|
|
{
|
|
insector = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!insector)
|
|
goto DoneSection2;
|
|
}
|
|
else
|
|
goto DoneSection2;
|
|
|
|
if (special == 3 && !P_MobjReadyToTrigger(players[i].mo, sector))
|
|
goto DoneSection2;
|
|
}
|
|
}
|
|
/* FALLTHRU */
|
|
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 = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
|
|
{
|
|
mo2 = (mobj_t *)th;
|
|
if (mo2->type == MT_EGGTRAP)
|
|
P_KillMobj(mo2, NULL, player->mo, 0);
|
|
}
|
|
|
|
// 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: // Unused
|
|
case 2: // Wind/Current
|
|
case 3: // Unused
|
|
case 4: // Conveyor Belt
|
|
break;
|
|
|
|
case 5: // Speed pad
|
|
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;
|
|
fixed_t sfxnum;
|
|
|
|
lineangle = R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y);
|
|
linespeed = sides[lines[i].sidenum[0]].textureoffset;
|
|
|
|
if (linespeed == 0)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "ERROR: Speed pad (tag %d) at zero speed.\n", sector->tag);
|
|
break;
|
|
}
|
|
|
|
player->mo->angle = lineangle;
|
|
|
|
if (!demoplayback || P_AnalogMove(player))
|
|
{
|
|
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 ((lines[i].flags & ML_EFFECT5) && (player->charability2 == CA2_SPINDASH)) // Roll!
|
|
{
|
|
if (!(player->pflags & PF_SPINNING))
|
|
player->pflags |= PF_SPINNING;
|
|
|
|
P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
|
|
}
|
|
|
|
player->powers[pw_flashing] = TICRATE/3;
|
|
|
|
sfxnum = sides[lines[i].sidenum[0]].toptexture;
|
|
|
|
if (!sfxnum)
|
|
sfxnum = sfx_spdpad;
|
|
|
|
S_StartSound(player->mo, sfxnum);
|
|
}
|
|
break;
|
|
|
|
case 6: // Unused
|
|
case 7: // Unused
|
|
case 8: // Unused
|
|
case 9: // Unused
|
|
case 10: // Unused
|
|
case 11: // Unused
|
|
case 12: // Unused
|
|
case 13: // Unused
|
|
case 14: // Unused
|
|
case 15: // Unused
|
|
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 (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap) && player->nightstime > 6)
|
|
player->nightstime = 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(V_AUTOFADEOUT|V_ALLOWLOWERCASE);
|
|
HU_SetCEchoDuration(5);
|
|
HU_DoCEcho(va(M_GetText("%s%s%s\\CAPTURED THE %sBLUE FLAG%s.\\\\\\\\"), "\x85", player_names[player-players], "\x80", "\x84", "\x80"));
|
|
|
|
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(V_AUTOFADEOUT|V_ALLOWLOWERCASE);
|
|
HU_SetCEchoDuration(5);
|
|
HU_DoCEcho(va(M_GetText("%s%s%s\\CAPTURED THE %sRED FLAG%s.\\\\\\\\"), "\x84", player_names[player-players], "\x80", "\x85", "\x80"));
|
|
|
|
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_FALL);
|
|
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_ROLL);
|
|
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 && player->powers[pw_carry] == CR_ZOOMTUBE)
|
|
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(sides[lines[lineindex].sidenum[0]].textureoffset)/8;
|
|
sequence = abs(sides[lines[lineindex].sidenum[0]].rowoffset)>>FRACBITS;
|
|
|
|
if (speed == 0)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "ERROR: Waypoint sequence %d at zero speed.\n", sequence);
|
|
break;
|
|
}
|
|
|
|
// scan the thinkers
|
|
// to find the first waypoint
|
|
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
|
|
{
|
|
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->powers[pw_carry] = CR_ZOOMTUBE;
|
|
player->speed = speed;
|
|
player->pflags |= PF_SPINNING;
|
|
player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_BOUNCING|PF_SLIDING|PF_CANCARRY);
|
|
player->climbing = 0;
|
|
|
|
if (player->mo->state-states != S_PLAY_ROLL)
|
|
{
|
|
P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
|
|
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 && player->powers[pw_carry] == CR_ZOOMTUBE)
|
|
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(sides[lines[lineindex].sidenum[0]].textureoffset)/8; // Negative means reverse
|
|
sequence = abs(sides[lines[lineindex].sidenum[0]].rowoffset)>>FRACBITS;
|
|
|
|
if (speed == 0)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "ERROR: Waypoint sequence %d at zero speed.\n", sequence);
|
|
break;
|
|
}
|
|
|
|
// scan the thinkers
|
|
// to find the last waypoint
|
|
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
|
|
{
|
|
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->powers[pw_carry] = CR_ZOOMTUBE;
|
|
player->speed = speed;
|
|
player->pflags |= PF_SPINNING;
|
|
player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_BOUNCING|PF_SLIDING|PF_CANCARRY);
|
|
player->climbing = 0;
|
|
|
|
if (player->mo->state-states != S_PLAY_ROLL)
|
|
{
|
|
P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
|
|
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->powers[pw_carry] == CR_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 && player->powers[pw_carry] == CR_ROPEHANG)
|
|
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(sides[lines[lineindex].sidenum[0]].textureoffset)/8;
|
|
sequence = abs(sides[lines[lineindex].sidenum[0]].rowoffset)>>FRACBITS;
|
|
|
|
if (speed == 0)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "ERROR: Waypoint sequence %d at zero speed.\n", sequence);
|
|
break;
|
|
}
|
|
|
|
// 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 = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
|
|
{
|
|
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 = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
|
|
{
|
|
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 = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
|
|
{
|
|
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);
|
|
player->powers[pw_carry] = CR_ROPEHANG;
|
|
|
|
// Option for static ropes.
|
|
if (lines[lineindex].flags & ML_NOCLIMB)
|
|
player->speed = 0;
|
|
else
|
|
player->speed = speed;
|
|
|
|
S_StartSound(player->mo, sfx_s3k4a);
|
|
|
|
player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_BOUNCING|PF_SLIDING|PF_CANCARRY);
|
|
player->climbing = 0;
|
|
P_SetThingPosition(player->mo);
|
|
P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
|
|
}
|
|
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;
|
|
fixed_t topheight, bottomheight;
|
|
|
|
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;
|
|
|
|
topheight = P_GetSpecialTopZ(mo, sectors + rover->secnum, sector);
|
|
bottomheight = P_GetSpecialBottomZ(mo, sectors + rover->secnum, sector);
|
|
|
|
// 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 != 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 != bottomheight)
|
|
continue;
|
|
}
|
|
else if (rover->master->frontsector->flags & SF_FLIPSPECIAL_BOTH)
|
|
{
|
|
if (!((mo->eflags & MFE_VERTICALFLIP && mo->z + mo->height == bottomheight)
|
|
|| (!(mo->eflags & MFE_VERTICALFLIP) && mo->z == topheight)))
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Water and intangible FOFs
|
|
if (mo->z > topheight || (mo->z + mo->height) < bottomheight)
|
|
continue;
|
|
}
|
|
|
|
return rover->master->frontsector;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#define TELEPORTED (player->mo->subsector->sector != originalsector)
|
|
|
|
/** 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)
|
|
{
|
|
sector_t *originalsector = player->mo->subsector->sector;
|
|
ffloor_t *rover;
|
|
fixed_t topheight, bottomheight;
|
|
|
|
for (rover = sector->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (!rover->master->frontsector->special)
|
|
continue;
|
|
|
|
if (!(rover->flags & FF_EXISTS))
|
|
continue;
|
|
|
|
topheight = P_GetSpecialTopZ(player->mo, sectors + rover->secnum, sector);
|
|
bottomheight = P_GetSpecialBottomZ(player->mo, sectors + rover->secnum, sector);
|
|
|
|
// 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 != 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 != bottomheight)
|
|
continue;
|
|
}
|
|
else if (rover->master->frontsector->flags & SF_FLIPSPECIAL_BOTH)
|
|
{
|
|
if (!((player->mo->eflags & MFE_VERTICALFLIP && player->mo->z + player->mo->height == bottomheight)
|
|
|| (!(player->mo->eflags & MFE_VERTICALFLIP) && player->mo->z == topheight)))
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Water and DEATH FOG!!! heh
|
|
if (player->mo->z > topheight || (player->mo->z + player->mo->height) < bottomheight)
|
|
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);
|
|
if TELEPORTED return;
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
if TELEPORTED return;
|
|
|
|
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 (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap))
|
|
{
|
|
// Special stage GOAL sector
|
|
// requires touching floor.
|
|
break;
|
|
}
|
|
/* FALLTHRU */
|
|
|
|
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 *originalsector;
|
|
sector_t *loopsector;
|
|
msecnode_t *node;
|
|
|
|
if (!player->mo)
|
|
return;
|
|
|
|
originalsector = player->mo->subsector->sector;
|
|
|
|
P_PlayerOnSpecial3DFloor(player, originalsector); // Handle FOFs first.
|
|
if TELEPORTED return;
|
|
|
|
P_RunSpecialSectorCheck(player, originalsector);
|
|
if TELEPORTED return;
|
|
|
|
// Iterate through touching_sectorlist for SF_TRIGGERSPECIAL_TOUCH
|
|
for (node = player->mo->touching_sectorlist; node; node = node->m_sectorlist_next)
|
|
{
|
|
loopsector = node->m_sector;
|
|
|
|
if (loopsector == originalsector) // Don't duplicate
|
|
continue;
|
|
|
|
// Check 3D floors...
|
|
P_PlayerOnSpecial3DFloor(player, loopsector);
|
|
if TELEPORTED return;
|
|
|
|
if (!(loopsector->flags & SF_TRIGGERSPECIAL_TOUCH))
|
|
continue;
|
|
|
|
P_RunSpecialSectorCheck(player, loopsector);
|
|
if TELEPORTED return;
|
|
}
|
|
}
|
|
|
|
#undef TELEPORTED
|
|
|
|
/** 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();
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Gets a 3Dfloor by control sector.
|
|
*
|
|
* \param sec Target sector.
|
|
* \param sec2 Control sector.
|
|
* \return Pointer to found 3Dfloor, or NULL.
|
|
* \sa P_GetFFloorByID
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/** Gets a 3Dfloor by ID number.
|
|
*
|
|
* \param sec Target sector.
|
|
* \param id ID of 3Dfloor in target sector. Note that the first FOF's ID is 0.
|
|
* \return Pointer to found 3Dfloor, or NULL.
|
|
* \sa P_GetFFloorBySec
|
|
*/
|
|
ffloor_t *P_GetFFloorByID(sector_t *sec, UINT16 id)
|
|
{
|
|
ffloor_t *rover;
|
|
UINT16 i = 0;
|
|
|
|
if (!sec->ffloors)
|
|
return NULL;
|
|
for (rover = sec->ffloors; rover; rover = rover->next)
|
|
if (i++ == id)
|
|
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;
|
|
ffloor->fadingdata = NULL;
|
|
|
|
// 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 = thlist[THINK_MAIN].next;
|
|
|
|
for(;;)
|
|
{
|
|
if(secthinkers)
|
|
{
|
|
if(i < secthinkers[sec2num].count)
|
|
th = secthinkers[sec2num].thinkers[i];
|
|
else break;
|
|
}
|
|
else if (th == &thlist[THINK_MAIN])
|
|
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))
|
|
{
|
|
if (!(flags & FF_SHATTERBOTTOM)) // Don't change the textures of a brick block, just a question block
|
|
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(THINK_MAIN, &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(THINK_MAIN, &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(THINK_MAIN, &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 plane displacement thinker.
|
|
* Whenever the "control" sector moves,
|
|
* the "affectee" sector's floor or ceiling plane moves too!
|
|
*
|
|
* \param speed Rate of movement relative to control sector
|
|
* \param control Control sector.
|
|
* \param affectee Target sector.
|
|
* \param reverse Reverse direction?
|
|
* \sa P_SpawnSpecials, T_PlaneDisplace
|
|
* \author Monster Iestyn
|
|
*/
|
|
static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control, INT32 affectee, UINT8 reverse)
|
|
{
|
|
planedisplace_t *displace;
|
|
|
|
// create and initialize new displacement thinker
|
|
displace = Z_Calloc(sizeof (*displace), PU_LEVSPEC, NULL);
|
|
P_AddThinker(THINK_MAIN, &displace->thinker);
|
|
|
|
displace->thinker.function.acp1 = (actionf_p1)T_PlaneDisplace;
|
|
displace->affectee = affectee;
|
|
displace->control = control;
|
|
displace->last_height = sectors[control].floorheight;
|
|
displace->speed = speed;
|
|
displace->type = type;
|
|
displace->reverse = reverse;
|
|
}
|
|
|
|
/** 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(THINK_MAIN, &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(THINK_MAIN, &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(THINK_MAIN, &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;
|
|
else
|
|
airbob->vars[7] = airbob->sector->ceilingheight - P_AproxDistance(sourceline->dx, sourceline->dy);
|
|
airbob->vars[6] = airbob->vars[7]
|
|
- (sec->ceilingheight - sec->floorheight);
|
|
|
|
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(THINK_MAIN, &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(THINK_MAIN, &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(THINK_MAIN, &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(THINK_MAIN, &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|FF_TRANSLUCENT;
|
|
|
|
/** 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 & 2)
|
|
//ffloor->flags |= FF_RENDERALL;
|
|
ffloor->alpha = 0xB0;
|
|
else
|
|
//ffloor->flags &= ~FF_RENDERALL;
|
|
ffloor->alpha = 0x90;
|
|
|
|
sourcesec = ffloor->master->frontsector; // Less to type!
|
|
|
|
#ifdef ESLOPE
|
|
top = (*ffloor->t_slope) ? P_GetZAt(*ffloor->t_slope, sector->soundorg.x, sector->soundorg.y)
|
|
: *ffloor->topheight;
|
|
bottom = (*ffloor->b_slope) ? P_GetZAt(*ffloor->b_slope, sector->soundorg.x, sector->soundorg.y)
|
|
: *ffloor->bottomheight;
|
|
sector->soundorg.z = (top + bottom)/2;
|
|
#else
|
|
sector->soundorg.z = (*ffloor->topheight + *ffloor->bottomheight)/2;
|
|
#endif
|
|
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_thinglist_next)
|
|
{
|
|
thing = node->m_thing;
|
|
|
|
if ((ffloor->master->flags & ML_EFFECT1)
|
|
&& thing->flags & MF_BOSS)
|
|
continue; // Don't hurt bosses
|
|
|
|
// Don't endlessly kill egg guard shields (or anything else for that matter)
|
|
if (thing->health <= 0)
|
|
continue;
|
|
|
|
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, 0);
|
|
else if (thing->type == MT_EGGSHIELD)
|
|
P_KillMobj(thing, NULL, NULL, 0);
|
|
}
|
|
}
|
|
|
|
/** 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(THINK_MAIN, &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);
|
|
}
|
|
}
|
|
|
|
/** Before things are loaded, initialises certain stuff in case they're needed
|
|
* by P_ResetDynamicSlopes or P_LoadThings. This was split off from
|
|
* P_SpawnSpecials, in case you couldn't tell.
|
|
*
|
|
* \sa P_SpawnSpecials, P_InitTagLists
|
|
* \author Monster Iestyn
|
|
*/
|
|
void P_InitSpecials(void)
|
|
{
|
|
// 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;
|
|
ssspheres = 1;
|
|
|
|
CheckForBustableBlocks = CheckForBouncySector = CheckForQuicksand = CheckForMarioBlocks = CheckForFloatBob = CheckForReverseGravity = false;
|
|
|
|
// Set curWeather
|
|
switch (mapheaderinfo[gamemap-1]->weather)
|
|
{
|
|
case PRECIP_SNOW: // snow
|
|
case PRECIP_RAIN: // rain
|
|
case PRECIP_STORM: // storm
|
|
case PRECIP_STORM_NORAIN: // storm w/o rain
|
|
case PRECIP_STORM_NOSTRIKES: // storm w/o lightning
|
|
curWeather = mapheaderinfo[gamemap-1]->weather;
|
|
break;
|
|
default: // blank/none
|
|
curWeather = PRECIP_NONE;
|
|
break;
|
|
}
|
|
|
|
// Set globalweather
|
|
globalweather = mapheaderinfo[gamemap-1]->weather;
|
|
|
|
P_InitTagLists(); // Create xref tables for tags
|
|
}
|
|
|
|
static void P_ApplyFlatAlignment(line_t *master, sector_t *sector, angle_t flatangle, fixed_t xoffs, fixed_t yoffs)
|
|
{
|
|
if (!(master->flags & ML_NOSONIC)) // Modify floor flat alignment unless NOSONIC flag is set
|
|
{
|
|
sector->spawn_flrpic_angle = sector->floorpic_angle = flatangle;
|
|
sector->floor_xoffs += xoffs;
|
|
sector->floor_yoffs += yoffs;
|
|
// saved for netgames
|
|
sector->spawn_flr_xoffs = sector->floor_xoffs;
|
|
sector->spawn_flr_yoffs = sector->floor_yoffs;
|
|
}
|
|
|
|
if (!(master->flags & ML_NOTAILS)) // Modify ceiling flat alignment unless NOTAILS flag is set
|
|
{
|
|
sector->spawn_ceilpic_angle = sector->ceilingpic_angle = flatangle;
|
|
sector->ceiling_xoffs += xoffs;
|
|
sector->ceiling_yoffs += yoffs;
|
|
// saved for netgames
|
|
sector->spawn_ceil_xoffs = sector->ceiling_xoffs;
|
|
sector->spawn_ceil_yoffs = sector->ceiling_yoffs;
|
|
}
|
|
|
|
}
|
|
|
|
/** 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;
|
|
|
|
// 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
|
|
ssspheres = 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;
|
|
}
|
|
}
|
|
|
|
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 = thlist[THINK_MAIN].next; th != &thlist[THINK_MAIN]; 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 = thlist[THINK_MAIN].next; th != &thlist[THINK_MAIN]; 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++)
|
|
{
|
|
if (lines[i].special != 7) // This is a hack. I can at least hope nobody wants to prevent flat alignment with arbitrary skin setups...
|
|
{
|
|
// 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))
|
|
|| (players[consoleplayer].charability == CA_FLY && (lines[i].flags & ML_NOTAILS))
|
|
|| (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 - redone by toast
|
|
if ((lines[i].flags & (ML_NOSONIC|ML_NOTAILS)) != (ML_NOSONIC|ML_NOTAILS)) // If you can do something...
|
|
{
|
|
angle_t flatangle = InvAngle(R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y));
|
|
fixed_t xoffs;
|
|
fixed_t yoffs;
|
|
|
|
if (lines[i].flags & ML_NOKNUX) // Set offset through x and y texture offsets if NOKNUX flag is set
|
|
{
|
|
xoffs = sides[lines[i].sidenum[0]].textureoffset;
|
|
yoffs = sides[lines[i].sidenum[0]].rowoffset;
|
|
}
|
|
else // Otherwise, set calculated offsets such that line's v1 is the apparent origin
|
|
{
|
|
xoffs = -lines[i].v1->x;
|
|
yoffs = lines[i].v1->y;
|
|
}
|
|
|
|
//If no tag is given, apply to front sector
|
|
if (lines[i].tag == 0)
|
|
P_ApplyFlatAlignment(lines + i, lines[i].frontsector, flatangle, xoffs, yoffs);
|
|
else
|
|
{
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0;)
|
|
P_ApplyFlatAlignment(lines + i, sectors + s, flatangle, xoffs, yoffs);
|
|
}
|
|
}
|
|
else // Otherwise, print a helpful warning. Can I do no less?
|
|
CONS_Alert(CONS_WARNING,
|
|
M_GetText("Flat alignment linedef (tag %d) doesn't have anything to do.\nConsider changing the linedef's flag configuration or removing it entirely.\n"),
|
|
lines[i].tag);
|
|
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;
|
|
/* FALLTHRU */
|
|
|
|
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
|
|
/* FALLTHRU */
|
|
|
|
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 66: // Displace floor by front sector
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
P_AddPlaneDisplaceThinker(pd_floor, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
|
|
break;
|
|
case 67: // Displace ceiling by front sector
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
P_AddPlaneDisplaceThinker(pd_ceiling, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
|
|
break;
|
|
case 68: // Displace both floor AND ceiling by front sector
|
|
for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
|
|
P_AddPlaneDisplaceThinker(pd_both, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
|
|
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|FF_BOTHPLANES|FF_ALLSIDES;
|
|
ffloorflags &= ~(FF_EXTRA|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|FF_BOTHPLANES|FF_ALLSIDES;
|
|
ffloorflags &= ~(FF_EXTRA|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|FF_BOTHPLANES|FF_ALLSIDES;
|
|
ffloorflags &= ~(FF_EXTRA|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|FF_BOTHPLANES|FF_ALLSIDES;
|
|
ffloorflags &= ~(FF_EXTRA|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|FF_BOTHPLANES|FF_ALLSIDES;
|
|
ffloorflags &= ~(FF_EXTRA|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
|
|
ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_MARIO;
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
ffloorflags |= FF_SHATTERBOTTOM;
|
|
if (lines[i].flags & ML_EFFECT1)
|
|
ffloorflags &= ~(FF_SOLID|FF_RENDERALL|FF_CUTLEVEL);
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, 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)
|
|
ffloorflags = FF_EXISTS|FF_RENDERALL|FF_BUSTUP|FF_SHATTER;
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
ffloorflags |= FF_SOLID|FF_SHATTERBOTTOM;
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, 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
|
|
ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_BUSTUP;
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
ffloorflags |= FF_ONLYKNUX;
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, 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
|
|
ffloorflags = FF_EXISTS|FF_QUICKSAND|FF_RENDERALL|FF_ALLSIDES|FF_CUTSPRITES;
|
|
if (lines[i].flags & ML_EFFECT5)
|
|
ffloorflags |= FF_RIPPLE;
|
|
|
|
P_AddFakeFloorsByLine(i, ffloorflags, 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;
|
|
UINT16 b;
|
|
|
|
if (W_IsLumpWad(lastloadedmaplumpnum)) // welp it's a map wad in a pk3
|
|
{ // HACK: Open wad file rather quickly so we can get the data from the sidedefs lump
|
|
UINT8 *wadData = W_CacheLumpNum(lastloadedmaplumpnum, PU_STATIC);
|
|
filelump_t *fileinfo = (filelump_t *)(wadData + ((wadinfo_t *)wadData)->infotableofs);
|
|
fileinfo += ML_SIDEDEFS; // we only need the SIDEDEFS lump
|
|
data = Z_Malloc(fileinfo->size, PU_STATIC, NULL);
|
|
M_Memcpy(data, wadData + fileinfo->filepos, fileinfo->size); // copy data
|
|
Z_Free(wadData); // we're done with this now
|
|
}
|
|
else // phew it's just a WAD
|
|
data = W_CacheLumpNum(lastloadedmaplumpnum + ML_SIDEDEFS,PU_STATIC);
|
|
|
|
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;
|
|
|
|
// NiGHTS trigger executors
|
|
case 323:
|
|
case 324:
|
|
case 325:
|
|
case 326:
|
|
case 327:
|
|
case 328:
|
|
case 329:
|
|
case 330:
|
|
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].extra_colormap = sectors[s].spawn_extra_colormap = sides[lines[i].sidenum[0]].colormap_data;
|
|
break;
|
|
|
|
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;
|
|
|
|
case 32: // Polyobj_RotDisplace
|
|
PolyRotDisplace(&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->eflags |= MFE_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;
|
|
ffloor_t *rover;
|
|
|
|
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;
|
|
|
|
// Find the FOF corresponding to the control linedef
|
|
for (rover = psec->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (rover->master == sec->lines[i])
|
|
break;
|
|
}
|
|
|
|
if (!rover) // This should be impossible, but don't complain if it is the case somehow
|
|
continue;
|
|
|
|
if (!(rover->flags & FF_EXISTS)) // If the FOF does not "exist", we pretend that nobody's there
|
|
continue;
|
|
|
|
for (node = psec->touching_thinglist; node; node = node->m_thinglist_next)
|
|
{
|
|
thing = node->m_thing;
|
|
|
|
if (thing->eflags & MFE_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_thinglist_next)
|
|
{
|
|
thing = node->m_thing;
|
|
|
|
if (thing->eflags & MFE_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;
|
|
|
|
// Find the FOF corresponding to the control linedef
|
|
for (rover = psec->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (rover->master == sec->lines[i])
|
|
break;
|
|
}
|
|
|
|
if (!rover) // This should be impossible, but don't complain if it is the case somehow
|
|
continue;
|
|
|
|
if (!(rover->flags & FF_EXISTS)) // If the FOF does not "exist", we pretend that nobody's there
|
|
continue;
|
|
|
|
for (node = psec->touching_thinglist; node; node = node->m_thinglist_next)
|
|
{
|
|
thing = node->m_thing;
|
|
|
|
if (thing->eflags & MFE_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_thinglist_next)
|
|
{
|
|
thing = node->m_thing;
|
|
|
|
if (thing->eflags & MFE_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(THINK_MAIN, &s->thinker);
|
|
}
|
|
|
|
/** 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; // direction and speed of scrolling
|
|
fixed_t dy = l->dy;
|
|
INT32 control = -1, accel = 0; // no control sector or acceleration
|
|
INT32 special = l->special;
|
|
|
|
// If front texture X offset provided, override the amount with it.
|
|
if (sides[l->sidenum[0]].textureoffset != 0)
|
|
{
|
|
fixed_t len = sides[l->sidenum[0]].textureoffset;
|
|
fixed_t h = FixedHypot(dx, dy);
|
|
dx = FixedMul(FixedDiv(dx, h), len);
|
|
dy = FixedMul(FixedDiv(dy, h), len);
|
|
}
|
|
dx = dx >> SCROLL_SHIFT;
|
|
dy = dy >> SCROLL_SHIFT;
|
|
|
|
// 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
|
|
if (l->tag == 0)
|
|
Add_Scroller(sc_ceiling, -dx, dy, control, l->frontsector - sectors, accel, l->flags & ML_NOCLIMB);
|
|
else
|
|
{
|
|
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;
|
|
/* FALLTHRU */
|
|
|
|
case 523: // carry objects on ceiling
|
|
dx = FixedMul(dx, CARRYFACTOR);
|
|
dy = FixedMul(dy, CARRYFACTOR);
|
|
|
|
if (l->tag == 0)
|
|
Add_Scroller(sc_carry_ceiling, dx, dy, control, l->frontsector - sectors, accel, l->flags & ML_NOCLIMB);
|
|
else
|
|
{
|
|
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
|
|
if (l->tag == 0)
|
|
Add_Scroller(sc_floor, -dx, dy, control, l->frontsector - sectors, accel, l->flags & ML_NOCLIMB);
|
|
else
|
|
{
|
|
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;
|
|
/* FALLTHRU */
|
|
|
|
case 520: // carry objects on floor
|
|
dx = FixedMul(dx, CARRYFACTOR);
|
|
dy = FixedMul(dy, CARRYFACTOR);
|
|
|
|
if (l->tag == 0)
|
|
Add_Scroller(sc_carry, dx, dy, control, l->frontsector - sectors, accel, l->flags & ML_NOCLIMB);
|
|
else
|
|
{
|
|
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_Scroller(sc_side, dx, dy, control, lines[s].sidenum[0], accel, 0);
|
|
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(THINK_MAIN, &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))
|
|
{
|
|
#ifdef ESLOPE
|
|
if (*rover->t_slope)
|
|
sectors[s].soundorg.z = P_GetZAt(*rover->t_slope, sectors[s].soundorg.x, sectors[s].soundorg.y);
|
|
else
|
|
#endif
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Removes fadingdata from FOF control sector
|
|
*
|
|
* \param line line to search for target faders
|
|
* \param data pointer to set new fadingdata to. Can be NULL to erase.
|
|
*/
|
|
static void P_ResetFakeFloorFader(ffloor_t *rover, fade_t *data, boolean finalize)
|
|
{
|
|
fade_t *fadingdata = (fade_t *)rover->fadingdata;
|
|
// find any existing thinkers and remove them, then replace with new data
|
|
if (fadingdata != data)
|
|
{
|
|
if (fadingdata)
|
|
{
|
|
if (finalize)
|
|
P_FadeFakeFloor(rover,
|
|
fadingdata->sourcevalue,
|
|
fadingdata->alpha >= fadingdata->destvalue ?
|
|
fadingdata->alpha - 1 : // trigger fade-out finish
|
|
fadingdata->alpha + 1, // trigger fade-in finish
|
|
0,
|
|
fadingdata->ticbased,
|
|
&fadingdata->timer,
|
|
fadingdata->doexists,
|
|
fadingdata->dotranslucent,
|
|
fadingdata->dolighting,
|
|
fadingdata->docolormap,
|
|
fadingdata->docollision,
|
|
fadingdata->doghostfade,
|
|
fadingdata->exactalpha);
|
|
rover->alpha = fadingdata->alpha;
|
|
|
|
if (fadingdata->dolighting)
|
|
P_RemoveLighting(§ors[rover->secnum]);
|
|
|
|
if (fadingdata->docolormap)
|
|
P_ResetColormapFader(§ors[rover->secnum]);
|
|
|
|
P_RemoveThinker(&fadingdata->thinker);
|
|
}
|
|
|
|
rover->fadingdata = data;
|
|
}
|
|
}
|
|
|
|
static boolean P_FadeFakeFloor(ffloor_t *rover, INT16 sourcevalue, INT16 destvalue, INT16 speed, boolean ticbased, INT32 *timer,
|
|
boolean doexists, boolean dotranslucent, boolean dolighting, boolean docolormap,
|
|
boolean docollision, boolean doghostfade, boolean exactalpha)
|
|
{
|
|
boolean stillfading = false;
|
|
INT32 alpha;
|
|
fade_t *fadingdata = (fade_t *)rover->fadingdata;
|
|
(void)docolormap; // *shrug* maybe we can use this in the future. For now, let's be consistent with our other function params
|
|
|
|
if (rover->master->special == 258) // Laser block
|
|
return false;
|
|
|
|
// If fading an invisible FOF whose render flags we did not yet set,
|
|
// initialize its alpha to 1
|
|
if (dotranslucent &&
|
|
(rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
|
|
!(rover->flags & FF_FOG) && // do not include fog
|
|
!(rover->spawnflags & FF_RENDERSIDES) &&
|
|
!(rover->spawnflags & FF_RENDERPLANES) &&
|
|
!(rover->flags & FF_RENDERALL))
|
|
rover->alpha = 1;
|
|
|
|
if (fadingdata)
|
|
alpha = fadingdata->alpha;
|
|
else
|
|
alpha = rover->alpha;
|
|
|
|
// routines specific to fade in and fade out
|
|
if (!ticbased && alpha == destvalue)
|
|
return stillfading;
|
|
else if (alpha > destvalue) // fade out
|
|
{
|
|
// finish fading out
|
|
if (speed < 1 || (!ticbased && alpha - speed <= destvalue + speed) ||
|
|
(ticbased && (--(*timer) <= 0 || alpha <= destvalue)))
|
|
{
|
|
alpha = destvalue;
|
|
|
|
if (docollision)
|
|
{
|
|
if (rover->spawnflags & FF_SOLID)
|
|
rover->flags &= ~FF_SOLID;
|
|
if (rover->spawnflags & FF_SWIMMABLE)
|
|
rover->flags &= ~FF_SWIMMABLE;
|
|
if (rover->spawnflags & FF_QUICKSAND)
|
|
rover->flags &= ~FF_QUICKSAND;
|
|
if (rover->spawnflags & FF_BUSTUP)
|
|
rover->flags &= ~FF_BUSTUP;
|
|
if (rover->spawnflags & FF_MARIO)
|
|
rover->flags &= ~FF_MARIO;
|
|
}
|
|
}
|
|
else // continue fading out
|
|
{
|
|
if (!ticbased)
|
|
alpha -= speed;
|
|
else
|
|
{
|
|
INT16 delta = abs(destvalue - sourcevalue);
|
|
fixed_t factor = min(FixedDiv(speed - (*timer), speed), 1*FRACUNIT);
|
|
alpha = max(min(alpha, sourcevalue - (INT16)FixedMul(delta, factor)), destvalue);
|
|
}
|
|
stillfading = true;
|
|
}
|
|
}
|
|
else // fade in
|
|
{
|
|
// finish fading in
|
|
if (speed < 1 || (!ticbased && alpha + speed >= destvalue - speed) ||
|
|
(ticbased && (--(*timer) <= 0 || alpha >= destvalue)))
|
|
{
|
|
alpha = destvalue;
|
|
|
|
if (docollision)
|
|
{
|
|
if (rover->spawnflags & FF_SOLID)
|
|
rover->flags |= FF_SOLID;
|
|
if (rover->spawnflags & FF_SWIMMABLE)
|
|
rover->flags |= FF_SWIMMABLE;
|
|
if (rover->spawnflags & FF_QUICKSAND)
|
|
rover->flags |= FF_QUICKSAND;
|
|
if (rover->spawnflags & FF_BUSTUP)
|
|
rover->flags |= FF_BUSTUP;
|
|
if (rover->spawnflags & FF_MARIO)
|
|
rover->flags |= FF_MARIO;
|
|
}
|
|
}
|
|
else // continue fading in
|
|
{
|
|
if (!ticbased)
|
|
alpha += speed;
|
|
else
|
|
{
|
|
INT16 delta = abs(destvalue - sourcevalue);
|
|
fixed_t factor = min(FixedDiv(speed - (*timer), speed), 1*FRACUNIT);
|
|
alpha = min(max(alpha, sourcevalue + (INT16)FixedMul(delta, factor)), destvalue);
|
|
}
|
|
stillfading = true;
|
|
}
|
|
}
|
|
|
|
// routines common to both fade in and fade out
|
|
if (!stillfading)
|
|
{
|
|
if (doexists && !(rover->spawnflags & FF_BUSTUP))
|
|
{
|
|
if (alpha <= 1)
|
|
rover->flags &= ~FF_EXISTS;
|
|
else
|
|
rover->flags |= FF_EXISTS;
|
|
|
|
// Re-render lighting at end of fade
|
|
if (dolighting && !(rover->spawnflags & FF_NOSHADE) && !(rover->flags & FF_EXISTS))
|
|
rover->target->moved = true;
|
|
}
|
|
|
|
if (dotranslucent && !(rover->flags & FF_FOG))
|
|
{
|
|
if (alpha >= 256)
|
|
{
|
|
if (!(rover->flags & FF_CUTSOLIDS) &&
|
|
(rover->spawnflags & FF_CUTSOLIDS))
|
|
{
|
|
rover->flags |= FF_CUTSOLIDS;
|
|
rover->target->moved = true;
|
|
}
|
|
|
|
rover->flags &= ~FF_TRANSLUCENT;
|
|
}
|
|
else
|
|
{
|
|
rover->flags |= FF_TRANSLUCENT;
|
|
|
|
if ((rover->flags & FF_CUTSOLIDS) &&
|
|
(rover->spawnflags & FF_CUTSOLIDS))
|
|
{
|
|
rover->flags &= ~FF_CUTSOLIDS;
|
|
rover->target->moved = true;
|
|
}
|
|
}
|
|
|
|
if ((rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
|
|
!(rover->spawnflags & FF_RENDERSIDES) &&
|
|
!(rover->spawnflags & FF_RENDERPLANES))
|
|
{
|
|
if (rover->alpha > 1)
|
|
rover->flags |= FF_RENDERALL;
|
|
else
|
|
rover->flags &= ~FF_RENDERALL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (doexists && !(rover->spawnflags & FF_BUSTUP))
|
|
{
|
|
// Re-render lighting if we haven't yet set FF_EXISTS (beginning of fade)
|
|
if (dolighting && !(rover->spawnflags & FF_NOSHADE) && !(rover->flags & FF_EXISTS))
|
|
rover->target->moved = true;
|
|
|
|
rover->flags |= FF_EXISTS;
|
|
}
|
|
|
|
if (dotranslucent && !(rover->flags & FF_FOG))
|
|
{
|
|
rover->flags |= FF_TRANSLUCENT;
|
|
|
|
if ((rover->flags & FF_CUTSOLIDS) &&
|
|
(rover->spawnflags & FF_CUTSOLIDS))
|
|
{
|
|
rover->flags &= ~FF_CUTSOLIDS;
|
|
rover->target->moved = true;
|
|
}
|
|
|
|
if ((rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
|
|
!(rover->spawnflags & FF_RENDERSIDES) &&
|
|
!(rover->spawnflags & FF_RENDERPLANES))
|
|
rover->flags |= FF_RENDERALL;
|
|
}
|
|
|
|
if (docollision)
|
|
{
|
|
if (doghostfade) // remove collision flags during fade
|
|
{
|
|
if (rover->spawnflags & FF_SOLID)
|
|
rover->flags &= ~FF_SOLID;
|
|
if (rover->spawnflags & FF_SWIMMABLE)
|
|
rover->flags &= ~FF_SWIMMABLE;
|
|
if (rover->spawnflags & FF_QUICKSAND)
|
|
rover->flags &= ~FF_QUICKSAND;
|
|
if (rover->spawnflags & FF_BUSTUP)
|
|
rover->flags &= ~FF_BUSTUP;
|
|
if (rover->spawnflags & FF_MARIO)
|
|
rover->flags &= ~FF_MARIO;
|
|
}
|
|
else // keep collision during fade
|
|
{
|
|
if (rover->spawnflags & FF_SOLID)
|
|
rover->flags |= FF_SOLID;
|
|
if (rover->spawnflags & FF_SWIMMABLE)
|
|
rover->flags |= FF_SWIMMABLE;
|
|
if (rover->spawnflags & FF_QUICKSAND)
|
|
rover->flags |= FF_QUICKSAND;
|
|
if (rover->spawnflags & FF_BUSTUP)
|
|
rover->flags |= FF_BUSTUP;
|
|
if (rover->spawnflags & FF_MARIO)
|
|
rover->flags |= FF_MARIO;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(rover->flags & FF_FOG)) // don't set FOG alpha
|
|
{
|
|
if (!stillfading || exactalpha)
|
|
rover->alpha = alpha;
|
|
else // clamp fadingdata->alpha to software's alpha levels
|
|
{
|
|
if (alpha < 12)
|
|
rover->alpha = destvalue < 12 ? destvalue : 1; // Don't even draw it
|
|
else if (alpha < 38)
|
|
rover->alpha = destvalue >= 12 && destvalue < 38 ? destvalue : 25;
|
|
else if (alpha < 64)
|
|
rover->alpha = destvalue >=38 && destvalue < 64 ? destvalue : 51;
|
|
else if (alpha < 89)
|
|
rover->alpha = destvalue >= 64 && destvalue < 89 ? destvalue : 76;
|
|
else if (alpha < 115)
|
|
rover->alpha = destvalue >= 89 && destvalue < 115 ? destvalue : 102;
|
|
else if (alpha < 140)
|
|
rover->alpha = destvalue >= 115 && destvalue < 140 ? destvalue : 128;
|
|
else if (alpha < 166)
|
|
rover->alpha = destvalue >= 140 && destvalue < 166 ? destvalue : 154;
|
|
else if (alpha < 192)
|
|
rover->alpha = destvalue >= 166 && destvalue < 192 ? destvalue : 179;
|
|
else if (alpha < 217)
|
|
rover->alpha = destvalue >= 192 && destvalue < 217 ? destvalue : 204;
|
|
else if (alpha < 243)
|
|
rover->alpha = destvalue >= 217 && destvalue < 243 ? destvalue : 230;
|
|
else // Opaque
|
|
rover->alpha = destvalue >= 243 ? destvalue : 256;
|
|
}
|
|
}
|
|
|
|
if (fadingdata)
|
|
fadingdata->alpha = alpha;
|
|
|
|
return stillfading;
|
|
}
|
|
|
|
/** Adds fake floor fader thinker.
|
|
*
|
|
* \param destvalue transparency value to fade to
|
|
* \param speed speed to fade by
|
|
* \param ticbased tic-based logic, speed = duration
|
|
* \param relative Destvalue is relative to rover->alpha
|
|
* \param doexists handle FF_EXISTS
|
|
* \param dotranslucent handle FF_TRANSLUCENT
|
|
* \param dolighting fade FOF light
|
|
* \param docollision handle interactive flags
|
|
* \param doghostfade no interactive flags during fading
|
|
* \param exactalpha use exact alpha values (opengl)
|
|
*/
|
|
static void P_AddFakeFloorFader(ffloor_t *rover, size_t sectornum, size_t ffloornum,
|
|
INT16 destvalue, INT16 speed, boolean ticbased, boolean relative,
|
|
boolean doexists, boolean dotranslucent, boolean dolighting, boolean docolormap,
|
|
boolean docollision, boolean doghostfade, boolean exactalpha)
|
|
{
|
|
fade_t *d;
|
|
|
|
// If fading an invisible FOF whose render flags we did not yet set,
|
|
// initialize its alpha to 1
|
|
if (dotranslucent &&
|
|
(rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
|
|
!(rover->spawnflags & FF_RENDERSIDES) &&
|
|
!(rover->spawnflags & FF_RENDERPLANES) &&
|
|
!(rover->flags & FF_RENDERALL))
|
|
rover->alpha = 1;
|
|
|
|
// already equal, nothing to do
|
|
if (rover->alpha == max(1, min(256, relative ? rover->alpha + destvalue : destvalue)))
|
|
return;
|
|
|
|
d = Z_Malloc(sizeof *d, PU_LEVSPEC, NULL);
|
|
|
|
d->thinker.function.acp1 = (actionf_p1)T_Fade;
|
|
d->rover = rover;
|
|
d->sectornum = (UINT32)sectornum;
|
|
d->ffloornum = (UINT32)ffloornum;
|
|
|
|
d->alpha = d->sourcevalue = rover->alpha;
|
|
d->destvalue = max(1, min(256, relative ? rover->alpha + destvalue : destvalue)); // ffloor->alpha is 1-256
|
|
|
|
if (ticbased)
|
|
{
|
|
d->ticbased = true;
|
|
d->timer = d->speed = abs(speed); // use d->speed as total duration
|
|
}
|
|
else
|
|
{
|
|
d->ticbased = false;
|
|
d->speed = max(1, speed); // minimum speed 1/tic // if speed < 1, alpha is set immediately in thinker
|
|
d->timer = -1;
|
|
}
|
|
|
|
d->doexists = doexists;
|
|
d->dotranslucent = dotranslucent;
|
|
d->dolighting = dolighting;
|
|
d->docolormap = docolormap;
|
|
d->docollision = docollision;
|
|
d->doghostfade = doghostfade;
|
|
d->exactalpha = exactalpha;
|
|
|
|
// find any existing thinkers and remove them, then replace with new data
|
|
P_ResetFakeFloorFader(rover, d, false);
|
|
|
|
// Set a separate thinker for shadow fading
|
|
if (dolighting && !(rover->flags & FF_NOSHADE))
|
|
{
|
|
UINT16 lightdelta = abs(sectors[rover->secnum].spawn_lightlevel - rover->target->lightlevel);
|
|
fixed_t alphapercent = min(FixedDiv(d->destvalue, rover->spawnalpha), 1*FRACUNIT); // don't make darker than spawn_lightlevel
|
|
fixed_t adjustedlightdelta = FixedMul(lightdelta, alphapercent);
|
|
|
|
if (rover->target->lightlevel >= sectors[rover->secnum].spawn_lightlevel) // fading out, get lighter
|
|
d->destlightlevel = rover->target->lightlevel - adjustedlightdelta;
|
|
else // fading in, get darker
|
|
d->destlightlevel = rover->target->lightlevel + adjustedlightdelta;
|
|
|
|
P_FadeLightBySector(§ors[rover->secnum],
|
|
d->destlightlevel,
|
|
ticbased ? d->timer :
|
|
FixedFloor(FixedDiv(abs(d->destvalue - d->alpha), d->speed))/FRACUNIT,
|
|
true);
|
|
}
|
|
else
|
|
d->destlightlevel = -1;
|
|
|
|
// Set a separate thinker for colormap fading
|
|
if (docolormap && !(rover->flags & FF_NOSHADE) && sectors[rover->secnum].spawn_extra_colormap)
|
|
{
|
|
extracolormap_t *dest_exc,
|
|
*source_exc = sectors[rover->secnum].extra_colormap ? sectors[rover->secnum].extra_colormap : R_GetDefaultColormap();
|
|
|
|
INT32 colordelta = R_GetRgbaA(sectors[rover->secnum].spawn_extra_colormap->rgba); // alpha is from 0
|
|
fixed_t alphapercent = min(FixedDiv(d->destvalue, rover->spawnalpha), 1*FRACUNIT); // don't make darker than spawn_lightlevel
|
|
fixed_t adjustedcolordelta = FixedMul(colordelta, alphapercent);
|
|
INT32 coloralpha;
|
|
|
|
coloralpha = adjustedcolordelta;
|
|
|
|
dest_exc = R_CopyColormap(sectors[rover->secnum].spawn_extra_colormap, false);
|
|
dest_exc->rgba = R_GetRgbaRGB(dest_exc->rgba) + R_PutRgbaA(coloralpha);
|
|
|
|
if (!(d->dest_exc = R_GetColormapFromList(dest_exc)))
|
|
{
|
|
dest_exc->colormap = R_CreateLightTable(dest_exc);
|
|
R_AddColormapToList(dest_exc);
|
|
d->dest_exc = dest_exc;
|
|
}
|
|
else
|
|
Z_Free(dest_exc);
|
|
|
|
// If fading from 0, set source_exc rgb same to dest_exc
|
|
if (!R_CheckDefaultColormap(d->dest_exc, true, false, false)
|
|
&& R_CheckDefaultColormap(source_exc, true, false, false))
|
|
{
|
|
extracolormap_t *exc = R_CopyColormap(source_exc, false);
|
|
exc->rgba = R_GetRgbaRGB(d->dest_exc->rgba) + R_PutRgbaA(R_GetRgbaA(source_exc->rgba));
|
|
exc->fadergba = R_GetRgbaRGB(d->dest_exc->rgba) + R_PutRgbaA(R_GetRgbaA(source_exc->fadergba));
|
|
|
|
if (!(source_exc = R_GetColormapFromList(exc)))
|
|
{
|
|
exc->colormap = R_CreateLightTable(exc);
|
|
R_AddColormapToList(exc);
|
|
source_exc = exc;
|
|
}
|
|
else
|
|
Z_Free(exc);
|
|
}
|
|
|
|
Add_ColormapFader(§ors[rover->secnum], source_exc, d->dest_exc, true,
|
|
ticbased ? d->timer :
|
|
FixedFloor(FixedDiv(abs(d->destvalue - d->alpha), d->speed))/FRACUNIT);
|
|
}
|
|
|
|
P_AddThinker(THINK_MAIN, &d->thinker);
|
|
}
|
|
|
|
/** Makes a FOF fade
|
|
*
|
|
* \param d Fade thinker.
|
|
* \sa P_AddFakeFloorFader
|
|
*/
|
|
void T_Fade(fade_t *d)
|
|
{
|
|
if (d->rover && !P_FadeFakeFloor(d->rover, d->sourcevalue, d->destvalue, d->speed, d->ticbased, &d->timer,
|
|
d->doexists, d->dotranslucent, d->dolighting, d->docolormap, d->docollision, d->doghostfade, d->exactalpha))
|
|
{
|
|
// Finalize lighting, copypasta from P_AddFakeFloorFader
|
|
if (d->dolighting && !(d->rover->flags & FF_NOSHADE) && d->destlightlevel > -1)
|
|
sectors[d->rover->secnum].lightlevel = d->destlightlevel;
|
|
|
|
// Finalize colormap
|
|
if (d->docolormap && !(d->rover->flags & FF_NOSHADE) && sectors[d->rover->secnum].spawn_extra_colormap)
|
|
sectors[d->rover->secnum].extra_colormap = d->dest_exc;
|
|
|
|
P_RemoveFakeFloorFader(d->rover);
|
|
}
|
|
}
|
|
|
|
static void P_ResetColormapFader(sector_t *sector)
|
|
{
|
|
if (sector->fadecolormapdata)
|
|
{
|
|
// The thinker is the first member in all the action structs,
|
|
// so just let the thinker get freed, and that will free the whole
|
|
// structure.
|
|
P_RemoveThinker(&((elevator_t *)sector->fadecolormapdata)->thinker);
|
|
sector->fadecolormapdata = NULL;
|
|
}
|
|
}
|
|
|
|
static void Add_ColormapFader(sector_t *sector, extracolormap_t *source_exc, extracolormap_t *dest_exc,
|
|
boolean ticbased, INT32 duration)
|
|
{
|
|
fadecolormap_t *d;
|
|
|
|
P_ResetColormapFader(sector);
|
|
|
|
// nothing to do, set immediately
|
|
if (!duration || R_CheckEqualColormaps(source_exc, dest_exc, true, true, true))
|
|
{
|
|
sector->extra_colormap = dest_exc;
|
|
return;
|
|
}
|
|
|
|
d = Z_Malloc(sizeof *d, PU_LEVSPEC, NULL);
|
|
d->thinker.function.acp1 = (actionf_p1)T_FadeColormap;
|
|
d->sector = sector;
|
|
d->source_exc = source_exc;
|
|
d->dest_exc = dest_exc;
|
|
|
|
if (ticbased)
|
|
{
|
|
d->ticbased = true;
|
|
d->duration = d->timer = duration;
|
|
}
|
|
else
|
|
{
|
|
d->ticbased = false;
|
|
d->timer = 256;
|
|
d->duration = duration; // use as speed
|
|
}
|
|
|
|
sector->fadecolormapdata = d;
|
|
P_AddThinker(THINK_MAIN, &d->thinker);
|
|
}
|
|
|
|
void T_FadeColormap(fadecolormap_t *d)
|
|
{
|
|
if ((d->ticbased && --d->timer <= 0)
|
|
|| (!d->ticbased && (d->timer -= d->duration) <= 0)) // d->duration used as speed decrement
|
|
{
|
|
d->sector->extra_colormap = d->dest_exc;
|
|
P_ResetColormapFader(d->sector);
|
|
}
|
|
else
|
|
{
|
|
extracolormap_t *exc;
|
|
INT32 duration = d->ticbased ? d->duration : 256;
|
|
fixed_t factor = min(FixedDiv(duration - d->timer, duration), 1*FRACUNIT);
|
|
INT16 cr, cg, cb, ca, fadestart, fadeend, fog;
|
|
INT32 rgba, fadergba;
|
|
|
|
// NULL failsafes (or intentionally set to signify default)
|
|
if (!d->sector->extra_colormap)
|
|
d->sector->extra_colormap = R_GetDefaultColormap();
|
|
|
|
if (!d->source_exc)
|
|
d->source_exc = R_GetDefaultColormap();
|
|
|
|
if (!d->dest_exc)
|
|
d->dest_exc = R_GetDefaultColormap();
|
|
|
|
// For each var (rgba + fadergba + params = 11 vars), we apply
|
|
// percentage fading: currentval = sourceval + (delta * percent of duration elapsed)
|
|
// delta is negative when fading out (destval is lower)
|
|
// max/min are used to ensure progressive calcs don't go backwards and to cap values to dest.
|
|
|
|
#define APPLYFADE(dest, src, cur) (\
|
|
(dest-src < 0) ? \
|
|
max(\
|
|
min(cur,\
|
|
src + (INT16)FixedMul(dest-src, factor)),\
|
|
dest)\
|
|
: (dest-src > 0) ? \
|
|
min(\
|
|
max(cur,\
|
|
src + (INT16)FixedMul(dest-src, factor)),\
|
|
dest)\
|
|
: \
|
|
dest\
|
|
)
|
|
|
|
cr = APPLYFADE(R_GetRgbaR(d->dest_exc->rgba), R_GetRgbaR(d->source_exc->rgba), R_GetRgbaR(d->sector->extra_colormap->rgba));
|
|
cg = APPLYFADE(R_GetRgbaG(d->dest_exc->rgba), R_GetRgbaG(d->source_exc->rgba), R_GetRgbaG(d->sector->extra_colormap->rgba));
|
|
cb = APPLYFADE(R_GetRgbaB(d->dest_exc->rgba), R_GetRgbaB(d->source_exc->rgba), R_GetRgbaB(d->sector->extra_colormap->rgba));
|
|
ca = APPLYFADE(R_GetRgbaA(d->dest_exc->rgba), R_GetRgbaA(d->source_exc->rgba), R_GetRgbaA(d->sector->extra_colormap->rgba));
|
|
|
|
rgba = R_PutRgbaRGBA(cr, cg, cb, ca);
|
|
|
|
cr = APPLYFADE(R_GetRgbaR(d->dest_exc->fadergba), R_GetRgbaR(d->source_exc->fadergba), R_GetRgbaR(d->sector->extra_colormap->fadergba));
|
|
cg = APPLYFADE(R_GetRgbaG(d->dest_exc->fadergba), R_GetRgbaG(d->source_exc->fadergba), R_GetRgbaG(d->sector->extra_colormap->fadergba));
|
|
cb = APPLYFADE(R_GetRgbaB(d->dest_exc->fadergba), R_GetRgbaB(d->source_exc->fadergba), R_GetRgbaB(d->sector->extra_colormap->fadergba));
|
|
ca = APPLYFADE(R_GetRgbaA(d->dest_exc->fadergba), R_GetRgbaA(d->source_exc->fadergba), R_GetRgbaA(d->sector->extra_colormap->fadergba));
|
|
|
|
fadergba = R_PutRgbaRGBA(cr, cg, cb, ca);
|
|
|
|
fadestart = APPLYFADE(d->dest_exc->fadestart, d->source_exc->fadestart, d->sector->extra_colormap->fadestart);
|
|
fadeend = APPLYFADE(d->dest_exc->fadeend, d->source_exc->fadeend, d->sector->extra_colormap->fadeend);
|
|
fog = abs(factor) > FRACUNIT/2 ? d->dest_exc->fog : d->source_exc->fog; // set new fog flag halfway through fade
|
|
|
|
#undef APPLYFADE
|
|
|
|
//////////////////
|
|
// setup new colormap
|
|
//////////////////
|
|
|
|
if (!(d->sector->extra_colormap = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, fog)))
|
|
{
|
|
exc = R_CreateDefaultColormap(false);
|
|
exc->fadestart = fadestart;
|
|
exc->fadeend = fadeend;
|
|
exc->fog = (boolean)fog;
|
|
exc->rgba = rgba;
|
|
exc->fadergba = fadergba;
|
|
exc->colormap = R_CreateLightTable(exc);
|
|
R_AddColormapToList(exc);
|
|
d->sector->extra_colormap = exc;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
SoM: 3/8/2000: Friction functions start.
|
|
Add_Friction,
|
|
T_Friction,
|
|
P_SpawnFriction
|
|
*/
|
|
|
|
/** Adds friction thinker.
|
|
*
|
|
* \param friction Friction value, 0xe800 is normal.
|
|
* \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(THINK_MAIN, &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;
|
|
|
|
// Get FOF control sector
|
|
if (f->roverfriction)
|
|
referrer = sectors + f->referrer;
|
|
|
|
// 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_thinglist_next;
|
|
continue;
|
|
}
|
|
|
|
if ((thing->friction == ORIG_FRICTION) // normal friction?
|
|
|| (f->friction < thing->friction))
|
|
{
|
|
thing->friction = f->friction;
|
|
if (thing->player)
|
|
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;
|
|
if (thing->player)
|
|
thing->movefactor = f->movefactor;
|
|
}
|
|
}
|
|
node = node->m_thinglist_next;
|
|
}
|
|
}
|
|
|
|
/** 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 strength; // frontside texture offset 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)
|
|
{
|
|
strength = sides[l->sidenum[0]].textureoffset>>FRACBITS;
|
|
if (strength > 0) // sludge
|
|
strength = strength*2; // otherwise, the maximum sludginess value is +967...
|
|
|
|
// The following 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'.
|
|
friction = ORIG_FRICTION - (0x1EB8*strength)/0x80; // ORIG_FRICTION is 0xE800
|
|
|
|
if (friction > FRACUNIT)
|
|
friction = FRACUNIT;
|
|
if (friction < 0)
|
|
friction = 0;
|
|
|
|
movefactor = FixedDiv(ORIG_FRICTION, friction);
|
|
if (movefactor < FRACUNIT)
|
|
movefactor = 8*movefactor - 7*FRACUNIT;
|
|
else
|
|
movefactor = FRACUNIT;
|
|
|
|
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(THINK_MAIN, &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->eflags & MFE_PUSHED)
|
|
return false;
|
|
|
|
if (thing->player && thing->player->powers[pw_carry] == CR_ROPEHANG)
|
|
return false;
|
|
|
|
// Allow this to affect pushable objects at some point?
|
|
if (thing->player && (!(thing->flags & (MF_NOGRAVITY | MF_NOCLIP)) || thing->player->powers[pw_carry] == CR_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->powers[pw_carry] != CR_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->eflags |= MFE_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)
|
|
return;
|
|
}
|
|
else if (GETSECSPECIAL(sec->special, 3) != 2)
|
|
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_thinglist_next)
|
|
{
|
|
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->eflags & MFE_PUSHED)
|
|
continue;
|
|
|
|
if (thing->player && thing->player->powers[pw_carry] == CR_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 || bottom > (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)
|
|
{
|
|
pflags_t jumped = (thing->player->pflags & (PF_JUMPED|PF_NOJUMPDAMAGE));
|
|
P_ResetPlayer (thing->player);
|
|
|
|
if (jumped)
|
|
thing->player->pflags |= jumped;
|
|
|
|
thing->player->pflags |= PF_SLIDING;
|
|
thing->angle = R_PointToAngle2 (0, 0, xspeed<<(FRACBITS-PUSH_FACTOR), yspeed<<(FRACBITS-PUSH_FACTOR));
|
|
|
|
if (!demoplayback || P_AnalogMove(thing->player))
|
|
{
|
|
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->eflags |= MFE_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;
|
|
pushertype_e pushertype;
|
|
fixed_t dx, dy;
|
|
|
|
for (i = 0; i < numlines; i++, l++)
|
|
{
|
|
switch (l->special)
|
|
{
|
|
case 541: // wind
|
|
pushertype = p_wind;
|
|
break;
|
|
|
|
case 544: // current
|
|
pushertype = p_current;
|
|
break;
|
|
case 547: // push/pull
|
|
if (l->tag == 0)
|
|
{
|
|
s = l->frontsector - sectors;
|
|
if ((thing = P_GetPushThing(s)) != NULL) // 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);
|
|
}
|
|
else
|
|
for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
|
|
{
|
|
if ((thing = P_GetPushThing(s)) != NULL) // 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);
|
|
}
|
|
|
|
continue;
|
|
|
|
case 545: // current up
|
|
pushertype = p_upcurrent;
|
|
break;
|
|
|
|
case 546: // current down
|
|
pushertype = p_downcurrent;
|
|
break;
|
|
|
|
case 542: // wind up
|
|
pushertype = p_upwind;
|
|
break;
|
|
|
|
case 543: // wind down
|
|
pushertype = p_downwind;
|
|
break;
|
|
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
dx = l->dx;
|
|
dy = l->dy;
|
|
|
|
// Obtain versor and scale it up according to texture offset, if provided; line length is ignored in this case.
|
|
|
|
if (sides[l->sidenum[0]].textureoffset != 0)
|
|
{
|
|
fixed_t len = sides[l->sidenum[0]].textureoffset;
|
|
fixed_t h = FixedHypot(dx, dy);
|
|
dx = FixedMul(FixedDiv(dx, h), len);
|
|
dy = FixedMul(FixedDiv(dy, h), len);
|
|
}
|
|
|
|
if (l->tag == 0)
|
|
Add_Pusher(pushertype, dx, dy, NULL, l->frontsector - sectors, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
|
|
else
|
|
{
|
|
for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
|
|
Add_Pusher(pushertype, dx, dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|