SRB2/src/p_spec.c
Nev3r a9110c0645 Fixed more mobj thinker iterator oversights and removed all mobj thinker function checks
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>
2019-04-21 16:39:57 +02:00

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= &sectors[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 = &sectors[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 = &sectors[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 = &sectors[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 = &sectors[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, &sectors[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, &sectors[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, &sectors[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, &sectors[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, &sectors[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, &sectors[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, &sectors[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, &sectors[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(&sectors[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(&sectors[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(&sectors[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(&sector->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 = &sectors[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(&sectors[sec], &sectors[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], &sectors[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(&sectors[sec], &sectors[s], &lines[i]);
P_AddFakeFloor(&sectors[s], &sectors[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(&sectors[s], &sectors[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(&sectors[sec], &lines[i]);
break;
// No More Enemies Linedef Exec
case 313:
sec = sides[*lines[i].sidenum].sector - sectors;
P_AddNoEnemiesThinker(&sectors[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(&sectors[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(&sectors[sec], &sectors[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(&sectors[sec], &sectors[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(&sectors[sec], &sectors[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(&sectors[sec], &sectors[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(&sectors[s], &sectors[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(&sectors[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(&sectors[rover->secnum]);
if (fadingdata->docolormap)
P_ResetColormapFader(&sectors[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(&sectors[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(&sectors[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 = &sectors[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;
}
}
}