UltimateZoneBuilder/Source/Native/VPO/p_setup.cpp
Magnus Norddahl 8eb522c873 Move vpo native code into BuilderNative as it is easier to manage. The plugins folder doesn't support including native dlls properly anyway.
Fix visplane explorer busy looping when waiting for data and reduce the used core count to 75% of the total available
Made vpo native code thread safe, removing the need for ungodly DLL patching hacks
2020-04-19 15:56:24 +02:00

896 lines
17 KiB
C++

//-----------------------------------------------------------------------------
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005 Simon Howard
// Copyright(C) 2014 Andrew Apted
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
// 02111-1307, USA.
//
// DESCRIPTION:
// Do all the WAD I/O, get map description,
// set up initial state and misc. LUTs.
//
//-----------------------------------------------------------------------------
#include "Precomp.h"
#include "vpo_local.h"
namespace vpo
{
// this exception is thrown during P_SetupLevel() when a map with bad data
// is detected (e.g. vertex number out of range). The above message buffer
// should contain the error message. [added by andrewj]
class invalid_data_exception { };
static int R_TextureNumForName (const char * name)
{
// "NoTexture" marker.
if (name[0] == '-')
return 0;
return 1; // dummy value
}
static int R_FlatNumForName (const char * name)
{
// SKY ?
if (name[0] == 'F' && name[1] == '_' && name[2] == 'S' && name[3] == 'K')
return skyflatnum;
return 1; // dummy value
}
void Context::LevelError(const char *msg, ...)
{
va_list argptr;
va_start(argptr, msg);
memset(level_error_msg, 0, sizeof(level_error_msg));
vsnprintf(level_error_msg, sizeof(level_error_msg) - 1, msg, argptr);
va_end(argptr);
throw invalid_data_exception();
}
//
// P_LoadVertexes
//
void Context::P_LoadVertexes (int lump)
{
byte* data;
int i;
mapvertex_t* ml;
vertex_t* li;
// Determine number of lumps:
// total lump length / vertex record length.
numvertexes = W_LumpLength (lump) / sizeof(mapvertex_t);
// Allocate zone memory for buffer.
vertexes = new vertex_t[numvertexes];
// Load data into cache.
data = W_LoadLump (lump);
ml = (mapvertex_t *)data;
li = vertexes;
// Copy and convert vertex coordinates,
// internal representation as fixed.
for (i=0 ; i < numvertexes ; i++, li++, ml++)
{
li->x = SHORT(ml->x)<<FRACBITS;
li->y = SHORT(ml->y)<<FRACBITS;
}
// Free buffer memory.
W_FreeLump(data);
}
//
// GetSectorAtNullAddress
//
sector_t * Context::GetSectorAtNullAddress(void)
{
return sectors + 0;
}
//
// P_LoadSegs
//
void Context::P_LoadSegs (int lump)
{
byte* data;
int i;
mapseg_t* ml;
seg_t* li;
line_t* ldef;
int side;
int sidenum;
numsegs = W_LumpLength (lump) / sizeof(mapseg_t);
segs = new seg_t[numsegs];
memset (segs, 0, numsegs*sizeof(seg_t));
data = W_LoadLump (lump);
ml = (mapseg_t *)data;
li = segs;
for (i=0 ; i < numsegs ; i++, li++, ml++)
{
int v1_idx = SHORT(ml->v1);
int v2_idx = SHORT(ml->v2);
if (v1_idx < 0 || v1_idx >= numvertexes ||
v2_idx < 0 || v2_idx >= numvertexes)
{
LevelError("Bad map data : vertex out of range (seg #%d)", i);
}
li->v1 = &vertexes[v1_idx];
li->v2 = &vertexes[v2_idx];
int line_idx = SHORT(ml->linedef);
if (line_idx < 0 || line_idx >= numlines)
{
LevelError("Bad map data : linedef out of range (seg #%d)", i);
}
li->angle = (SHORT(ml->angle))<<16;
li->offset = (SHORT(ml->offset))<<16;
ldef = &lines[line_idx];
li->linedef = ldef;
side = SHORT(ml->side);
li->sidedef = &sides[ldef->sidenum[side]];
li->frontsector = sides[ldef->sidenum[side]].sector;
if (ldef-> flags & ML_TWOSIDED)
{
sidenum = ldef->sidenum[side ^ 1];
// If the sidenum is out of range, this may be a "glass hack"
// impassible window. Point at side #0 (this may not be
// the correct Vanilla behavior; however, it seems to work for
// OTTAWAU.WAD, which is the one place I've seen this trick
// used).
if (sidenum < 0 || sidenum >= numsides)
{
li->backsector = GetSectorAtNullAddress();
}
else
{
li->backsector = sides[sidenum].sector;
}
}
else
{
li->backsector = NULL;
}
}
W_FreeLump(data);
}
//
// P_LoadSubsectors
//
void Context::P_LoadSubsectors (int lump)
{
byte* data;
int i;
mapsubsector_t* ms;
subsector_t* ss;
numsubsectors = W_LumpLength (lump) / sizeof(mapsubsector_t);
subsectors = new subsector_t[numsubsectors];
data = W_LoadLump (lump);
ms = (mapsubsector_t *)data;
memset (subsectors,0, numsubsectors*sizeof(subsector_t));
ss = subsectors;
for (i=0 ; i < numsubsectors ; i++, ss++, ms++)
{
ss->numlines = SHORT(ms->numsegs);
ss->firstline = SHORT(ms->firstseg);
}
W_FreeLump(data);
}
// andrewj: added this
void Context::ValidateSubsectors ()
{
int i;
subsector_t * ss = subsectors;
for (i=0 ; i < numsubsectors ; i++, ss++)
{
if (ss->firstline < 0 || ss->numlines < 0 ||
ss->firstline + ss->numlines > numsegs)
{
LevelError("Bad map data : invalid seg range in subsector #%d\n", i);
}
}
}
//
// P_LoadSectors
//
void Context::P_LoadSectors (int lump)
{
byte* data;
int i;
mapsector_t* ms;
sector_t* ss;
numsectors = W_LumpLength (lump) / sizeof(mapsector_t);
sectors = new sector_t[numsectors];
memset (sectors, 0, numsectors*sizeof(sector_t));
data = W_LoadLump (lump);
ms = (mapsector_t *)data;
ss = sectors;
for (i=0 ; i < numsectors ; i++, ss++, ms++)
{
ss->floorheight = SHORT(ms->floorheight)<<FRACBITS;
ss->ceilingheight = SHORT(ms->ceilingheight)<<FRACBITS;
ss->floorpic = R_FlatNumForName(ms->floorpic);
ss->ceilingpic = R_FlatNumForName(ms->ceilingpic);
ss->lightlevel = SHORT(ms->lightlevel);
ss->special = SHORT(ms->special);
ss->tag = SHORT(ms->tag);
/// ss->thinglist = NULL;
}
W_FreeLump(data);
}
bool Context::isChildValid(unsigned short child)
{
if (child & NF_SUBSECTOR)
return ((child & ~NF_SUBSECTOR) < numsubsectors);
else
return (child < numnodes);
}
//
// P_LoadNodes
//
void Context::P_LoadNodes (int lump)
{
byte* data;
int i;
int j;
int k;
mapnode_t* mn;
node_t* no;
numnodes = W_LumpLength (lump) / sizeof(mapnode_t);
nodes = new node_t[numnodes];
data = W_LoadLump (lump);
mn = (mapnode_t *)data;
no = nodes;
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++)
{
unsigned short child = SHORT(mn->children[j]);
if (! isChildValid(child))
LevelError("Bad map data : invalid child in node #%d", i);
no->children[j] = child;
for (k=0 ; k < 4 ; k++)
no->bbox[j][k] = SHORT(mn->bbox[j][k])<<FRACBITS;
}
}
W_FreeLump(data);
}
/* andrewj : removed P_LoadThings(), not needed for Visplane Explorer */
void Context::LineDef_CommonSetup(line_t *ld)
{
vertex_t *v1 = ld->v1;
vertex_t *v2 = ld->v2;
ld->dx = v2->x - v1->x;
ld->dy = v2->y - v1->y;
if (!ld->dx)
ld->slopetype = ST_VERTICAL;
else if (!ld->dy)
ld->slopetype = ST_HORIZONTAL;
else
{
if (FixedDiv (ld->dy , ld->dx) > 0)
ld->slopetype = ST_POSITIVE;
else
ld->slopetype = ST_NEGATIVE;
}
if (v1->x < v2->x)
{
ld->bbox[BOXLEFT] = v1->x;
ld->bbox[BOXRIGHT] = v2->x;
}
else
{
ld->bbox[BOXLEFT] = v2->x;
ld->bbox[BOXRIGHT] = v1->x;
}
if (v1->y < v2->y)
{
ld->bbox[BOXBOTTOM] = v1->y;
ld->bbox[BOXTOP] = v2->y;
}
else
{
ld->bbox[BOXBOTTOM] = v2->y;
ld->bbox[BOXTOP] = v1->y;
}
// andrewj: be tolerant of bad sidedef numbers
if (ld->sidenum[0] < 0 || ld->sidenum[0] >= numsides)
{
if (numsides == 0)
LevelError("Bad map data : no sidedefs!");
ld->sidenum[0] = 0;
}
if (ld->sidenum[1] < -1 || ld->sidenum[1] >= numsides)
ld->sidenum[1] = -1;
if (ld->sidenum[0] != -1)
ld->frontsector = sides[ld->sidenum[0]].sector;
else
ld->frontsector = NULL;
if (ld->sidenum[1] != -1)
ld->backsector = sides[ld->sidenum[1]].sector;
else
ld->backsector = NULL;
}
//
// P_LoadLineDefs
// Also counts secret lines for intermissions.
//
void Context::P_LoadLineDefs (int lump)
{
byte* data;
int i;
maplinedef_t* mld;
line_t* ld;
numlines = W_LumpLength (lump) / sizeof(maplinedef_t);
lines = new line_t[numlines];
memset (lines, 0, numlines*sizeof(line_t));
data = W_LoadLump (lump);
mld = (maplinedef_t *)data;
ld = lines;
for (i=0 ; i < numlines ; i++, mld++, ld++)
{
int v1_idx = SHORT(mld->v1);
int v2_idx = SHORT(mld->v2);
if (v1_idx < 0 || v1_idx >= numvertexes ||
v2_idx < 0 || v2_idx >= numvertexes)
{
LevelError("Bad map data : vertex out of range (line #%d)", i);
}
ld->v1 = &vertexes[v1_idx];
ld->v2 = &vertexes[v2_idx];
ld->flags = SHORT(mld->flags);
ld->special = SHORT(mld->special);
ld->tag = SHORT(mld->tag);
ld->sidenum[0] = SHORT(mld->sidenum[0]);
ld->sidenum[1] = SHORT(mld->sidenum[1]);
LineDef_CommonSetup(ld);
}
W_FreeLump(data);
}
// andrewj: added this for Hexen support
void Context::P_LoadLineDefs_Hexen (int lump)
{
byte* data;
int i, k;
maplinedef_hexen_t* mld;
line_t* ld;
numlines = W_LumpLength (lump) / sizeof(maplinedef_hexen_t);
lines = new line_t[numlines];
memset (lines, 0, numlines*sizeof(line_t));
data = W_LoadLump (lump);
mld = (maplinedef_hexen_t *)data;
ld = lines;
for (i=0 ; i < numlines ; i++, mld++, ld++)
{
int v1_idx = SHORT(mld->v1);
int v2_idx = SHORT(mld->v2);
if (v1_idx < 0 || v1_idx >= numvertexes ||
v2_idx < 0 || v2_idx >= numvertexes)
{
LevelError("Bad map data : vertex out of range (line #%d)", i);
}
ld->v1 = &vertexes[v1_idx];
ld->v2 = &vertexes[v2_idx];
ld->flags = SHORT(mld->flags);
ld->special = mld->special;
ld->tag = 0;
ld->sidenum[0] = SHORT(mld->sidenum[0]);
ld->sidenum[1] = SHORT(mld->sidenum[1]);
for (k = 0 ; k < 5 ; k++)
ld->args[k] = mld->args[k];
LineDef_CommonSetup(ld);
}
W_FreeLump(data);
}
//
// P_LoadSideDefs
//
void Context::P_LoadSideDefs (int lump)
{
byte* data;
int i;
mapsidedef_t* msd;
side_t* sd;
numsides = W_LumpLength (lump) / sizeof(mapsidedef_t);
sides = new side_t[numsides];
memset (sides, 0, numsides*sizeof(side_t));
data = W_LoadLump (lump);
msd = (mapsidedef_t *)data;
sd = sides;
for (i=0 ; i < numsides ; i++, msd++, sd++)
{
int sec_idx = SHORT(msd->sector);
// andrewj : silently fix a bad sector number
if (sec_idx < 0 || sec_idx >= numsectors)
{
if (numsectors == 0)
LevelError("Bad map data : no sectors!");
sec_idx = 0;
}
sd->textureoffset = SHORT(msd->textureoffset)<<FRACBITS;
sd->rowoffset = SHORT(msd->rowoffset)<<FRACBITS;
sd->toptexture = R_TextureNumForName(msd->toptexture);
sd->bottomtexture = R_TextureNumForName(msd->bottomtexture);
sd->midtexture = R_TextureNumForName(msd->midtexture);
sd->sector = &sectors[sec_idx];
}
W_FreeLump(data);
}
//
// P_GroupLines
// Builds sector line lists and subsector sector numbers.
// Finds block bounding boxes for sectors.
//
void Context::P_GroupLines (void)
{
line_t** linebuffer;
int i;
int j;
line_t* li;
sector_t* sector;
subsector_t* ss;
seg_t* seg;
fixed_t bbox[4];
int totallines;
// look up sector number for each subsector
ss = subsectors;
for (i=0 ; i < numsubsectors ; i++, ss++)
{
seg = &segs[ss->firstline];
ss->sector = seg->sidedef->sector;
}
// count number of lines in each sector
li = lines;
totallines = 0;
for (i=0 ; i < numlines ; i++, li++)
{
totallines++;
li->frontsector->linecount++;
if (li->backsector && li->backsector != li->frontsector)
{
li->backsector->linecount++;
totallines++;
}
}
// build line tables for each sector
linebuffer = new line_t* [totallines];
for (i=0; i < numsectors; ++i)
{
// Assign the line buffer for this sector
sectors[i].lines = linebuffer;
linebuffer += sectors[i].linecount;
// Reset linecount to zero so in the next stage we can count
// lines into the list.
sectors[i].linecount = 0;
}
// Assign lines to sectors
for (i=0; i < numlines; ++i)
{
li = &lines[i];
if (li->frontsector != NULL)
{
sector = li->frontsector;
sector->lines[sector->linecount] = li;
++sector->linecount;
}
if (li->backsector != NULL && li->frontsector != li->backsector)
{
sector = li->backsector;
sector->lines[sector->linecount] = li;
++sector->linecount;
}
}
// Generate bounding boxes for sectors
sector = sectors;
for (i=0 ; i < numsectors ; i++, sector++)
{
M_ClearBox (bbox);
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);
}
}
// andrewj : generate bounding box for level
M_ClearBox (Map_bbox);
for (i=0; i < numlines; ++i)
{
li = &lines[i];
M_AddToBox (Map_bbox, li->v1->x, li->v1->y);
M_AddToBox (Map_bbox, li->v2->x, li->v2->y);
}
}
//
// andrewj: added this
//
// Find sectors which seem to be doors.
// Main criterion is that sector is closed, and either has a tag
// or one of the linedefs has a manual door type.
//
int Context::HasManualDoor(const sector_t *sec)
{
int k;
for (k = 0 ; k < sec->linecount ; k++)
{
const line_t *L = sec->lines[k];
if (level_is_hexen)
{
switch (L->special)
{
case 10: case 11: case 12: case 13:
case 202: /* zdoom's Generic_Door */
{
if (L->args[0] == 0)
return +1;
}
default: break;
}
}
else
{
switch (L->special)
{
case 1: case 26: case 27: case 28:
case 31: case 32: case 33: case 34:
case 117: case 118:
return +1;
default: break;
}
}
}
return 0;
}
void Context::CalcDoorAltHeight(sector_t *sec)
{
fixed_t door_h = sec->floorheight; // == sec->ceilingheight
// compute lowest ceiling and highest floor of neighbor sectors
fixed_t low_ceil = 32767 << FRACBITS;
fixed_t high_floor = -32767 << FRACBITS;
int k, pass;
for (k = 0 ; k < sec->linecount ; k++)
{
const line_t *L = sec->lines[k];
for (pass = 0 ; pass < 2 ; pass++)
{
const sector_t *nb = pass ? L->backsector : L->frontsector;
if (nb && nb != sec)
{
if (low_ceil > nb->ceilingheight)
low_ceil = nb->ceilingheight;
if (high_floor < nb->floorheight)
high_floor = nb->floorheight;
}
}
}
// see if this is actually a lowering floor
// (like used on MAP12 of DOOM 2)
fixed_t mid_h = (low_ceil >> 1) + (high_floor >> 1);
if (door_h > mid_h)
{
sec->is_door = -1;
sec->alt_height = high_floor;
}
else
{
// is_door already set to +1
sec->alt_height = low_ceil - (4 * FRACUNIT);
}
}
void Context::P_DetectDoorSectors()
{
int i;
for (i = 0 ; i < numsectors ; i++)
{
sector_t *sec = &sectors[i];
if (sec->floorheight != sec->ceilingheight)
continue;
if (sec->tag || HasManualDoor(sec))
{
sec->is_door = +1;
CalcDoorAltHeight(sec);
}
}
}
//
// P_SetupLevel
//
// Returns an error message if something went wrong
// or NULL on success.
//
const char * Context::P_SetupLevel ( const char *lumpname, bool *is_hexen )
{
int base = W_CheckNumForName(lumpname);
if (base < 0 || ! lumpinfo[base].is_map_header)
{
sprintf(level_error_msg, "No such map in wad: %s", lumpname);
return level_error_msg;
}
level_is_hexen = lumpinfo[base].is_hexen;
if (is_hexen)
*is_hexen = level_is_hexen;
// check that we have some nodes
if (lumpinfo[base + ML_SEGS].size == 0)
{
sprintf(level_error_msg, "Missing nodes for: %s", lumpname);
return level_error_msg;
}
W_BeginRead();
try
{
// note: most of this ordering is important
/// P_LoadBlockMap (base + ML_BLOCKMAP);
P_LoadVertexes (base + ML_VERTEXES);
P_LoadSectors (base + ML_SECTORS);
P_LoadSideDefs (base + ML_SIDEDEFS);
if (level_is_hexen)
P_LoadLineDefs_Hexen (base + ML_LINEDEFS);
else
P_LoadLineDefs (base + ML_LINEDEFS);
P_LoadSubsectors (base + ML_SSECTORS);
P_LoadNodes (base + ML_NODES);
P_LoadSegs (base + ML_SEGS);
ValidateSubsectors();
}
catch (invalid_data_exception)
{
W_EndRead();
P_FreeLevelData();
return level_error_msg;
}
W_EndRead();
P_GroupLines ();
// andrewj: added this
P_DetectDoorSectors();
return NULL;
}
void Context::P_FreeLevelData ()
{
if (vertexes)
{
delete[] vertexes;
vertexes = NULL;
numvertexes = 0;
}
if (sectors)
{
delete[] sectors;
sectors = NULL;
numsectors = 0;
}
if (sides)
{
delete[] sides;
sides = NULL;
numsides = 0;
}
if (lines)
{
delete[] lines;
lines = NULL;
numlines = 0;
}
if (segs)
{
delete[] segs;
segs = NULL;
numsegs = 0;
}
if (subsectors)
{
delete[] subsectors;
subsectors = NULL;
numsubsectors = 0;
}
if (nodes)
{
delete[] nodes;
nodes = NULL;
numnodes = 0;
}
}
} // namespace vpo
//--- editor settings ---
// vi:ts=4:sw=4:noexpandtab
// Emacs style mode select -*- C++ -*-