mirror of
https://git.do.srb2.org/STJr/SRB2.git
synced 2024-12-19 09:20:59 +00:00
4586 lines
123 KiB
C
4586 lines
123 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;
|
|
|
|
// special stage
|
|
stagefailed = true; // assume failed unless proven otherwise - P_GiveEmerald or emerald touchspecial
|
|
// 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;
|
|
}
|