mirror of
https://git.do.srb2.org/STJr/SRB2.git
synced 2024-12-19 09:20:59 +00:00
d59f25a6cd
- Can now apply to normal stages, simply defaults to "false" in normal stages. - Post-level cutscenes are now always skipped when the stage was failed. - Exposed the boolean as a Lua read+write global. Desired for SUGOI, as it allows for visited flags not be updated, and level completion emblems to not be awarded. Which means a lot less crappy non-ideal workarounds. Normal stage intermission currently does not reflect failure state at all. Maybe it could always skip, never award score bonuses, have different text... etc. Probably would leave that up to vanilla dev opinion.
4588 lines
124 KiB
C
4588 lines
124 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-2020 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_setup.c
|
|
/// \brief Do all the WAD I/O, get map description, set up initial state and misc. LUTs
|
|
|
|
#include "doomdef.h"
|
|
#include "d_main.h"
|
|
#include "byteptr.h"
|
|
#include "g_game.h"
|
|
|
|
#include "p_local.h"
|
|
#include "p_setup.h"
|
|
#include "p_spec.h"
|
|
#include "p_saveg.h"
|
|
|
|
#include "i_sound.h" // for I_PlayCD()..
|
|
#include "i_video.h" // for I_FinishUpdate()..
|
|
#include "r_sky.h"
|
|
#include "i_system.h"
|
|
|
|
#include "r_data.h"
|
|
#include "r_things.h" // for R_AddSpriteDefs
|
|
#include "r_textures.h"
|
|
#include "r_patch.h"
|
|
#include "r_picformats.h"
|
|
#include "r_sky.h"
|
|
#include "r_draw.h"
|
|
|
|
#include "s_sound.h"
|
|
#include "st_stuff.h"
|
|
#include "w_wad.h"
|
|
#include "z_zone.h"
|
|
#include "r_splats.h"
|
|
|
|
#include "hu_stuff.h"
|
|
#include "console.h"
|
|
|
|
#include "m_misc.h"
|
|
#include "m_fixed.h"
|
|
#include "m_random.h"
|
|
|
|
#include "dehacked.h" // for map headers
|
|
#include "r_main.h"
|
|
#include "m_cond.h" // for emblems
|
|
|
|
#include "m_argv.h"
|
|
|
|
#include "p_polyobj.h"
|
|
|
|
#include "v_video.h"
|
|
|
|
#include "filesrch.h" // refreshdirmenu
|
|
|
|
#include "lua_hud.h" // level title
|
|
|
|
#include "f_finale.h" // wipes
|
|
|
|
#include "md5.h" // map MD5
|
|
|
|
// for LUAh_MapLoad
|
|
#include "lua_script.h"
|
|
#include "lua_hook.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <malloc.h>
|
|
#include <math.h>
|
|
#endif
|
|
#ifdef HWRENDER
|
|
#include "hardware/hw_main.h"
|
|
#include "hardware/hw_light.h"
|
|
#include "hardware/hw_model.h"
|
|
#endif
|
|
|
|
#include "p_slopes.h"
|
|
|
|
#include "fastcmp.h" // textmap parsing
|
|
|
|
#include "taglist.h"
|
|
|
|
//
|
|
// Map MD5, calculated on level load.
|
|
// Sent to clients in PT_SERVERINFO.
|
|
//
|
|
unsigned char mapmd5[16];
|
|
|
|
//
|
|
// MAP related Lookup tables.
|
|
// Store VERTEXES, LINEDEFS, SIDEDEFS, etc.
|
|
//
|
|
|
|
boolean udmf;
|
|
size_t numvertexes, numsegs, numsectors, numsubsectors, numnodes, numlines, numsides, nummapthings;
|
|
vertex_t *vertexes;
|
|
seg_t *segs;
|
|
sector_t *sectors;
|
|
subsector_t *subsectors;
|
|
node_t *nodes;
|
|
line_t *lines;
|
|
side_t *sides;
|
|
mapthing_t *mapthings;
|
|
sector_t *spawnsectors;
|
|
line_t *spawnlines;
|
|
side_t *spawnsides;
|
|
INT32 numstarposts;
|
|
UINT16 bossdisabled;
|
|
boolean stoppedclock;
|
|
boolean levelloading;
|
|
UINT8 levelfadecol;
|
|
|
|
// BLOCKMAP
|
|
// Created from axis aligned bounding box
|
|
// of the map, a rectangular array of
|
|
// blocks of size ...
|
|
// Used to speed up collision detection
|
|
// by spatial subdivision in 2D.
|
|
//
|
|
// Blockmap size.
|
|
INT32 bmapwidth, bmapheight; // size in mapblocks
|
|
|
|
INT32 *blockmap; // INT32 for large maps
|
|
// offsets in blockmap are from here
|
|
INT32 *blockmaplump; // Big blockmap
|
|
|
|
// origin of block map
|
|
fixed_t bmaporgx, bmaporgy;
|
|
// for thing chains
|
|
mobj_t **blocklinks;
|
|
|
|
// REJECT
|
|
// For fast sight rejection.
|
|
// Speeds up enemy AI by skipping detailed LineOf Sight calculation.
|
|
// Without special effect, this could be used as a PVS lookup as well.
|
|
//
|
|
UINT8 *rejectmatrix;
|
|
|
|
// Maintain single and multi player starting spots.
|
|
INT32 numdmstarts, numcoopstarts, numredctfstarts, numbluectfstarts;
|
|
|
|
mapthing_t *deathmatchstarts[MAX_DM_STARTS];
|
|
mapthing_t *playerstarts[MAXPLAYERS];
|
|
mapthing_t *bluectfstarts[MAXPLAYERS];
|
|
mapthing_t *redctfstarts[MAXPLAYERS];
|
|
|
|
// Maintain waypoints
|
|
mobj_t *waypoints[NUMWAYPOINTSEQUENCES][WAYPOINTSEQUENCESIZE];
|
|
UINT16 numwaypoints[NUMWAYPOINTSEQUENCES];
|
|
|
|
void P_AddWaypoint(UINT8 sequence, UINT8 id, mobj_t *waypoint)
|
|
{
|
|
waypoints[sequence][id] = waypoint;
|
|
if (id >= numwaypoints[sequence])
|
|
numwaypoints[sequence] = id + 1;
|
|
}
|
|
|
|
static void P_ResetWaypoints(void)
|
|
{
|
|
UINT16 sequence, id;
|
|
for (sequence = 0; sequence < NUMWAYPOINTSEQUENCES; sequence++)
|
|
{
|
|
for (id = 0; id < numwaypoints[sequence]; id++)
|
|
waypoints[sequence][id] = NULL;
|
|
|
|
numwaypoints[sequence] = 0;
|
|
}
|
|
}
|
|
|
|
mobj_t *P_GetFirstWaypoint(UINT8 sequence)
|
|
{
|
|
return waypoints[sequence][0];
|
|
}
|
|
|
|
mobj_t *P_GetLastWaypoint(UINT8 sequence)
|
|
{
|
|
return waypoints[sequence][numwaypoints[sequence] - 1];
|
|
}
|
|
|
|
mobj_t *P_GetPreviousWaypoint(mobj_t *current, boolean wrap)
|
|
{
|
|
UINT8 sequence = current->threshold;
|
|
UINT8 id = current->health;
|
|
|
|
if (id == 0)
|
|
{
|
|
if (!wrap)
|
|
return NULL;
|
|
|
|
id = numwaypoints[sequence] - 1;
|
|
}
|
|
else
|
|
id--;
|
|
|
|
return waypoints[sequence][id];
|
|
}
|
|
|
|
mobj_t *P_GetNextWaypoint(mobj_t *current, boolean wrap)
|
|
{
|
|
UINT8 sequence = current->threshold;
|
|
UINT8 id = current->health;
|
|
|
|
if (id == numwaypoints[sequence] - 1)
|
|
{
|
|
if (!wrap)
|
|
return NULL;
|
|
|
|
id = 0;
|
|
}
|
|
else
|
|
id++;
|
|
|
|
return waypoints[sequence][id];
|
|
}
|
|
|
|
mobj_t *P_GetClosestWaypoint(UINT8 sequence, mobj_t *mo)
|
|
{
|
|
UINT8 wp;
|
|
mobj_t *mo2, *result = NULL;
|
|
fixed_t bestdist = 0;
|
|
fixed_t curdist;
|
|
|
|
for (wp = 0; wp < numwaypoints[sequence]; wp++)
|
|
{
|
|
mo2 = waypoints[sequence][wp];
|
|
|
|
if (!mo2)
|
|
continue;
|
|
|
|
curdist = P_AproxDistance(P_AproxDistance(mo->x - mo2->x, mo->y - mo2->y), mo->z - mo2->z);
|
|
|
|
if (result && curdist > bestdist)
|
|
continue;
|
|
|
|
result = mo2;
|
|
bestdist = curdist;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Return true if all waypoints are in the same location
|
|
boolean P_IsDegeneratedWaypointSequence(UINT8 sequence)
|
|
{
|
|
mobj_t *first, *waypoint;
|
|
UINT8 wp;
|
|
|
|
if (numwaypoints[sequence] <= 1)
|
|
return true;
|
|
|
|
first = waypoints[sequence][0];
|
|
|
|
for (wp = 1; wp < numwaypoints[sequence]; wp++)
|
|
{
|
|
waypoint = waypoints[sequence][wp];
|
|
|
|
if (!waypoint)
|
|
continue;
|
|
|
|
if (waypoint->x != first->x)
|
|
return false;
|
|
|
|
if (waypoint->y != first->y)
|
|
return false;
|
|
|
|
if (waypoint->z != first->z)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/** Logs an error about a map being corrupt, then terminate.
|
|
* This allows reporting highly technical errors for usefulness, without
|
|
* confusing a novice map designer who simply needs to run ZenNode.
|
|
*
|
|
* If logging is disabled in this compile, or the log file is not opened, the
|
|
* full technical details are printed in the I_Error() message.
|
|
*
|
|
* \param msg The message to log. This message can safely result from a call
|
|
* to va(), since that function is not used here.
|
|
* \todo Fix the I_Error() message. On some implementations the logfile may
|
|
* not be called log.txt.
|
|
* \sa CON_LogMessage, I_Error
|
|
*/
|
|
FUNCNORETURN static ATTRNORETURN void CorruptMapError(const char *msg)
|
|
{
|
|
// don't use va() because the calling function probably uses it
|
|
char mapnum[10];
|
|
|
|
sprintf(mapnum, "%hd", gamemap);
|
|
CON_LogMessage("Map ");
|
|
CON_LogMessage(mapnum);
|
|
CON_LogMessage(" is corrupt: ");
|
|
CON_LogMessage(msg);
|
|
CON_LogMessage("\n");
|
|
I_Error("Invalid or corrupt map.\nLook in log file or text console for technical details.");
|
|
}
|
|
|
|
/** Sets a header's flickies to be equivalent to the original Freed Animals
|
|
*
|
|
* \param i The header to set flickies for
|
|
*/
|
|
void P_SetDemoFlickies(INT16 i)
|
|
{
|
|
mapheaderinfo[i]->numFlickies = 5;
|
|
mapheaderinfo[i]->flickies = Z_Realloc(mapheaderinfo[i]->flickies, 5*sizeof(mobjtype_t), PU_STATIC, NULL);
|
|
mapheaderinfo[i]->flickies[0] = MT_FLICKY_02/*MT_BUNNY*/;
|
|
mapheaderinfo[i]->flickies[1] = MT_FLICKY_01/*MT_BIRD*/;
|
|
mapheaderinfo[i]->flickies[2] = MT_FLICKY_12/*MT_MOUSE*/;
|
|
mapheaderinfo[i]->flickies[3] = MT_FLICKY_11/*MT_COW*/;
|
|
mapheaderinfo[i]->flickies[4] = MT_FLICKY_03/*MT_CHICKEN*/;
|
|
}
|
|
|
|
/** Clears a header's flickies
|
|
*
|
|
* \param i The header to clear flickies for
|
|
*/
|
|
void P_DeleteFlickies(INT16 i)
|
|
{
|
|
if (mapheaderinfo[i]->flickies)
|
|
Z_Free(mapheaderinfo[i]->flickies);
|
|
mapheaderinfo[i]->flickies = NULL;
|
|
mapheaderinfo[i]->numFlickies = 0;
|
|
}
|
|
|
|
#define NUMLAPS_DEFAULT 4
|
|
|
|
/** Clears the data from a single map header.
|
|
*
|
|
* \param i Map number to clear header for.
|
|
* \sa P_ClearMapHeaderInfo
|
|
*/
|
|
static void P_ClearSingleMapHeaderInfo(INT16 i)
|
|
{
|
|
const INT16 num = (INT16)(i-1);
|
|
mapheaderinfo[num]->lvlttl[0] = '\0';
|
|
mapheaderinfo[num]->selectheading[0] = '\0';
|
|
mapheaderinfo[num]->subttl[0] = '\0';
|
|
mapheaderinfo[num]->ltzzpatch[0] = '\0';
|
|
mapheaderinfo[num]->ltzztext[0] = '\0';
|
|
mapheaderinfo[num]->ltactdiamond[0] = '\0';
|
|
mapheaderinfo[num]->actnum = 0;
|
|
mapheaderinfo[num]->typeoflevel = 0;
|
|
mapheaderinfo[num]->nextlevel = (INT16)(i + 1);
|
|
mapheaderinfo[num]->marathonnext = 0;
|
|
mapheaderinfo[num]->startrings = 0;
|
|
mapheaderinfo[num]->sstimer = 90;
|
|
mapheaderinfo[num]->ssspheres = 1;
|
|
mapheaderinfo[num]->gravity = FRACUNIT/2;
|
|
mapheaderinfo[num]->keywords[0] = '\0';
|
|
snprintf(mapheaderinfo[num]->musname, 7, "%sM", G_BuildMapName(i));
|
|
mapheaderinfo[num]->musname[6] = 0;
|
|
mapheaderinfo[num]->mustrack = 0;
|
|
mapheaderinfo[num]->muspos = 0;
|
|
mapheaderinfo[num]->musinterfadeout = 0;
|
|
mapheaderinfo[num]->musintername[0] = 0;
|
|
mapheaderinfo[num]->muspostbossname[0] = 0;
|
|
mapheaderinfo[num]->muspostbosstrack = 0;
|
|
mapheaderinfo[num]->muspostbosspos = 0;
|
|
mapheaderinfo[num]->muspostbossfadein = 0;
|
|
mapheaderinfo[num]->musforcereset = -1;
|
|
mapheaderinfo[num]->forcecharacter[0] = '\0';
|
|
mapheaderinfo[num]->weather = 0;
|
|
mapheaderinfo[num]->skynum = 1;
|
|
mapheaderinfo[num]->skybox_scalex = 16;
|
|
mapheaderinfo[num]->skybox_scaley = 16;
|
|
mapheaderinfo[num]->skybox_scalez = 16;
|
|
mapheaderinfo[num]->interscreen[0] = '#';
|
|
mapheaderinfo[num]->runsoc[0] = '#';
|
|
mapheaderinfo[num]->scriptname[0] = '#';
|
|
mapheaderinfo[num]->precutscenenum = 0;
|
|
mapheaderinfo[num]->cutscenenum = 0;
|
|
mapheaderinfo[num]->countdown = 0;
|
|
mapheaderinfo[num]->palette = UINT16_MAX;
|
|
mapheaderinfo[num]->numlaps = NUMLAPS_DEFAULT;
|
|
mapheaderinfo[num]->unlockrequired = -1;
|
|
mapheaderinfo[num]->levelselect = 0;
|
|
mapheaderinfo[num]->bonustype = 0;
|
|
mapheaderinfo[num]->maxbonuslives = -1;
|
|
mapheaderinfo[num]->levelflags = 0;
|
|
mapheaderinfo[num]->menuflags = 0;
|
|
#if 1 // equivalent to "FlickyList = DEMO"
|
|
P_SetDemoFlickies(num);
|
|
#else // equivalent to "FlickyList = NONE"
|
|
P_DeleteFlickies(num);
|
|
#endif
|
|
P_DeleteGrades(num);
|
|
mapheaderinfo[num]->customopts = NULL;
|
|
mapheaderinfo[num]->numCustomOptions = 0;
|
|
}
|
|
|
|
/** Allocates a new map-header structure.
|
|
*
|
|
* \param i Index of header to allocate.
|
|
*/
|
|
void P_AllocMapHeader(INT16 i)
|
|
{
|
|
if (!mapheaderinfo[i])
|
|
{
|
|
mapheaderinfo[i] = Z_Malloc(sizeof(mapheader_t), PU_STATIC, NULL);
|
|
mapheaderinfo[i]->flickies = NULL;
|
|
mapheaderinfo[i]->grades = NULL;
|
|
}
|
|
P_ClearSingleMapHeaderInfo(i + 1);
|
|
}
|
|
|
|
/** NiGHTS Grades are a special structure,
|
|
* we initialize them here.
|
|
*
|
|
* \param i Index of header to allocate grades for
|
|
* \param mare The mare we're adding grades for
|
|
* \param grades the string from DeHackEd, we work with it ourselves
|
|
*/
|
|
void P_AddGradesForMare(INT16 i, UINT8 mare, char *gtext)
|
|
{
|
|
INT32 g;
|
|
char *spos = gtext;
|
|
|
|
CONS_Debug(DBG_SETUP, "Map %d Mare %d: ", i+1, (UINT16)mare+1);
|
|
|
|
if (mapheaderinfo[i]->numGradedMares < mare+1)
|
|
{
|
|
mapheaderinfo[i]->numGradedMares = mare+1;
|
|
mapheaderinfo[i]->grades = Z_Realloc(mapheaderinfo[i]->grades, sizeof(nightsgrades_t) * mapheaderinfo[i]->numGradedMares, PU_STATIC, NULL);
|
|
}
|
|
|
|
for (g = 0; g < 6; ++g)
|
|
{
|
|
// Allow "partial" grading systems
|
|
if (spos != NULL)
|
|
{
|
|
mapheaderinfo[i]->grades[mare].grade[g] = atoi(spos);
|
|
CONS_Debug(DBG_SETUP, "%u ", atoi(spos));
|
|
// Grab next comma
|
|
spos = strchr(spos, ',');
|
|
if (spos)
|
|
++spos;
|
|
}
|
|
else
|
|
{
|
|
// Grade not reachable
|
|
mapheaderinfo[i]->grades[mare].grade[g] = UINT32_MAX;
|
|
}
|
|
}
|
|
|
|
CONS_Debug(DBG_SETUP, "\n");
|
|
}
|
|
|
|
/** And this removes the grades safely.
|
|
*
|
|
* \param i The header to remove grades from
|
|
*/
|
|
void P_DeleteGrades(INT16 i)
|
|
{
|
|
if (mapheaderinfo[i]->grades)
|
|
Z_Free(mapheaderinfo[i]->grades);
|
|
|
|
mapheaderinfo[i]->grades = NULL;
|
|
mapheaderinfo[i]->numGradedMares = 0;
|
|
}
|
|
|
|
/** And this fetches the grades
|
|
*
|
|
* \param pscore The player's score.
|
|
* \param map The game map.
|
|
* \param mare The mare to test.
|
|
*/
|
|
UINT8 P_GetGrade(UINT32 pscore, INT16 map, UINT8 mare)
|
|
{
|
|
INT32 i;
|
|
|
|
// Determining the grade
|
|
if (mapheaderinfo[map-1] && mapheaderinfo[map-1]->grades && mapheaderinfo[map-1]->numGradedMares >= mare + 1)
|
|
{
|
|
INT32 pgrade = 0;
|
|
for (i = 0; i < 6; ++i)
|
|
{
|
|
if (pscore >= mapheaderinfo[map-1]->grades[mare].grade[i])
|
|
++pgrade;
|
|
}
|
|
return (UINT8)pgrade;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
UINT8 P_HasGrades(INT16 map, UINT8 mare)
|
|
{
|
|
// Determining the grade
|
|
// Mare 0 is treated as overall and is true if ANY grades exist
|
|
if (mapheaderinfo[map-1] && mapheaderinfo[map-1]->grades
|
|
&& (mare == 0 || mapheaderinfo[map-1]->numGradedMares >= mare))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
UINT32 P_GetScoreForGrade(INT16 map, UINT8 mare, UINT8 grade)
|
|
{
|
|
// Get the score for the grade... if it exists
|
|
if (grade == GRADE_F || grade > GRADE_S || !P_HasGrades(map, mare)) return 0;
|
|
|
|
return mapheaderinfo[map-1]->grades[mare].grade[grade-1];
|
|
}
|
|
|
|
//
|
|
// levelflats
|
|
//
|
|
#define MAXLEVELFLATS 256
|
|
|
|
size_t numlevelflats;
|
|
levelflat_t *levelflats;
|
|
levelflat_t *foundflats;
|
|
|
|
//SoM: Other files want this info.
|
|
size_t P_PrecacheLevelFlats(void)
|
|
{
|
|
lumpnum_t lump;
|
|
size_t i;
|
|
|
|
//SoM: 4/18/2000: New flat code to make use of levelflats.
|
|
flatmemory = 0;
|
|
for (i = 0; i < numlevelflats; i++)
|
|
{
|
|
if (levelflats[i].type == LEVELFLAT_FLAT)
|
|
{
|
|
lump = levelflats[i].u.flat.lumpnum;
|
|
if (devparm)
|
|
flatmemory += W_LumpLength(lump);
|
|
R_GetFlat(lump);
|
|
}
|
|
}
|
|
return flatmemory;
|
|
}
|
|
|
|
/*
|
|
levelflat refers to an array of level flats,
|
|
or NULL if we want to allocate it now.
|
|
*/
|
|
static INT32
|
|
Ploadflat (levelflat_t *levelflat, const char *flatname, boolean resize)
|
|
{
|
|
#ifndef NO_PNG_LUMPS
|
|
UINT8 buffer[8];
|
|
#endif
|
|
|
|
lumpnum_t flatnum;
|
|
int texturenum;
|
|
UINT8 *flatpatch;
|
|
size_t lumplength;
|
|
|
|
size_t i;
|
|
|
|
// Scan through the already found flats, return if it matches.
|
|
for (i = 0; i < numlevelflats; i++)
|
|
{
|
|
if (strnicmp(levelflat[i].name, flatname, 8) == 0)
|
|
return i;
|
|
}
|
|
|
|
if (resize)
|
|
{
|
|
// allocate new flat memory
|
|
levelflats = Z_Realloc(levelflats, (numlevelflats + 1) * sizeof(*levelflats), PU_LEVEL, NULL);
|
|
levelflat = levelflats + numlevelflats;
|
|
}
|
|
else
|
|
{
|
|
if (numlevelflats >= MAXLEVELFLATS)
|
|
I_Error("Too many flats in level\n");
|
|
|
|
levelflat += numlevelflats;
|
|
}
|
|
|
|
// Store the name.
|
|
strlcpy(levelflat->name, flatname, sizeof (levelflat->name));
|
|
strupr(levelflat->name);
|
|
|
|
/* If we can't find a flat, try looking for a texture! */
|
|
if (( flatnum = R_GetFlatNumForName(levelflat->name) ) == LUMPERROR)
|
|
{
|
|
if (( texturenum = R_CheckTextureNumForName(levelflat->name) ) == -1)
|
|
{
|
|
// check for REDWALL
|
|
if (( texturenum = R_CheckTextureNumForName("REDWALL") ) != -1)
|
|
goto texturefound;
|
|
// check for REDFLR
|
|
else if (( flatnum = R_GetFlatNumForName("REDFLR") ) != LUMPERROR)
|
|
goto flatfound;
|
|
// nevermind
|
|
levelflat->type = LEVELFLAT_NONE;
|
|
}
|
|
else
|
|
{
|
|
texturefound:
|
|
levelflat->type = LEVELFLAT_TEXTURE;
|
|
levelflat->u.texture. num = texturenum;
|
|
levelflat->u.texture.lastnum = texturenum;
|
|
/* start out unanimated */
|
|
levelflat->u.texture.basenum = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
flatfound:
|
|
/* This could be a flat, patch, or PNG. */
|
|
flatpatch = W_CacheLumpNum(flatnum, PU_CACHE);
|
|
lumplength = W_LumpLength(flatnum);
|
|
if (Picture_CheckIfDoomPatch((softwarepatch_t *)flatpatch, lumplength))
|
|
levelflat->type = LEVELFLAT_PATCH;
|
|
else
|
|
{
|
|
#ifndef NO_PNG_LUMPS
|
|
/*
|
|
Only need eight bytes for PNG headers.
|
|
FIXME: Put this elsewhere.
|
|
*/
|
|
W_ReadLumpHeader(flatnum, buffer, 8, 0);
|
|
if (Picture_IsLumpPNG(buffer, lumplength))
|
|
levelflat->type = LEVELFLAT_PNG;
|
|
else
|
|
#endif/*NO_PNG_LUMPS*/
|
|
levelflat->type = LEVELFLAT_FLAT;/* phew */
|
|
}
|
|
if (flatpatch)
|
|
Z_Free(flatpatch);
|
|
|
|
levelflat->u.flat. lumpnum = flatnum;
|
|
levelflat->u.flat.baselumpnum = LUMPERROR;
|
|
}
|
|
|
|
#ifndef ZDEBUG
|
|
CONS_Debug(DBG_SETUP, "flat #%03d: %s\n", atoi(sizeu1(numlevelflats)), levelflat->name);
|
|
#endif
|
|
|
|
return ( numlevelflats++ );
|
|
}
|
|
|
|
// Auxiliary function. Find a flat in the active wad files,
|
|
// allocate an id for it, and set the levelflat (to speedup search)
|
|
INT32 P_AddLevelFlat(const char *flatname, levelflat_t *levelflat)
|
|
{
|
|
return Ploadflat(levelflat, flatname, false);
|
|
}
|
|
|
|
// help function for Lua and $$$.sav reading
|
|
// same as P_AddLevelFlat, except this is not setup so we must realloc levelflats to fit in the new flat
|
|
// no longer a static func in lua_maplib.c because p_saveg.c also needs it
|
|
//
|
|
INT32 P_AddLevelFlatRuntime(const char *flatname)
|
|
{
|
|
return Ploadflat(levelflats, flatname, true);
|
|
}
|
|
|
|
// help function for $$$.sav checking
|
|
// this simply returns the flat # for the name given
|
|
//
|
|
INT32 P_CheckLevelFlat(const char *flatname)
|
|
{
|
|
size_t i;
|
|
levelflat_t *levelflat = levelflats;
|
|
|
|
//
|
|
// scan through the already found flats
|
|
//
|
|
for (i = 0; i < numlevelflats; i++, levelflat++)
|
|
if (strnicmp(levelflat->name,flatname,8)==0)
|
|
break;
|
|
|
|
if (i == numlevelflats)
|
|
return 0; // ??? flat was not found, this should not happen!
|
|
|
|
// level flat id
|
|
return (INT32)i;
|
|
}
|
|
|
|
//
|
|
// P_ReloadRings
|
|
// Used by NiGHTS, clears all ring/sphere/hoop/etc items and respawns them
|
|
//
|
|
void P_ReloadRings(void)
|
|
{
|
|
mobj_t *mo;
|
|
thinker_t *th;
|
|
size_t i, numHoops = 0;
|
|
// Okay, if you have more than 4000 hoops in your map,
|
|
// you're insane.
|
|
mapthing_t *hoopsToRespawn[4096];
|
|
mapthing_t *mt = mapthings;
|
|
|
|
// scan the thinkers to find rings/spheres/hoops to unset
|
|
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
|
|
{
|
|
if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
|
|
continue;
|
|
|
|
mo = (mobj_t *)th;
|
|
|
|
if (mo->type == MT_HOOPCENTER)
|
|
{
|
|
// Hoops give me a headache
|
|
if (mo->threshold == 4242) // Dead hoop
|
|
{
|
|
hoopsToRespawn[numHoops++] = mo->spawnpoint;
|
|
P_RemoveMobj(mo);
|
|
}
|
|
continue;
|
|
}
|
|
if (!(mo->type == MT_RING || mo->type == MT_COIN
|
|
|| mo->type == MT_BLUESPHERE || mo->type == MT_BOMBSPHERE
|
|
|| mo->type == MT_NIGHTSCHIP || mo->type == MT_NIGHTSSTAR))
|
|
continue;
|
|
|
|
// Don't auto-disintegrate things being pulled to us
|
|
if (mo->flags2 & MF2_NIGHTSPULL)
|
|
continue;
|
|
|
|
P_RemoveMobj(mo);
|
|
}
|
|
|
|
// Reiterate through mapthings
|
|
for (i = 0; i < nummapthings; i++, mt++)
|
|
{
|
|
// Notice an omission? We handle hoops differently.
|
|
if (mt->type == mobjinfo[MT_RING].doomednum || mt->type == mobjinfo[MT_COIN].doomednum
|
|
|| mt->type == mobjinfo[MT_REDTEAMRING].doomednum || mt->type == mobjinfo[MT_BLUETEAMRING].doomednum
|
|
|| mt->type == mobjinfo[MT_BLUESPHERE].doomednum || mt->type == mobjinfo[MT_BOMBSPHERE].doomednum)
|
|
{
|
|
mt->mobj = NULL;
|
|
P_SetBonusTime(P_SpawnMapThing(mt));
|
|
}
|
|
else if (mt->type >= 600 && mt->type <= 609) // Item patterns
|
|
{
|
|
mt->mobj = NULL;
|
|
P_SpawnItemPattern(mt, true);
|
|
}
|
|
}
|
|
for (i = 0; i < numHoops; i++)
|
|
{
|
|
P_SpawnHoop(hoopsToRespawn[i]);
|
|
}
|
|
}
|
|
|
|
void P_SwitchSpheresBonusMode(boolean bonustime)
|
|
{
|
|
mobj_t *mo;
|
|
thinker_t *th;
|
|
|
|
// scan the thinkers to find spheres to switch
|
|
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
|
|
{
|
|
if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
|
|
continue;
|
|
|
|
mo = (mobj_t *)th;
|
|
|
|
if (mo->type != MT_BLUESPHERE && mo->type != MT_NIGHTSCHIP
|
|
&& mo->type != MT_FLINGBLUESPHERE && mo->type != MT_FLINGNIGHTSCHIP)
|
|
continue;
|
|
|
|
if (!mo->health)
|
|
continue;
|
|
|
|
P_SetMobjState(mo, ((bonustime) ? mo->info->raisestate : mo->info->spawnstate));
|
|
}
|
|
}
|
|
|
|
#ifdef SCANTHINGS
|
|
void P_ScanThings(INT16 mapnum, INT16 wadnum, INT16 lumpnum)
|
|
{
|
|
size_t i, n;
|
|
UINT8 *data, *datastart;
|
|
UINT16 type, maprings;
|
|
INT16 tol;
|
|
UINT32 flags;
|
|
|
|
tol = mapheaderinfo[mapnum-1]->typeoflevel;
|
|
if (!(tol & TOL_SP))
|
|
return;
|
|
flags = mapheaderinfo[mapnum-1]->levelflags;
|
|
|
|
n = W_LumpLengthPwad(wadnum, lumpnum) / (5 * sizeof (INT16));
|
|
//CONS_Printf("%u map things found!\n", n);
|
|
|
|
maprings = 0;
|
|
data = datastart = W_CacheLumpNumPwad(wadnum, lumpnum, PU_STATIC);
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
data += 3 * sizeof (INT16); // skip x y position, angle
|
|
type = READUINT16(data) & 4095;
|
|
data += sizeof (INT16); // skip options
|
|
|
|
switch (type)
|
|
{
|
|
case 300: // MT_RING
|
|
case 1800: // MT_COIN
|
|
case 308: // red team ring
|
|
case 309: // blue team ring
|
|
maprings++;
|
|
break;
|
|
case 400: // MT_SUPERRINGBOX
|
|
case 414: // red ring box
|
|
case 415: // blue ring box
|
|
case 603: // 10 diagonal rings
|
|
maprings += 10;
|
|
break;
|
|
case 600: // 5 vertical rings
|
|
case 601: // 5 vertical rings
|
|
case 602: // 5 diagonal rings
|
|
maprings += 5;
|
|
break;
|
|
case 604: // 8 circle rings
|
|
case 609: // 16 circle rings & wings
|
|
maprings += 8;
|
|
break;
|
|
case 605: // 16 circle rings
|
|
maprings += 16;
|
|
break;
|
|
case 608: // 8 circle rings & wings
|
|
maprings += 4;
|
|
break;
|
|
}
|
|
}
|
|
Z_Free(datastart);
|
|
|
|
if (maprings)
|
|
CONS_Printf("%s has %u rings\n", G_BuildMapName(mapnum), maprings);
|
|
}
|
|
#endif
|
|
|
|
static void P_SpawnEmeraldHunt(void)
|
|
{
|
|
INT32 emer[3], num[MAXHUNTEMERALDS], i, randomkey;
|
|
fixed_t x, y, z;
|
|
|
|
for (i = 0; i < numhuntemeralds; i++)
|
|
num[i] = i;
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
// generate random index, shuffle afterwards
|
|
randomkey = P_RandomKey(numhuntemeralds--);
|
|
emer[i] = num[randomkey];
|
|
num[randomkey] = num[numhuntemeralds];
|
|
num[numhuntemeralds] = emer[i];
|
|
|
|
// spawn emerald
|
|
x = huntemeralds[emer[i]]->x<<FRACBITS;
|
|
y = huntemeralds[emer[i]]->y<<FRACBITS;
|
|
z = P_GetMapThingSpawnHeight(MT_EMERHUNT, huntemeralds[emer[i]], x, y);
|
|
P_SetMobjStateNF(P_SpawnMobj(x, y, z, MT_EMERHUNT),
|
|
mobjinfo[MT_EMERHUNT].spawnstate+i);
|
|
}
|
|
}
|
|
|
|
static void P_SpawnMapThings(boolean spawnemblems)
|
|
{
|
|
size_t i;
|
|
mapthing_t *mt;
|
|
|
|
// Spawn axis points first so they are at the front of the list for fast searching.
|
|
for (i = 0, mt = mapthings; i < nummapthings; i++, mt++)
|
|
{
|
|
switch (mt->type)
|
|
{
|
|
case 1700: // MT_AXIS
|
|
case 1701: // MT_AXISTRANSFER
|
|
case 1702: // MT_AXISTRANSFERLINE
|
|
mt->mobj = NULL;
|
|
P_SpawnMapThing(mt);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
numhuntemeralds = 0;
|
|
|
|
for (i = 0, mt = mapthings; i < nummapthings; i++, mt++)
|
|
{
|
|
if (mt->type == 1700 // MT_AXIS
|
|
|| mt->type == 1701 // MT_AXISTRANSFER
|
|
|| mt->type == 1702) // MT_AXISTRANSFERLINE
|
|
continue; // These were already spawned
|
|
|
|
if (!spawnemblems && mt->type == mobjinfo[MT_EMBLEM].doomednum)
|
|
continue;
|
|
|
|
mt->mobj = NULL;
|
|
|
|
if (mt->type >= 600 && mt->type <= 609) // item patterns
|
|
P_SpawnItemPattern(mt, false);
|
|
else if (mt->type == 1705 || mt->type == 1713) // hoops
|
|
P_SpawnHoop(mt);
|
|
else // Everything else
|
|
P_SpawnMapThing(mt);
|
|
}
|
|
|
|
// random emeralds for hunt
|
|
if (numhuntemeralds)
|
|
P_SpawnEmeraldHunt();
|
|
}
|
|
|
|
// Experimental groovy write function!
|
|
void P_WriteThings(void)
|
|
{
|
|
size_t i, length;
|
|
mapthing_t *mt;
|
|
UINT8 *savebuffer, *savebuf_p;
|
|
INT16 temp;
|
|
|
|
savebuf_p = savebuffer = (UINT8 *)malloc(nummapthings * sizeof (mapthing_t));
|
|
|
|
if (!savebuf_p)
|
|
{
|
|
CONS_Alert(CONS_ERROR, M_GetText("No more free memory for thing writing!\n"));
|
|
return;
|
|
}
|
|
|
|
mt = mapthings;
|
|
for (i = 0; i < nummapthings; i++, mt++)
|
|
{
|
|
WRITEINT16(savebuf_p, mt->x);
|
|
WRITEINT16(savebuf_p, mt->y);
|
|
|
|
WRITEINT16(savebuf_p, mt->angle);
|
|
|
|
temp = (INT16)(mt->type + ((INT16)mt->extrainfo << 12));
|
|
WRITEINT16(savebuf_p, temp);
|
|
WRITEUINT16(savebuf_p, mt->options);
|
|
}
|
|
|
|
length = savebuf_p - savebuffer;
|
|
|
|
FIL_WriteFile(va("newthings%d.lmp", gamemap), savebuffer, length);
|
|
free(savebuffer);
|
|
savebuf_p = NULL;
|
|
|
|
CONS_Printf(M_GetText("newthings%d.lmp saved.\n"), gamemap);
|
|
}
|
|
|
|
//
|
|
// MAP LOADING FUNCTIONS
|
|
//
|
|
|
|
static void P_LoadVertices(UINT8 *data)
|
|
{
|
|
mapvertex_t *mv = (mapvertex_t *)data;
|
|
vertex_t *v = vertexes;
|
|
size_t i;
|
|
|
|
// Copy and convert vertex coordinates, internal representation as fixed.
|
|
for (i = 0; i < numvertexes; i++, v++, mv++)
|
|
{
|
|
v->x = SHORT(mv->x)<<FRACBITS;
|
|
v->y = SHORT(mv->y)<<FRACBITS;
|
|
v->floorzset = v->ceilingzset = false;
|
|
v->floorz = v->ceilingz = 0;
|
|
}
|
|
}
|
|
|
|
static void P_InitializeSector(sector_t *ss)
|
|
{
|
|
memset(&ss->soundorg, 0, sizeof(ss->soundorg));
|
|
|
|
ss->validcount = 0;
|
|
|
|
ss->thinglist = NULL;
|
|
|
|
ss->floordata = NULL;
|
|
ss->ceilingdata = NULL;
|
|
ss->lightingdata = NULL;
|
|
ss->fadecolormapdata = NULL;
|
|
|
|
ss->heightsec = -1;
|
|
ss->camsec = -1;
|
|
|
|
ss->floorlightsec = ss->ceilinglightsec = -1;
|
|
ss->crumblestate = CRUMBLE_NONE;
|
|
|
|
ss->touching_thinglist = NULL;
|
|
|
|
ss->linecount = 0;
|
|
ss->lines = NULL;
|
|
|
|
ss->ffloors = NULL;
|
|
ss->attached = NULL;
|
|
ss->attachedsolid = NULL;
|
|
ss->numattached = 0;
|
|
ss->maxattached = 1;
|
|
ss->lightlist = NULL;
|
|
ss->numlights = 0;
|
|
ss->moved = true;
|
|
|
|
ss->extra_colormap = NULL;
|
|
|
|
ss->gravity = NULL;
|
|
ss->verticalflip = false;
|
|
ss->flags = SF_FLIPSPECIAL_FLOOR;
|
|
|
|
ss->cullheight = NULL;
|
|
|
|
ss->floorspeed = ss->ceilspeed = 0;
|
|
|
|
ss->preciplist = NULL;
|
|
ss->touching_preciplist = NULL;
|
|
|
|
ss->f_slope = NULL;
|
|
ss->c_slope = NULL;
|
|
ss->hasslope = false;
|
|
|
|
ss->spawn_lightlevel = ss->lightlevel;
|
|
|
|
ss->spawn_extra_colormap = NULL;
|
|
}
|
|
|
|
static void P_LoadSectors(UINT8 *data)
|
|
{
|
|
mapsector_t *ms = (mapsector_t *)data;
|
|
sector_t *ss = sectors;
|
|
size_t i;
|
|
|
|
// For each counted sector, copy the sector raw data from our cache pointer ms, to the global table pointer ss.
|
|
for (i = 0; i < numsectors; i++, ss++, ms++)
|
|
{
|
|
ss->floorheight = SHORT(ms->floorheight)<<FRACBITS;
|
|
ss->ceilingheight = SHORT(ms->ceilingheight)<<FRACBITS;
|
|
|
|
ss->floorpic = P_AddLevelFlat(ms->floorpic, foundflats);
|
|
ss->ceilingpic = P_AddLevelFlat(ms->ceilingpic, foundflats);
|
|
|
|
ss->lightlevel = SHORT(ms->lightlevel);
|
|
ss->special = SHORT(ms->special);
|
|
Tag_FSet(&ss->tags, SHORT(ms->tag));
|
|
|
|
ss->floor_xoffs = ss->floor_yoffs = 0;
|
|
ss->ceiling_xoffs = ss->ceiling_yoffs = 0;
|
|
|
|
ss->floorpic_angle = ss->ceilingpic_angle = 0;
|
|
|
|
ss->colormap_protected = false;
|
|
|
|
P_InitializeSector(ss);
|
|
}
|
|
}
|
|
|
|
static void P_InitializeLinedef(line_t *ld)
|
|
{
|
|
vertex_t *v1 = ld->v1;
|
|
vertex_t *v2 = ld->v2;
|
|
UINT8 j;
|
|
|
|
ld->dx = v2->x - v1->x;
|
|
ld->dy = v2->y - v1->y;
|
|
|
|
ld->bbox[BOXLEFT] = min(v1->x, v2->x);
|
|
ld->bbox[BOXRIGHT] = max(v1->x, v2->x);
|
|
ld->bbox[BOXBOTTOM] = min(v1->y, v2->y);
|
|
ld->bbox[BOXTOP] = max(v1->y, v2->y);
|
|
|
|
if (!ld->dx)
|
|
ld->slopetype = ST_VERTICAL;
|
|
else if (!ld->dy)
|
|
ld->slopetype = ST_HORIZONTAL;
|
|
else if ((ld->dy > 0) == (ld->dx > 0))
|
|
ld->slopetype = ST_POSITIVE;
|
|
else
|
|
ld->slopetype = ST_NEGATIVE;
|
|
|
|
ld->frontsector = ld->backsector = NULL;
|
|
|
|
ld->validcount = 0;
|
|
ld->polyobj = NULL;
|
|
|
|
ld->text = NULL;
|
|
ld->callcount = 0;
|
|
|
|
// cph 2006/09/30 - fix sidedef errors right away.
|
|
// cph 2002/07/20 - these errors are fatal if not fixed, so apply them
|
|
for (j = 0; j < 2; j++)
|
|
if (ld->sidenum[j] != 0xffff && ld->sidenum[j] >= (UINT16)numsides)
|
|
{
|
|
ld->sidenum[j] = 0xffff;
|
|
CONS_Debug(DBG_SETUP, "P_InitializeLinedef: Linedef %s has out-of-range sidedef number\n", sizeu1((size_t)(ld - lines)));
|
|
}
|
|
|
|
// killough 11/98: fix common wad errors (missing sidedefs):
|
|
if (ld->sidenum[0] == 0xffff)
|
|
{
|
|
ld->sidenum[0] = 0; // Substitute dummy sidedef for missing right side
|
|
// cph - print a warning about the bug
|
|
CONS_Debug(DBG_SETUP, "P_InitializeLinedef: Linedef %s missing first sidedef\n", sizeu1((size_t)(ld - lines)));
|
|
}
|
|
|
|
if ((ld->sidenum[1] == 0xffff) && (ld->flags & ML_TWOSIDED))
|
|
{
|
|
ld->flags &= ~ML_TWOSIDED; // Clear 2s flag for missing left side
|
|
// cph - print a warning about the bug
|
|
CONS_Debug(DBG_SETUP, "P_InitializeLinedef: Linedef %s has two-sided flag set, but no second sidedef\n", sizeu1((size_t)(ld - lines)));
|
|
}
|
|
|
|
if (ld->sidenum[0] != 0xffff)
|
|
{
|
|
sides[ld->sidenum[0]].special = ld->special;
|
|
sides[ld->sidenum[0]].line = ld;
|
|
}
|
|
if (ld->sidenum[1] != 0xffff)
|
|
{
|
|
sides[ld->sidenum[1]].special = ld->special;
|
|
sides[ld->sidenum[1]].line = ld;
|
|
}
|
|
}
|
|
|
|
static void P_SetLinedefV1(size_t i, UINT16 vertex_num)
|
|
{
|
|
if (vertex_num >= numvertexes)
|
|
{
|
|
CONS_Debug(DBG_SETUP, "P_SetLinedefV1: linedef %s has out-of-range v1 num %u\n", sizeu1(i), vertex_num);
|
|
vertex_num = 0;
|
|
}
|
|
lines[i].v1 = &vertexes[vertex_num];
|
|
}
|
|
|
|
static void P_SetLinedefV2(size_t i, UINT16 vertex_num)
|
|
{
|
|
if (vertex_num >= numvertexes)
|
|
{
|
|
CONS_Debug(DBG_SETUP, "P_SetLinedefV2: linedef %s has out-of-range v2 num %u\n", sizeu1(i), vertex_num);
|
|
vertex_num = 0;
|
|
}
|
|
lines[i].v2 = &vertexes[vertex_num];
|
|
}
|
|
|
|
static void P_LoadLinedefs(UINT8 *data)
|
|
{
|
|
maplinedef_t *mld = (maplinedef_t *)data;
|
|
line_t *ld = lines;
|
|
size_t i;
|
|
|
|
for (i = 0; i < numlines; i++, mld++, ld++)
|
|
{
|
|
ld->flags = SHORT(mld->flags);
|
|
ld->special = SHORT(mld->special);
|
|
Tag_FSet(&ld->tags, SHORT(mld->tag));
|
|
memset(ld->args, 0, NUMLINEARGS*sizeof(*ld->args));
|
|
memset(ld->stringargs, 0x00, NUMLINESTRINGARGS*sizeof(*ld->stringargs));
|
|
ld->alpha = FRACUNIT;
|
|
ld->executordelay = 0;
|
|
P_SetLinedefV1(i, SHORT(mld->v1));
|
|
P_SetLinedefV2(i, SHORT(mld->v2));
|
|
|
|
ld->sidenum[0] = SHORT(mld->sidenum[0]);
|
|
ld->sidenum[1] = SHORT(mld->sidenum[1]);
|
|
|
|
P_InitializeLinedef(ld);
|
|
}
|
|
}
|
|
|
|
static void P_SetSidedefSector(size_t i, UINT16 sector_num)
|
|
{
|
|
// cph 2006/09/30 - catch out-of-range sector numbers; use sector 0 instead
|
|
if (sector_num >= numsectors)
|
|
{
|
|
CONS_Debug(DBG_SETUP, "P_SetSidedefSector: sidedef %s has out-of-range sector num %u\n", sizeu1(i), sector_num);
|
|
sector_num = 0;
|
|
}
|
|
sides[i].sector = §ors[sector_num];
|
|
}
|
|
|
|
static void P_InitializeSidedef(side_t *sd)
|
|
{
|
|
if (!sd->line)
|
|
{
|
|
CONS_Debug(DBG_SETUP, "P_LoadSidedefs: Sidedef %s is not used by any linedef\n", sizeu1((size_t)(sd - sides)));
|
|
sd->line = &lines[0];
|
|
sd->special = sd->line->special;
|
|
}
|
|
|
|
sd->text = NULL;
|
|
sd->colormap_data = NULL;
|
|
}
|
|
|
|
static void P_LoadSidedefs(UINT8 *data)
|
|
{
|
|
mapsidedef_t *msd = (mapsidedef_t*)data;
|
|
side_t *sd = sides;
|
|
size_t i;
|
|
|
|
for (i = 0; i < numsides; i++, sd++, msd++)
|
|
{
|
|
INT16 textureoffset = SHORT(msd->textureoffset);
|
|
boolean isfrontside;
|
|
|
|
P_InitializeSidedef(sd);
|
|
|
|
isfrontside = sd->line->sidenum[0] == i;
|
|
|
|
// Repeat count for midtexture
|
|
if (((sd->line->flags & (ML_TWOSIDED|ML_EFFECT5)) == (ML_TWOSIDED|ML_EFFECT5))
|
|
&& !(sd->special >= 300 && sd->special < 500)) // exempt linedef exec specials
|
|
{
|
|
sd->repeatcnt = (INT16)(((UINT16)textureoffset) >> 12);
|
|
sd->textureoffset = (((UINT16)textureoffset) & 2047) << FRACBITS;
|
|
}
|
|
else
|
|
{
|
|
sd->repeatcnt = 0;
|
|
sd->textureoffset = textureoffset << FRACBITS;
|
|
}
|
|
sd->rowoffset = SHORT(msd->rowoffset)<<FRACBITS;
|
|
|
|
P_SetSidedefSector(i, SHORT(msd->sector));
|
|
|
|
// Special info stored in texture fields!
|
|
switch (sd->special)
|
|
{
|
|
case 606: //SoM: 4/4/2000: Just colormap transfer
|
|
case 447: // Change colormap of tagged sectors! -- Monster Iestyn 14/06/18
|
|
case 455: // Fade colormaps! mazmazz 9/12/2018 (:flag_us:)
|
|
// SoM: R_CreateColormap will only create a colormap in software mode...
|
|
// Perhaps we should just call it instead of doing the calculations here.
|
|
if (!udmf)
|
|
{
|
|
sd->colormap_data = R_CreateColormapFromLinedef(msd->toptexture, msd->midtexture, msd->bottomtexture);
|
|
sd->toptexture = sd->midtexture = sd->bottomtexture = 0;
|
|
}
|
|
break;
|
|
|
|
case 413: // Change music
|
|
{
|
|
char process[8+1];
|
|
|
|
sd->toptexture = sd->midtexture = sd->bottomtexture = 0;
|
|
if (msd->bottomtexture[0] != '-' || msd->bottomtexture[1] != '\0')
|
|
{
|
|
M_Memcpy(process,msd->bottomtexture,8);
|
|
process[8] = '\0';
|
|
sd->bottomtexture = get_number(process);
|
|
}
|
|
|
|
if (!(msd->midtexture[0] == '-' && msd->midtexture[1] == '\0') || msd->midtexture[1] != '\0')
|
|
{
|
|
M_Memcpy(process,msd->midtexture,8);
|
|
process[8] = '\0';
|
|
sd->midtexture = get_number(process);
|
|
}
|
|
|
|
sd->text = Z_Malloc(7, PU_LEVEL, NULL);
|
|
if (isfrontside && !(msd->toptexture[0] == '-' && msd->toptexture[1] == '\0'))
|
|
{
|
|
M_Memcpy(process,msd->toptexture,8);
|
|
process[8] = '\0';
|
|
|
|
// If they type in O_ or D_ and their music name, just shrug,
|
|
// then copy the rest instead.
|
|
if ((process[0] == 'O' || process[0] == 'D') && process[7])
|
|
M_Memcpy(sd->text, process+2, 6);
|
|
else // Assume it's a proper music name.
|
|
M_Memcpy(sd->text, process, 6);
|
|
sd->text[6] = 0;
|
|
}
|
|
else
|
|
sd->text[0] = 0;
|
|
break;
|
|
}
|
|
|
|
case 4: // Speed pad parameters
|
|
case 414: // Play SFX
|
|
{
|
|
sd->toptexture = sd->midtexture = sd->bottomtexture = 0;
|
|
if (msd->toptexture[0] != '-' || msd->toptexture[1] != '\0')
|
|
{
|
|
char process[8+1];
|
|
M_Memcpy(process,msd->toptexture,8);
|
|
process[8] = '\0';
|
|
sd->toptexture = get_number(process);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 9: // Mace parameters
|
|
case 14: // Bustable block parameters
|
|
case 15: // Fan particle spawner parameters
|
|
case 334: // Trigger linedef executor: Object dye - Continuous
|
|
case 335: // Trigger linedef executor: Object dye - Each time
|
|
case 336: // Trigger linedef executor: Object dye - Once
|
|
case 425: // Calls P_SetMobjState on calling mobj
|
|
case 434: // Custom Power
|
|
case 442: // Calls P_SetMobjState on mobjs of a given type in the tagged sectors
|
|
case 461: // Spawns an object on the map based on texture offsets
|
|
case 463: // Colorizes an object
|
|
{
|
|
char process[8*3+1];
|
|
memset(process,0,8*3+1);
|
|
sd->toptexture = sd->midtexture = sd->bottomtexture = 0;
|
|
if (msd->toptexture[0] == '-' && msd->toptexture[1] == '\0')
|
|
break;
|
|
else
|
|
M_Memcpy(process,msd->toptexture,8);
|
|
if (msd->midtexture[0] != '-' || msd->midtexture[1] != '\0')
|
|
M_Memcpy(process+strlen(process), msd->midtexture, 8);
|
|
if (msd->bottomtexture[0] != '-' || msd->bottomtexture[1] != '\0')
|
|
M_Memcpy(process+strlen(process), msd->bottomtexture, 8);
|
|
sd->toptexture = get_number(process);
|
|
break;
|
|
}
|
|
|
|
case 331: // Trigger linedef executor: Skin - Continuous
|
|
case 332: // Trigger linedef executor: Skin - Each time
|
|
case 333: // Trigger linedef executor: Skin - Once
|
|
case 443: // Calls a named Lua function
|
|
case 459: // Control text prompt (named tag)
|
|
{
|
|
char process[8*3+1];
|
|
memset(process,0,8*3+1);
|
|
sd->toptexture = sd->midtexture = sd->bottomtexture = 0;
|
|
if (msd->toptexture[0] == '-' && msd->toptexture[1] == '\0')
|
|
break;
|
|
else
|
|
M_Memcpy(process,msd->toptexture,8);
|
|
if (msd->midtexture[0] != '-' || msd->midtexture[1] != '\0')
|
|
M_Memcpy(process+strlen(process), msd->midtexture, 8);
|
|
if (msd->bottomtexture[0] != '-' || msd->bottomtexture[1] != '\0')
|
|
M_Memcpy(process+strlen(process), msd->bottomtexture, 8);
|
|
sd->text = Z_Malloc(strlen(process)+1, PU_LEVEL, NULL);
|
|
M_Memcpy(sd->text, process, strlen(process)+1);
|
|
break;
|
|
}
|
|
|
|
case 259: // Custom FOF
|
|
if (!isfrontside)
|
|
{
|
|
if ((msd->toptexture[0] >= '0' && msd->toptexture[0] <= '9')
|
|
|| (msd->toptexture[0] >= 'A' && msd->toptexture[0] <= 'F'))
|
|
sd->toptexture = axtoi(msd->toptexture);
|
|
else
|
|
I_Error("Custom FOF (line id %s) needs a value in the linedef's back side upper texture field.", sizeu1(sd->line - lines));
|
|
|
|
sd->midtexture = R_TextureNumForName(msd->midtexture);
|
|
sd->bottomtexture = R_TextureNumForName(msd->bottomtexture);
|
|
break;
|
|
}
|
|
// FALLTHRU
|
|
default: // normal cases
|
|
if (msd->toptexture[0] == '#')
|
|
{
|
|
char *col = msd->toptexture;
|
|
sd->toptexture = sd->bottomtexture =
|
|
((col[1]-'0')*100 + (col[2]-'0')*10 + col[3]-'0') + 1;
|
|
sd->midtexture = R_TextureNumForName(msd->midtexture);
|
|
}
|
|
else
|
|
{
|
|
sd->midtexture = R_TextureNumForName(msd->midtexture);
|
|
sd->toptexture = R_TextureNumForName(msd->toptexture);
|
|
sd->bottomtexture = R_TextureNumForName(msd->bottomtexture);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void P_LoadThings(UINT8 *data)
|
|
{
|
|
mapthing_t *mt;
|
|
size_t i;
|
|
|
|
for (i = 0, mt = mapthings; i < nummapthings; i++, mt++)
|
|
{
|
|
mt->x = READINT16(data);
|
|
mt->y = READINT16(data);
|
|
|
|
mt->angle = READINT16(data);
|
|
mt->type = READUINT16(data);
|
|
mt->options = READUINT16(data);
|
|
mt->extrainfo = (UINT8)(mt->type >> 12);
|
|
Tag_FSet(&mt->tags, 0);
|
|
mt->scale = FRACUNIT;
|
|
memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
|
|
memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
|
|
mt->pitch = mt->roll = 0;
|
|
|
|
mt->type &= 4095;
|
|
|
|
if (mt->type == 1705 || (mt->type == 750 && mt->extrainfo))
|
|
mt->z = mt->options; // NiGHTS Hoops use the full flags bits to set the height.
|
|
else
|
|
mt->z = mt->options >> ZSHIFT;
|
|
|
|
mt->mobj = NULL;
|
|
}
|
|
}
|
|
|
|
// Stores positions for relevant map data spread through a TEXTMAP.
|
|
UINT32 mapthingsPos[UINT16_MAX];
|
|
UINT32 linesPos[UINT16_MAX];
|
|
UINT32 sidesPos[UINT16_MAX];
|
|
UINT32 vertexesPos[UINT16_MAX];
|
|
UINT32 sectorsPos[UINT16_MAX];
|
|
|
|
// Determine total amount of map data in TEXTMAP.
|
|
static boolean TextmapCount(UINT8 *data, size_t size)
|
|
{
|
|
char *tkn = M_GetToken((char *)data);
|
|
UINT8 brackets = 0;
|
|
|
|
nummapthings = 0;
|
|
numlines = 0;
|
|
numsides = 0;
|
|
numvertexes = 0;
|
|
numsectors = 0;
|
|
|
|
// Look for namespace at the beginning.
|
|
if (!fastcmp(tkn, "namespace"))
|
|
{
|
|
Z_Free(tkn);
|
|
CONS_Alert(CONS_ERROR, "No namespace at beginning of lump!\n");
|
|
return false;
|
|
}
|
|
Z_Free(tkn);
|
|
|
|
// Check if namespace is valid.
|
|
tkn = M_GetToken(NULL);
|
|
if (!fastcmp(tkn, "srb2"))
|
|
CONS_Alert(CONS_WARNING, "Invalid namespace '%s', only 'srb2' is supported.\n", tkn);
|
|
Z_Free(tkn);
|
|
|
|
tkn = M_GetToken(NULL);
|
|
while (tkn && M_GetTokenPos() < size)
|
|
{
|
|
// Avoid anything inside bracketed stuff, only look for external keywords.
|
|
if (brackets)
|
|
{
|
|
if (fastcmp(tkn, "}"))
|
|
brackets--;
|
|
}
|
|
else if (fastcmp(tkn, "{"))
|
|
brackets++;
|
|
// Check for valid fields.
|
|
else if (fastcmp(tkn, "thing"))
|
|
mapthingsPos[nummapthings++] = M_GetTokenPos();
|
|
else if (fastcmp(tkn, "linedef"))
|
|
linesPos[numlines++] = M_GetTokenPos();
|
|
else if (fastcmp(tkn, "sidedef"))
|
|
sidesPos[numsides++] = M_GetTokenPos();
|
|
else if (fastcmp(tkn, "vertex"))
|
|
vertexesPos[numvertexes++] = M_GetTokenPos();
|
|
else if (fastcmp(tkn, "sector"))
|
|
sectorsPos[numsectors++] = M_GetTokenPos();
|
|
else
|
|
CONS_Alert(CONS_NOTICE, "Unknown field '%s'.\n", tkn);
|
|
|
|
Z_Free(tkn);
|
|
tkn = M_GetToken(NULL);
|
|
}
|
|
|
|
Z_Free(tkn);
|
|
|
|
if (brackets)
|
|
{
|
|
CONS_Alert(CONS_ERROR, "Unclosed brackets detected in textmap lump.\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void ParseTextmapVertexParameter(UINT32 i, char *param, char *val)
|
|
{
|
|
if (fastcmp(param, "x"))
|
|
vertexes[i].x = FLOAT_TO_FIXED(atof(val));
|
|
else if (fastcmp(param, "y"))
|
|
vertexes[i].y = FLOAT_TO_FIXED(atof(val));
|
|
else if (fastcmp(param, "zfloor"))
|
|
{
|
|
vertexes[i].floorz = FLOAT_TO_FIXED(atof(val));
|
|
vertexes[i].floorzset = true;
|
|
}
|
|
else if (fastcmp(param, "zceiling"))
|
|
{
|
|
vertexes[i].ceilingz = FLOAT_TO_FIXED(atof(val));
|
|
vertexes[i].ceilingzset = true;
|
|
}
|
|
}
|
|
|
|
typedef struct textmap_colormap_s {
|
|
boolean used;
|
|
INT32 lightcolor;
|
|
UINT8 lightalpha;
|
|
INT32 fadecolor;
|
|
UINT8 fadealpha;
|
|
UINT8 fadestart;
|
|
UINT8 fadeend;
|
|
UINT8 flags;
|
|
} textmap_colormap_t;
|
|
|
|
textmap_colormap_t textmap_colormap = { false, 0, 25, 0, 25, 0, 31, 0 };
|
|
|
|
static void ParseTextmapSectorParameter(UINT32 i, char *param, char *val)
|
|
{
|
|
if (fastcmp(param, "heightfloor"))
|
|
sectors[i].floorheight = atol(val) << FRACBITS;
|
|
else if (fastcmp(param, "heightceiling"))
|
|
sectors[i].ceilingheight = atol(val) << FRACBITS;
|
|
if (fastcmp(param, "texturefloor"))
|
|
sectors[i].floorpic = P_AddLevelFlat(val, foundflats);
|
|
else if (fastcmp(param, "textureceiling"))
|
|
sectors[i].ceilingpic = P_AddLevelFlat(val, foundflats);
|
|
else if (fastcmp(param, "lightlevel"))
|
|
sectors[i].lightlevel = atol(val);
|
|
else if (fastcmp(param, "special"))
|
|
sectors[i].special = atol(val);
|
|
else if (fastcmp(param, "id"))
|
|
Tag_FSet(§ors[i].tags, atol(val));
|
|
else if (fastcmp(param, "moreids"))
|
|
{
|
|
char* id = val;
|
|
while (id)
|
|
{
|
|
Tag_Add(§ors[i].tags, atol(id));
|
|
if ((id = strchr(id, ' ')))
|
|
id++;
|
|
}
|
|
}
|
|
else if (fastcmp(param, "xpanningfloor"))
|
|
sectors[i].floor_xoffs = FLOAT_TO_FIXED(atof(val));
|
|
else if (fastcmp(param, "ypanningfloor"))
|
|
sectors[i].floor_yoffs = FLOAT_TO_FIXED(atof(val));
|
|
else if (fastcmp(param, "xpanningceiling"))
|
|
sectors[i].ceiling_xoffs = FLOAT_TO_FIXED(atof(val));
|
|
else if (fastcmp(param, "ypanningceiling"))
|
|
sectors[i].ceiling_yoffs = FLOAT_TO_FIXED(atof(val));
|
|
else if (fastcmp(param, "rotationfloor"))
|
|
sectors[i].floorpic_angle = FixedAngle(FLOAT_TO_FIXED(atof(val)));
|
|
else if (fastcmp(param, "rotationceiling"))
|
|
sectors[i].ceilingpic_angle = FixedAngle(FLOAT_TO_FIXED(atof(val)));
|
|
else if (fastcmp(param, "lightcolor"))
|
|
{
|
|
textmap_colormap.used = true;
|
|
textmap_colormap.lightcolor = atol(val);
|
|
}
|
|
else if (fastcmp(param, "lightalpha"))
|
|
{
|
|
textmap_colormap.used = true;
|
|
textmap_colormap.lightalpha = atol(val);
|
|
}
|
|
else if (fastcmp(param, "fadecolor"))
|
|
{
|
|
textmap_colormap.used = true;
|
|
textmap_colormap.fadecolor = atol(val);
|
|
}
|
|
else if (fastcmp(param, "fadealpha"))
|
|
{
|
|
textmap_colormap.used = true;
|
|
textmap_colormap.fadealpha = atol(val);
|
|
}
|
|
else if (fastcmp(param, "fadestart"))
|
|
{
|
|
textmap_colormap.used = true;
|
|
textmap_colormap.fadestart = atol(val);
|
|
}
|
|
else if (fastcmp(param, "fadeend"))
|
|
{
|
|
textmap_colormap.used = true;
|
|
textmap_colormap.fadeend = atol(val);
|
|
}
|
|
else if (fastcmp(param, "colormapfog") && fastcmp("true", val))
|
|
{
|
|
textmap_colormap.used = true;
|
|
textmap_colormap.flags |= CMF_FOG;
|
|
}
|
|
else if (fastcmp(param, "colormapfadesprites") && fastcmp("true", val))
|
|
{
|
|
textmap_colormap.used = true;
|
|
textmap_colormap.flags |= CMF_FADEFULLBRIGHTSPRITES;
|
|
}
|
|
else if (fastcmp(param, "colormapprotected") && fastcmp("true", val))
|
|
sectors[i].colormap_protected = true;
|
|
}
|
|
|
|
static void ParseTextmapSidedefParameter(UINT32 i, char *param, char *val)
|
|
{
|
|
if (fastcmp(param, "offsetx"))
|
|
sides[i].textureoffset = atol(val)<<FRACBITS;
|
|
else if (fastcmp(param, "offsety"))
|
|
sides[i].rowoffset = atol(val)<<FRACBITS;
|
|
else if (fastcmp(param, "texturetop"))
|
|
sides[i].toptexture = R_TextureNumForName(val);
|
|
else if (fastcmp(param, "texturebottom"))
|
|
sides[i].bottomtexture = R_TextureNumForName(val);
|
|
else if (fastcmp(param, "texturemiddle"))
|
|
sides[i].midtexture = R_TextureNumForName(val);
|
|
else if (fastcmp(param, "sector"))
|
|
P_SetSidedefSector(i, atol(val));
|
|
else if (fastcmp(param, "repeatcnt"))
|
|
sides[i].repeatcnt = atol(val);
|
|
}
|
|
|
|
static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
|
|
{
|
|
if (fastcmp(param, "id"))
|
|
Tag_FSet(&lines[i].tags, atol(val));
|
|
else if (fastcmp(param, "moreids"))
|
|
{
|
|
char* id = val;
|
|
while (id)
|
|
{
|
|
Tag_Add(&lines[i].tags, atol(id));
|
|
if ((id = strchr(id, ' ')))
|
|
id++;
|
|
}
|
|
}
|
|
else if (fastcmp(param, "special"))
|
|
lines[i].special = atol(val);
|
|
else if (fastcmp(param, "v1"))
|
|
P_SetLinedefV1(i, atol(val));
|
|
else if (fastcmp(param, "v2"))
|
|
P_SetLinedefV2(i, atol(val));
|
|
else if (strlen(param) == 7 && fastncmp(param, "arg", 3) && fastncmp(param + 4, "str", 3))
|
|
{
|
|
size_t argnum = param[3] - '0';
|
|
if (argnum >= NUMLINESTRINGARGS)
|
|
return;
|
|
lines[i].stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL);
|
|
M_Memcpy(lines[i].stringargs[argnum], val, strlen(val) + 1);
|
|
}
|
|
else if (fastncmp(param, "arg", 3) && strlen(param) > 3)
|
|
{
|
|
size_t argnum = atol(param + 3);
|
|
if (argnum >= NUMLINEARGS)
|
|
return;
|
|
lines[i].args[argnum] = atol(val);
|
|
}
|
|
else if (fastcmp(param, "sidefront"))
|
|
lines[i].sidenum[0] = atol(val);
|
|
else if (fastcmp(param, "sideback"))
|
|
lines[i].sidenum[1] = atol(val);
|
|
else if (fastcmp(param, "alpha"))
|
|
lines[i].alpha = FLOAT_TO_FIXED(atof(val));
|
|
else if (fastcmp(param, "executordelay"))
|
|
lines[i].executordelay = atol(val);
|
|
|
|
// Flags
|
|
else if (fastcmp(param, "blocking") && fastcmp("true", val))
|
|
lines[i].flags |= ML_IMPASSIBLE;
|
|
else if (fastcmp(param, "blockmonsters") && fastcmp("true", val))
|
|
lines[i].flags |= ML_BLOCKMONSTERS;
|
|
else if (fastcmp(param, "twosided") && fastcmp("true", val))
|
|
lines[i].flags |= ML_TWOSIDED;
|
|
else if (fastcmp(param, "dontpegtop") && fastcmp("true", val))
|
|
lines[i].flags |= ML_DONTPEGTOP;
|
|
else if (fastcmp(param, "dontpegbottom") && fastcmp("true", val))
|
|
lines[i].flags |= ML_DONTPEGBOTTOM;
|
|
else if (fastcmp(param, "skewtd") && fastcmp("true", val))
|
|
lines[i].flags |= ML_EFFECT1;
|
|
else if (fastcmp(param, "noclimb") && fastcmp("true", val))
|
|
lines[i].flags |= ML_NOCLIMB;
|
|
else if (fastcmp(param, "noskew") && fastcmp("true", val))
|
|
lines[i].flags |= ML_EFFECT2;
|
|
else if (fastcmp(param, "midpeg") && fastcmp("true", val))
|
|
lines[i].flags |= ML_EFFECT3;
|
|
else if (fastcmp(param, "midsolid") && fastcmp("true", val))
|
|
lines[i].flags |= ML_EFFECT4;
|
|
else if (fastcmp(param, "wrapmidtex") && fastcmp("true", val))
|
|
lines[i].flags |= ML_EFFECT5;
|
|
else if (fastcmp(param, "effect6") && fastcmp("true", val))
|
|
lines[i].flags |= ML_EFFECT6;
|
|
else if (fastcmp(param, "nonet") && fastcmp("true", val))
|
|
lines[i].flags |= ML_NONET;
|
|
else if (fastcmp(param, "netonly") && fastcmp("true", val))
|
|
lines[i].flags |= ML_NETONLY;
|
|
else if (fastcmp(param, "bouncy") && fastcmp("true", val))
|
|
lines[i].flags |= ML_BOUNCY;
|
|
else if (fastcmp(param, "transfer") && fastcmp("true", val))
|
|
lines[i].flags |= ML_TFERLINE;
|
|
}
|
|
|
|
static void ParseTextmapThingParameter(UINT32 i, char *param, char *val)
|
|
{
|
|
if (fastcmp(param, "id"))
|
|
Tag_FSet(&mapthings[i].tags, atol(val));
|
|
else if (fastcmp(param, "moreids"))
|
|
{
|
|
char* id = val;
|
|
while (id)
|
|
{
|
|
Tag_Add(&mapthings[i].tags, atol(id));
|
|
if ((id = strchr(id, ' ')))
|
|
id++;
|
|
}
|
|
}
|
|
else if (fastcmp(param, "x"))
|
|
mapthings[i].x = atol(val);
|
|
else if (fastcmp(param, "y"))
|
|
mapthings[i].y = atol(val);
|
|
else if (fastcmp(param, "height"))
|
|
mapthings[i].z = atol(val);
|
|
else if (fastcmp(param, "angle"))
|
|
mapthings[i].angle = atol(val);
|
|
else if (fastcmp(param, "pitch"))
|
|
mapthings[i].pitch = atol(val);
|
|
else if (fastcmp(param, "roll"))
|
|
mapthings[i].roll = atol(val);
|
|
else if (fastcmp(param, "type"))
|
|
mapthings[i].type = atol(val);
|
|
else if (fastcmp(param, "scale") || fastcmp(param, "scalex") || fastcmp(param, "scaley"))
|
|
mapthings[i].scale = FLOAT_TO_FIXED(atof(val));
|
|
// Flags
|
|
else if (fastcmp(param, "extra") && fastcmp("true", val))
|
|
mapthings[i].options |= MTF_EXTRA;
|
|
else if (fastcmp(param, "flip") && fastcmp("true", val))
|
|
mapthings[i].options |= MTF_OBJECTFLIP;
|
|
else if (fastcmp(param, "objectspecial") && fastcmp("true", val))
|
|
mapthings[i].options |= MTF_OBJECTSPECIAL;
|
|
else if (fastcmp(param, "ambush") && fastcmp("true", val))
|
|
mapthings[i].options |= MTF_AMBUSH;
|
|
|
|
else if (strlen(param) == 7 && fastncmp(param, "arg", 3) && fastncmp(param + 4, "str", 3))
|
|
{
|
|
size_t argnum = param[3] - '0';
|
|
if (argnum >= NUMMAPTHINGSTRINGARGS)
|
|
return;
|
|
mapthings[i].stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL);
|
|
M_Memcpy(mapthings[i].stringargs[argnum], val, strlen(val) + 1);
|
|
}
|
|
else if (fastncmp(param, "arg", 3) && strlen(param) > 3)
|
|
{
|
|
size_t argnum = atol(param + 3);
|
|
if (argnum >= NUMMAPTHINGARGS)
|
|
return;
|
|
mapthings[i].args[argnum] = atol(val);
|
|
}
|
|
}
|
|
|
|
/** From a given position table, run a specified parser function through a {}-encapsuled text.
|
|
*
|
|
* \param Position of the data to parse, in the textmap.
|
|
* \param Structure number (mapthings, sectors, ...).
|
|
* \param Parser function pointer.
|
|
*/
|
|
static void TextmapParse(UINT32 dataPos, size_t num, void (*parser)(UINT32, char *, char *))
|
|
{
|
|
char *param, *val;
|
|
|
|
M_SetTokenPos(dataPos);
|
|
param = M_GetToken(NULL);
|
|
if (!fastcmp(param, "{"))
|
|
{
|
|
Z_Free(param);
|
|
CONS_Alert(CONS_WARNING, "Invalid UDMF data capsule!\n");
|
|
return;
|
|
}
|
|
Z_Free(param);
|
|
|
|
while (true)
|
|
{
|
|
param = M_GetToken(NULL);
|
|
if (fastcmp(param, "}"))
|
|
{
|
|
Z_Free(param);
|
|
break;
|
|
}
|
|
val = M_GetToken(NULL);
|
|
parser(num, param, val);
|
|
Z_Free(param);
|
|
Z_Free(val);
|
|
}
|
|
}
|
|
|
|
/** Provides a fix to the flat alignment coordinate transform from standard Textmaps.
|
|
*/
|
|
static void TextmapFixFlatOffsets(sector_t *sec)
|
|
{
|
|
if (sec->floorpic_angle)
|
|
{
|
|
fixed_t pc = FINECOSINE(sec->floorpic_angle>>ANGLETOFINESHIFT);
|
|
fixed_t ps = FINESINE (sec->floorpic_angle>>ANGLETOFINESHIFT);
|
|
fixed_t xoffs = sec->floor_xoffs;
|
|
fixed_t yoffs = sec->floor_yoffs;
|
|
sec->floor_xoffs = (FixedMul(xoffs, pc) % MAXFLATSIZE) - (FixedMul(yoffs, ps) % MAXFLATSIZE);
|
|
sec->floor_yoffs = (FixedMul(xoffs, ps) % MAXFLATSIZE) + (FixedMul(yoffs, pc) % MAXFLATSIZE);
|
|
}
|
|
|
|
if (sec->ceilingpic_angle)
|
|
{
|
|
fixed_t pc = FINECOSINE(sec->ceilingpic_angle>>ANGLETOFINESHIFT);
|
|
fixed_t ps = FINESINE (sec->ceilingpic_angle>>ANGLETOFINESHIFT);
|
|
fixed_t xoffs = sec->ceiling_xoffs;
|
|
fixed_t yoffs = sec->ceiling_yoffs;
|
|
sec->ceiling_xoffs = (FixedMul(xoffs, pc) % MAXFLATSIZE) - (FixedMul(yoffs, ps) % MAXFLATSIZE);
|
|
sec->ceiling_yoffs = (FixedMul(xoffs, ps) % MAXFLATSIZE) + (FixedMul(yoffs, pc) % MAXFLATSIZE);
|
|
}
|
|
}
|
|
|
|
static INT32 P_ColorToRGBA(INT32 color, UINT8 alpha)
|
|
{
|
|
UINT8 r = (color >> 16) & 0xFF;
|
|
UINT8 g = (color >> 8) & 0xFF;
|
|
UINT8 b = color & 0xFF;
|
|
return R_PutRgbaRGBA(r, g, b, alpha);
|
|
}
|
|
|
|
/** Loads the textmap data, after obtaining the elements count and allocating their respective space.
|
|
*/
|
|
static void P_LoadTextmap(void)
|
|
{
|
|
UINT32 i;
|
|
|
|
vertex_t *vt;
|
|
sector_t *sc;
|
|
line_t *ld;
|
|
side_t *sd;
|
|
mapthing_t *mt;
|
|
|
|
CONS_Alert(CONS_NOTICE, "UDMF support is still a work-in-progress; its specs and features are prone to change until it is fully implemented.\n");
|
|
|
|
/// Given the UDMF specs, some fields are given a default value.
|
|
/// If an element's field has a default value set, it is omitted
|
|
/// from the textmap, and therefore we have to account for it by
|
|
/// preemptively setting that value beforehand.
|
|
|
|
for (i = 0, vt = vertexes; i < numvertexes; i++, vt++)
|
|
{
|
|
// Defaults.
|
|
vt->x = vt->y = INT32_MAX;
|
|
vt->floorzset = vt->ceilingzset = false;
|
|
vt->floorz = vt->ceilingz = 0;
|
|
|
|
TextmapParse(vertexesPos[i], i, ParseTextmapVertexParameter);
|
|
|
|
if (vt->x == INT32_MAX)
|
|
I_Error("P_LoadTextmap: vertex %s has no x value set!\n", sizeu1(i));
|
|
if (vt->y == INT32_MAX)
|
|
I_Error("P_LoadTextmap: vertex %s has no y value set!\n", sizeu1(i));
|
|
}
|
|
|
|
for (i = 0, sc = sectors; i < numsectors; i++, sc++)
|
|
{
|
|
// Defaults.
|
|
sc->floorheight = 0;
|
|
sc->ceilingheight = 0;
|
|
|
|
sc->floorpic = 0;
|
|
sc->ceilingpic = 0;
|
|
|
|
sc->lightlevel = 255;
|
|
|
|
sc->special = 0;
|
|
Tag_FSet(&sc->tags, 0);
|
|
|
|
sc->floor_xoffs = sc->floor_yoffs = 0;
|
|
sc->ceiling_xoffs = sc->ceiling_yoffs = 0;
|
|
|
|
sc->floorpic_angle = sc->ceilingpic_angle = 0;
|
|
|
|
sc->colormap_protected = false;
|
|
|
|
textmap_colormap.used = false;
|
|
textmap_colormap.lightcolor = 0;
|
|
textmap_colormap.lightalpha = 25;
|
|
textmap_colormap.fadecolor = 0;
|
|
textmap_colormap.fadealpha = 25;
|
|
textmap_colormap.fadestart = 0;
|
|
textmap_colormap.fadeend = 31;
|
|
textmap_colormap.flags = 0;
|
|
TextmapParse(sectorsPos[i], i, ParseTextmapSectorParameter);
|
|
|
|
P_InitializeSector(sc);
|
|
if (textmap_colormap.used)
|
|
{
|
|
INT32 rgba = P_ColorToRGBA(textmap_colormap.lightcolor, textmap_colormap.lightalpha);
|
|
INT32 fadergba = P_ColorToRGBA(textmap_colormap.fadecolor, textmap_colormap.fadealpha);
|
|
sc->extra_colormap = sc->spawn_extra_colormap = R_CreateColormap(rgba, fadergba, textmap_colormap.fadestart, textmap_colormap.fadeend, textmap_colormap.flags);
|
|
}
|
|
TextmapFixFlatOffsets(sc);
|
|
}
|
|
|
|
for (i = 0, ld = lines; i < numlines; i++, ld++)
|
|
{
|
|
// Defaults.
|
|
ld->v1 = ld->v2 = NULL;
|
|
ld->flags = 0;
|
|
ld->special = 0;
|
|
Tag_FSet(&ld->tags, 0);
|
|
|
|
memset(ld->args, 0, NUMLINEARGS*sizeof(*ld->args));
|
|
memset(ld->stringargs, 0x00, NUMLINESTRINGARGS*sizeof(*ld->stringargs));
|
|
ld->alpha = FRACUNIT;
|
|
ld->executordelay = 0;
|
|
ld->sidenum[0] = 0xffff;
|
|
ld->sidenum[1] = 0xffff;
|
|
|
|
TextmapParse(linesPos[i], i, ParseTextmapLinedefParameter);
|
|
|
|
if (!ld->v1)
|
|
I_Error("P_LoadTextmap: linedef %s has no v1 value set!\n", sizeu1(i));
|
|
if (!ld->v2)
|
|
I_Error("P_LoadTextmap: linedef %s has no v2 value set!\n", sizeu1(i));
|
|
if (ld->sidenum[0] == 0xffff)
|
|
I_Error("P_LoadTextmap: linedef %s has no sidefront value set!\n", sizeu1(i));
|
|
|
|
P_InitializeLinedef(ld);
|
|
}
|
|
|
|
for (i = 0, sd = sides; i < numsides; i++, sd++)
|
|
{
|
|
// Defaults.
|
|
sd->textureoffset = 0;
|
|
sd->rowoffset = 0;
|
|
sd->toptexture = R_TextureNumForName("-");
|
|
sd->midtexture = R_TextureNumForName("-");
|
|
sd->bottomtexture = R_TextureNumForName("-");
|
|
sd->sector = NULL;
|
|
sd->repeatcnt = 0;
|
|
|
|
TextmapParse(sidesPos[i], i, ParseTextmapSidedefParameter);
|
|
|
|
if (!sd->sector)
|
|
I_Error("P_LoadTextmap: sidedef %s has no sector value set!\n", sizeu1(i));
|
|
|
|
P_InitializeSidedef(sd);
|
|
}
|
|
|
|
for (i = 0, mt = mapthings; i < nummapthings; i++, mt++)
|
|
{
|
|
// Defaults.
|
|
mt->x = mt->y = 0;
|
|
mt->angle = mt->pitch = mt->roll = 0;
|
|
mt->type = 0;
|
|
mt->options = 0;
|
|
mt->z = 0;
|
|
mt->extrainfo = 0;
|
|
Tag_FSet(&mt->tags, 0);
|
|
mt->scale = FRACUNIT;
|
|
memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
|
|
memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
|
|
mt->mobj = NULL;
|
|
|
|
TextmapParse(mapthingsPos[i], i, ParseTextmapThingParameter);
|
|
}
|
|
}
|
|
|
|
static void P_ProcessLinedefsAfterSidedefs(void)
|
|
{
|
|
size_t i = numlines;
|
|
register line_t *ld = lines;
|
|
for (; i--; ld++)
|
|
{
|
|
ld->frontsector = sides[ld->sidenum[0]].sector; //e6y: Can't be -1 here
|
|
ld->backsector = ld->sidenum[1] != 0xffff ? sides[ld->sidenum[1]].sector : 0;
|
|
|
|
switch (ld->special)
|
|
{
|
|
// Compile linedef 'text' from both sidedefs 'text' for appropriate specials.
|
|
case 331: // Trigger linedef executor: Skin - Continuous
|
|
case 332: // Trigger linedef executor: Skin - Each time
|
|
case 333: // Trigger linedef executor: Skin - Once
|
|
case 443: // Calls a named Lua function
|
|
if (sides[ld->sidenum[0]].text)
|
|
{
|
|
size_t len = strlen(sides[ld->sidenum[0]].text) + 1;
|
|
if (ld->sidenum[1] != 0xffff && sides[ld->sidenum[1]].text)
|
|
len += strlen(sides[ld->sidenum[1]].text);
|
|
ld->text = Z_Malloc(len, PU_LEVEL, NULL);
|
|
M_Memcpy(ld->text, sides[ld->sidenum[0]].text, strlen(sides[ld->sidenum[0]].text) + 1);
|
|
if (ld->sidenum[1] != 0xffff && sides[ld->sidenum[1]].text)
|
|
M_Memcpy(ld->text + strlen(ld->text) + 1, sides[ld->sidenum[1]].text, strlen(sides[ld->sidenum[1]].text) + 1);
|
|
}
|
|
break;
|
|
case 447: // Change colormap
|
|
case 455: // Fade colormap
|
|
if (udmf)
|
|
break;
|
|
if (ld->flags & ML_DONTPEGBOTTOM) // alternate alpha (by texture offsets)
|
|
{
|
|
extracolormap_t *exc = R_CopyColormap(sides[ld->sidenum[0]].colormap_data, false);
|
|
INT16 alpha = max(min(sides[ld->sidenum[0]].textureoffset >> FRACBITS, 25), -25);
|
|
INT16 fadealpha = max(min(sides[ld->sidenum[0]].rowoffset >> FRACBITS, 25), -25);
|
|
|
|
// If alpha is negative, set "subtract alpha" flag and store absolute value
|
|
if (alpha < 0)
|
|
{
|
|
alpha *= -1;
|
|
ld->args[2] |= TMCF_SUBLIGHTA;
|
|
}
|
|
if (fadealpha < 0)
|
|
{
|
|
fadealpha *= -1;
|
|
ld->args[2] |= TMCF_SUBFADEA;
|
|
}
|
|
|
|
exc->rgba = R_GetRgbaRGB(exc->rgba) + R_PutRgbaA(alpha);
|
|
exc->fadergba = R_GetRgbaRGB(exc->fadergba) + R_PutRgbaA(fadealpha);
|
|
|
|
if (!(sides[ld->sidenum[0]].colormap_data = R_GetColormapFromList(exc)))
|
|
{
|
|
exc->colormap = R_CreateLightTable(exc);
|
|
R_AddColormapToList(exc);
|
|
sides[ld->sidenum[0]].colormap_data = exc;
|
|
}
|
|
else
|
|
Z_Free(exc);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static boolean P_LoadMapData(const virtres_t *virt)
|
|
{
|
|
virtlump_t *virtvertexes = NULL, *virtsectors = NULL, *virtsidedefs = NULL, *virtlinedefs = NULL, *virtthings = NULL;
|
|
|
|
// Count map data.
|
|
if (udmf) // Count how many entries for each type we got in textmap.
|
|
{
|
|
virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
|
|
if (!TextmapCount(textmap->data, textmap->size))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
virtthings = vres_Find(virt, "THINGS");
|
|
virtvertexes = vres_Find(virt, "VERTEXES");
|
|
virtsectors = vres_Find(virt, "SECTORS");
|
|
virtsidedefs = vres_Find(virt, "SIDEDEFS");
|
|
virtlinedefs = vres_Find(virt, "LINEDEFS");
|
|
|
|
if (!virtthings)
|
|
I_Error("THINGS lump not found");
|
|
if (!virtvertexes)
|
|
I_Error("VERTEXES lump not found");
|
|
if (!virtsectors)
|
|
I_Error("SECTORS lump not found");
|
|
if (!virtsidedefs)
|
|
I_Error("SIDEDEFS lump not found");
|
|
if (!virtlinedefs)
|
|
I_Error("LINEDEFS lump not found");
|
|
|
|
// Traditional doom map format just assumes the number of elements from the lump sizes.
|
|
numvertexes = virtvertexes->size / sizeof (mapvertex_t);
|
|
numsectors = virtsectors->size / sizeof (mapsector_t);
|
|
numsides = virtsidedefs->size / sizeof (mapsidedef_t);
|
|
numlines = virtlinedefs->size / sizeof (maplinedef_t);
|
|
nummapthings = virtthings->size / (5 * sizeof (INT16));
|
|
}
|
|
|
|
if (numvertexes <= 0)
|
|
I_Error("Level has no vertices");
|
|
if (numsectors <= 0)
|
|
I_Error("Level has no sectors");
|
|
if (numsides <= 0)
|
|
I_Error("Level has no sidedefs");
|
|
if (numlines <= 0)
|
|
I_Error("Level has no linedefs");
|
|
|
|
vertexes = Z_Calloc(numvertexes * sizeof (*vertexes), PU_LEVEL, NULL);
|
|
sectors = Z_Calloc(numsectors * sizeof (*sectors), PU_LEVEL, NULL);
|
|
sides = Z_Calloc(numsides * sizeof (*sides), PU_LEVEL, NULL);
|
|
lines = Z_Calloc(numlines * sizeof (*lines), PU_LEVEL, NULL);
|
|
mapthings = Z_Calloc(nummapthings * sizeof (*mapthings), PU_LEVEL, NULL);
|
|
|
|
// Allocate a big chunk of memory as big as our MAXLEVELFLATS limit.
|
|
//Fab : FIXME: allocate for whatever number of flats - 512 different flats per level should be plenty
|
|
foundflats = calloc(MAXLEVELFLATS, sizeof (*foundflats));
|
|
if (foundflats == NULL)
|
|
I_Error("Ran out of memory while loading sectors\n");
|
|
|
|
numlevelflats = 0;
|
|
|
|
// Load map data.
|
|
if (udmf)
|
|
P_LoadTextmap();
|
|
else
|
|
{
|
|
P_LoadVertices(virtvertexes->data);
|
|
P_LoadSectors(virtsectors->data);
|
|
P_LoadLinedefs(virtlinedefs->data);
|
|
P_LoadSidedefs(virtsidedefs->data);
|
|
P_LoadThings(virtthings->data);
|
|
}
|
|
|
|
P_ProcessLinedefsAfterSidedefs();
|
|
|
|
R_ClearTextureNumCache(true);
|
|
|
|
// set the sky flat num
|
|
skyflatnum = P_AddLevelFlat(SKYFLATNAME, foundflats);
|
|
|
|
// copy table for global usage
|
|
levelflats = M_Memcpy(Z_Calloc(numlevelflats * sizeof (*levelflats), PU_LEVEL, NULL), foundflats, numlevelflats * sizeof (levelflat_t));
|
|
free(foundflats);
|
|
|
|
// search for animated flats and set up
|
|
P_SetupLevelFlatAnims();
|
|
|
|
return true;
|
|
}
|
|
|
|
static void P_InitializeSubsector(subsector_t *ss)
|
|
{
|
|
ss->sector = NULL;
|
|
ss->validcount = 0;
|
|
}
|
|
|
|
static inline void P_LoadSubsectors(UINT8 *data)
|
|
{
|
|
mapsubsector_t *ms = (mapsubsector_t*)data;
|
|
subsector_t *ss = subsectors;
|
|
size_t i;
|
|
|
|
for (i = 0; i < numsubsectors; i++, ss++, ms++)
|
|
{
|
|
ss->numlines = SHORT(ms->numsegs);
|
|
ss->firstline = SHORT(ms->firstseg);
|
|
P_InitializeSubsector(ss);
|
|
}
|
|
}
|
|
|
|
static void P_LoadNodes(UINT8 *data)
|
|
{
|
|
UINT8 j, k;
|
|
mapnode_t *mn = (mapnode_t*)data;
|
|
node_t *no = nodes;
|
|
size_t i;
|
|
|
|
for (i = 0; i < numnodes; i++, no++, mn++)
|
|
{
|
|
no->x = SHORT(mn->x)<<FRACBITS;
|
|
no->y = SHORT(mn->y)<<FRACBITS;
|
|
no->dx = SHORT(mn->dx)<<FRACBITS;
|
|
no->dy = SHORT(mn->dy)<<FRACBITS;
|
|
for (j = 0; j < 2; j++)
|
|
{
|
|
no->children[j] = SHORT(mn->children[j]);
|
|
for (k = 0; k < 4; k++)
|
|
no->bbox[j][k] = SHORT(mn->bbox[j][k])<<FRACBITS;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Computes the length of a seg in fracunits.
|
|
*
|
|
* \param seg Seg to compute length for.
|
|
* \return Length in fracunits.
|
|
*/
|
|
static fixed_t P_SegLength(seg_t *seg)
|
|
{
|
|
INT64 dx = (seg->v2->x - seg->v1->x)>>1;
|
|
INT64 dy = (seg->v2->y - seg->v1->y)>>1;
|
|
return FixedHypot(dx, dy)<<1;
|
|
}
|
|
|
|
#ifdef HWRENDER
|
|
/** Computes the length of a seg as a float.
|
|
* This is needed for OpenGL.
|
|
*
|
|
* \param seg Seg to compute length for.
|
|
* \return Length as a float.
|
|
*/
|
|
static inline float P_SegLengthFloat(seg_t *seg)
|
|
{
|
|
float dx, dy;
|
|
|
|
// make a vector (start at origin)
|
|
dx = FIXED_TO_FLOAT(seg->v2->x - seg->v1->x);
|
|
dy = FIXED_TO_FLOAT(seg->v2->y - seg->v1->y);
|
|
|
|
return (float)hypot(dx, dy);
|
|
}
|
|
#endif
|
|
|
|
static void P_InitializeSeg(seg_t *seg)
|
|
{
|
|
if (seg->linedef)
|
|
{
|
|
UINT16 side = seg->linedef->sidenum[seg->side];
|
|
|
|
if (side == 0xffff)
|
|
I_Error("P_InitializeSeg: Seg %s refers to side %d of linedef %s, which doesn't exist!\n", sizeu1((size_t)(seg - segs)), seg->side, sizeu1((size_t)(seg->linedef - lines)));
|
|
|
|
seg->sidedef = &sides[side];
|
|
|
|
seg->frontsector = seg->sidedef->sector;
|
|
seg->backsector = (seg->linedef->flags & ML_TWOSIDED) ? sides[seg->linedef->sidenum[seg->side ^ 1]].sector : NULL;
|
|
}
|
|
|
|
#ifdef HWRENDER
|
|
seg->pv1 = seg->pv2 = NULL;
|
|
|
|
//Hurdler: 04/12/2000: for now, only used in hardware mode
|
|
seg->lightmaps = NULL; // list of static lightmap for this seg
|
|
#endif
|
|
|
|
seg->numlights = 0;
|
|
seg->rlights = NULL;
|
|
seg->polyseg = NULL;
|
|
seg->dontrenderme = false;
|
|
}
|
|
|
|
static void P_LoadSegs(UINT8 *data)
|
|
{
|
|
mapseg_t *ms = (mapseg_t*)data;
|
|
seg_t *seg = segs;
|
|
size_t i;
|
|
|
|
for (i = 0; i < numsegs; i++, seg++, ms++)
|
|
{
|
|
seg->v1 = &vertexes[SHORT(ms->v1)];
|
|
seg->v2 = &vertexes[SHORT(ms->v2)];
|
|
|
|
seg->side = SHORT(ms->side);
|
|
|
|
seg->offset = (SHORT(ms->offset)) << FRACBITS;
|
|
|
|
seg->angle = (SHORT(ms->angle)) << FRACBITS;
|
|
|
|
seg->linedef = &lines[SHORT(ms->linedef)];
|
|
|
|
seg->length = P_SegLength(seg);
|
|
#ifdef HWRENDER
|
|
seg->flength = (rendermode == render_opengl) ? P_SegLengthFloat(seg) : 0;
|
|
#endif
|
|
|
|
seg->glseg = false;
|
|
P_InitializeSeg(seg);
|
|
}
|
|
}
|
|
|
|
typedef enum {
|
|
NT_DOOM,
|
|
NT_XNOD,
|
|
NT_ZNOD,
|
|
NT_XGLN,
|
|
NT_ZGLN,
|
|
NT_XGL2,
|
|
NT_ZGL2,
|
|
NT_XGL3,
|
|
NT_ZGL3,
|
|
NT_UNSUPPORTED,
|
|
NUMNODETYPES
|
|
} nodetype_t;
|
|
|
|
// Find out the BSP format.
|
|
static nodetype_t P_GetNodetype(const virtres_t *virt, UINT8 **nodedata)
|
|
{
|
|
boolean supported[NUMNODETYPES] = {0};
|
|
nodetype_t nodetype = NT_UNSUPPORTED;
|
|
char signature[4 + 1];
|
|
|
|
if (udmf)
|
|
{
|
|
*nodedata = vres_Find(virt, "ZNODES")->data;
|
|
supported[NT_XGLN] = supported[NT_XGL3] = true;
|
|
}
|
|
else
|
|
{
|
|
virtlump_t *virtsegs = vres_Find(virt, "SEGS");
|
|
virtlump_t *virtssectors;
|
|
|
|
if (virtsegs && virtsegs->size)
|
|
{
|
|
*nodedata = vres_Find(virt, "NODES")->data;
|
|
return NT_DOOM; // Traditional map format BSP tree.
|
|
}
|
|
|
|
virtssectors = vres_Find(virt, "SSECTORS");
|
|
|
|
if (virtssectors && virtssectors->size)
|
|
{ // Possibly GL nodes: NODES ignored, SSECTORS takes precedence as nodes lump, (It is confusing yeah) and has a signature.
|
|
*nodedata = virtssectors->data;
|
|
supported[NT_XGLN] = supported[NT_ZGLN] = supported[NT_XGL3] = true;
|
|
}
|
|
else
|
|
{ // Possibly ZDoom extended nodes: SSECTORS is empty, NODES has a signature.
|
|
*nodedata = vres_Find(virt, "NODES")->data;
|
|
supported[NT_XNOD] = supported[NT_ZNOD] = true;
|
|
}
|
|
}
|
|
|
|
M_Memcpy(signature, *nodedata, 4);
|
|
signature[4] = '\0';
|
|
(*nodedata) += 4;
|
|
|
|
if (!strcmp(signature, "XNOD"))
|
|
nodetype = NT_XNOD;
|
|
else if (!strcmp(signature, "ZNOD"))
|
|
nodetype = NT_ZNOD;
|
|
else if (!strcmp(signature, "XGLN"))
|
|
nodetype = NT_XGLN;
|
|
else if (!strcmp(signature, "ZGLN"))
|
|
nodetype = NT_ZGLN;
|
|
else if (!strcmp(signature, "XGL3"))
|
|
nodetype = NT_XGL3;
|
|
|
|
return supported[nodetype] ? nodetype : NT_UNSUPPORTED;
|
|
}
|
|
|
|
// Extended node formats feature additional vertices; useful for OpenGL, but totally useless in gamelogic.
|
|
static boolean P_LoadExtraVertices(UINT8 **data)
|
|
{
|
|
UINT32 origvrtx = READUINT32((*data));
|
|
UINT32 xtrvrtx = READUINT32((*data));
|
|
line_t* ld = lines;
|
|
vertex_t *oldpos = vertexes;
|
|
ssize_t offset;
|
|
size_t i;
|
|
|
|
if (numvertexes != origvrtx) // If native vertex count doesn't match node original vertex count, bail out (broken data?).
|
|
{
|
|
CONS_Alert(CONS_WARNING, "Vertex count in map data and nodes differ!\n");
|
|
return false;
|
|
}
|
|
|
|
if (!xtrvrtx)
|
|
return true;
|
|
|
|
// If extra vertexes were generated, reallocate the vertex array and fix the pointers.
|
|
numvertexes += xtrvrtx;
|
|
vertexes = Z_Realloc(vertexes, numvertexes*sizeof(*vertexes), PU_LEVEL, NULL);
|
|
offset = (size_t)(vertexes - oldpos);
|
|
|
|
for (i = 0, ld = lines; i < numlines; i++, ld++)
|
|
{
|
|
ld->v1 += offset;
|
|
ld->v2 += offset;
|
|
}
|
|
|
|
// Read extra vertex data.
|
|
for (i = origvrtx; i < numvertexes; i++)
|
|
{
|
|
vertexes[i].x = READFIXED((*data));
|
|
vertexes[i].y = READFIXED((*data));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype)
|
|
{
|
|
size_t i, k;
|
|
INT16 m;
|
|
seg_t *seg;
|
|
|
|
// Subsectors
|
|
numsubsectors = READUINT32((*data));
|
|
subsectors = Z_Calloc(numsubsectors*sizeof(*subsectors), PU_LEVEL, NULL);
|
|
|
|
for (i = 0; i < numsubsectors; i++)
|
|
subsectors[i].numlines = READUINT32((*data));
|
|
|
|
// Segs
|
|
numsegs = READUINT32((*data));
|
|
segs = Z_Calloc(numsegs*sizeof(*segs), PU_LEVEL, NULL);
|
|
|
|
for (i = 0, k = 0; i < numsubsectors; i++)
|
|
{
|
|
subsectors[i].firstline = k;
|
|
P_InitializeSubsector(&subsectors[i]);
|
|
|
|
switch (nodetype)
|
|
{
|
|
case NT_XGLN:
|
|
case NT_XGL3:
|
|
for (m = 0; m < subsectors[i].numlines; m++, k++)
|
|
{
|
|
UINT32 vertexnum = READUINT32((*data));
|
|
UINT16 linenum;
|
|
|
|
if (vertexnum >= numvertexes)
|
|
I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid vertex %d!\n", sizeu1(k), m, vertexnum);
|
|
|
|
segs[k - 1 + ((m == 0) ? subsectors[i].numlines : 0)].v2 = segs[k].v1 = &vertexes[vertexnum];
|
|
|
|
READUINT32((*data)); // partner, can be ignored by software renderer
|
|
|
|
linenum = (nodetype == NT_XGL3) ? READUINT32((*data)) : READUINT16((*data));
|
|
if (linenum != 0xFFFF && linenum >= numlines)
|
|
I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid linedef %d!\n", sizeu1(k), m, linenum);
|
|
segs[k].glseg = (linenum == 0xFFFF);
|
|
segs[k].linedef = (linenum == 0xFFFF) ? NULL : &lines[linenum];
|
|
segs[k].side = READUINT8((*data));
|
|
}
|
|
break;
|
|
|
|
case NT_XNOD:
|
|
for (m = 0; m < subsectors[i].numlines; m++, k++)
|
|
{
|
|
UINT32 v1num = READUINT32((*data));
|
|
UINT32 v2num = READUINT32((*data));
|
|
UINT16 linenum = READUINT16((*data));
|
|
|
|
if (v1num >= numvertexes)
|
|
I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid v1 %d!\n", sizeu1(k), m, v1num);
|
|
if (v2num >= numvertexes)
|
|
I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid v2 %d!\n", sizeu1(k), m, v2num);
|
|
if (linenum >= numlines)
|
|
I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid linedef %d!\n", sizeu1(k), m, linenum);
|
|
|
|
segs[k].v1 = &vertexes[v1num];
|
|
segs[k].v2 = &vertexes[v2num];
|
|
segs[k].linedef = &lines[linenum];
|
|
segs[k].side = READUINT8((*data));
|
|
segs[k].glseg = false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (i = 0, seg = segs; i < numsegs; i++, seg++)
|
|
{
|
|
vertex_t *v1 = seg->v1;
|
|
vertex_t *v2 = seg->v2;
|
|
P_InitializeSeg(seg);
|
|
seg->angle = R_PointToAngle2(v1->x, v1->y, v2->x, v2->y);
|
|
if (seg->linedef)
|
|
segs[i].offset = FixedHypot(v1->x - seg->linedef->v1->x, v1->y - seg->linedef->v1->y);
|
|
seg->length = P_SegLength(seg);
|
|
#ifdef HWRENDER
|
|
seg->flength = (rendermode == render_opengl) ? P_SegLengthFloat(seg) : 0;
|
|
#endif
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Auxiliary function: Shrink node ID from 32-bit to 16-bit.
|
|
static UINT16 ShrinkNodeID(UINT32 x) {
|
|
UINT16 mask = (x >> 16) & 0xC000;
|
|
UINT16 result = x;
|
|
return result | mask;
|
|
}
|
|
|
|
static void P_LoadExtendedNodes(UINT8 **data, nodetype_t nodetype)
|
|
{
|
|
node_t *mn;
|
|
size_t i, j, k;
|
|
boolean xgl3 = (nodetype == NT_XGL3);
|
|
|
|
numnodes = READINT32((*data));
|
|
nodes = Z_Calloc(numnodes*sizeof(*nodes), PU_LEVEL, NULL);
|
|
|
|
for (i = 0, mn = nodes; i < numnodes; i++, mn++)
|
|
{
|
|
// Splitter
|
|
mn->x = xgl3 ? READINT32((*data)) : (READINT16((*data)) << FRACBITS);
|
|
mn->y = xgl3 ? READINT32((*data)) : (READINT16((*data)) << FRACBITS);
|
|
mn->dx = xgl3 ? READINT32((*data)) : (READINT16((*data)) << FRACBITS);
|
|
mn->dy = xgl3 ? READINT32((*data)) : (READINT16((*data)) << FRACBITS);
|
|
|
|
// Bounding boxes
|
|
for (j = 0; j < 2; j++)
|
|
for (k = 0; k < 4; k++)
|
|
mn->bbox[j][k] = READINT16((*data)) << FRACBITS;
|
|
|
|
//Children
|
|
mn->children[0] = ShrinkNodeID(READUINT32((*data))); /// \todo Use UINT32 for node children in a future, instead?
|
|
mn->children[1] = ShrinkNodeID(READUINT32((*data)));
|
|
}
|
|
}
|
|
|
|
static void P_LoadMapBSP(const virtres_t *virt)
|
|
{
|
|
UINT8 *nodedata = NULL;
|
|
nodetype_t nodetype = P_GetNodetype(virt, &nodedata);
|
|
|
|
switch (nodetype)
|
|
{
|
|
case NT_DOOM:
|
|
{
|
|
virtlump_t *virtssectors = vres_Find(virt, "SSECTORS");
|
|
virtlump_t* virtnodes = vres_Find(virt, "NODES");
|
|
virtlump_t *virtsegs = vres_Find(virt, "SEGS");
|
|
|
|
numsubsectors = virtssectors->size / sizeof(mapsubsector_t);
|
|
numnodes = virtnodes->size / sizeof(mapnode_t);
|
|
numsegs = virtsegs->size / sizeof(mapseg_t);
|
|
|
|
if (numsubsectors <= 0)
|
|
I_Error("Level has no subsectors (did you forget to run it through a nodesbuilder?)");
|
|
if (numnodes <= 0)
|
|
I_Error("Level has no nodes");
|
|
if (numsegs <= 0)
|
|
I_Error("Level has no segs");
|
|
|
|
subsectors = Z_Calloc(numsubsectors * sizeof(*subsectors), PU_LEVEL, NULL);
|
|
nodes = Z_Calloc(numnodes * sizeof(*nodes), PU_LEVEL, NULL);
|
|
segs = Z_Calloc(numsegs * sizeof(*segs), PU_LEVEL, NULL);
|
|
|
|
P_LoadSubsectors(virtssectors->data);
|
|
P_LoadNodes(virtnodes->data);
|
|
P_LoadSegs(virtsegs->data);
|
|
break;
|
|
}
|
|
case NT_XNOD:
|
|
case NT_XGLN:
|
|
case NT_XGL3:
|
|
if (!P_LoadExtraVertices(&nodedata))
|
|
return;
|
|
if (!P_LoadExtendedSubsectorsAndSegs(&nodedata, nodetype))
|
|
return;
|
|
P_LoadExtendedNodes(&nodedata, nodetype);
|
|
break;
|
|
default:
|
|
CONS_Alert(CONS_WARNING, "Unsupported BSP format detected.\n");
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Split from P_LoadBlockMap for convenience
|
|
// -- Monster Iestyn 08/01/18
|
|
static void P_ReadBlockMapLump(INT16 *wadblockmaplump, size_t count)
|
|
{
|
|
size_t i;
|
|
blockmaplump = Z_Calloc(sizeof (*blockmaplump) * count, PU_LEVEL, NULL);
|
|
|
|
// killough 3/1/98: Expand wad blockmap into larger internal one,
|
|
// by treating all offsets except -1 as unsigned and zero-extending
|
|
// them. This potentially doubles the size of blockmaps allowed,
|
|
// because Doom originally considered the offsets as always signed.
|
|
|
|
blockmaplump[0] = SHORT(wadblockmaplump[0]);
|
|
blockmaplump[1] = SHORT(wadblockmaplump[1]);
|
|
blockmaplump[2] = (INT32)(SHORT(wadblockmaplump[2])) & 0xffff;
|
|
blockmaplump[3] = (INT32)(SHORT(wadblockmaplump[3])) & 0xffff;
|
|
|
|
for (i = 4; i < count; i++)
|
|
{
|
|
INT16 t = SHORT(wadblockmaplump[i]); // killough 3/1/98
|
|
blockmaplump[i] = t == -1 ? (INT32)-1 : (INT32) t & 0xffff;
|
|
}
|
|
}
|
|
|
|
// This needs to be a separate function
|
|
// because making both the WAD and PK3 loading code use
|
|
// the same functions is trickier than it looks for blockmap
|
|
// -- Monster Iestyn 09/01/18
|
|
static boolean P_LoadBlockMap(UINT8 *data, size_t count)
|
|
{
|
|
if (!count || count >= 0x20000)
|
|
return false;
|
|
|
|
//CONS_Printf("Reading blockmap lump for pk3...\n");
|
|
|
|
// no need to malloc anything, assume the data is uncompressed for now
|
|
count /= 2;
|
|
P_ReadBlockMapLump((INT16 *)data, count);
|
|
|
|
bmaporgx = blockmaplump[0]<<FRACBITS;
|
|
bmaporgy = blockmaplump[1]<<FRACBITS;
|
|
bmapwidth = blockmaplump[2];
|
|
bmapheight = blockmaplump[3];
|
|
|
|
// clear out mobj chains
|
|
count = sizeof (*blocklinks)* bmapwidth*bmapheight;
|
|
blocklinks = Z_Calloc(count, PU_LEVEL, NULL);
|
|
blockmap = blockmaplump+4;
|
|
|
|
// haleyjd 2/22/06: setup polyobject blockmap
|
|
count = sizeof(*polyblocklinks) * bmapwidth * bmapheight;
|
|
polyblocklinks = Z_Calloc(count, PU_LEVEL, NULL);
|
|
return true;
|
|
}
|
|
|
|
static boolean LineInBlock(fixed_t cx1, fixed_t cy1, fixed_t cx2, fixed_t cy2, fixed_t bx1, fixed_t by1)
|
|
{
|
|
fixed_t bbox[4];
|
|
line_t testline;
|
|
vertex_t vtest;
|
|
|
|
bbox[BOXRIGHT] = bx1 + MAPBLOCKUNITS;
|
|
bbox[BOXTOP] = by1 + MAPBLOCKUNITS;
|
|
bbox[BOXLEFT] = bx1;
|
|
bbox[BOXBOTTOM] = by1;
|
|
|
|
// Trivial rejection
|
|
if (cx1 < bbox[BOXLEFT] && cx2 < bbox[BOXLEFT])
|
|
return false;
|
|
|
|
if (cx1 > bbox[BOXRIGHT] && cx2 > bbox[BOXRIGHT])
|
|
return false;
|
|
|
|
if (cy1 < bbox[BOXBOTTOM] && cy2 < bbox[BOXBOTTOM])
|
|
return false;
|
|
|
|
if (cy1 > bbox[BOXTOP] && cy2 > bbox[BOXTOP])
|
|
return false;
|
|
|
|
// Rats, guess we gotta check
|
|
// if the line intersects
|
|
// any sides of the block.
|
|
cx1 <<= FRACBITS;
|
|
cy1 <<= FRACBITS;
|
|
cx2 <<= FRACBITS;
|
|
cy2 <<= FRACBITS;
|
|
bbox[BOXTOP] <<= FRACBITS;
|
|
bbox[BOXBOTTOM] <<= FRACBITS;
|
|
bbox[BOXLEFT] <<= FRACBITS;
|
|
bbox[BOXRIGHT] <<= FRACBITS;
|
|
|
|
testline.v1 = &vtest;
|
|
|
|
testline.v1->x = cx1;
|
|
testline.v1->y = cy1;
|
|
testline.dx = cx2 - cx1;
|
|
testline.dy = cy2 - cy1;
|
|
|
|
if ((testline.dx > 0) ^ (testline.dy > 0))
|
|
testline.slopetype = ST_NEGATIVE;
|
|
else
|
|
testline.slopetype = ST_POSITIVE;
|
|
|
|
return P_BoxOnLineSide(bbox, &testline) == -1;
|
|
}
|
|
|
|
//
|
|
// killough 10/98:
|
|
//
|
|
// Rewritten to use faster algorithm.
|
|
//
|
|
// SSN Edit: Killough's wasn't accurate enough, sometimes excluding
|
|
// blocks that the line did in fact exist in, so now we use
|
|
// a fail-safe approach that puts a 'box' around each line.
|
|
//
|
|
// Please note: This section of code is not interchangable with TeamTNT's
|
|
// code which attempts to fix the same problem.
|
|
static void P_CreateBlockMap(void)
|
|
{
|
|
register size_t i;
|
|
fixed_t minx = INT32_MAX, miny = INT32_MAX, maxx = INT32_MIN, maxy = INT32_MIN;
|
|
// First find limits of map
|
|
|
|
for (i = 0; i < numvertexes; i++)
|
|
{
|
|
if (vertexes[i].x>>FRACBITS < minx)
|
|
minx = vertexes[i].x>>FRACBITS;
|
|
else if (vertexes[i].x>>FRACBITS > maxx)
|
|
maxx = vertexes[i].x>>FRACBITS;
|
|
if (vertexes[i].y>>FRACBITS < miny)
|
|
miny = vertexes[i].y>>FRACBITS;
|
|
else if (vertexes[i].y>>FRACBITS > maxy)
|
|
maxy = vertexes[i].y>>FRACBITS;
|
|
}
|
|
|
|
// Save blockmap parameters
|
|
bmaporgx = minx << FRACBITS;
|
|
bmaporgy = miny << FRACBITS;
|
|
bmapwidth = ((maxx-minx) >> MAPBTOFRAC) + 1;
|
|
bmapheight = ((maxy-miny) >> MAPBTOFRAC)+ 1;
|
|
|
|
// Compute blockmap, which is stored as a 2d array of variable-sized lists.
|
|
//
|
|
// Pseudocode:
|
|
//
|
|
// For each linedef:
|
|
//
|
|
// Map the starting and ending vertices to blocks.
|
|
//
|
|
// Starting in the starting vertex's block, do:
|
|
//
|
|
// Add linedef to current block's list, dynamically resizing it.
|
|
//
|
|
// If current block is the same as the ending vertex's block, exit loop.
|
|
//
|
|
// Move to an adjacent block by moving towards the ending block in
|
|
// either the x or y direction, to the block which contains the linedef.
|
|
|
|
{
|
|
typedef struct
|
|
{
|
|
INT32 n, nalloc;
|
|
INT32 *list;
|
|
} bmap_t; // blocklist structure
|
|
|
|
size_t tot = bmapwidth * bmapheight; // size of blockmap
|
|
bmap_t *bmap = calloc(tot, sizeof (*bmap)); // array of blocklists
|
|
boolean straight;
|
|
|
|
if (bmap == NULL) I_Error("%s: Out of memory making blockmap", "P_CreateBlockMap");
|
|
|
|
for (i = 0; i < numlines; i++)
|
|
{
|
|
// starting coordinates
|
|
INT32 x = (lines[i].v1->x>>FRACBITS) - minx;
|
|
INT32 y = (lines[i].v1->y>>FRACBITS) - miny;
|
|
INT32 bxstart, bxend, bystart, byend, v2x, v2y, curblockx, curblocky;
|
|
|
|
v2x = lines[i].v2->x>>FRACBITS;
|
|
v2y = lines[i].v2->y>>FRACBITS;
|
|
|
|
// Draw a "box" around the line.
|
|
bxstart = (x >> MAPBTOFRAC);
|
|
bystart = (y >> MAPBTOFRAC);
|
|
|
|
v2x -= minx;
|
|
v2y -= miny;
|
|
|
|
bxend = ((v2x) >> MAPBTOFRAC);
|
|
byend = ((v2y) >> MAPBTOFRAC);
|
|
|
|
if (bxend < bxstart)
|
|
{
|
|
INT32 temp = bxstart;
|
|
bxstart = bxend;
|
|
bxend = temp;
|
|
}
|
|
|
|
if (byend < bystart)
|
|
{
|
|
INT32 temp = bystart;
|
|
bystart = byend;
|
|
byend = temp;
|
|
}
|
|
|
|
// Catch straight lines
|
|
// This fixes the error where straight lines
|
|
// directly on a blockmap boundary would not
|
|
// be included in the proper blocks.
|
|
if (lines[i].v1->y == lines[i].v2->y)
|
|
{
|
|
straight = true;
|
|
bystart--;
|
|
byend++;
|
|
}
|
|
else if (lines[i].v1->x == lines[i].v2->x)
|
|
{
|
|
straight = true;
|
|
bxstart--;
|
|
bxend++;
|
|
}
|
|
else
|
|
straight = false;
|
|
|
|
// Now we simply iterate block-by-block until we reach the end block.
|
|
for (curblockx = bxstart; curblockx <= bxend; curblockx++)
|
|
for (curblocky = bystart; curblocky <= byend; curblocky++)
|
|
{
|
|
size_t b = curblocky * bmapwidth + curblockx;
|
|
|
|
if (b >= tot)
|
|
continue;
|
|
|
|
if (!straight && !(LineInBlock((fixed_t)x, (fixed_t)y, (fixed_t)v2x, (fixed_t)v2y, (fixed_t)(curblockx << MAPBTOFRAC), (fixed_t)(curblocky << MAPBTOFRAC))))
|
|
continue;
|
|
|
|
// Increase size of allocated list if necessary
|
|
if (bmap[b].n >= bmap[b].nalloc)
|
|
{
|
|
// Graue 02-29-2004: make code more readable, don't realloc a null pointer
|
|
// (because it crashes for me, and because the comp.lang.c FAQ says so)
|
|
if (bmap[b].nalloc == 0)
|
|
bmap[b].nalloc = 8;
|
|
else
|
|
bmap[b].nalloc *= 2;
|
|
bmap[b].list = Z_Realloc(bmap[b].list, bmap[b].nalloc * sizeof (*bmap->list), PU_CACHE, &bmap[b].list);
|
|
if (!bmap[b].list)
|
|
I_Error("Out of Memory in P_CreateBlockMap");
|
|
}
|
|
|
|
// Add linedef to end of list
|
|
bmap[b].list[bmap[b].n++] = (INT32)i;
|
|
}
|
|
}
|
|
|
|
// Compute the total size of the blockmap.
|
|
//
|
|
// Compression of empty blocks is performed by reserving two offset words
|
|
// at tot and tot+1.
|
|
//
|
|
// 4 words, unused if this routine is called, are reserved at the start.
|
|
{
|
|
size_t count = tot + 6; // we need at least 1 word per block, plus reserved's
|
|
|
|
for (i = 0; i < tot; i++)
|
|
if (bmap[i].n)
|
|
count += bmap[i].n + 2; // 1 header word + 1 trailer word + blocklist
|
|
|
|
// Allocate blockmap lump with computed count
|
|
blockmaplump = Z_Calloc(sizeof (*blockmaplump) * count, PU_LEVEL, NULL);
|
|
}
|
|
|
|
// Now compress the blockmap.
|
|
{
|
|
size_t ndx = tot += 4; // Advance index to start of linedef lists
|
|
bmap_t *bp = bmap; // Start of uncompressed blockmap
|
|
|
|
blockmaplump[ndx++] = 0; // Store an empty blockmap list at start
|
|
blockmaplump[ndx++] = -1; // (Used for compression)
|
|
|
|
for (i = 4; i < tot; i++, bp++)
|
|
if (bp->n) // Non-empty blocklist
|
|
{
|
|
blockmaplump[blockmaplump[i] = (INT32)(ndx++)] = 0; // Store index & header
|
|
do
|
|
blockmaplump[ndx++] = bp->list[--bp->n]; // Copy linedef list
|
|
while (bp->n);
|
|
blockmaplump[ndx++] = -1; // Store trailer
|
|
Z_Free(bp->list); // Free linedef list
|
|
}
|
|
else // Empty blocklist: point to reserved empty blocklist
|
|
blockmaplump[i] = (INT32)tot;
|
|
|
|
free(bmap); // Free uncompressed blockmap
|
|
}
|
|
}
|
|
{
|
|
size_t count = sizeof (*blocklinks) * bmapwidth * bmapheight;
|
|
// clear out mobj chains (copied from from P_LoadBlockMap)
|
|
blocklinks = Z_Calloc(count, PU_LEVEL, NULL);
|
|
blockmap = blockmaplump + 4;
|
|
|
|
// haleyjd 2/22/06: setup polyobject blockmap
|
|
count = sizeof(*polyblocklinks) * bmapwidth * bmapheight;
|
|
polyblocklinks = Z_Calloc(count, PU_LEVEL, NULL);
|
|
}
|
|
}
|
|
|
|
// PK3 version
|
|
// -- Monster Iestyn 09/01/18
|
|
static void P_LoadReject(UINT8 *data, size_t count)
|
|
{
|
|
if (!count) // zero length, someone probably used ZDBSP
|
|
{
|
|
rejectmatrix = NULL;
|
|
CONS_Debug(DBG_SETUP, "P_LoadReject: REJECT lump has size 0, will not be loaded\n");
|
|
}
|
|
else
|
|
{
|
|
rejectmatrix = Z_Malloc(count, PU_LEVEL, NULL); // allocate memory for the reject matrix
|
|
M_Memcpy(rejectmatrix, data, count); // copy the data into it
|
|
}
|
|
}
|
|
|
|
static void P_LoadMapLUT(const virtres_t *virt)
|
|
{
|
|
virtlump_t* virtblockmap = vres_Find(virt, "BLOCKMAP");
|
|
virtlump_t* virtreject = vres_Find(virt, "REJECT");
|
|
|
|
// Lookup tables
|
|
if (virtreject)
|
|
P_LoadReject(virtreject->data, virtreject->size);
|
|
else
|
|
rejectmatrix = NULL;
|
|
|
|
if (!(virtblockmap && P_LoadBlockMap(virtblockmap->data, virtblockmap->size)))
|
|
P_CreateBlockMap();
|
|
}
|
|
|
|
//
|
|
// P_LinkMapData
|
|
// Builds sector line lists and subsector sector numbers.
|
|
// Finds block bounding boxes for sectors.
|
|
//
|
|
static void P_LinkMapData(void)
|
|
{
|
|
size_t i, j;
|
|
line_t *li;
|
|
sector_t *sector;
|
|
subsector_t *ss = subsectors;
|
|
size_t sidei;
|
|
seg_t *seg;
|
|
fixed_t bbox[4];
|
|
|
|
// look up sector number for each subsector
|
|
for (i = 0; i < numsubsectors; i++, ss++)
|
|
{
|
|
if (ss->firstline >= numsegs)
|
|
CorruptMapError(va("P_LinkMapData: ss->firstline invalid "
|
|
"(subsector %s, firstline refers to %d of %s)", sizeu1(i), ss->firstline,
|
|
sizeu2(numsegs)));
|
|
seg = &segs[ss->firstline];
|
|
sidei = (size_t)(seg->sidedef - sides);
|
|
if (!seg->sidedef)
|
|
CorruptMapError(va("P_LinkMapData: seg->sidedef is NULL "
|
|
"(subsector %s, firstline is %d)", sizeu1(i), ss->firstline));
|
|
if (seg->sidedef - sides < 0 || seg->sidedef - sides > (UINT16)numsides)
|
|
CorruptMapError(va("P_LinkMapData: seg->sidedef refers to sidedef %s of %s "
|
|
"(subsector %s, firstline is %d)", sizeu1(sidei), sizeu2(numsides),
|
|
sizeu3(i), ss->firstline));
|
|
if (!seg->sidedef->sector)
|
|
CorruptMapError(va("P_LinkMapData: seg->sidedef->sector is NULL "
|
|
"(subsector %s, firstline is %d, sidedef is %s)", sizeu1(i), ss->firstline,
|
|
sizeu1(sidei)));
|
|
ss->sector = seg->sidedef->sector;
|
|
}
|
|
|
|
// count number of lines in each sector
|
|
for (i = 0, li = lines; i < numlines; i++, li++)
|
|
{
|
|
li->frontsector->linecount++;
|
|
|
|
if (li->backsector && li->backsector != li->frontsector)
|
|
li->backsector->linecount++;
|
|
}
|
|
|
|
// allocate linebuffers for each sector
|
|
for (i = 0, sector = sectors; i < numsectors; i++, sector++)
|
|
{
|
|
if (sector->linecount == 0) // no lines found?
|
|
{
|
|
sector->lines = NULL;
|
|
CONS_Debug(DBG_SETUP, "P_LinkMapData: sector %s has no lines\n", sizeu1(i));
|
|
}
|
|
else
|
|
{
|
|
sector->lines = Z_Calloc(sector->linecount * sizeof(line_t*), PU_LEVEL, NULL);
|
|
|
|
// zero the count, since we'll later use this to track how many we've recorded
|
|
sector->linecount = 0;
|
|
}
|
|
}
|
|
|
|
// iterate through lines, assigning them to sectors' linebuffers,
|
|
// and recalculate the counts in the process
|
|
for (i = 0, li = lines; i < numlines; i++, li++)
|
|
{
|
|
li->frontsector->lines[li->frontsector->linecount++] = li;
|
|
|
|
if (li->backsector && li->backsector != li->frontsector)
|
|
li->backsector->lines[li->backsector->linecount++] = li;
|
|
}
|
|
|
|
// set soundorg's position for each sector
|
|
for (i = 0, sector = sectors; i < numsectors; i++, sector++)
|
|
{
|
|
M_ClearBox(bbox);
|
|
|
|
if (sector->linecount != 0)
|
|
{
|
|
for (j = 0; j < sector->linecount; j++)
|
|
{
|
|
li = sector->lines[j];
|
|
M_AddToBox(bbox, li->v1->x, li->v1->y);
|
|
M_AddToBox(bbox, li->v2->x, li->v2->y);
|
|
}
|
|
}
|
|
|
|
// set the degenmobj_t to the middle of the bounding box
|
|
sector->soundorg.x = (((bbox[BOXRIGHT]>>FRACBITS) + (bbox[BOXLEFT]>>FRACBITS))/2)<<FRACBITS;
|
|
sector->soundorg.y = (((bbox[BOXTOP]>>FRACBITS) + (bbox[BOXBOTTOM]>>FRACBITS))/2)<<FRACBITS;
|
|
sector->soundorg.z = sector->floorheight; // default to sector's floor height
|
|
}
|
|
}
|
|
|
|
//For maps in binary format, converts setup of specials to UDMF format.
|
|
static void P_ConvertBinaryMap(void)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < numlines; i++)
|
|
{
|
|
mtag_t tag = Tag_FGet(&lines[i].tags);
|
|
|
|
switch (lines[i].special)
|
|
{
|
|
case 20: //PolyObject first line
|
|
{
|
|
INT32 check = -1;
|
|
INT32 paramline = -1;
|
|
|
|
TAG_ITER_DECLARECOUNTER(0);
|
|
|
|
TAG_ITER_LINES(0, tag, check)
|
|
{
|
|
if (lines[check].special == 22)
|
|
{
|
|
paramline = check;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//PolyObject ID
|
|
lines[i].args[0] = tag;
|
|
|
|
//Default: Invisible planes
|
|
lines[i].args[3] |= TMPF_INVISIBLEPLANES;
|
|
|
|
//Linedef executor tag
|
|
lines[i].args[4] = 32000 + lines[i].args[0];
|
|
|
|
if (paramline == -1)
|
|
break; // no extra settings to apply, let's leave it
|
|
|
|
//Parent ID
|
|
lines[i].args[1] = lines[paramline].frontsector->special;
|
|
//Translucency
|
|
lines[i].args[2] = (lines[paramline].flags & ML_DONTPEGTOP)
|
|
? (sides[lines[paramline].sidenum[0]].textureoffset >> FRACBITS)
|
|
: ((lines[paramline].frontsector->floorheight >> FRACBITS) / 100);
|
|
|
|
//Flags
|
|
if (lines[paramline].flags & ML_EFFECT1)
|
|
lines[i].args[3] |= TMPF_NOINSIDES;
|
|
if (lines[paramline].flags & ML_EFFECT2)
|
|
lines[i].args[3] |= TMPF_INTANGIBLE;
|
|
if (lines[paramline].flags & ML_EFFECT3)
|
|
lines[i].args[3] |= TMPF_PUSHABLESTOP;
|
|
if (lines[paramline].flags & ML_EFFECT4)
|
|
lines[i].args[3] &= ~TMPF_INVISIBLEPLANES;
|
|
/*if (lines[paramline].flags & ML_EFFECT5)
|
|
lines[i].args[3] |= TMPF_DONTCLIPPLANES;*/
|
|
if (lines[paramline].flags & ML_EFFECT6)
|
|
lines[i].args[3] |= TMPF_SPLAT;
|
|
if (lines[paramline].flags & ML_NOCLIMB)
|
|
lines[i].args[3] |= TMPF_EXECUTOR;
|
|
|
|
break;
|
|
}
|
|
case 443: //Call Lua function
|
|
if (lines[i].text)
|
|
{
|
|
lines[i].stringargs[0] = Z_Malloc(strlen(lines[i].text) + 1, PU_LEVEL, NULL);
|
|
M_Memcpy(lines[i].stringargs[0], lines[i].text, strlen(lines[i].text) + 1);
|
|
}
|
|
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(i));
|
|
break;
|
|
case 447: //Change colormap
|
|
lines[i].args[0] = Tag_FGet(&lines[i].tags);
|
|
if (lines[i].flags & ML_EFFECT3)
|
|
lines[i].args[2] |= TMCF_RELATIVE;
|
|
if (lines[i].flags & ML_EFFECT1)
|
|
lines[i].args[2] |= TMCF_SUBLIGHTR|TMCF_SUBFADER;
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
lines[i].args[2] |= TMCF_SUBLIGHTG|TMCF_SUBFADEG;
|
|
if (lines[i].flags & ML_EFFECT2)
|
|
lines[i].args[2] |= TMCF_SUBLIGHTB|TMCF_SUBFADEB;
|
|
break;
|
|
case 455: //Fade colormap
|
|
{
|
|
INT32 speed = (INT32)((((lines[i].flags & ML_DONTPEGBOTTOM) || !sides[lines[i].sidenum[0]].rowoffset) && lines[i].sidenum[1] != 0xFFFF) ?
|
|
abs(sides[lines[i].sidenum[1]].rowoffset >> FRACBITS)
|
|
: abs(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS));
|
|
|
|
lines[i].args[0] = Tag_FGet(&lines[i].tags);
|
|
if (lines[i].flags & ML_EFFECT4)
|
|
lines[i].args[2] = speed;
|
|
else
|
|
lines[i].args[2] = (256 + speed - 1)/speed;
|
|
if (lines[i].flags & ML_EFFECT3)
|
|
lines[i].args[3] |= TMCF_RELATIVE;
|
|
if (lines[i].flags & ML_EFFECT1)
|
|
lines[i].args[3] |= TMCF_SUBLIGHTR|TMCF_SUBFADER;
|
|
if (lines[i].flags & ML_NOCLIMB)
|
|
lines[i].args[3] |= TMCF_SUBLIGHTG|TMCF_SUBFADEG;
|
|
if (lines[i].flags & ML_EFFECT2)
|
|
lines[i].args[3] |= TMCF_SUBLIGHTB|TMCF_SUBFADEB;
|
|
if (lines[i].flags & ML_BOUNCY)
|
|
lines[i].args[3] |= TMCF_FROMBLACK;
|
|
if (lines[i].flags & ML_EFFECT5)
|
|
lines[i].args[3] |= TMCF_OVERRIDE;
|
|
break;
|
|
}
|
|
case 456: //Stop fading colormap
|
|
lines[i].args[0] = Tag_FGet(&lines[i].tags);
|
|
break;
|
|
case 606: //Colormap
|
|
lines[i].args[0] = Tag_FGet(&lines[i].tags);
|
|
break;
|
|
case 700: //Slope front sector floor
|
|
case 701: //Slope front sector ceiling
|
|
case 702: //Slope front sector floor and ceiling
|
|
case 703: //Slope front sector floor and back sector ceiling
|
|
case 710: //Slope back sector floor
|
|
case 711: //Slope back sector ceiling
|
|
case 712: //Slope back sector floor and ceiling
|
|
case 713: //Slope back sector floor and front sector ceiling
|
|
{
|
|
boolean frontfloor = (lines[i].special == 700 || lines[i].special == 702 || lines[i].special == 703);
|
|
boolean backfloor = (lines[i].special == 710 || lines[i].special == 712 || lines[i].special == 713);
|
|
boolean frontceil = (lines[i].special == 701 || lines[i].special == 702 || lines[i].special == 713);
|
|
boolean backceil = (lines[i].special == 711 || lines[i].special == 712 || lines[i].special == 703);
|
|
|
|
lines[i].args[0] = backfloor ? TMS_BACK : (frontfloor ? TMS_FRONT : TMS_NONE);
|
|
lines[i].args[1] = backceil ? TMS_BACK : (frontceil ? TMS_FRONT : TMS_NONE);
|
|
|
|
if (lines[i].flags & ML_NETONLY)
|
|
lines[i].args[2] |= TMSL_NOPHYSICS;
|
|
if (lines[i].flags & ML_NONET)
|
|
lines[i].args[2] |= TMSL_DYNAMIC;
|
|
|
|
lines[i].special = 700;
|
|
break;
|
|
}
|
|
case 704: //Slope front sector floor by 3 tagged vertices
|
|
case 705: //Slope front sector ceiling by 3 tagged vertices
|
|
case 714: //Slope back sector floor by 3 tagged vertices
|
|
case 715: //Slope back sector ceiling by 3 tagged vertices
|
|
{
|
|
if (lines[i].special == 704)
|
|
lines[i].args[0] = TMSP_FRONTFLOOR;
|
|
else if (lines[i].special == 705)
|
|
lines[i].args[0] = TMSP_FRONTCEILING;
|
|
else if (lines[i].special == 714)
|
|
lines[i].args[0] = TMSP_BACKFLOOR;
|
|
else if (lines[i].special == 715)
|
|
lines[i].args[0] = TMSP_BACKCEILING;
|
|
|
|
lines[i].args[1] = tag;
|
|
|
|
if (lines[i].flags & ML_EFFECT6)
|
|
{
|
|
UINT8 side = lines[i].special >= 714;
|
|
|
|
if (side == 1 && lines[i].sidenum[1] == 0xffff)
|
|
CONS_Debug(DBG_GAMELOGIC, "P_ConvertBinaryMap: Line special %d (line #%s) missing 2nd side!\n", lines[i].special, sizeu1(i));
|
|
else
|
|
{
|
|
lines[i].args[2] = sides[lines[i].sidenum[side]].textureoffset >> FRACBITS;
|
|
lines[i].args[3] = sides[lines[i].sidenum[side]].rowoffset >> FRACBITS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lines[i].args[2] = lines[i].args[1];
|
|
lines[i].args[3] = lines[i].args[1];
|
|
}
|
|
|
|
if (lines[i].flags & ML_NETONLY)
|
|
lines[i].args[4] |= TMSL_NOPHYSICS;
|
|
if (lines[i].flags & ML_NONET)
|
|
lines[i].args[4] |= TMSL_DYNAMIC;
|
|
|
|
lines[i].special = 704;
|
|
break;
|
|
}
|
|
case 720: //Copy front side floor slope
|
|
case 721: //Copy front side ceiling slope
|
|
case 722: //Copy front side floor and ceiling slope
|
|
if (lines[i].special != 721)
|
|
lines[i].args[0] = tag;
|
|
if (lines[i].special != 720)
|
|
lines[i].args[1] = tag;
|
|
lines[i].special = 720;
|
|
break;
|
|
case 900: //Translucent wall (10%)
|
|
case 901: //Translucent wall (20%)
|
|
case 902: //Translucent wall (30%)
|
|
case 903: //Translucent wall (40%)
|
|
case 904: //Translucent wall (50%)
|
|
case 905: //Translucent wall (60%)
|
|
case 906: //Translucent wall (70%)
|
|
case 907: //Translucent wall (80%)
|
|
case 908: //Translucent wall (90%)
|
|
lines[i].alpha = ((909 - lines[i].special) << FRACBITS)/10;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//Linedef executor delay
|
|
if (lines[i].special >= 400 && lines[i].special < 500)
|
|
{
|
|
//Dummy value to indicate that this executor is delayed.
|
|
//The real value is taken from the back sector at runtime.
|
|
if (lines[i].flags & ML_DONTPEGTOP)
|
|
lines[i].executordelay = 1;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < nummapthings; i++)
|
|
{
|
|
switch (mapthings[i].type)
|
|
{
|
|
case 750:
|
|
Tag_FSet(&mapthings[i].tags, mapthings[i].angle);
|
|
break;
|
|
case 760:
|
|
case 761:
|
|
Tag_FSet(&mapthings[i].tags, mapthings[i].angle);
|
|
break;
|
|
case 762:
|
|
{
|
|
INT32 check = -1;
|
|
INT32 firstline = -1;
|
|
mtag_t tag = mapthings[i].angle;
|
|
|
|
TAG_ITER_DECLARECOUNTER(0);
|
|
|
|
Tag_FSet(&mapthings[i].tags, tag);
|
|
|
|
TAG_ITER_LINES(0, tag, check)
|
|
{
|
|
if (lines[check].special == 20)
|
|
{
|
|
firstline = check;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (firstline != -1)
|
|
lines[firstline].args[3] |= TMPF_CRUSH;
|
|
|
|
mapthings[i].type = 761;
|
|
break;
|
|
}
|
|
case 780:
|
|
Tag_FSet(&mapthings[i].tags, mapthings[i].extrainfo);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Compute MD5 message digest for bytes read from memory source
|
|
*
|
|
* The resulting message digest number will be written into the 16 bytes
|
|
* beginning at RESBLOCK.
|
|
*
|
|
* \param filename path of file
|
|
* \param resblock resulting MD5 checksum
|
|
* \return 0 if MD5 checksum was made, and is at resblock, 1 if error was found
|
|
*/
|
|
static INT32 P_MakeBufferMD5(const char *buffer, size_t len, void *resblock)
|
|
{
|
|
#ifdef NOMD5
|
|
(void)buffer;
|
|
(void)len;
|
|
memset(resblock, 0x00, 16);
|
|
return 1;
|
|
#else
|
|
tic_t t = I_GetTime();
|
|
CONS_Debug(DBG_SETUP, "Making MD5\n");
|
|
if (md5_buffer(buffer, len, resblock) == NULL)
|
|
return 1;
|
|
CONS_Debug(DBG_SETUP, "MD5 calc took %f seconds\n", (float)(I_GetTime() - t)/NEWTICRATE);
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static void P_MakeMapMD5(virtres_t *virt, void *dest)
|
|
{
|
|
unsigned char resmd5[16];
|
|
|
|
if (udmf)
|
|
{
|
|
virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
|
|
P_MakeBufferMD5((char*)textmap->data, textmap->size, resmd5);
|
|
}
|
|
else
|
|
{
|
|
unsigned char linemd5[16];
|
|
unsigned char sectormd5[16];
|
|
unsigned char thingmd5[16];
|
|
unsigned char sidedefmd5[16];
|
|
UINT8 i;
|
|
|
|
// Create a hash for the current map
|
|
// get the actual lumps!
|
|
virtlump_t* virtlines = vres_Find(virt, "LINEDEFS");
|
|
virtlump_t* virtsectors = vres_Find(virt, "SECTORS");
|
|
virtlump_t* virtmthings = vres_Find(virt, "THINGS");
|
|
virtlump_t* virtsides = vres_Find(virt, "SIDEDEFS");
|
|
|
|
P_MakeBufferMD5((char*)virtlines->data, virtlines->size, linemd5);
|
|
P_MakeBufferMD5((char*)virtsectors->data, virtsectors->size, sectormd5);
|
|
P_MakeBufferMD5((char*)virtmthings->data, virtmthings->size, thingmd5);
|
|
P_MakeBufferMD5((char*)virtsides->data, virtsides->size, sidedefmd5);
|
|
|
|
for (i = 0; i < 16; i++)
|
|
resmd5[i] = (linemd5[i] + sectormd5[i] + thingmd5[i] + sidedefmd5[i]) & 0xFF;
|
|
}
|
|
|
|
M_Memcpy(dest, &resmd5, 16);
|
|
}
|
|
|
|
static boolean P_LoadMapFromFile(void)
|
|
{
|
|
virtres_t *virt = vres_GetMap(lastloadedmaplumpnum);
|
|
virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
|
|
size_t i;
|
|
udmf = textmap != NULL;
|
|
|
|
if (!P_LoadMapData(virt))
|
|
return false;
|
|
P_LoadMapBSP(virt);
|
|
P_LoadMapLUT(virt);
|
|
|
|
P_LinkMapData();
|
|
|
|
Taglist_InitGlobalTables();
|
|
|
|
if (!udmf)
|
|
P_ConvertBinaryMap();
|
|
|
|
// Copy relevant map data for NetArchive purposes.
|
|
spawnsectors = Z_Calloc(numsectors * sizeof(*sectors), PU_LEVEL, NULL);
|
|
spawnlines = Z_Calloc(numlines * sizeof(*lines), PU_LEVEL, NULL);
|
|
spawnsides = Z_Calloc(numsides * sizeof(*sides), PU_LEVEL, NULL);
|
|
|
|
memcpy(spawnsectors, sectors, numsectors * sizeof(*sectors));
|
|
memcpy(spawnlines, lines, numlines * sizeof(*lines));
|
|
memcpy(spawnsides, sides, numsides * sizeof(*sides));
|
|
|
|
for (i = 0; i < numsectors; i++)
|
|
if (sectors[i].tags.count)
|
|
spawnsectors[i].tags.tags = memcpy(Z_Malloc(sectors[i].tags.count*sizeof(mtag_t), PU_LEVEL, NULL), sectors[i].tags.tags, sectors[i].tags.count*sizeof(mtag_t));
|
|
|
|
P_MakeMapMD5(virt, &mapmd5);
|
|
|
|
vres_Free(virt);
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// LEVEL INITIALIZATION FUNCTIONS
|
|
//
|
|
|
|
/** Sets up a sky texture to use for the level.
|
|
* The sky texture is used instead of F_SKY1.
|
|
*/
|
|
void P_SetupLevelSky(INT32 skynum, boolean global)
|
|
{
|
|
char skytexname[12];
|
|
|
|
sprintf(skytexname, "SKY%d", skynum);
|
|
skytexture = R_TextureNumForName(skytexname);
|
|
levelskynum = skynum;
|
|
|
|
// Global change
|
|
if (global)
|
|
globallevelskynum = levelskynum;
|
|
|
|
// Don't go beyond for dedicated servers
|
|
if (dedicated)
|
|
return;
|
|
|
|
// scale up the old skies, if needed
|
|
R_SetupSkyDraw();
|
|
}
|
|
|
|
static const char *maplumpname;
|
|
lumpnum_t lastloadedmaplumpnum; // for comparative savegame
|
|
|
|
//
|
|
// P_LevelInitStuff
|
|
//
|
|
// Some player initialization for map start.
|
|
//
|
|
static void P_InitLevelSettings(void)
|
|
{
|
|
INT32 i;
|
|
boolean canresetlives = true;
|
|
|
|
leveltime = 0;
|
|
|
|
modulothing = 0;
|
|
|
|
// special stage tokens, emeralds, and ring total
|
|
tokenbits = 0;
|
|
runemeraldmanager = false;
|
|
emeraldspawndelay = 60*TICRATE;
|
|
if ((netgame || multiplayer) && !G_IsSpecialStage(gamemap))
|
|
nummaprings = -1;
|
|
else
|
|
nummaprings = mapheaderinfo[gamemap-1]->startrings;
|
|
|
|
// emerald hunt
|
|
hunt1 = hunt2 = hunt3 = NULL;
|
|
|
|
// map time limit
|
|
if (mapheaderinfo[gamemap-1]->countdown)
|
|
{
|
|
tic_t maxtime = 0;
|
|
countdowntimer = mapheaderinfo[gamemap-1]->countdown * TICRATE;
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i])
|
|
continue;
|
|
if (players[i].starposttime > maxtime)
|
|
maxtime = players[i].starposttime;
|
|
}
|
|
countdowntimer -= maxtime;
|
|
}
|
|
else
|
|
countdowntimer = 0;
|
|
countdowntimeup = false;
|
|
|
|
// clear ctf pointers
|
|
redflag = blueflag = NULL;
|
|
rflagpoint = bflagpoint = NULL;
|
|
|
|
// circuit, race and competition stuff
|
|
circuitmap = false;
|
|
numstarposts = 0;
|
|
ssspheres = timeinmap = 0;
|
|
|
|
// Assume Special Stages were failed in unless proven otherwise - via P_GiveEmerald or emerald touchspecial
|
|
// Normal stages will default to be OK, unless a Lua script sets to false.
|
|
stagefailed = G_IsSpecialStage(gamemap);
|
|
|
|
// Reset temporary record data
|
|
memset(&ntemprecords, 0, sizeof(nightsdata_t));
|
|
|
|
// earthquake camera
|
|
memset(&quake,0,sizeof(struct quake));
|
|
|
|
if ((netgame || multiplayer) && G_GametypeUsesCoopStarposts() && cv_coopstarposts.value == 2)
|
|
{
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (playeringame[i] && players[i].lives > 0)
|
|
{
|
|
canresetlives = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
countdown = countdown2 = exitfadestarted = 0;
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
G_PlayerReborn(i, true);
|
|
|
|
if (canresetlives && (netgame || multiplayer) && playeringame[i] && (G_CompetitionGametype() || players[i].lives <= 0))
|
|
{
|
|
// In Co-Op, replenish a user's lives if they are depleted.
|
|
players[i].lives = cv_startinglives.value;
|
|
}
|
|
|
|
// obliteration station...
|
|
players[i].numboxes = players[i].totalring =\
|
|
players[i].laps = players[i].marescore = players[i].lastmarescore =\
|
|
players[i].mare = players[i].exiting = 0;
|
|
|
|
players[i].drillmeter = 40*20;
|
|
|
|
// hit these too
|
|
players[i].pflags &= ~(PF_GAMETYPEOVER);
|
|
}
|
|
|
|
if (botingame)
|
|
CV_SetValue(&cv_analog[1], true);
|
|
}
|
|
|
|
// Respawns all the mapthings and mobjs in the map from the already loaded map data.
|
|
void P_RespawnThings(void)
|
|
{
|
|
// Search through all the thinkers.
|
|
thinker_t *think;
|
|
INT32 i, viewid = -1, centerid = -1; // for skyboxes
|
|
|
|
// check if these are any of the normal viewpoint/centerpoint mobjs in the level or not
|
|
if (skyboxmo[0] || skyboxmo[1])
|
|
for (i = 0; i < 16; i++)
|
|
{
|
|
if (skyboxmo[0] && skyboxmo[0] == skyboxviewpnts[i])
|
|
viewid = i; // save id just in case
|
|
if (skyboxmo[1] && skyboxmo[1] == skyboxcenterpnts[i])
|
|
centerid = i; // save id just in case
|
|
}
|
|
|
|
for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
|
|
{
|
|
if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
|
|
continue;
|
|
P_RemoveMobj((mobj_t *)think);
|
|
}
|
|
|
|
P_InitLevelSettings();
|
|
|
|
localaiming = 0;
|
|
localaiming2 = 0;
|
|
|
|
P_SpawnMapThings(true);
|
|
|
|
// restore skybox viewpoint/centerpoint if necessary, set them to defaults if we can't do that
|
|
skyboxmo[0] = skyboxviewpnts[(viewid >= 0) ? viewid : 0];
|
|
skyboxmo[1] = skyboxcenterpnts[(centerid >= 0) ? centerid : 0];
|
|
}
|
|
|
|
static void P_RunLevelScript(const char *scriptname)
|
|
{
|
|
if (!(mapheaderinfo[gamemap-1]->levelflags & LF_SCRIPTISFILE))
|
|
{
|
|
lumpnum_t lumpnum;
|
|
char newname[9];
|
|
|
|
strncpy(newname, scriptname, 8);
|
|
|
|
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);
|
|
return;
|
|
}
|
|
|
|
COM_BufInsertText(W_CacheLumpNum(lumpnum, PU_CACHE));
|
|
}
|
|
else
|
|
{
|
|
COM_BufAddText(va("exec %s\n", scriptname));
|
|
}
|
|
COM_BufExecute(); // Run it!
|
|
}
|
|
|
|
static void P_ForceCharacter(const char *forcecharskin)
|
|
{
|
|
if (netgame)
|
|
{
|
|
char skincmd[33];
|
|
if (splitscreen)
|
|
{
|
|
sprintf(skincmd, "skin2 %s\n", forcecharskin);
|
|
CV_Set(&cv_skin2, forcecharskin);
|
|
}
|
|
|
|
sprintf(skincmd, "skin %s\n", forcecharskin);
|
|
COM_BufAddText(skincmd);
|
|
}
|
|
else
|
|
{
|
|
if (splitscreen)
|
|
{
|
|
SetPlayerSkin(secondarydisplayplayer, forcecharskin);
|
|
if ((unsigned)cv_playercolor2.value != skins[players[secondarydisplayplayer].skin].prefcolor)
|
|
{
|
|
CV_StealthSetValue(&cv_playercolor2, skins[players[secondarydisplayplayer].skin].prefcolor);
|
|
players[secondarydisplayplayer].skincolor = skins[players[secondarydisplayplayer].skin].prefcolor;
|
|
}
|
|
}
|
|
|
|
SetPlayerSkin(consoleplayer, forcecharskin);
|
|
// normal player colors in single player
|
|
if ((unsigned)cv_playercolor.value != skins[players[consoleplayer].skin].prefcolor)
|
|
{
|
|
CV_StealthSetValue(&cv_playercolor, skins[players[consoleplayer].skin].prefcolor);
|
|
players[consoleplayer].skincolor = skins[players[consoleplayer].skin].prefcolor;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void P_ResetSpawnpoints(void)
|
|
{
|
|
UINT8 i;
|
|
|
|
numdmstarts = numredctfstarts = numbluectfstarts = 0;
|
|
|
|
// reset the player starts
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
playerstarts[i] = bluectfstarts[i] = redctfstarts[i] = NULL;
|
|
|
|
for (i = 0; i < MAX_DM_STARTS; i++)
|
|
deathmatchstarts[i] = NULL;
|
|
|
|
for (i = 0; i < 2; i++)
|
|
skyboxmo[i] = NULL;
|
|
|
|
for (i = 0; i < 16; i++)
|
|
skyboxviewpnts[i] = skyboxcenterpnts[i] = NULL;
|
|
}
|
|
|
|
static void P_LoadRecordGhosts(void)
|
|
{
|
|
const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
|
|
char *gpath = malloc(glen);
|
|
INT32 i;
|
|
|
|
if (!gpath)
|
|
return;
|
|
|
|
sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap));
|
|
|
|
// Best Score ghost
|
|
if (cv_ghost_bestscore.value)
|
|
{
|
|
for (i = 0; i < numskins; ++i)
|
|
{
|
|
if (cv_ghost_bestscore.value == 1 && players[consoleplayer].skin != i)
|
|
continue;
|
|
|
|
if (FIL_FileExists(va("%s-%s-score-best.lmp", gpath, skins[i].name)))
|
|
G_AddGhost(va("%s-%s-score-best.lmp", gpath, skins[i].name));
|
|
}
|
|
}
|
|
|
|
// Best Time ghost
|
|
if (cv_ghost_besttime.value)
|
|
{
|
|
for (i = 0; i < numskins; ++i)
|
|
{
|
|
if (cv_ghost_besttime.value == 1 && players[consoleplayer].skin != i)
|
|
continue;
|
|
|
|
if (FIL_FileExists(va("%s-%s-time-best.lmp", gpath, skins[i].name)))
|
|
G_AddGhost(va("%s-%s-time-best.lmp", gpath, skins[i].name));
|
|
}
|
|
}
|
|
|
|
// Best Rings ghost
|
|
if (cv_ghost_bestrings.value)
|
|
{
|
|
for (i = 0; i < numskins; ++i)
|
|
{
|
|
if (cv_ghost_bestrings.value == 1 && players[consoleplayer].skin != i)
|
|
continue;
|
|
|
|
if (FIL_FileExists(va("%s-%s-rings-best.lmp", gpath, skins[i].name)))
|
|
G_AddGhost(va("%s-%s-rings-best.lmp", gpath, skins[i].name));
|
|
}
|
|
}
|
|
|
|
// Last ghost
|
|
if (cv_ghost_last.value)
|
|
{
|
|
for (i = 0; i < numskins; ++i)
|
|
{
|
|
if (cv_ghost_last.value == 1 && players[consoleplayer].skin != i)
|
|
continue;
|
|
|
|
if (FIL_FileExists(va("%s-%s-last.lmp", gpath, skins[i].name)))
|
|
G_AddGhost(va("%s-%s-last.lmp", gpath, skins[i].name));
|
|
}
|
|
}
|
|
|
|
// Guest ghost
|
|
if (cv_ghost_guest.value && FIL_FileExists(va("%s-guest.lmp", gpath)))
|
|
G_AddGhost(va("%s-guest.lmp", gpath));
|
|
|
|
free(gpath);
|
|
}
|
|
|
|
static void P_LoadNightsGhosts(void)
|
|
{
|
|
const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
|
|
char *gpath = malloc(glen);
|
|
INT32 i;
|
|
|
|
if (!gpath)
|
|
return;
|
|
|
|
sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap));
|
|
|
|
// Best Score ghost
|
|
if (cv_ghost_bestscore.value)
|
|
{
|
|
for (i = 0; i < numskins; ++i)
|
|
{
|
|
if (cv_ghost_bestscore.value == 1 && players[consoleplayer].skin != i)
|
|
continue;
|
|
|
|
if (FIL_FileExists(va("%s-%s-score-best.lmp", gpath, skins[i].name)))
|
|
G_AddGhost(va("%s-%s-score-best.lmp", gpath, skins[i].name));
|
|
}
|
|
}
|
|
|
|
// Best Time ghost
|
|
if (cv_ghost_besttime.value)
|
|
{
|
|
for (i = 0; i < numskins; ++i)
|
|
{
|
|
if (cv_ghost_besttime.value == 1 && players[consoleplayer].skin != i)
|
|
continue;
|
|
|
|
if (FIL_FileExists(va("%s-%s-time-best.lmp", gpath, skins[i].name)))
|
|
G_AddGhost(va("%s-%s-time-best.lmp", gpath, skins[i].name));
|
|
}
|
|
}
|
|
|
|
// Last ghost
|
|
if (cv_ghost_last.value)
|
|
{
|
|
for (i = 0; i < numskins; ++i)
|
|
{
|
|
if (cv_ghost_last.value == 1 && players[consoleplayer].skin != i)
|
|
continue;
|
|
|
|
if (FIL_FileExists(va("%s-%s-last.lmp", gpath, skins[i].name)))
|
|
G_AddGhost(va("%s-%s-last.lmp", gpath, skins[i].name));
|
|
}
|
|
}
|
|
|
|
// Guest ghost
|
|
if (cv_ghost_guest.value && FIL_FileExists(va("%s-guest.lmp", gpath)))
|
|
G_AddGhost(va("%s-guest.lmp", gpath));
|
|
|
|
free(gpath);
|
|
}
|
|
|
|
static void P_InitTagGametype(void)
|
|
{
|
|
UINT8 i;
|
|
INT32 realnumplayers = 0;
|
|
INT32 playersactive[MAXPLAYERS];
|
|
|
|
//I just realized how problematic this code can be.
|
|
//D_NumPlayers() will not always cover the scope of the netgame.
|
|
//What if one player is node 0 and the other node 31?
|
|
//The solution? Make a temp array of all players that are currently playing and pick from them.
|
|
//Future todo? When a player leaves, shift all nodes down so D_NumPlayers() can be used as intended?
|
|
//Also, you'd never have to loop through all 32 players slots to find anything ever again.
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (playeringame[i] && !(players[i].spectator || players[i].quittime))
|
|
{
|
|
playersactive[realnumplayers] = i; //stores the player's node in the array.
|
|
realnumplayers++;
|
|
}
|
|
}
|
|
|
|
if (!realnumplayers) //this should also fix the dedicated crash bug. You only pick a player if one exists to be picked.
|
|
{
|
|
CONS_Printf(M_GetText("No player currently available to become IT. Awaiting available players.\n"));
|
|
return;
|
|
}
|
|
|
|
i = P_RandomKey(realnumplayers);
|
|
players[playersactive[i]].pflags |= PF_TAGIT; //choose our initial tagger before map starts.
|
|
|
|
// Taken and modified from G_DoReborn()
|
|
// Remove the player so he can respawn elsewhere.
|
|
// first disassociate the corpse
|
|
if (players[playersactive[i]].mo)
|
|
P_RemoveMobj(players[playersactive[i]].mo);
|
|
|
|
G_SpawnPlayer(playersactive[i]); //respawn the lucky player in his dedicated spawn location.
|
|
}
|
|
|
|
static void P_SetupCamera(void)
|
|
{
|
|
if (players[displayplayer].mo && (server || addedtogame))
|
|
{
|
|
camera.x = players[displayplayer].mo->x;
|
|
camera.y = players[displayplayer].mo->y;
|
|
camera.z = players[displayplayer].mo->z;
|
|
camera.angle = players[displayplayer].mo->angle;
|
|
camera.subsector = R_PointInSubsector(camera.x, camera.y); // make sure camera has a subsector set -- Monster Iestyn (12/11/18)
|
|
}
|
|
else
|
|
{
|
|
mapthing_t *thing;
|
|
|
|
if (gametyperules & GTR_DEATHMATCHSTARTS)
|
|
thing = deathmatchstarts[0];
|
|
else
|
|
thing = playerstarts[0];
|
|
|
|
if (thing)
|
|
{
|
|
camera.x = thing->x;
|
|
camera.y = thing->y;
|
|
camera.z = thing->z;
|
|
camera.angle = FixedAngle((fixed_t)thing->angle << FRACBITS);
|
|
camera.subsector = R_PointInSubsector(camera.x, camera.y); // make sure camera has a subsector set -- Monster Iestyn (12/11/18)
|
|
}
|
|
}
|
|
}
|
|
|
|
static void P_InitCamera(void)
|
|
{
|
|
if (!dedicated)
|
|
{
|
|
P_SetupCamera();
|
|
|
|
// Salt: CV_ClearChangedFlags() messes with your settings :(
|
|
/*if (!cv_cam_height.changed)
|
|
CV_Set(&cv_cam_height, cv_cam_height.defaultvalue);
|
|
if (!cv_cam2_height.changed)
|
|
CV_Set(&cv_cam2_height, cv_cam2_height.defaultvalue);
|
|
|
|
if (!cv_cam_dist.changed)
|
|
CV_Set(&cv_cam_dist, cv_cam_dist.defaultvalue);
|
|
if (!cv_cam2_dist.changed)
|
|
CV_Set(&cv_cam2_dist, cv_cam2_dist.defaultvalue);*/
|
|
|
|
// Though, I don't think anyone would care about cam_rotate being reset back to the only value that makes sense :P
|
|
if (!cv_cam_rotate.changed)
|
|
CV_Set(&cv_cam_rotate, cv_cam_rotate.defaultvalue);
|
|
if (!cv_cam2_rotate.changed)
|
|
CV_Set(&cv_cam2_rotate, cv_cam2_rotate.defaultvalue);
|
|
|
|
if (!cv_analog[0].changed)
|
|
CV_SetValue(&cv_analog[0], 0);
|
|
if (!cv_analog[1].changed)
|
|
CV_SetValue(&cv_analog[1], 0);
|
|
|
|
displayplayer = consoleplayer; // Start with your OWN view, please!
|
|
}
|
|
|
|
if (twodlevel)
|
|
{
|
|
CV_SetValue(&cv_analog[0], false);
|
|
CV_SetValue(&cv_analog[1], false);
|
|
}
|
|
else
|
|
{
|
|
if (cv_useranalog[0].value)
|
|
CV_SetValue(&cv_analog[0], true);
|
|
|
|
if ((splitscreen && cv_useranalog[1].value) || botingame)
|
|
CV_SetValue(&cv_analog[1], true);
|
|
}
|
|
}
|
|
|
|
static void P_RunSpecialStageWipe(void)
|
|
{
|
|
tic_t starttime = I_GetTime();
|
|
tic_t endtime = starttime + (3*TICRATE)/2;
|
|
tic_t nowtime;
|
|
|
|
S_StartSound(NULL, sfx_s3kaf);
|
|
|
|
// Fade music! Time it to S3KAF: 0.25 seconds is snappy.
|
|
if (RESETMUSIC ||
|
|
strnicmp(S_MusicName(),
|
|
(mapmusflags & MUSIC_RELOADRESET) ? mapheaderinfo[gamemap - 1]->musname : mapmusname, 7))
|
|
S_FadeOutStopMusic(MUSICRATE/4); //FixedMul(FixedDiv(F_GetWipeLength(wipedefs[wipe_speclevel_towhite])*NEWTICRATERATIO, NEWTICRATE), MUSICRATE)
|
|
|
|
F_WipeStartScreen();
|
|
wipestyleflags |= (WSF_FADEOUT|WSF_TOWHITE);
|
|
|
|
#ifdef HWRENDER
|
|
// uh..........
|
|
if (rendermode == render_opengl)
|
|
F_WipeColorFill(0);
|
|
#endif
|
|
|
|
F_WipeEndScreen();
|
|
F_RunWipe(wipedefs[wipe_speclevel_towhite], false);
|
|
|
|
I_OsPolling();
|
|
I_FinishUpdate(); // page flip or blit buffer
|
|
if (moviemode)
|
|
M_SaveFrame();
|
|
|
|
nowtime = lastwipetic;
|
|
|
|
// Hold on white for extra effect.
|
|
while (nowtime < endtime)
|
|
{
|
|
// wait loop
|
|
while (!((nowtime = I_GetTime()) - lastwipetic))
|
|
I_Sleep();
|
|
lastwipetic = nowtime;
|
|
if (moviemode) // make sure we save frames for the white hold too
|
|
M_SaveFrame();
|
|
}
|
|
}
|
|
|
|
static void P_RunLevelWipe(void)
|
|
{
|
|
F_WipeStartScreen();
|
|
wipestyleflags |= WSF_FADEOUT;
|
|
|
|
#ifdef HWRENDER
|
|
// uh..........
|
|
if (rendermode == render_opengl)
|
|
F_WipeColorFill(31);
|
|
#endif
|
|
|
|
F_WipeEndScreen();
|
|
// for titlemap: run a specific wipe if specified
|
|
// needed for exiting time attack
|
|
if (wipetypepre != INT16_MAX)
|
|
F_RunWipe(
|
|
(wipetypepre >= 0 && F_WipeExists(wipetypepre)) ? wipetypepre : wipedefs[wipe_level_toblack],
|
|
false);
|
|
wipetypepre = -1;
|
|
}
|
|
|
|
static void P_InitPlayers(void)
|
|
{
|
|
UINT8 i;
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i])
|
|
continue;
|
|
|
|
// Start players with pity shields if possible
|
|
players[i].pity = -1;
|
|
|
|
players[i].mo = NULL;
|
|
|
|
if (!G_PlatformGametype())
|
|
G_DoReborn(i);
|
|
else // gametype is GT_COOP or GT_RACE
|
|
{
|
|
G_SpawnPlayer(i);
|
|
if (players[i].starposttime)
|
|
P_ClearStarPost(players[i].starpostnum);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void P_WriteLetter(void)
|
|
{
|
|
char *buf, *b;
|
|
|
|
if (!unlockables[27].unlocked) // pandora's box
|
|
return;
|
|
|
|
if (modeattacking)
|
|
return;
|
|
|
|
#ifndef DEVELOP
|
|
if (modifiedgame)
|
|
return;
|
|
#endif
|
|
|
|
if (netgame || multiplayer)
|
|
return;
|
|
|
|
if (gamemap != 0x1d35 - 016464)
|
|
return;
|
|
|
|
P_SpawnMobj(0640370000, 0x11000000, 0x3180000, MT_LETTER)->angle = ANGLE_90;
|
|
|
|
if (textprompts[199]->page[1].backcolor == 259)
|
|
return;
|
|
|
|
buf = W_CacheLumpName("WATERMAP", PU_STATIC);
|
|
b = buf;
|
|
|
|
while ((*b != 65) && (b - buf < 256))
|
|
{
|
|
*b = (*b - 65) & 255;
|
|
b++;
|
|
}
|
|
*b = '\0';
|
|
|
|
Z_Free(textprompts[199]->page[1].text);
|
|
textprompts[199]->page[1].text = Z_StrDup(buf);
|
|
textprompts[199]->page[1].lines = 4;
|
|
textprompts[199]->page[1].backcolor = 259;
|
|
Z_Free(buf);
|
|
}
|
|
|
|
static void P_InitGametype(void)
|
|
{
|
|
UINT8 i;
|
|
|
|
P_InitPlayers();
|
|
|
|
// restore time in netgame (see also g_game.c)
|
|
if ((netgame || multiplayer) && G_GametypeUsesCoopStarposts() && cv_coopstarposts.value == 2)
|
|
{
|
|
// is this a hack? maybe
|
|
tic_t maxstarposttime = 0;
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (playeringame[i] && players[i].starposttime > maxstarposttime)
|
|
maxstarposttime = players[i].starposttime;
|
|
}
|
|
leveltime = maxstarposttime;
|
|
}
|
|
|
|
P_WriteLetter();
|
|
|
|
if (modeattacking == ATTACKING_RECORD && !demoplayback)
|
|
P_LoadRecordGhosts();
|
|
else if (modeattacking == ATTACKING_NIGHTS && !demoplayback)
|
|
P_LoadNightsGhosts();
|
|
|
|
if (G_TagGametype())
|
|
P_InitTagGametype();
|
|
else if (((gametyperules & (GTR_RACE|GTR_LIVES)) == GTR_RACE) && server)
|
|
CV_StealthSetValue(&cv_numlaps,
|
|
(cv_basenumlaps.value)
|
|
? cv_basenumlaps.value
|
|
: mapheaderinfo[gamemap - 1]->numlaps);
|
|
}
|
|
|
|
/** Loads a level from a lump or external wad.
|
|
*
|
|
* \param fromnetsave If true, skip some stuff because we're loading a netgame snapshot.
|
|
* \todo Clean up, refactor, split up; get rid of the bloat.
|
|
*/
|
|
boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
|
|
{
|
|
// use gamemap to get map number.
|
|
// 99% of the things already did, so.
|
|
// Map header should always be in place at this point
|
|
INT32 i, ranspecialwipe = 0;
|
|
sector_t *ss;
|
|
levelloading = true;
|
|
|
|
// This is needed. Don't touch.
|
|
maptol = mapheaderinfo[gamemap-1]->typeoflevel;
|
|
gametyperules = gametypedefaultrules[gametype];
|
|
|
|
CON_Drawer(); // let the user know what we are going to do
|
|
I_FinishUpdate(); // page flip or blit buffer
|
|
|
|
// Reset the palette
|
|
if (rendermode != render_none)
|
|
V_SetPaletteLump("PLAYPAL");
|
|
|
|
// Initialize sector node list.
|
|
P_Initsecnode();
|
|
|
|
if (netgame || multiplayer)
|
|
cv_debug = botskin = 0;
|
|
|
|
if (metalplayback)
|
|
G_StopMetalDemo();
|
|
|
|
// Clear CECHO messages
|
|
HU_ClearCEcho();
|
|
|
|
if (mapheaderinfo[gamemap-1]->runsoc[0] != '#')
|
|
P_RunSOC(mapheaderinfo[gamemap-1]->runsoc);
|
|
|
|
if (cv_runscripts.value && mapheaderinfo[gamemap-1]->scriptname[0] != '#')
|
|
P_RunLevelScript(mapheaderinfo[gamemap-1]->scriptname);
|
|
|
|
P_InitLevelSettings();
|
|
|
|
postimgtype = postimgtype2 = postimg_none;
|
|
|
|
if (mapheaderinfo[gamemap-1]->forcecharacter[0] != '\0')
|
|
P_ForceCharacter(mapheaderinfo[gamemap-1]->forcecharacter);
|
|
|
|
if (!dedicated)
|
|
{
|
|
// chasecam on in first-person gametypes and 2D
|
|
boolean chase = (!(gametyperules & GTR_FIRSTPERSON)) || (maptol & TOL_2D);
|
|
|
|
// Salt: CV_ClearChangedFlags() messes with your settings :(
|
|
/*if (!cv_cam_speed.changed)
|
|
CV_Set(&cv_cam_speed, cv_cam_speed.defaultvalue);*/
|
|
|
|
CV_UpdateCamDist();
|
|
CV_UpdateCam2Dist();
|
|
|
|
if (!cv_chasecam.changed)
|
|
CV_SetValue(&cv_chasecam, chase);
|
|
|
|
// same for second player
|
|
if (!cv_chasecam2.changed)
|
|
CV_SetValue(&cv_chasecam2, chase);
|
|
}
|
|
|
|
// Initial height of PointOfView
|
|
// will be set by player think.
|
|
players[consoleplayer].viewz = 1;
|
|
|
|
// Cancel all d_main.c fadeouts (keep fade in though).
|
|
if (reloadinggamestate)
|
|
wipegamestate = gamestate; // Don't fade if reloading the gamestate
|
|
else
|
|
wipegamestate = FORCEWIPEOFF;
|
|
wipestyleflags = 0;
|
|
|
|
// Special stage & record attack retry fade to white
|
|
// This is handled BEFORE sounds are stopped.
|
|
if (G_GetModeAttackRetryFlag())
|
|
{
|
|
if (modeattacking && !demoplayback)
|
|
{
|
|
ranspecialwipe = 2;
|
|
wipestyleflags |= (WSF_FADEOUT|WSF_TOWHITE);
|
|
}
|
|
G_ClearModeAttackRetryFlag();
|
|
}
|
|
else if (rendermode != render_none && G_IsSpecialStage(gamemap))
|
|
{
|
|
P_RunSpecialStageWipe();
|
|
ranspecialwipe = 1;
|
|
}
|
|
|
|
// Make sure all sounds are stopped before Z_FreeTags.
|
|
S_StopSounds();
|
|
S_ClearSfx();
|
|
|
|
// Fade out music here. Deduct 2 tics so the fade volume actually reaches 0.
|
|
// But don't halt the music! S_Start will take care of that. This dodges a MIDI crash bug.
|
|
if (!(reloadinggamestate || titlemapinaction) && (RESETMUSIC ||
|
|
strnicmp(S_MusicName(),
|
|
(mapmusflags & MUSIC_RELOADRESET) ? mapheaderinfo[gamemap-1]->musname : mapmusname, 7)))
|
|
{
|
|
S_FadeMusic(0, FixedMul(
|
|
FixedDiv((F_GetWipeLength(wipedefs[wipe_level_toblack])-2)*NEWTICRATERATIO, NEWTICRATE), MUSICRATE));
|
|
}
|
|
|
|
// Let's fade to black here
|
|
// But only if we didn't do the special stage wipe
|
|
if (rendermode != render_none && !(ranspecialwipe || reloadinggamestate))
|
|
P_RunLevelWipe();
|
|
|
|
if (!(reloadinggamestate || titlemapinaction))
|
|
{
|
|
if (ranspecialwipe == 2)
|
|
{
|
|
pausedelay = -3; // preticker plus one
|
|
S_StartSound(NULL, sfx_s3k73);
|
|
}
|
|
|
|
// Print "SPEEDING OFF TO [ZONE] [ACT 1]..."
|
|
if (rendermode != render_none)
|
|
{
|
|
// Don't include these in the fade!
|
|
char tx[64];
|
|
V_DrawSmallString(1, 191, V_ALLOWLOWERCASE|V_TRANSLUCENT|V_SNAPTOLEFT|V_SNAPTOBOTTOM, M_GetText("Speeding off to..."));
|
|
snprintf(tx, 63, "%s%s%s",
|
|
mapheaderinfo[gamemap-1]->lvlttl,
|
|
(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE) ? "" : " Zone",
|
|
(mapheaderinfo[gamemap-1]->actnum > 0) ? va(" %d",mapheaderinfo[gamemap-1]->actnum) : "");
|
|
V_DrawSmallString(1, 195, V_ALLOWLOWERCASE|V_TRANSLUCENT|V_SNAPTOLEFT|V_SNAPTOBOTTOM, tx);
|
|
I_UpdateNoVsync();
|
|
}
|
|
|
|
// As oddly named as this is, this handles music only.
|
|
// We should be fine starting it here.
|
|
// Don't do this during titlemap, because the menu code handles music by itself.
|
|
S_Start();
|
|
}
|
|
|
|
levelfadecol = (ranspecialwipe) ? 0 : 31;
|
|
|
|
// Close text prompt before freeing the old level
|
|
F_EndTextPrompt(false, true);
|
|
|
|
LUA_InvalidateLevel();
|
|
|
|
for (ss = sectors; sectors+numsectors != ss; ss++)
|
|
{
|
|
Z_Free(ss->attached);
|
|
Z_Free(ss->attachedsolid);
|
|
}
|
|
|
|
// Clear pointers that would be left dangling by the purge
|
|
R_FlushTranslationColormapCache();
|
|
|
|
#ifdef HWRENDER
|
|
// Free GPU textures before freeing patches.
|
|
if (rendermode == render_opengl && (vid.glstate == VID_GL_LIBRARY_LOADED))
|
|
HWR_ClearAllTextures();
|
|
#endif
|
|
|
|
Patch_FreeTag(PU_PATCH_LOWPRIORITY);
|
|
Patch_FreeTag(PU_PATCH_ROTATED);
|
|
Z_FreeTags(PU_LEVEL, PU_PURGELEVEL - 1);
|
|
|
|
P_InitThinkers();
|
|
P_InitCachedActions();
|
|
|
|
if (!fromnetsave && savedata.lives > 0)
|
|
{
|
|
numgameovers = savedata.numgameovers;
|
|
players[consoleplayer].continues = savedata.continues;
|
|
players[consoleplayer].lives = savedata.lives;
|
|
players[consoleplayer].score = savedata.score;
|
|
if ((botingame = ((botskin = savedata.botskin) != 0)))
|
|
botcolor = skins[botskin-1].prefcolor;
|
|
emeralds = savedata.emeralds;
|
|
savedata.lives = 0;
|
|
}
|
|
|
|
// internal game map
|
|
maplumpname = G_BuildMapName(gamemap);
|
|
lastloadedmaplumpnum = W_CheckNumForName(maplumpname);
|
|
if (lastloadedmaplumpnum == LUMPERROR)
|
|
I_Error("Map %s not found.\n", maplumpname);
|
|
|
|
R_ReInitColormaps(mapheaderinfo[gamemap-1]->palette);
|
|
CON_SetupBackColormap();
|
|
|
|
// SRB2 determines the sky texture to be used depending on the map header.
|
|
P_SetupLevelSky(mapheaderinfo[gamemap-1]->skynum, true);
|
|
|
|
P_ResetSpawnpoints();
|
|
|
|
P_ResetWaypoints();
|
|
|
|
P_MapStart();
|
|
|
|
if (!P_LoadMapFromFile())
|
|
return false;
|
|
|
|
// init anything that P_SpawnSlopes/P_LoadThings needs to know
|
|
P_InitSpecials();
|
|
|
|
P_SpawnSlopes(fromnetsave);
|
|
|
|
P_SpawnMapThings(!fromnetsave);
|
|
skyboxmo[0] = skyboxviewpnts[0];
|
|
skyboxmo[1] = skyboxcenterpnts[0];
|
|
|
|
for (numcoopstarts = 0; numcoopstarts < MAXPLAYERS; numcoopstarts++)
|
|
if (!playerstarts[numcoopstarts])
|
|
break;
|
|
|
|
// set up world state
|
|
P_SpawnSpecials(fromnetsave);
|
|
|
|
if (!fromnetsave) // ugly hack for P_NetUnArchiveMisc (and P_LoadNetGame)
|
|
P_SpawnPrecipitation();
|
|
|
|
#ifdef HWRENDER // not win32 only 19990829 by Kin
|
|
gl_maploaded = false;
|
|
|
|
// Lactozilla: Free extrasubsectors regardless of renderer.
|
|
HWR_FreeExtraSubsectors();
|
|
|
|
// Create plane polygons.
|
|
if (rendermode == render_opengl)
|
|
HWR_LoadLevel();
|
|
#endif
|
|
|
|
// oh god I hope this helps
|
|
// (addendum: apparently it does!
|
|
// none of this needs to be done because it's not the beginning of the map when
|
|
// a netgame save is being loaded, and could actively be harmful by messing with
|
|
// the client's view of the data.)
|
|
if (!fromnetsave)
|
|
P_InitGametype();
|
|
|
|
if (!reloadinggamestate)
|
|
{
|
|
P_InitCamera();
|
|
localaiming = 0;
|
|
localaiming2 = 0;
|
|
}
|
|
|
|
// clear special respawning que
|
|
iquehead = iquetail = 0;
|
|
|
|
P_MapEnd();
|
|
|
|
// Remove the loading shit from the screen
|
|
if (rendermode != render_none && !(titlemapinaction || reloadinggamestate))
|
|
F_WipeColorFill(levelfadecol);
|
|
|
|
if (precache || dedicated)
|
|
R_PrecacheLevel();
|
|
|
|
nextmapoverride = 0;
|
|
skipstats = 0;
|
|
|
|
if (!(netgame || multiplayer || demoplayback) && (!modifiedgame || savemoddata))
|
|
mapvisited[gamemap-1] |= MV_VISITED;
|
|
else if (netgame || multiplayer)
|
|
mapvisited[gamemap-1] |= MV_MP; // you want to record that you've been there this session, but not permanently
|
|
|
|
levelloading = false;
|
|
|
|
P_RunCachedActions();
|
|
|
|
// Took me 3 hours to figure out why my progression kept on getting overwritten with the titlemap...
|
|
if (!titlemapinaction)
|
|
{
|
|
if (!lastmaploaded) // Start a new game?
|
|
{
|
|
// I'd love to do this in the menu code instead of here, but everything's a mess and I can't guarantee saving proper player struct info before the first act's started. You could probably refactor it, but it'd be a lot of effort. Easier to just work off known good code. ~toast 22/06/2020
|
|
if (!(ultimatemode || netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking || marathonmode)
|
|
&& (!modifiedgame || savemoddata) && cursaveslot > 0)
|
|
G_SaveGame((UINT32)cursaveslot, gamemap);
|
|
// If you're looking for saving sp file progression (distinct from G_SaveGameOver), check G_DoCompleted.
|
|
}
|
|
lastmaploaded = gamemap; // HAS to be set after saving!!
|
|
}
|
|
|
|
if (!fromnetsave) // uglier hack
|
|
{ // to make a newly loaded level start on the second frame.
|
|
INT32 buf = gametic % BACKUPTICS;
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (playeringame[i])
|
|
G_CopyTiccmd(&players[i].cmd, &netcmds[buf][i], 1);
|
|
}
|
|
P_PreTicker(2);
|
|
LUAh_MapLoad();
|
|
}
|
|
|
|
// No render mode or reloading gamestate, stop here.
|
|
if (rendermode == render_none || reloadinggamestate)
|
|
return true;
|
|
|
|
// Title card!
|
|
G_StartTitleCard();
|
|
|
|
// Can the title card actually run, though?
|
|
if (!WipeStageTitle)
|
|
return true;
|
|
if (ranspecialwipe == 2)
|
|
return true;
|
|
|
|
// If so...
|
|
G_PreLevelTitleCard();
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// P_RunSOC
|
|
//
|
|
// Runs a SOC file or a lump, depending on if ".SOC" exists in the filename
|
|
//
|
|
boolean P_RunSOC(const char *socfilename)
|
|
{
|
|
lumpnum_t lump;
|
|
|
|
if (strstr(socfilename, ".soc") != NULL)
|
|
return P_AddWadFile(socfilename);
|
|
|
|
lump = W_CheckNumForName(socfilename);
|
|
if (lump == LUMPERROR)
|
|
return false;
|
|
|
|
CONS_Printf(M_GetText("Loading SOC lump: %s\n"), socfilename);
|
|
DEH_LoadDehackedLump(lump);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Auxiliary function for PK3 loading - looks for sound replacements.
|
|
// NOTE: it does not really add any new sound entry or anything.
|
|
void P_LoadSoundsRange(UINT16 wadnum, UINT16 first, UINT16 num)
|
|
{
|
|
size_t j;
|
|
lumpinfo_t *lumpinfo = wadfiles[wadnum]->lumpinfo + first;
|
|
for (; num > 0; num--, lumpinfo++)
|
|
{
|
|
// Let's check whether it's replacing an existing sound or it's a brand new one.
|
|
for (j = 1; j < NUMSFX; j++)
|
|
{
|
|
if (S_sfx[j].name && !strnicmp(S_sfx[j].name, lumpinfo->name + 2, 6))
|
|
{
|
|
// the sound will be reloaded when needed,
|
|
// since sfx->data will be NULL
|
|
CONS_Debug(DBG_SETUP, "Sound %.8s replaced\n", lumpinfo->name);
|
|
|
|
I_FreeSfx(&S_sfx[j]);
|
|
break; // there shouldn't be two sounds with the same name, so stop looking
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Auxiliary function for PK3 loading - looks for music and music replacements.
|
|
// NOTE: does nothing but print debug messages. The code is handled somewhere else.
|
|
void P_LoadMusicsRange(UINT16 wadnum, UINT16 first, UINT16 num)
|
|
{
|
|
lumpinfo_t *lumpinfo = wadfiles[wadnum]->lumpinfo + first;
|
|
char *name;
|
|
for (; num > 0; num--, lumpinfo++)
|
|
{
|
|
name = lumpinfo->name;
|
|
if (name[0] == 'O' && name[1] == '_')
|
|
{
|
|
CONS_Debug(DBG_SETUP, "Music %.8s replaced\n", name);
|
|
}
|
|
else if (name[0] == 'D' && name[1] == '_')
|
|
{
|
|
CONS_Debug(DBG_SETUP, "Music %.8s replaced\n", name);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Auxiliary function - input a folder name and gives us the resource markers positions.
|
|
static lumpinfo_t* FindFolder(const char *folName, UINT16 *start, UINT16 *end, lumpinfo_t *lumpinfo, UINT16 *pnumlumps, size_t *pi)
|
|
{
|
|
UINT16 numlumps = *pnumlumps;
|
|
size_t i = *pi;
|
|
if (!stricmp(lumpinfo->fullname, folName))
|
|
{
|
|
lumpinfo++;
|
|
*start = ++i;
|
|
for (; i < numlumps; i++, lumpinfo++)
|
|
if (strnicmp(lumpinfo->fullname, folName, strlen(folName)))
|
|
break;
|
|
lumpinfo--;
|
|
*end = i-- - *start;
|
|
*pi = i;
|
|
*pnumlumps = numlumps;
|
|
return lumpinfo;
|
|
}
|
|
return lumpinfo;
|
|
}
|
|
|
|
//
|
|
// Add a wadfile to the active wad files,
|
|
// replace sounds, musics, patches, textures, sprites and maps
|
|
//
|
|
boolean P_AddWadFile(const char *wadfilename)
|
|
{
|
|
size_t i, j, sreplaces = 0, mreplaces = 0, digmreplaces = 0;
|
|
UINT16 numlumps, wadnum;
|
|
char *name;
|
|
lumpinfo_t *lumpinfo;
|
|
|
|
//boolean texturechange = false; ///\todo Useless; broken when back-frontporting PK3 changes?
|
|
boolean mapsadded = false;
|
|
boolean replacedcurrentmap = false;
|
|
|
|
// Vars to help us with the position start and amount of each resource type.
|
|
// Useful for PK3s since they use folders.
|
|
// WADs use markers for some resources, but others such as sounds are checked lump-by-lump anyway.
|
|
// UINT16 luaPos, luaNum = 0;
|
|
// UINT16 socPos, socNum = 0;
|
|
UINT16 sfxPos = 0, sfxNum = 0;
|
|
UINT16 musPos = 0, musNum = 0;
|
|
// UINT16 sprPos, sprNum = 0;
|
|
UINT16 texPos = 0, texNum = 0;
|
|
// UINT16 patPos, patNum = 0;
|
|
// UINT16 flaPos, flaNum = 0;
|
|
// UINT16 mapPos, mapNum = 0;
|
|
|
|
// Init file.
|
|
if ((numlumps = W_InitFile(wadfilename, false, false)) == INT16_MAX)
|
|
{
|
|
refreshdirmenu |= REFRESHDIR_NOTLOADED;
|
|
return false;
|
|
}
|
|
else
|
|
wadnum = (UINT16)(numwadfiles-1);
|
|
|
|
switch(wadfiles[wadnum]->type)
|
|
{
|
|
case RET_PK3:
|
|
// Look for the lumps that act as resource delimitation markers.
|
|
lumpinfo = wadfiles[wadnum]->lumpinfo;
|
|
for (i = 0; i < numlumps; i++, lumpinfo++)
|
|
{
|
|
// lumpinfo = FindFolder("Lua/", &luaPos, &luaNum, lumpinfo, &numlumps, &i);
|
|
// lumpinfo = FindFolder("SOC/", &socPos, &socNum, lumpinfo, &numlumps, &i);
|
|
lumpinfo = FindFolder("Sounds/", &sfxPos, &sfxNum, lumpinfo, &numlumps, &i);
|
|
lumpinfo = FindFolder("Music/", &musPos, &musNum, lumpinfo, &numlumps, &i);
|
|
// lumpinfo = FindFolder("Sprites/", &sprPos, &sprNum, lumpinfo, &numlumps, &i);
|
|
lumpinfo = FindFolder("Textures/", &texPos, &texNum, lumpinfo, &numlumps, &i);
|
|
// lumpinfo = FindFolder("Patches/", &patPos, &patNum, lumpinfo, &numlumps, &i);
|
|
// lumpinfo = FindFolder("Flats/", &flaPos, &flaNum, lumpinfo, &numlumps, &i);
|
|
// lumpinfo = FindFolder("Maps/", &mapPos, &mapNum, lumpinfo, &numlumps, &i);
|
|
}
|
|
|
|
// Update the detected resources.
|
|
// Note: ALWAYS load Lua scripts first, SOCs right after, and the remaining resources afterwards.
|
|
// if (luaNum) // Lua scripts.
|
|
// P_LoadLuaScrRange(wadnum, luaPos, luaNum);
|
|
// if (socNum) // SOCs.
|
|
// P_LoadDehackRange(wadnum, socPos, socNum);
|
|
if (sfxNum) // Sounds. TODO: Function currently only updates already existing sounds, the rest is handled somewhere else.
|
|
P_LoadSoundsRange(wadnum, sfxPos, sfxNum);
|
|
if (musNum) // Music. TODO: Useless function right now.
|
|
P_LoadMusicsRange(wadnum, musPos, musNum);
|
|
// if (sprNum) // Sprites.
|
|
// R_LoadSpritsRange(wadnum, sprPos, sprNum);
|
|
// if (texNum) // Textures. TODO: R_LoadTextures() does the folder positioning once again. New function maybe?
|
|
// R_LoadTextures();
|
|
// if (mapNum) // Maps. TODO: Actually implement the map WAD loading code, lulz.
|
|
// P_LoadWadMapRange(wadnum, mapPos, mapNum);
|
|
break;
|
|
default:
|
|
lumpinfo = wadfiles[wadnum]->lumpinfo;
|
|
for (i = 0; i < numlumps; i++, lumpinfo++)
|
|
{
|
|
name = lumpinfo->name;
|
|
if (name[0] == 'D')
|
|
{
|
|
if (name[1] == 'S')
|
|
{
|
|
for (j = 1; j < NUMSFX; j++)
|
|
{
|
|
if (S_sfx[j].name && !strnicmp(S_sfx[j].name, name + 2, 6))
|
|
{
|
|
// the sound will be reloaded when needed,
|
|
// since sfx->data will be NULL
|
|
CONS_Debug(DBG_SETUP, "Sound %.8s replaced\n", name);
|
|
|
|
I_FreeSfx(&S_sfx[j]);
|
|
|
|
sreplaces++;
|
|
break; // there shouldn't be two sounds with the same name, so stop looking
|
|
}
|
|
}
|
|
}
|
|
else if (name[1] == '_')
|
|
{
|
|
CONS_Debug(DBG_SETUP, "Music %.8s replaced\n", name);
|
|
mreplaces++;
|
|
}
|
|
}
|
|
else if (name[0] == 'O' && name[1] == '_')
|
|
{
|
|
CONS_Debug(DBG_SETUP, "Music %.8s replaced\n", name);
|
|
digmreplaces++;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if (!devparm && sreplaces)
|
|
CONS_Printf(M_GetText("%s sounds replaced\n"), sizeu1(sreplaces));
|
|
if (!devparm && mreplaces)
|
|
CONS_Printf(M_GetText("%s midi musics replaced\n"), sizeu1(mreplaces));
|
|
if (!devparm && digmreplaces)
|
|
CONS_Printf(M_GetText("%s digital musics replaced\n"), sizeu1(digmreplaces));
|
|
|
|
#ifdef HWRENDER
|
|
// Free GPU textures before freeing patches.
|
|
if (rendermode == render_opengl && (vid.glstate == VID_GL_LIBRARY_LOADED))
|
|
HWR_ClearAllTextures();
|
|
#endif
|
|
|
|
//
|
|
// search for sprite replacements
|
|
//
|
|
Patch_FreeTag(PU_SPRITE);
|
|
Patch_FreeTag(PU_PATCH_ROTATED);
|
|
R_AddSpriteDefs(wadnum);
|
|
|
|
// Reload it all anyway, just in case they
|
|
// added some textures but didn't insert a
|
|
// TEXTURES/etc. list.
|
|
R_LoadTextures(); // numtexture changes
|
|
|
|
// Reload ANIMDEFS
|
|
P_InitPicAnims();
|
|
|
|
// Flush and reload HUD graphics
|
|
ST_UnloadGraphics();
|
|
HU_LoadGraphics();
|
|
ST_LoadGraphics();
|
|
|
|
//
|
|
// look for skins
|
|
//
|
|
R_AddSkins(wadnum); // faB: wadfile index in wadfiles[]
|
|
R_PatchSkins(wadnum); // toast: PATCH PATCH
|
|
ST_ReloadSkinFaceGraphics();
|
|
|
|
//
|
|
// edit music defs
|
|
//
|
|
S_LoadMusicDefs(wadnum);
|
|
|
|
//
|
|
// search for maps
|
|
//
|
|
lumpinfo = wadfiles[wadnum]->lumpinfo;
|
|
for (i = 0; i < numlumps; i++, lumpinfo++)
|
|
{
|
|
name = lumpinfo->name;
|
|
if (name[0] == 'M' && name[1] == 'A' && name[2] == 'P') // Ignore the headers
|
|
{
|
|
INT16 num;
|
|
if (name[5]!='\0')
|
|
continue;
|
|
num = (INT16)M_MapNumber(name[3], name[4]);
|
|
|
|
//If you replaced the map you're on, end the level when done.
|
|
if (num == gamemap)
|
|
replacedcurrentmap = true;
|
|
|
|
CONS_Printf("%s\n", name);
|
|
mapsadded = true;
|
|
}
|
|
}
|
|
if (!mapsadded)
|
|
CONS_Printf(M_GetText("No maps added\n"));
|
|
|
|
R_LoadSpriteInfoLumps(wadnum, numlumps);
|
|
|
|
#ifdef HWRENDER
|
|
HWR_ReloadModels();
|
|
#endif
|
|
|
|
// reload status bar (warning should have valid player!)
|
|
if (gamestate == GS_LEVEL)
|
|
ST_Start();
|
|
|
|
// Prevent savefile cheating
|
|
if (cursaveslot > 0)
|
|
cursaveslot = 0;
|
|
|
|
if (replacedcurrentmap && gamestate == GS_LEVEL && (netgame || multiplayer))
|
|
{
|
|
CONS_Printf(M_GetText("Current map %d replaced by added file, ending the level to ensure consistency.\n"), gamemap);
|
|
if (server)
|
|
SendNetXCmd(XD_EXITLEVEL, NULL, 0);
|
|
}
|
|
|
|
return true;
|
|
}
|