//----------------------------------------------------------------------------- // // 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 "vpo_local.h" namespace vpo { // the error message returned from P_SetupLevel() static char level_error_msg[1024]; // 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 bool level_is_hexen; // // MAP related Lookup tables. // Store VERTEXES, LINEDEFS, SIDEDEFS, etc. // vertex_t* vertexes; int numvertexes; seg_t* segs; int numsegs; sector_t* sectors; int numsectors; subsector_t* subsectors; int numsubsectors; node_t* nodes; int numnodes; line_t* lines; int numlines; side_t* sides; int numsides; fixed_t Map_bbox[4]; 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 } static void 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 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)<y = SHORT(ml->y)<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 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 static void 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 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)<ceilingheight = SHORT(ms->ceilingheight)<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); } static bool isChildValid(unsigned short child) { if (child & NF_SUBSECTOR) return ((child & ~NF_SUBSECTOR) < numsubsectors); else return (child < numnodes); } // // P_LoadNodes // void 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)<y = SHORT(mn->y)<dx = SHORT(mn->dx)<dy = SHORT(mn->dy)<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])<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 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 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 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)<rowoffset = SHORT(msd->rowoffset)<toptexture = R_TextureNumForName(msd->toptexture); sd->bottomtexture = R_TextureNumForName(msd->bottomtexture); sd->midtexture = R_TextureNumForName(msd->midtexture); sd->sector = §ors[sec_idx]; } W_FreeLump(data); } // // P_GroupLines // Builds sector line lists and subsector sector numbers. // Finds block bounding boxes for sectors. // void 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. // static int 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; } static void 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 P_DetectDoorSectors(void) { int i; for (i = 0 ; i < numsectors ; i++) { sector_t *sec = §ors[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 * 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 P_FreeLevelData (void) { 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++ -*-