// Emacs style mode select -*- C++ -*- //----------------------------------------------------------------------------- // // $Id:$ // // Copyright (C) 1993-1996 by id Software, Inc. // // This source is available for distribution and/or modification // only under the terms of the DOOM Source Code License as // published by id Software. All rights reserved. // // The source is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License // for more details. // // $Log:$ // // DESCRIPTION: // Do all the WAD I/O, get map description, // set up initial state and misc. LUTs. // //----------------------------------------------------------------------------- #include #ifdef _MSC_VER #include // for alloca() #endif #include "templates.h" #include "m_argv.h" #include "m_swap.h" #include "m_bbox.h" #include "g_game.h" #include "i_system.h" #include "x86.h" #include "w_wad.h" #include "doomdef.h" #include "p_local.h" #include "p_effect.h" #include "p_terrain.h" #include "nodebuild.h" #include "s_sound.h" #include "doomstat.h" #include "p_lnspec.h" #include "v_palette.h" #include "c_console.h" #include "c_cvars.h" #include "p_acs.h" #include "announcer.h" #include "wi_stuff.h" #include "stats.h" #include "doomerrors.h" #include "gi.h" #include "p_conversation.h" #include "a_keys.h" #include "s_sndseq.h" #include "sbar.h" #include "p_setup.h" #include "r_translate.h" #include "r_interpolate.h" #include "r_sky.h" #include "cmdlib.h" #include "g_level.h" #include "md5.h" #include "compatibility.h" #include "po_man.h" #include "fragglescript/t_fs.h" #define MISSING_TEXTURE_WARN_LIMIT 20 void P_SpawnSlopeMakers (FMapThing *firstmt, FMapThing *lastmt); void P_SetSlopes (); void P_CopySlopes(); void BloodCrypt (void *data, int key, int len); void P_ClearUDMFKeys(); extern AActor *P_SpawnMapThing (FMapThing *mthing, int position); extern bool P_LoadBuildMap (BYTE *mapdata, size_t len, FMapThing **things, int *numthings); extern void P_TranslateTeleportThings (void); void P_ParseTextMap(MapData *map, FMissingTextureTracker &); extern int numinterpolations; extern unsigned int R_OldBlend; EXTERN_CVAR(Bool, am_textured) CVAR (Bool, genblockmap, false, CVAR_SERVERINFO|CVAR_GLOBALCONFIG); CVAR (Bool, gennodes, false, CVAR_SERVERINFO|CVAR_GLOBALCONFIG); CVAR (Bool, genglnodes, false, CVAR_SERVERINFO); CVAR (Bool, showloadtimes, false, 0); static void P_InitTagLists (); static void P_Shutdown (); bool P_IsBuildMap(MapData *map); // // MAP related Lookup tables. // Store VERTEXES, LINEDEFS, SIDEDEFS, etc. // int numvertexes; vertex_t* vertexes; int numvertexdatas; vertexdata_t* vertexdatas; int numsegs; seg_t* segs; glsegextra_t* glsegextras; int numsectors; sector_t* sectors; int numsubsectors; subsector_t* subsectors; int numnodes; node_t* nodes; int numlines; line_t* lines; int numsides; side_t* sides; int numzones; zone_t* zones; node_t * gamenodes; int numgamenodes; subsector_t * gamesubsectors; int numgamesubsectors; bool hasglnodes; TArray MapThingsConverted; int sidecount; sidei_t *sidetemp; TArray linemap; // BLOCKMAP // Created from axis aligned bounding box // of the map, a rectangular array of // blocks of size 256x256. // Used to speed up collision detection // by spatial subdivision in 2D. // // Blockmap size. int bmapwidth; int bmapheight; // size in mapblocks int *blockmap; // int for larger maps ([RH] Made int because BOOM does) int *blockmaplump; // offsets in blockmap are from here fixed_t bmaporgx; // origin of block map fixed_t bmaporgy; FBlockNode** blocklinks; // for thing chains // 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. // BYTE* rejectmatrix; bool ForceNodeBuild; // Maintain single and multi player starting spots. TArray deathmatchstarts (16); FMapThing playerstarts[MAXPLAYERS]; static void P_AllocateSideDefs (int count); //=========================================================================== // // GetMapIndex // // Gets the type of map lump or -1 if invalid or -2 if required and not found. // //=========================================================================== struct checkstruct { const char lumpname[9]; bool required; }; static int GetMapIndex(const char *mapname, int lastindex, const char *lumpname, bool needrequired) { static const checkstruct check[] = { {"", true}, {"THINGS", true}, {"LINEDEFS", true}, {"SIDEDEFS", true}, {"VERTEXES", true}, {"SEGS", false}, {"SSECTORS", false}, {"NODES", false}, {"SECTORS", true}, {"REJECT", false}, {"BLOCKMAP", false}, {"BEHAVIOR", false}, //{"SCRIPTS", false}, }; if (lumpname==NULL) lumpname=""; for(size_t i=lastindex+1;ifile = new FileReader(mapname); map->CloseOnDestruct = true; } else { FString fmt; int lump_wad; int lump_map; int lump_name; // Check for both *.wad and *.map in order to load Build maps // as well. The higher one will take precedence. lump_name = Wads.CheckNumForName(mapname); fmt.Format("maps/%s.wad", mapname); lump_wad = Wads.CheckNumForFullName(fmt); fmt.Format("maps/%s.map", mapname); lump_map = Wads.CheckNumForFullName(fmt); if (lump_name > lump_wad && lump_name > lump_map && lump_name != -1) { int lumpfile = Wads.GetLumpFile(lump_name); int nextfile = Wads.GetLumpFile(lump_name+1); map->file = Wads.GetFileReader(lumpfile); map->CloseOnDestruct = false; map->lumpnum = lump_name; if (lumpfile != nextfile) { // The following lump is from a different file so whatever this is, // it is not a multi-lump Doom level so let's assume it is a Build map. map->MapLumps[0].FilePos = 0; map->MapLumps[0].Size = Wads.LumpLength(lump_name); map->file = Wads.ReopenLumpNum(lump_name); map->CloseOnDestruct = true; if (!P_IsBuildMap(map)) { delete map; return NULL; } return map; } // This case can only happen if the lump is inside a real WAD file. // As such any special handling for other types of lumps is skipped. map->MapLumps[0].FilePos = Wads.GetLumpOffset(lump_name); map->MapLumps[0].Size = Wads.LumpLength(lump_name); map->Encrypted = Wads.IsEncryptedFile(lump_name); if (map->Encrypted) { // If it's encrypted, then it's a Blood file, presumably a map. map->file = Wads.ReopenLumpNum(lump_name); map->CloseOnDestruct = true; map->MapLumps[0].FilePos = 0; if (!P_IsBuildMap(map)) { delete map; return NULL; } return map; } int index = 0; if (stricmp(Wads.GetLumpFullName(lump_name + 1), "TEXTMAP") != 0) { for(int i = 1;; i++) { // Since levels must be stored in WADs they can't really have full // names and for any valid level lump this always returns the short name. const char * lumpname = Wads.GetLumpFullName(lump_name + i); index = GetMapIndex(mapname, index, lumpname, i != 1 || map->MapLumps[0].Size == 0); if (index == ML_BEHAVIOR) map->HasBehavior = true; // The next lump is not part of this map anymore if (index < 0) break; map->MapLumps[index].FilePos = Wads.GetLumpOffset(lump_name + i); map->MapLumps[index].Size = Wads.LumpLength(lump_name + i); strncpy(map->MapLumps[index].Name, lumpname, 8); } } else { map->isText = true; map->MapLumps[1].FilePos = Wads.GetLumpOffset(lump_name + 1); map->MapLumps[1].Size = Wads.LumpLength(lump_name + 1); for(int i = 2;; i++) { const char * lumpname = Wads.GetLumpFullName(lump_name + i); if (lumpname == NULL) { I_Error("Invalid map definition for %s", mapname); } else if (!stricmp(lumpname, "ZNODES")) { index = ML_GLZNODES; } else if (!stricmp(lumpname, "BLOCKMAP")) { // there is no real point in creating a blockmap but let's use it anyway index = ML_BLOCKMAP; } else if (!stricmp(lumpname, "REJECT")) { index = ML_REJECT; } else if (!stricmp(lumpname, "DIALOGUE")) { index = ML_CONVERSATION; } else if (!stricmp(lumpname, "BEHAVIOR")) { index = ML_BEHAVIOR; map->HasBehavior = true; } else if (!stricmp(lumpname, "ENDMAP")) { break; } else continue; map->MapLumps[index].FilePos = Wads.GetLumpOffset(lump_name + i); map->MapLumps[index].Size = Wads.LumpLength(lump_name + i); strncpy(map->MapLumps[index].Name, lumpname, 8); } } return map; } else { if (lump_map > lump_wad) { lump_wad = lump_map; } if (lump_wad == -1) { delete map; return NULL; } map->lumpnum = lump_wad; map->file = Wads.ReopenLumpNum(lump_wad); map->CloseOnDestruct = true; } } DWORD id; map->file->Read(&id, sizeof(id)); if (id == IWAD_ID || id == PWAD_ID) { char maplabel[9]=""; int index=0; DWORD dirofs, numentries; (*map->file) >> numentries >> dirofs; map->file->Seek(dirofs, SEEK_SET); (*map->file) >> map->MapLumps[0].FilePos >> map->MapLumps[0].Size; map->file->Read(map->MapLumps[0].Name, 8); for(DWORD i = 1; i < numentries; i++) { DWORD offset, size; char lumpname[8]; (*map->file) >> offset >> size; map->file->Read(lumpname, 8); if (i == 1 && !strnicmp(lumpname, "TEXTMAP", 8)) { map->isText = true; map->MapLumps[1].FilePos = offset; map->MapLumps[1].Size = size; for(int i = 2;; i++) { (*map->file) >> offset >> size; long v = map->file->Read(lumpname, 8); if (v < 8) { I_Error("Invalid map definition for %s", mapname); } else if (!strnicmp(lumpname, "ZNODES",8)) { index = ML_GLZNODES; } else if (!strnicmp(lumpname, "BLOCKMAP",8)) { // there is no real point in creating a blockmap but let's use it anyway index = ML_BLOCKMAP; } else if (!strnicmp(lumpname, "REJECT",8)) { index = ML_REJECT; } else if (!strnicmp(lumpname, "DIALOGUE",8)) { index = ML_CONVERSATION; } else if (!strnicmp(lumpname, "BEHAVIOR",8)) { index = ML_BEHAVIOR; map->HasBehavior = true; } else if (!strnicmp(lumpname, "ENDMAP",8)) { return map; } else continue; map->MapLumps[index].FilePos = offset; map->MapLumps[index].Size = size; strncpy(map->MapLumps[index].Name, lumpname, 8); } } if (i>0) { index = GetMapIndex(maplabel, index, lumpname, true); if (index == ML_BEHAVIOR) map->HasBehavior = true; // The next lump is not part of this map anymore if (index < 0) break; } else { strncpy(maplabel, lumpname, 8); maplabel[8]=0; } map->MapLumps[index].FilePos = offset; map->MapLumps[index].Size = size; strncpy(map->MapLumps[index].Name, lumpname, 8); } } else { // This is a Build map and not subject to WAD consistency checks. map->MapLumps[0].Size = map->file->GetLength(); if (!P_IsBuildMap(map)) { delete map; return NULL; } } return map; } bool P_CheckMapData(const char *mapname) { MapData *mapd = P_OpenMapData(mapname); if (mapd == NULL) return false; delete mapd; return true; } //=========================================================================== // // MapData :: GetChecksum // // Hashes a map based on its header, THINGS, LINEDEFS, SIDEDEFS, SECTORS, // and BEHAVIOR lumps. Node-builder generated lumps are not included. // //=========================================================================== void MapData::GetChecksum(BYTE cksum[16]) { MD5Context md5; if (file != NULL) { if (isText) { file->Seek(MapLumps[ML_TEXTMAP].FilePos, SEEK_SET); md5.Update(file, MapLumps[ML_TEXTMAP].Size); } else { if (MapLumps[ML_LABEL].Size != 0) { file->Seek(MapLumps[ML_LABEL].FilePos, SEEK_SET); md5.Update(file, MapLumps[ML_LABEL].Size); } file->Seek(MapLumps[ML_THINGS].FilePos, SEEK_SET); md5.Update(file, MapLumps[ML_THINGS].Size); file->Seek(MapLumps[ML_LINEDEFS].FilePos, SEEK_SET); md5.Update(file, MapLumps[ML_LINEDEFS].Size); file->Seek(MapLumps[ML_SIDEDEFS].FilePos, SEEK_SET); md5.Update(file, MapLumps[ML_SIDEDEFS].Size); file->Seek(MapLumps[ML_SECTORS].FilePos, SEEK_SET); md5.Update(file, MapLumps[ML_SECTORS].Size); } if (HasBehavior) { file->Seek(MapLumps[ML_BEHAVIOR].FilePos, SEEK_SET); md5.Update(file, MapLumps[ML_BEHAVIOR].Size); } } md5.Final(cksum); } //=========================================================================== // // Sets a sidedef's texture and prints a message if it's not present. // //=========================================================================== static void SetTexture (side_t *side, int position, const char *name8, FMissingTextureTracker &track) { static const char *positionnames[] = { "top", "middle", "bottom" }; static const char *sidenames[] = { "first", "second" }; char name[9]; strncpy (name, name8, 8); name[8] = 0; FTextureID texture = TexMan.CheckForTexture (name, FTexture::TEX_Wall, FTextureManager::TEXMAN_Overridable|FTextureManager::TEXMAN_TryAny); if (!texture.Exists()) { if (++track[name].Count <= MISSING_TEXTURE_WARN_LIMIT) { // Print an error that lists all references to this sidedef. // We must scan the linedefs manually for all references to this sidedef. for(int i = 0; i < numlines; i++) { for(int j = 0; j < 2; j++) { if (lines[i].sidedef[j] == side) { Printf("Unknown %s texture '%s' on %s side of linedef %d\n", positionnames[position], name, sidenames[j], i); } } } } texture = TexMan.GetDefaultTexture(); } side->SetTexture(position, texture); } //=========================================================================== // // Sets a sidedef's texture and prints a message if it's not present. // (Passing index separately is for UDMF which does not have sectors allocated yet) // //=========================================================================== void SetTexture (sector_t *sector, int index, int position, const char *name8, FMissingTextureTracker &track) { static const char *positionnames[] = { "floor", "ceiling" }; char name[9]; strncpy (name, name8, 8); name[8] = 0; FTextureID texture = TexMan.CheckForTexture (name, FTexture::TEX_Flat, FTextureManager::TEXMAN_Overridable|FTextureManager::TEXMAN_TryAny); if (!texture.Exists()) { if (++track[name].Count <= MISSING_TEXTURE_WARN_LIMIT) { Printf("Unknown %s texture '%s' in sector %d\n", positionnames[position], name, index); } texture = TexMan.GetDefaultTexture(); } sector->SetTexture(position, texture); } //=========================================================================== // // SummarizeMissingTextures // // Lists textures that were missing more than MISSING_TEXTURE_WARN_LIMIT // times. // //=========================================================================== static void SummarizeMissingTextures(const FMissingTextureTracker &missing) { FMissingTextureTracker::ConstIterator it(missing); FMissingTextureTracker::ConstPair *pair; while (it.NextPair(pair)) { if (pair->Value.Count > MISSING_TEXTURE_WARN_LIMIT) { Printf("Missing texture '%s' is used %d more times\n", pair->Key.GetChars(), pair->Value.Count - MISSING_TEXTURE_WARN_LIMIT); } } } //=========================================================================== // // [RH] Figure out blends for deep water sectors // //=========================================================================== static void SetTexture (side_t *side, int position, DWORD *blend, char *name8) { char name[9]; strncpy (name, name8, 8); name[8] = 0; FTextureID texture; if ((*blend = R_ColormapNumForName (name)) == 0) { texture = TexMan.CheckForTexture (name, FTexture::TEX_Wall, FTextureManager::TEXMAN_Overridable|FTextureManager::TEXMAN_TryAny); if (!texture.Exists()) { char name2[9]; char *stop; strncpy (name2, name, 8); name2[8] = 0; *blend = strtoul (name2, &stop, 16); texture = FNullTextureID(); } else { *blend = 0; } } else { texture = FNullTextureID(); } side->SetTexture(position, texture); } static void SetTextureNoErr (side_t *side, int position, DWORD *color, char *name8, bool *validcolor, bool isFog) { char name[9]; FTextureID texture; strncpy (name, name8, 8); name[8] = 0; *validcolor = false; texture = TexMan.CheckForTexture (name, FTexture::TEX_Wall, FTextureManager::TEXMAN_Overridable|FTextureManager::TEXMAN_TryAny); if (!texture.Exists()) { char name2[9]; char *stop; strncpy (name2, name+1, 7); name2[7] = 0; if (*name != '#') { *color = strtoul (name, &stop, 16); texture = FNullTextureID(); *validcolor = (*stop == 0) && (stop >= name + 2) && (stop <= name + 6); return; } else // Support for Legacy's color format! { int l=(int)strlen(name); texture = FNullTextureID(); *validcolor = false; if (l>=7) { for(stop=name2;stop ((name2[6]&223)-'A', 0, 25); name2[6]=0; int blue=strtol(name2+4,NULL,16); name2[4]=0; int green=strtol(name2+2,NULL,16); name2[2]=0; int red=strtol(name2,NULL,16); if (!isFog) { if (factor==0) { *validcolor=false; return; } factor = factor * 255 / 25; } else { factor=0; } *color=MAKEARGB(factor, red, green, blue); texture = FNullTextureID(); *validcolor = true; return; } } texture = FNullTextureID(); } side->SetTexture(position, texture); } //=========================================================================== // // Sound enviroment handling // //=========================================================================== void P_FloodZone (sector_t *sec, int zonenum) { int i; if (sec->ZoneNumber == zonenum) return; sec->ZoneNumber = zonenum; for (i = 0; i < sec->linecount; ++i) { line_t *check = sec->lines[i]; sector_t *other; if (check->sidedef[1] == NULL || (check->flags & ML_ZONEBOUNDARY)) continue; if (check->frontsector == sec) { assert(check->backsector != NULL); other = check->backsector; } else { assert(check->frontsector != NULL); other = check->frontsector; } if (other->ZoneNumber != zonenum) P_FloodZone (other, zonenum); } } void P_FloodZones () { int z = 0, i; ReverbContainer *reverb; for (i = 0; i < numsectors; ++i) { if (sectors[i].ZoneNumber == 0xFFFF) { P_FloodZone (§ors[i], z++); } } numzones = z; zones = new zone_t[z]; reverb = S_FindEnvironment(level.DefaultEnvironment); if (reverb == NULL) { Printf("Sound environment %d, %d not found\n", level.DefaultEnvironment >> 8, level.DefaultEnvironment & 255); reverb = DefaultEnvironments[0]; } for (i = 0; i < z; ++i) { zones[i].Environment = reverb; } } //=========================================================================== // // P_LoadVertexes // //=========================================================================== void P_LoadVertexes (MapData * map) { int i; // Determine number of vertices: // total lump length / vertex record length. numvertexes = map->MapLumps[ML_VERTEXES].Size / sizeof(mapvertex_t); numvertexdatas = 0; if (numvertexes == 0) { I_Error ("Map has no vertices.\n"); } // Allocate memory for buffer. vertexes = new vertex_t[numvertexes]; vertexdatas = NULL; map->Seek(ML_VERTEXES); // Copy and convert vertex coordinates, internal representation as fixed. for (i = 0; i < numvertexes; i++) { SWORD x, y; (*map->file) >> x >> y; vertexes[i].x = x << FRACBITS; vertexes[i].y = y << FRACBITS; } } //=========================================================================== // // P_LoadZSegs // //=========================================================================== void P_LoadZSegs (FileReaderBase &data) { for (int i = 0; i < numsegs; ++i) { line_t *ldef; DWORD v1, v2; WORD line; BYTE side; data >> v1 >> v2 >> line >> side; segs[i].v1 = &vertexes[v1]; segs[i].v2 = &vertexes[v2]; segs[i].linedef = ldef = &lines[line]; segs[i].sidedef = ldef->sidedef[side]; segs[i].frontsector = ldef->sidedef[side]->sector; if (ldef->flags & ML_TWOSIDED && ldef->sidedef[side^1] != NULL) { segs[i].backsector = ldef->sidedef[side^1]->sector; } else { segs[i].backsector = 0; ldef->flags &= ~ML_TWOSIDED; } } } //=========================================================================== // // P_LoadGLZSegs // // This is the GL nodes version of the above function. // //=========================================================================== void P_LoadGLZSegs (FileReaderBase &data, int type) { for (int i = 0; i < numsubsectors; ++i) { for (size_t j = 0; j < subsectors[i].numlines; ++j) { seg_t *seg; DWORD v1, partner; DWORD line; WORD lineword; BYTE side; data >> v1 >> partner; if (type == 2) { data >> line; } else { data >> lineword; line = lineword == 0xFFFF ? 0xFFFFFFFF : lineword; } data >> side; seg = subsectors[i].firstline + j; seg->v1 = &vertexes[v1]; if (j == 0) { seg[subsectors[i].numlines - 1].v2 = seg->v1; } else { seg[-1].v2 = seg->v1; } glsegextras[seg - segs].PartnerSeg = partner; if (line != 0xFFFFFFFF) { line_t *ldef; seg->linedef = ldef = &lines[line]; seg->sidedef = ldef->sidedef[side]; seg->frontsector = ldef->sidedef[side]->sector; if (ldef->flags & ML_TWOSIDED && ldef->sidedef[side^1] != NULL) { seg->backsector = ldef->sidedef[side^1]->sector; } else { seg->backsector = 0; ldef->flags &= ~ML_TWOSIDED; } } else { seg->linedef = NULL; seg->sidedef = NULL; seg->frontsector = seg->backsector = subsectors[i].firstline->frontsector; } } } } //=========================================================================== // // P_LoadZNodes // //=========================================================================== void LoadZNodes(FileReaderBase &data, int glnodes) { // Read extra vertices added during node building DWORD orgVerts, newVerts; vertex_t *newvertarray; unsigned int i; data >> orgVerts >> newVerts; if (orgVerts + newVerts == (DWORD)numvertexes) { newvertarray = vertexes; } else { newvertarray = new vertex_t[orgVerts + newVerts]; memcpy (newvertarray, vertexes, orgVerts * sizeof(vertex_t)); } for (i = 0; i < newVerts; ++i) { data >> newvertarray[i + orgVerts].x >> newvertarray[i + orgVerts].y; } if (vertexes != newvertarray) { for (i = 0; i < (DWORD)numlines; ++i) { lines[i].v1 = lines[i].v1 - vertexes + newvertarray; lines[i].v2 = lines[i].v2 - vertexes + newvertarray; } delete[] vertexes; vertexes = newvertarray; numvertexes = orgVerts + newVerts; } // Read the subsectors DWORD numSubs, currSeg; data >> numSubs; numsubsectors = numSubs; subsectors = new subsector_t[numSubs]; memset (subsectors, 0, numsubsectors*sizeof(subsector_t)); for (i = currSeg = 0; i < numSubs; ++i) { DWORD numsegs; data >> numsegs; subsectors[i].firstline = (seg_t *)(size_t)currSeg; // Oh damn. I should have stored the seg count sooner. subsectors[i].numlines = numsegs; currSeg += numsegs; } // Read the segs DWORD numSegs; data >> numSegs; // The number of segs stored should match the number of // segs used by subsectors. if (numSegs != currSeg) { Printf ("Incorrect number of segs in nodes.\n"); delete[] subsectors; ForceNodeBuild = true; return; } numsegs = numSegs; segs = new seg_t[numsegs]; memset (segs, 0, numsegs*sizeof(seg_t)); glsegextras = NULL; for (i = 0; i < numSubs; ++i) { subsectors[i].firstline = &segs[(size_t)subsectors[i].firstline]; } if (glnodes == 0) { P_LoadZSegs (data); } else { glsegextras = new glsegextra_t[numsegs]; P_LoadGLZSegs (data, glnodes); } // Read nodes DWORD numNodes; data >> numNodes; numnodes = numNodes; nodes = new node_t[numNodes]; memset (nodes, 0, sizeof(node_t)*numNodes); for (i = 0; i < numNodes; ++i) { SWORD x, y, dx, dy; data >> x >> y >> dx >> dy; nodes[i].x = x << FRACBITS; nodes[i].y = y << FRACBITS; nodes[i].dx = dx << FRACBITS; nodes[i].dy = dy << FRACBITS; for (int j = 0; j < 2; ++j) { for (int k = 0; k < 4; ++k) { SWORD coord; data >> coord; nodes[i].bbox[j][k] = coord << FRACBITS; } } for (int m = 0; m < 2; ++m) { DWORD child; data >> child; if (child & 0x80000000) { nodes[i].children[m] = (BYTE *)&subsectors[child & 0x7FFFFFFF] + 1; } else { nodes[i].children[m] = &nodes[child]; } } } } void P_LoadZNodes (FileReader &dalump, DWORD id) { int type; bool compressed; switch (id) { case MAKE_ID('Z','N','O','D'): type = 0; compressed = true; break; case MAKE_ID('Z','G','L','N'): type = 1; compressed = true; break; case MAKE_ID('Z','G','L','2'): type = 2; compressed = true; break; case MAKE_ID('X','N','O','D'): type = 0; compressed = false; break; case MAKE_ID('X','G','L','N'): type = 1; compressed = false; break; case MAKE_ID('X','G','L','2'): type = 2; compressed = false; break; default: return; } if (compressed) { FileReaderZ data (dalump); LoadZNodes(data, type); } else { LoadZNodes(dalump, type); } } //=========================================================================== // // P_CheckV4Nodes // http://www.sbsoftware.com/files/DeePBSPV4specs.txt // //=========================================================================== static bool P_CheckV4Nodes(MapData *map) { char header[8]; map->Read(ML_NODES, header, 8); return !memcmp(header, "xNd4\0\0\0\0", 8); } //=========================================================================== // // P_LoadSegs // // killough 5/3/98: reformatted, cleaned up // //=========================================================================== struct badseg { badseg(int t, int s, int d) : badtype(t), badsegnum(s), baddata(d) {} int badtype; int badsegnum; int baddata; }; template void P_LoadSegs (MapData * map) { int i; BYTE *data; BYTE *vertchanged = new BYTE[numvertexes]; // phares 10/4/98 DWORD segangle; line_t* line; // phares 10/4/98 int ptp_angle; // phares 10/4/98 int delta_angle; // phares 10/4/98 int dis; // phares 10/4/98 int dx,dy; // phares 10/4/98 int vnum1,vnum2; // phares 10/4/98 int lumplen = map->MapLumps[ML_SEGS].Size; memset (vertchanged,0,numvertexes); // phares 10/4/98 numsegs = lumplen / sizeof(segtype); if (numsegs == 0) { Printf ("This map has no segs.\n"); delete[] subsectors; delete[] nodes; delete[] vertchanged; ForceNodeBuild = true; return; } segs = new seg_t[numsegs]; memset (segs, 0, numsegs*sizeof(seg_t)); data = new BYTE[lumplen]; map->Read(ML_SEGS, data); for (i = 0; i < numsubsectors; ++i) { subsectors[i].firstline = &segs[(size_t)subsectors[i].firstline]; } // phares: 10/4/98: Vertchanged is an array that represents the vertices. // Mark those used by linedefs. A marked vertex is one that is not a // candidate for movement further down. line = lines; for (i = 0; i < numlines ; i++, line++) { vertchanged[line->v1 - vertexes] = vertchanged[line->v2 - vertexes] = 1; } try { for (i = 0; i < numsegs; i++) { seg_t *li = segs + i; segtype *ml = ((segtype *) data) + i; int side, linedef; line_t *ldef; vnum1 = ml->V1(); vnum2 = ml->V2(); if (vnum1 >= numvertexes || vnum2 >= numvertexes) { throw badseg(0, i, MAX(vnum1, vnum2)); } li->v1 = &vertexes[vnum1]; li->v2 = &vertexes[vnum2]; segangle = (WORD)LittleShort(ml->angle); // phares 10/4/98: In the case of a lineseg that was created by splitting // another line, it appears that the line angle is inherited from the // father line. Due to roundoff, the new vertex may have been placed 'off // the line'. When you get close to such a line, and it is very short, // it's possible that the roundoff error causes 'firelines', the thin // lines that can draw from screen top to screen bottom occasionally. This // is due to all the angle calculations that are done based on the line // angle, the angles from the viewer to the vertices, and the viewer's // angle in the world. In the case of firelines, the rounded-off position // of one of the vertices determines one of these angles, and introduces // an error in the scaling factor for mapping textures and determining // where on the screen the ceiling and floor spans should be shown. For a // fireline, the engine thinks the ceiling bottom and floor top are at the // midpoint of the screen. So you get ceilings drawn all the way down to the // screen midpoint, and floors drawn all the way up. Thus 'firelines'. The // name comes from the original sighting, which involved a fire texture. // // To correct this, reset the vertex that was added so that it sits ON the // split line. // // To know which of the two vertices was added, its number is greater than // that of the last of the author-created vertices. If both vertices of the // line were added by splitting, pick the higher-numbered one. Once you've // changed a vertex, don't change it again if it shows up in another seg. // // To determine if there's an error in the first place, find the // angle of the line between the two seg vertices. If it's one degree or more // off, then move one vertex. This may seem insignificant, but one degree // errors _can_ cause firelines. ptp_angle = R_PointToAngle2 (li->v1->x, li->v1->y, li->v2->x, li->v2->y); dis = 0; delta_angle = (abs(ptp_angle-(segangle<<16))>>ANGLETOFINESHIFT)*360/FINEANGLES; if (delta_angle != 0) { segangle >>= (ANGLETOFINESHIFT-16); dx = (li->v1->x - li->v2->x)>>FRACBITS; dy = (li->v1->y - li->v2->y)>>FRACBITS; dis = ((int) sqrt((double)(dx*dx + dy*dy)))< vnum1) && (vertchanged[vnum2] == 0)) { li->v2->x = li->v1->x + FixedMul(dis,dx); li->v2->y = li->v1->y + FixedMul(dis,dy); vertchanged[vnum2] = 1; // this was changed } else if (vertchanged[vnum1] == 0) { li->v1->x = li->v2->x - FixedMul(dis,dx); li->v1->y = li->v2->y - FixedMul(dis,dy); vertchanged[vnum1] = 1; // this was changed } } linedef = LittleShort(ml->linedef); if ((unsigned)linedef >= (unsigned)numlines) { throw badseg(1, i, linedef); } ldef = &lines[linedef]; li->linedef = ldef; side = LittleShort(ml->side); if ((unsigned)(ldef->sidedef[side] - sides) >= (unsigned)numsides) { throw badseg(2, i, int(ldef->sidedef[side] - sides)); } li->sidedef = ldef->sidedef[side]; li->frontsector = ldef->sidedef[side]->sector; // killough 5/3/98: ignore 2s flag if second sidedef missing: if (ldef->flags & ML_TWOSIDED && ldef->sidedef[side^1] != NULL) { li->backsector = ldef->sidedef[side^1]->sector; } else { li->backsector = 0; ldef->flags &= ~ML_TWOSIDED; } } } catch (badseg bad) { switch (bad.badtype) { case 0: Printf ("Seg %d references a nonexistant vertex %d (max %d).\n", bad.badsegnum, bad.baddata, numvertexes); break; case 1: Printf ("Seg %d references a nonexistant linedef %d (max %d).\n", bad.badsegnum, bad.baddata, numlines); break; case 2: Printf ("The linedef for seg %d references a nonexistant sidedef %d (max %d).\n", bad.badsegnum, bad.baddata, numsides); break; } Printf ("The BSP will be rebuilt.\n"); delete[] segs; delete[] subsectors; delete[] nodes; ForceNodeBuild = true; } delete[] vertchanged; // phares 10/4/98 delete[] data; } //=========================================================================== // // P_LoadSubsectors // //=========================================================================== template void P_LoadSubsectors (MapData * map) { int i; DWORD maxseg = map->Size(ML_SEGS) / sizeof(segtype); numsubsectors = map->MapLumps[ML_SSECTORS].Size / sizeof(subsectortype); if (numsubsectors == 0 || maxseg == 0 ) { Printf ("This map has an incomplete BSP tree.\n"); delete[] nodes; ForceNodeBuild = true; return; } subsectors = new subsector_t[numsubsectors]; map->Seek(ML_SSECTORS); memset (subsectors, 0, numsubsectors*sizeof(subsector_t)); for (i = 0; i < numsubsectors; i++) { subsectortype subd; (*map->file) >> subd.numsegs >> subd.firstseg; if (subd.numsegs == 0) { Printf ("Subsector %i is empty.\n", i); delete[] subsectors; delete[] nodes; ForceNodeBuild = true; return; } subsectors[i].numlines = subd.numsegs; subsectors[i].firstline = (seg_t *)(size_t)subd.firstseg; if ((size_t)subsectors[i].firstline >= maxseg) { Printf ("Subsector %d contains invalid segs %u-%u\n" "The BSP will be rebuilt.\n", i, (unsigned)((size_t)subsectors[i].firstline), (unsigned)((size_t)subsectors[i].firstline) + subsectors[i].numlines - 1); ForceNodeBuild = true; delete[] nodes; delete[] subsectors; break; } else if ((size_t)subsectors[i].firstline + subsectors[i].numlines > maxseg) { Printf ("Subsector %d contains invalid segs %u-%u\n" "The BSP will be rebuilt.\n", i, maxseg, (unsigned)((size_t)subsectors[i].firstline) + subsectors[i].numlines - 1); ForceNodeBuild = true; delete[] nodes; delete[] subsectors; break; } } } //=========================================================================== // // P_LoadSectors // //=========================================================================== void P_LoadSectors (MapData *map, FMissingTextureTracker &missingtex) { char fname[9]; int i; char *msp; mapsector_t *ms; sector_t* ss; int defSeqType; FDynamicColormap *fogMap, *normMap; int lumplen = map->Size(ML_SECTORS); numsectors = lumplen / sizeof(mapsector_t); sectors = new sector_t[numsectors]; memset (sectors, 0, numsectors*sizeof(sector_t)); if (level.flags & LEVEL_SNDSEQTOTALCTRL) defSeqType = 0; else defSeqType = -1; fogMap = normMap = NULL; fname[8] = 0; msp = new char[lumplen]; map->Read(ML_SECTORS, msp); ms = (mapsector_t*)msp; ss = sectors; // Extended properties sectors[0].e = new extsector_t[numsectors]; for (i = 0; i < numsectors; i++, ss++, ms++) { ss->e = §ors[0].e[i]; if (!map->HasBehavior) ss->Flags |= SECF_FLOORDROP; ss->SetPlaneTexZ(sector_t::floor, LittleShort(ms->floorheight)<floorplane.d = -ss->GetPlaneTexZ(sector_t::floor); ss->floorplane.c = FRACUNIT; ss->floorplane.ic = FRACUNIT; ss->SetPlaneTexZ(sector_t::ceiling, LittleShort(ms->ceilingheight)<ceilingplane.d = ss->GetPlaneTexZ(sector_t::ceiling); ss->ceilingplane.c = -FRACUNIT; ss->ceilingplane.ic = -FRACUNIT; SetTexture(ss, i, sector_t::floor, ms->floorpic, missingtex); SetTexture(ss, i, sector_t::ceiling, ms->ceilingpic, missingtex); ss->lightlevel = (BYTE)clamp (LittleShort(ms->lightlevel), (short)0, (short)255); if (map->HasBehavior) ss->special = LittleShort(ms->special); else // [RH] Translate to new sector special ss->special = P_TranslateSectorSpecial (LittleShort(ms->special)); ss->secretsector = !!(ss->special&SECRET_MASK); ss->tag = LittleShort(ms->tag); ss->thinglist = NULL; ss->touching_thinglist = NULL; // phares 3/14/98 ss->seqType = defSeqType; ss->SeqName = NAME_None; ss->nextsec = -1; //jff 2/26/98 add fields to support locking out ss->prevsec = -1; // stair retriggering until build completes ss->SetAlpha(sector_t::floor, FRACUNIT); ss->SetAlpha(sector_t::ceiling, FRACUNIT); ss->SetXScale(sector_t::floor, FRACUNIT); // [RH] floor and ceiling scaling ss->SetYScale(sector_t::floor, FRACUNIT); ss->SetXScale(sector_t::ceiling, FRACUNIT); ss->SetYScale(sector_t::ceiling, FRACUNIT); ss->heightsec = NULL; // sector used to get floor and ceiling height // killough 3/7/98: end changes ss->gravity = 1.f; // [RH] Default sector gravity of 1.0 ss->ZoneNumber = 0xFFFF; // [RH] Sectors default to white light with the default fade. // If they are outside (have a sky ceiling), they use the outside fog. if (level.outsidefog != 0xff000000 && (ss->GetTexture(sector_t::ceiling) == skyflatnum || (ss->special&0xff) == Sector_Outside)) { if (fogMap == NULL) fogMap = GetSpecialLights (PalEntry (255,255,255), level.outsidefog, 0); ss->ColorMap = fogMap; } else { if (normMap == NULL) normMap = GetSpecialLights (PalEntry (255,255,255), level.fadeto, NormalLight.Desaturate); ss->ColorMap = normMap; } // killough 8/28/98: initialize all sectors to normal friction ss->friction = ORIG_FRICTION; ss->movefactor = ORIG_FRICTION_FACTOR; ss->sectornum = i; } delete[] msp; } //=========================================================================== // // P_LoadNodes // //=========================================================================== template void P_LoadNodes (MapData * map) { FMemLump data; int i; int j; int k; char *mnp; nodetype *mn; node_t* no; WORD* used; int lumplen = map->Size(ML_NODES); int maxss = map->Size(ML_SSECTORS) / sizeof(subsectortype); numnodes = (lumplen - nodetype::NF_LUMPOFFSET) / sizeof(nodetype); if ((numnodes == 0 && maxss != 1) || maxss == 0) { ForceNodeBuild = true; return; } nodes = new node_t[numnodes]; used = (WORD *)alloca (sizeof(WORD)*numnodes); memset (used, 0, sizeof(WORD)*numnodes); mnp = new char[lumplen]; mn = (nodetype*)(mnp + nodetype::NF_LUMPOFFSET); map->Read(ML_NODES, mnp); no = nodes; for (i = 0; i < numnodes; i++, no++, mn++) { no->x = LittleShort(mn->x)<y = LittleShort(mn->y)<dx = LittleShort(mn->dx)<dy = LittleShort(mn->dy)<Child(j); if (child & nodetype::NF_SUBSECTOR) { child &= ~nodetype::NF_SUBSECTOR; if (child >= maxss) { Printf ("BSP node %d references invalid subsector %d.\n" "The BSP will be rebuilt.\n", i, child); ForceNodeBuild = true; delete[] nodes; delete[] mnp; return; } no->children[j] = (BYTE *)&subsectors[child] + 1; } else if (child >= numnodes) { Printf ("BSP node %d references invalid node %td.\n" "The BSP will be rebuilt.\n", i, (node_t *)no->children[j] - nodes); ForceNodeBuild = true; delete[] nodes; delete[] mnp; return; } else if (used[child]) { Printf ("BSP node %d references node %d,\n" "which is already used by node %d.\n" "The BSP will be rebuilt.\n", i, child, used[child]-1); ForceNodeBuild = true; delete[] nodes; delete[] mnp; return; } else { no->children[j] = &nodes[child]; used[child] = j + 1; } for (k = 0; k < 4; k++) { no->bbox[j][k] = LittleShort(mn->bbox[j][k])<x>>FRACBITS, mt->y>>FRACBITS, mt->z>>FRACBITS, mt->type, mt->flags, spawned? spawned->GetClass()->TypeName.GetChars() : "(none)"); } T_AddSpawnedThing(spawned); } //=========================================================================== // // P_LoadThings // //=========================================================================== WORD MakeSkill(int flags) { WORD res = 0; if (flags & 1) res |= 1+2; if (flags & 2) res |= 4; if (flags & 4) res |= 8+16; return res; } void P_LoadThings (MapData * map) { int lumplen = map->Size(ML_THINGS); int numthings = lumplen / sizeof(mapthing_t); char *mtp; mapthing_t *mt; mtp = new char[lumplen]; map->Read(ML_THINGS, mtp); mt = (mapthing_t*)mtp; MapThingsConverted.Resize(numthings); FMapThing *mti = &MapThingsConverted[0]; // [RH] ZDoom now uses Hexen-style maps as its native format. // Since this is the only place where Doom-style Things are ever // referenced, we translate them into a Hexen-style thing. for (int i=0 ; i < numthings; i++, mt++) { // [RH] At this point, monsters unique to Doom II were weeded out // if the IWAD wasn't for Doom II. R_SpawnMapThing() can now // handle these and more cases better, so we just pass it // everything and let it decide what to do with them. // [RH] Need to translate the spawn flags to Hexen format. short flags = LittleShort(mt->options); memset (&mti[i], 0, sizeof(mti[i])); mti[i].Conversation = 0; mti[i].SkillFilter = MakeSkill(flags); mti[i].ClassFilter = 0xffff; // Doom map format doesn't have class flags so spawn for all player classes flags &= ~MTF_SKILLMASK; mti[i].flags = (short)((flags & 0xf) | 0x7e0); if (gameinfo.gametype == GAME_Strife) { mti[i].flags &= ~MTF_AMBUSH; if (flags & STF_SHADOW) mti[i].flags |= MTF_SHADOW; if (flags & STF_ALTSHADOW) mti[i].flags |= MTF_ALTSHADOW; if (flags & STF_STANDSTILL) mti[i].flags |= MTF_STANDSTILL; if (flags & STF_AMBUSH) mti[i].flags |= MTF_AMBUSH; if (flags & STF_FRIENDLY) mti[i].flags |= MTF_FRIENDLY; } else { if (flags & BTF_BADEDITORCHECK) { flags &= 0x1F; } if (flags & BTF_NOTDEATHMATCH) mti[i].flags &= ~MTF_DEATHMATCH; if (flags & BTF_NOTCOOPERATIVE) mti[i].flags &= ~MTF_COOPERATIVE; if (flags & BTF_FRIENDLY) mti[i].flags |= MTF_FRIENDLY; } if (flags & BTF_NOTSINGLE) mti[i].flags &= ~MTF_SINGLE; mti[i].x = LittleShort(mt->x) << FRACBITS; mti[i].y = LittleShort(mt->y) << FRACBITS; mti[i].angle = LittleShort(mt->angle); mti[i].type = LittleShort(mt->type); } delete [] mtp; } //=========================================================================== // // [RH] // P_LoadThings2 // // Same as P_LoadThings() except it assumes Things are // saved Hexen-style. Position also controls which single- // player start spots are spawned by filtering out those // whose first parameter don't match position. // //=========================================================================== void P_LoadThings2 (MapData * map) { int lumplen = map->MapLumps[ML_THINGS].Size; int numthings = lumplen / sizeof(mapthinghexen_t); char *mtp; MapThingsConverted.Resize(numthings); FMapThing *mti = &MapThingsConverted[0]; mtp = new char[lumplen]; map->Read(ML_THINGS, mtp); mapthinghexen_t *mth = (mapthinghexen_t*)mtp; for(int i = 0; i< numthings; i++) { mti[i].thingid = LittleShort(mth[i].thingid); mti[i].x = LittleShort(mth[i].x)<> MTF_CLASS_SHIFT; mti[i].flags &= ~(MTF_SKILLMASK|MTF_CLASS_MASK); mti[i].Conversation = 0; } delete[] mtp; } void P_SpawnThings (int position) { int numthings = MapThingsConverted.Size(); for (int i=0; i < numthings; i++) { SpawnMapThing (i, &MapThingsConverted[i], position); } for(int i=0; iv1; v2 = ld->v2; ld->dx = v2->x - v1->x; ld->dy = v2->y - v1->y; if (ld->dx == 0) ld->slopetype = ST_VERTICAL; else if (ld->dy == 0) ld->slopetype = ST_HORIZONTAL; else ld->slopetype = ((ld->dy ^ ld->dx) >= 0) ? ST_POSITIVE : 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; } } void P_SetLineID (line_t *ld) { // [RH] Set line id (as appropriate) here // for Doom format maps this must be done in P_TranslateLineDef because // the tag doesn't always go into the first arg. if (level.maptype == MAPTYPE_HEXEN) { switch (ld->special) { case Line_SetIdentification: if (!(level.flags2 & LEVEL2_HEXENHACK)) { ld->id = ld->args[0] + 256 * ld->args[4]; ld->flags |= ld->args[1]<<16; } else { ld->id = ld->args[0]; } ld->special = 0; break; case TranslucentLine: ld->id = ld->args[0]; ld->flags |= ld->args[3]<<16; break; case Teleport_Line: case Scroll_Texture_Model: ld->id = ld->args[0]; break; case Polyobj_StartLine: ld->id = ld->args[3]; break; case Polyobj_ExplicitLine: ld->id = ld->args[4]; break; case Plane_Align: ld->id = ld->args[2]; break; case Static_Init: if (ld->args[1] == Init_SectorLink) ld->id = ld->args[0]; break; } } } void P_SaveLineSpecial (line_t *ld) { if (ld->sidedef[0] == NULL) return; DWORD sidenum = DWORD(ld->sidedef[0]-sides); // killough 4/4/98: support special sidedef interpretation below // [RH] Save Static_Init only if it's interested in the textures if (ld->special != Static_Init || ld->args[1] == Init_Color) { sidetemp[sidenum].a.special = ld->special; sidetemp[sidenum].a.tag = ld->args[0]; } else { sidetemp[sidenum].a.special = 0; } } void P_FinishLoadingLineDef(line_t *ld, int alpha) { bool additive = false; ld->frontsector = ld->sidedef[0] != NULL ? ld->sidedef[0]->sector : NULL; ld->backsector = ld->sidedef[1] != NULL ? ld->sidedef[1]->sector : NULL; double dx = FIXED2DBL(ld->v2->x - ld->v1->x); double dy = FIXED2DBL(ld->v2->y - ld->v1->y); int linenum = int(ld-lines); if (ld->frontsector == NULL) { Printf ("Line %d has no front sector\n", linemap[linenum]); } // [RH] Set some new sidedef properties int len = (int)(sqrt (dx*dx + dy*dy) + 0.5f); if (ld->sidedef[0] != NULL) { ld->sidedef[0]->linedef = ld; ld->sidedef[0]->TexelLength = len; } if (ld->sidedef[1] != NULL) { ld->sidedef[1]->linedef = ld; ld->sidedef[1]->TexelLength = len; } switch (ld->special) { // killough 4/11/98: handle special types int j; case TranslucentLine: // killough 4/11/98: translucent 2s textures // [RH] Second arg controls how opaque it is. if (alpha == SHRT_MIN) { alpha = ld->args[1]; additive = !!ld->args[2]; } else if (alpha < 0) { alpha = -alpha; additive = true; } alpha = Scale(alpha, FRACUNIT, 255); if (!ld->args[0]) { ld->Alpha = alpha; if (additive) { ld->flags |= ML_ADDTRANS; } } else { for (j = 0; j < numlines; j++) { if (lines[j].id == ld->args[0]) { lines[j].Alpha = alpha; if (additive) { lines[j].flags |= ML_ADDTRANS; } } } } ld->special = 0; break; } } // killough 4/4/98: delay using sidedefs until they are loaded void P_FinishLoadingLineDefs () { for (int i = 0; i < numlines; i++) { P_FinishLoadingLineDef(&lines[i], sidetemp[lines[i].sidedef[0]-sides].a.alpha); } } static void P_SetSideNum (side_t **sidenum_p, WORD sidenum) { if (sidenum == NO_INDEX) { *sidenum_p = NULL; } else if (sidecount < numsides) { sidetemp[sidecount].a.map = sidenum; *sidenum_p = &sides[sidecount++]; } else { I_Error ("%d sidedefs is not enough\n", sidecount); } } void P_LoadLineDefs (MapData * map) { int i, skipped; line_t *ld; int lumplen = map->Size(ML_LINEDEFS); char * mldf; maplinedef_t *mld; numlines = lumplen / sizeof(maplinedef_t); lines = new line_t[numlines]; linemap.Resize(numlines); memset (lines, 0, numlines*sizeof(line_t)); mldf = new char[lumplen]; map->Read(ML_LINEDEFS, mldf); // [RH] Count the number of sidedef references. This is the number of // sidedefs we need. The actual number in the SIDEDEFS lump might be less. // Lines with 0 length are also removed. for (skipped = sidecount = i = 0; i < numlines; ) { mld = ((maplinedef_t*)mldf) + i; int v1 = LittleShort(mld->v1); int v2 = LittleShort(mld->v2); if (v1 >= numvertexes || v2 >= numvertexes) { delete [] mldf; I_Error ("Line %d has invalid vertices: %d and/or %d.\nThe map only contains %d vertices.", i+skipped, v1, v2, numvertexes); } else if (v1 == v2 || (vertexes[LittleShort(mld->v1)].x == vertexes[LittleShort(mld->v2)].x && vertexes[LittleShort(mld->v1)].y == vertexes[LittleShort(mld->v2)].y)) { Printf ("Removing 0-length line %d\n", i+skipped); memmove (mld, mld+1, sizeof(*mld)*(numlines-i-1)); ForceNodeBuild = true; skipped++; numlines--; } else { // patch missing first sides instead of crashing out. // Visual glitches are better than not being able to play. if (LittleShort(mld->sidenum[0]) == NO_INDEX) { Printf("Line %d has no first side.\n", i); mld->sidenum[0] = 0; } sidecount++; if (LittleShort(mld->sidenum[1]) != NO_INDEX) sidecount++; linemap[i] = i+skipped; i++; } } P_AllocateSideDefs (sidecount); mld = (maplinedef_t *)mldf; ld = lines; for (i = numlines; i > 0; i--, mld++, ld++) { ld->Alpha = FRACUNIT; // [RH] Opaque by default // [RH] Translate old linedef special and flags to be // compatible with the new format. P_TranslateLineDef (ld, mld); ld->v1 = &vertexes[LittleShort(mld->v1)]; ld->v2 = &vertexes[LittleShort(mld->v2)]; //ld->id = -1; ID has been assigned in P_TranslateLineDef P_SetSideNum (&ld->sidedef[0], LittleShort(mld->sidenum[0])); P_SetSideNum (&ld->sidedef[1], LittleShort(mld->sidenum[1])); P_AdjustLine (ld); P_SaveLineSpecial (ld); if (level.flags2 & LEVEL2_CLIPMIDTEX) ld->flags |= ML_CLIP_MIDTEX; if (level.flags2 & LEVEL2_WRAPMIDTEX) ld->flags |= ML_WRAP_MIDTEX; if (level.flags2 & LEVEL2_CHECKSWITCHRANGE) ld->flags |= ML_CHECKSWITCHRANGE; } delete[] mldf; } // [RH] Same as P_LoadLineDefs() except it uses Hexen-style LineDefs. void P_LoadLineDefs2 (MapData * map) { int i, skipped; line_t *ld; int lumplen = map->Size(ML_LINEDEFS); char * mldf; maplinedef2_t *mld; numlines = lumplen / sizeof(maplinedef2_t); lines = new line_t[numlines]; linemap.Resize(numlines); memset (lines, 0, numlines*sizeof(line_t)); mldf = new char[lumplen]; map->Read(ML_LINEDEFS, mldf); // [RH] Remove any lines that have 0 length and count sidedefs used for (skipped = sidecount = i = 0; i < numlines; ) { mld = ((maplinedef2_t*)mldf) + i; if (mld->v1 == mld->v2 || (vertexes[LittleShort(mld->v1)].x == vertexes[LittleShort(mld->v2)].x && vertexes[LittleShort(mld->v1)].y == vertexes[LittleShort(mld->v2)].y)) { Printf ("Removing 0-length line %d\n", i+skipped); memmove (mld, mld+1, sizeof(*mld)*(numlines-i-1)); skipped++; numlines--; } else { // patch missing first sides instead of crashing out. // Visual glitches are better than not being able to play. if (LittleShort(mld->sidenum[0]) == NO_INDEX) { Printf("Line %d has no first side.\n", i); mld->sidenum[0] = 0; } sidecount++; if (LittleShort(mld->sidenum[1]) != NO_INDEX) sidecount++; linemap[i] = i+skipped; i++; } } if (skipped > 0) { ForceNodeBuild = true; } P_AllocateSideDefs (sidecount); mld = (maplinedef2_t *)mldf; ld = lines; for (i = numlines; i > 0; i--, mld++, ld++) { int j; for (j = 0; j < 5; j++) ld->args[j] = mld->args[j]; ld->flags = LittleShort(mld->flags); ld->special = mld->special; ld->v1 = &vertexes[LittleShort(mld->v1)]; ld->v2 = &vertexes[LittleShort(mld->v2)]; ld->Alpha = FRACUNIT; // [RH] Opaque by default ld->id = -1; P_SetSideNum (&ld->sidedef[0], LittleShort(mld->sidenum[0])); P_SetSideNum (&ld->sidedef[1], LittleShort(mld->sidenum[1])); P_AdjustLine (ld); P_SetLineID(ld); P_SaveLineSpecial (ld); if (level.flags2 & LEVEL2_CLIPMIDTEX) ld->flags |= ML_CLIP_MIDTEX; if (level.flags2 & LEVEL2_WRAPMIDTEX) ld->flags |= ML_WRAP_MIDTEX; if (level.flags2 & LEVEL2_CHECKSWITCHRANGE) ld->flags |= ML_CHECKSWITCHRANGE; // convert the activation type ld->activation = 1 << GET_SPAC(ld->flags); if (ld->activation == SPAC_AnyCross) ld->activation = SPAC_Impact|SPAC_PCross; // this is really PTouch ld->flags &= ~ML_SPAC_MASK; } delete[] mldf; } // // P_LoadSideDefs // // killough 4/4/98: split into two functions void P_LoadSideDefs (MapData * map) { numsides = map->Size(ML_SIDEDEFS) / sizeof(mapsidedef_t); } static void P_AllocateSideDefs (int count) { int i; sides = new side_t[count]; memset (sides, 0, count*sizeof(side_t)); sidetemp = new sidei_t[MAX(count,numvertexes)]; for (i = 0; i < count; i++) { sidetemp[i].a.special = sidetemp[i].a.tag = 0; sidetemp[i].a.alpha = SHRT_MIN; sidetemp[i].a.map = NO_SIDE; } if (count < numsides) { Printf ("Map has %d unused sidedefs\n", numsides - count); } numsides = count; sidecount = 0; } // [RH] Group sidedefs into loops so that we can easily determine // what walls any particular wall neighbors. static void P_LoopSidedefs (bool firstloop) { int i; if (sidetemp != NULL) { delete[] sidetemp; } sidetemp = new sidei_t[MAX(numvertexes, numsides)]; for (i = 0; i < numvertexes; ++i) { sidetemp[i].b.first = NO_SIDE; sidetemp[i].b.next = NO_SIDE; } for (; i < numsides; ++i) { sidetemp[i].b.next = NO_SIDE; } for (i = 0; i < numsides; ++i) { // For each vertex, build a list of sidedefs that use that vertex // as their left edge. line_t *line = sides[i].linedef; int lineside = (line->sidedef[0] != &sides[i]); int vert = int((lineside ? line->v2 : line->v1) - vertexes); sidetemp[i].b.lineside = lineside; sidetemp[i].b.next = sidetemp[vert].b.first; sidetemp[vert].b.first = i; // Set each side so that it is the only member of its loop sides[i].LeftSide = NO_SIDE; sides[i].RightSide = NO_SIDE; } // For each side, find the side that is to its right and set the // loop pointers accordingly. If two sides share a left vertex, the // one that forms the smallest angle is assumed to be the right one. for (i = 0; i < numsides; ++i) { DWORD right; line_t *line = sides[i].linedef; // If the side's line only exists in a single sector, // then consider that line to be a self-contained loop // instead of as part of another loop if (line->frontsector == line->backsector) { right = DWORD(line->sidedef[!sidetemp[i].b.lineside] - sides); } else { if (sidetemp[i].b.lineside) { right = int(line->v1 - vertexes); } else { right = int(line->v2 - vertexes); } right = sidetemp[right].b.first; if (right == NO_SIDE) { // There is no right side! if (firstloop) Printf ("Line %d's right edge is unconnected\n", linemap[unsigned(line-lines)]); continue; } if (sidetemp[right].b.next != NO_SIDE) { int bestright = right; // Shut up, GCC angle_t bestang = ANGLE_MAX; line_t *leftline, *rightline; angle_t ang1, ang2, ang; leftline = sides[i].linedef; ang1 = R_PointToAngle2 (0, 0, leftline->dx, leftline->dy); if (!sidetemp[i].b.lineside) { ang1 += ANGLE_180; } while (right != NO_SIDE) { if (sides[right].LeftSide == NO_SIDE) { rightline = sides[right].linedef; if (rightline->frontsector != rightline->backsector) { ang2 = R_PointToAngle2 (0, 0, rightline->dx, rightline->dy); if (sidetemp[right].b.lineside) { ang2 += ANGLE_180; } ang = ang2 - ang1; if (ang != 0 && ang <= bestang) { bestright = right; bestang = ang; } } } right = sidetemp[right].b.next; } right = bestright; } } assert((unsigned)i<(unsigned)numsides); assert(right<(unsigned)numsides); sides[i].RightSide = right; sides[right].LeftSide = i; } // We keep the sidedef init info around until after polyobjects are initialized, // so don't delete just yet. } int P_DetermineTranslucency (int lumpnum) { FWadLump tranmap = Wads.OpenLumpNum (lumpnum); BYTE index; PalEntry newcolor; PalEntry newcolor2; tranmap.Seek (GPalette.BlackIndex * 256 + GPalette.WhiteIndex, SEEK_SET); tranmap.Read (&index, 1); newcolor = GPalette.BaseColors[GPalette.Remap[index]]; tranmap.Seek (GPalette.WhiteIndex * 256 + GPalette.BlackIndex, SEEK_SET); tranmap.Read (&index, 1); newcolor2 = GPalette.BaseColors[GPalette.Remap[index]]; if (newcolor2.r == 255) // if black on white results in white it's either // fully transparent or additive { if (developer) { char lumpname[9]; lumpname[8] = 0; Wads.GetLumpName (lumpname, lumpnum); Printf ("%s appears to be additive translucency %d (%d%%)\n", lumpname, newcolor.r, newcolor.r*100/255); } return -newcolor.r; } if (developer) { char lumpname[9]; lumpname[8] = 0; Wads.GetLumpName (lumpname, lumpnum); Printf ("%s appears to be translucency %d (%d%%)\n", lumpname, newcolor.r, newcolor.r*100/255); } return newcolor.r; } void P_ProcessSideTextures(bool checktranmap, side_t *sd, sector_t *sec, mapsidedef_t *msd, int special, int tag, short *alpha, FMissingTextureTracker &missingtex) { char name[9]; name[8] = 0; switch (special) { case Transfer_Heights: // variable colormap via 242 linedef // [RH] The colormap num we get here isn't really a colormap, // but a packed ARGB word for blending, so we also allow // the blend to be specified directly by the texture names // instead of figuring something out from the colormap. if (sec != NULL) { SetTexture (sd, side_t::bottom, &sec->bottommap, msd->bottomtexture); SetTexture (sd, side_t::mid, &sec->midmap, msd->midtexture); SetTexture (sd, side_t::top, &sec->topmap, msd->toptexture); } break; case Static_Init: // [RH] Set sector color and fog // upper "texture" is light color // lower "texture" is fog color { DWORD color = MAKERGB(255,255,255), fog = 0; bool colorgood, foggood; SetTextureNoErr (sd, side_t::bottom, &fog, msd->bottomtexture, &foggood, true); SetTextureNoErr (sd, side_t::top, &color, msd->toptexture, &colorgood, false); strncpy (name, msd->midtexture, 8); SetTexture(sd, side_t::mid, msd->midtexture, missingtex); if (colorgood | foggood) { int s; FDynamicColormap *colormap = NULL; for (s = 0; s < numsectors; s++) { if (sectors[s].tag == tag) { if (!colorgood) color = sectors[s].ColorMap->Color; if (!foggood) fog = sectors[s].ColorMap->Fade; if (colormap == NULL || colormap->Color != color || colormap->Fade != fog) { colormap = GetSpecialLights (color, fog, 0); } sectors[s].ColorMap = colormap; } } } } break; #ifdef _3DFLOORS case Sector_Set3DFloor: if (msd->toptexture[0]=='#') { strncpy (name, msd->toptexture, 8); sd->SetTexture(side_t::top, FNullTextureID() +(-strtol(name+1, NULL, 10))); // store the alpha as a negative texture index // This will be sorted out by the 3D-floor code later. } else { SetTexture(sd, side_t::top, msd->toptexture, missingtex); } SetTexture(sd, side_t::mid, msd->midtexture, missingtex); SetTexture(sd, side_t::bottom, msd->bottomtexture, missingtex); break; #endif case TranslucentLine: // killough 4/11/98: apply translucency to 2s normal texture if (checktranmap) { int lumpnum; if (strnicmp ("TRANMAP", msd->midtexture, 8) == 0) { // The translator set the alpha argument already; no reason to do it again. sd->SetTexture(side_t::mid, FNullTextureID()); } else if ((lumpnum = Wads.CheckNumForName (msd->midtexture)) > 0 && Wads.LumpLength (lumpnum) == 65536) { *alpha = (short)P_DetermineTranslucency (lumpnum); sd->SetTexture(side_t::mid, FNullTextureID()); } else { SetTexture(sd, side_t::mid, msd->midtexture, missingtex); } SetTexture(sd, side_t::top, msd->toptexture, missingtex); SetTexture(sd, side_t::bottom, msd->bottomtexture, missingtex); break; } // Fallthrough for Hexen maps is intentional default: // normal cases SetTexture(sd, side_t::mid, msd->midtexture, missingtex); SetTexture(sd, side_t::top, msd->toptexture, missingtex); SetTexture(sd, side_t::bottom, msd->bottomtexture, missingtex); break; } } // killough 4/4/98: delay using texture names until // after linedefs are loaded, to allow overloading. // killough 5/3/98: reformatted, cleaned up void P_LoadSideDefs2 (MapData *map, FMissingTextureTracker &missingtex) { int i; char * msdf = new char[map->Size(ML_SIDEDEFS)]; map->Read(ML_SIDEDEFS, msdf); for (i = 0; i < numsides; i++) { mapsidedef_t *msd = ((mapsidedef_t*)msdf) + sidetemp[i].a.map; side_t *sd = sides + i; sector_t *sec; // [RH] The Doom renderer ignored the patch y locations when // drawing mid textures. ZDoom does not, so fix the laser beams in Strife. if (gameinfo.gametype == GAME_Strife && strncmp (msd->midtexture, "LASERB01", 8) == 0) { msd->rowoffset += 102; } sd->SetTextureXOffset(LittleShort(msd->textureoffset)<SetTextureYOffset(LittleShort(msd->rowoffset)<SetTextureXScale(FRACUNIT); sd->SetTextureYScale(FRACUNIT); sd->linedef = NULL; sd->Flags = 0; sd->Index = i; // killough 4/4/98: allow sidedef texture names to be overloaded // killough 4/11/98: refined to allow colormaps to work as wall // textures if invalid as colormaps but valid as textures. if ((unsigned)LittleShort(msd->sector)>=(unsigned)numsectors) { Printf (PRINT_HIGH, "Sidedef %d has a bad sector\n", i); sd->sector = sec = NULL; } else { sd->sector = sec = §ors[LittleShort(msd->sector)]; } P_ProcessSideTextures(!map->HasBehavior, sd, sec, msd, sidetemp[i].a.special, sidetemp[i].a.tag, &sidetemp[i].a.alpha, missingtex); } delete[] msdf; } // // [RH] My own blockmap builder, not Killough's or TeamTNT's. // // Killough's turned out not to be correct enough, and I had // written this for ZDBSP before I discovered that, so // replacing the one he wrote for MBF seemed like the easiest // thing to do. (Doom E3M6, near vertex 0--the one furthest east // on the map--had problems.) // // Using a hash table to get the minimum possible blockmap size // seems like overkill, but I wanted to change the code as little // as possible from its ZDBSP incarnation. // static unsigned int BlockHash (TArray *block) { int hash = 0; int *ar = &(*block)[0]; for (size_t i = 0; i < block->Size(); ++i) { hash = hash * 12235 + ar[i]; } return hash & 0x7fffffff; } static bool BlockCompare (TArray *block1, TArray *block2) { size_t size = block1->Size(); if (size != block2->Size()) { return false; } if (size == 0) { return true; } int *ar1 = &(*block1)[0]; int *ar2 = &(*block2)[0]; for (size_t i = 0; i < size; ++i) { if (ar1[i] != ar2[i]) { return false; } } return true; } static void CreatePackedBlockmap (TArray &BlockMap, TArray *blocks, int bmapwidth, int bmapheight) { int buckets[4096]; int *hashes, hashblock; TArray *block; int zero = 0; int terminator = -1; int *array; int i, hash; int hashed = 0, nothashed = 0; hashes = new int[bmapwidth * bmapheight]; memset (hashes, 0xff, sizeof(int)*bmapwidth*bmapheight); memset (buckets, 0xff, sizeof(buckets)); for (i = 0; i < bmapwidth * bmapheight; ++i) { block = &blocks[i]; hash = BlockHash (block) % 4096; hashblock = buckets[hash]; while (hashblock != -1) { if (BlockCompare (block, &blocks[hashblock])) { break; } hashblock = hashes[hashblock]; } if (hashblock != -1) { BlockMap[4+i] = BlockMap[4+hashblock]; hashed++; } else { hashes[i] = buckets[hash]; buckets[hash] = i; BlockMap[4+i] = BlockMap.Size (); BlockMap.Push (zero); array = &(*block)[0]; for (size_t j = 0; j < block->Size(); ++j) { BlockMap.Push (array[j]); } BlockMap.Push (terminator); nothashed++; } } delete[] hashes; // printf ("%d blocks written, %d blocks saved\n", nothashed, hashed); } #define BLOCKBITS 7 #define BLOCKSIZE 128 static void P_CreateBlockMap () { TArray *BlockLists, *block, *endblock; int adder; int bmapwidth, bmapheight; int minx, maxx, miny, maxy; int i; int line; if (numvertexes <= 0) return; // Find map extents for the blockmap minx = maxx = vertexes[0].x; miny = maxy = vertexes[0].y; for (i = 1; i < numvertexes; ++i) { if (vertexes[i].x < minx) minx = vertexes[i].x; else if (vertexes[i].x > maxx) maxx = vertexes[i].x; if (vertexes[i].y < miny) miny = vertexes[i].y; else if (vertexes[i].y > maxy) maxy = vertexes[i].y; } maxx >>= FRACBITS; minx >>= FRACBITS; maxy >>= FRACBITS; miny >>= FRACBITS; bmapwidth = ((maxx - minx) >> BLOCKBITS) + 1; bmapheight = ((maxy - miny) >> BLOCKBITS) + 1; TArray BlockMap (bmapwidth * bmapheight * 3 + 4); adder = minx; BlockMap.Push (adder); adder = miny; BlockMap.Push (adder); adder = bmapwidth; BlockMap.Push (adder); adder = bmapheight; BlockMap.Push (adder); BlockLists = new TArray[bmapwidth * bmapheight]; for (line = 0; line < numlines; ++line) { int x1 = lines[line].v1->x >> FRACBITS; int y1 = lines[line].v1->y >> FRACBITS; int x2 = lines[line].v2->x >> FRACBITS; int y2 = lines[line].v2->y >> FRACBITS; int dx = x2 - x1; int dy = y2 - y1; int bx = (x1 - minx) >> BLOCKBITS; int by = (y1 - miny) >> BLOCKBITS; int bx2 = (x2 - minx) >> BLOCKBITS; int by2 = (y2 - miny) >> BLOCKBITS; block = &BlockLists[bx + by * bmapwidth]; endblock = &BlockLists[bx2 + by2 * bmapwidth]; if (block == endblock) // Single block { block->Push (line); } else if (by == by2) // Horizontal line { if (bx > bx2) { swapvalues (block, endblock); } do { block->Push (line); block += 1; } while (block <= endblock); } else if (bx == bx2) // Vertical line { if (by > by2) { swapvalues (block, endblock); } do { block->Push (line); block += bmapwidth; } while (block <= endblock); } else // Diagonal line { int xchange = (dx < 0) ? -1 : 1; int ychange = (dy < 0) ? -1 : 1; int ymove = ychange * bmapwidth; int adx = abs (dx); int ady = abs (dy); if (adx == ady) // 45 degrees { int xb = (x1 - minx) & (BLOCKSIZE-1); int yb = (y1 - miny) & (BLOCKSIZE-1); if (dx < 0) { xb = BLOCKSIZE-xb; } if (dy < 0) { yb = BLOCKSIZE-yb; } if (xb < yb) adx--; } if (adx >= ady) // X-major { int yadd = dy < 0 ? -1 : BLOCKSIZE; do { int stop = (Scale ((by << BLOCKBITS) + yadd - (y1 - miny), dx, dy) + (x1 - minx)) >> BLOCKBITS; while (bx != stop) { block->Push (line); block += xchange; bx += xchange; } block->Push (line); block += ymove; by += ychange; } while (by != by2); while (block != endblock) { block->Push (line); block += xchange; } block->Push (line); } else // Y-major { int xadd = dx < 0 ? -1 : BLOCKSIZE; do { int stop = (Scale ((bx << BLOCKBITS) + xadd - (x1 - minx), dy, dx) + (y1 - miny)) >> BLOCKBITS; while (by != stop) { block->Push (line); block += ymove; by += ychange; } block->Push (line); block += xchange; bx += xchange; } while (bx != bx2); while (block != endblock) { block->Push (line); block += ymove; } block->Push (line); } } } BlockMap.Reserve (bmapwidth * bmapheight); CreatePackedBlockmap (BlockMap, BlockLists, bmapwidth, bmapheight); delete[] BlockLists; blockmaplump = new int[BlockMap.Size()]; for (unsigned int ii = 0; ii < BlockMap.Size(); ++ii) { blockmaplump[ii] = BlockMap[ii]; } } // // P_VerifyBlockMap // // haleyjd 03/04/10: do verification on validity of blockmap. // static bool P_VerifyBlockMap(int count) { int x, y; int *maxoffs = blockmaplump + count; int bmapwidth = blockmaplump[2]; int bmapheight = blockmaplump[3]; for(y = 0; y < bmapheight; y++) { for(x = 0; x < bmapwidth; x++) { int offset; int *list, *tmplist; int *blockoffset; offset = y * bmapwidth + x; blockoffset = blockmaplump + offset + 4; // check that block offset is in bounds if(blockoffset >= maxoffs) { Printf(PRINT_HIGH, "P_VerifyBlockMap: block offset overflow\n"); return false; } offset = *blockoffset; // check that list offset is in bounds if(offset < 4 || offset >= count) { Printf(PRINT_HIGH, "P_VerifyBlockMap: list offset overflow\n"); return false; } list = blockmaplump + offset; // scan forward for a -1 terminator before maxoffs for(tmplist = list; ; tmplist++) { // we have overflowed the lump? if(tmplist >= maxoffs) { Printf(PRINT_HIGH, "P_VerifyBlockMap: open blocklist\n"); return false; } if(*tmplist == -1) // found -1 break; } // scan the list for out-of-range linedef indicies in list for(tmplist = list; *tmplist != -1; tmplist++) { if(*tmplist < 0 || *tmplist >= numlines) { Printf(PRINT_HIGH, "P_VerifyBlockMap: index >= numlines\n"); return false; } } } } return true; } // // P_LoadBlockMap // // killough 3/1/98: substantially modified to work // towards removing blockmap limit (a wad limitation) // // killough 3/30/98: Rewritten to remove blockmap limit // void P_LoadBlockMap (MapData * map) { int count = map->Size(ML_BLOCKMAP); if (ForceNodeBuild || genblockmap || count/2 >= 0x10000 || count == 0 || Args->CheckParm("-blockmap") ) { DPrintf ("Generating BLOCKMAP\n"); P_CreateBlockMap (); } else { BYTE *data = new BYTE[count]; map->Read(ML_BLOCKMAP, data); const short *wadblockmaplump = (short *)data; int i; count/=2; blockmaplump = new int[count]; // 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] = LittleShort(wadblockmaplump[0]); blockmaplump[1] = LittleShort(wadblockmaplump[1]); blockmaplump[2] = (DWORD)(LittleShort(wadblockmaplump[2])) & 0xffff; blockmaplump[3] = (DWORD)(LittleShort(wadblockmaplump[3])) & 0xffff; for (i = 4; i < count; i++) { short t = LittleShort(wadblockmaplump[i]); // killough 3/1/98 blockmaplump[i] = t == -1 ? (DWORD)0xffffffff : (DWORD) t & 0xffff; } delete[] data; if (!P_VerifyBlockMap(count)) { DPrintf ("Generating BLOCKMAP\n"); P_CreateBlockMap(); } } bmaporgx = blockmaplump[0]<sidedef->sector; } if (glsegextras != NULL) { for (i = 0; i < numsubsectors; i++) { for (jj = 0; jj < subsectors[i].numlines; ++jj) { glsegextras[subsectors[i].firstline - segs + jj].Subsector = &subsectors[i]; } } } times[0].Unclock(); // count number of lines in each sector times[1].Clock(); total = 0; totallights = 0; for (i = 0, li = lines; i < numlines; i++, li++) { if (li->frontsector == NULL) { if (!flaggedNoFronts) { flaggedNoFronts = true; Printf ("The following lines do not have a front sidedef:\n"); } Printf (" %d\n", i); } else { li->frontsector->linecount++; total++; } if (li->backsector && li->backsector != li->frontsector) { li->backsector->linecount++; total++; } } if (flaggedNoFronts) { I_Error ("You need to fix these lines to play this map.\n"); } times[1].Unclock(); // build line tables for each sector times[3].Clock(); linebuffer = new line_t *[total]; line_t **lineb_p = linebuffer; linesDoneInEachSector = new int[numsectors]; memset (linesDoneInEachSector, 0, sizeof(int)*numsectors); for (sector = sectors, i = 0; i < numsectors; i++, sector++) { if (sector->linecount == 0) { Printf ("Sector %i (tag %i) has no lines\n", i, sector->tag); // 0 the sector's tag so that no specials can use it sector->tag = 0; } else { sector->lines = lineb_p; lineb_p += sector->linecount; } } for (i = numlines, li = lines; i > 0; --i, ++li) { if (li->frontsector != NULL) { li->frontsector->lines[linesDoneInEachSector[li->frontsector - sectors]++] = li; } if (li->backsector != NULL && li->backsector != li->frontsector) { li->backsector->lines[linesDoneInEachSector[li->backsector - sectors]++] = li; } } for (i = 0, sector = sectors; i < numsectors; ++i, ++sector) { if (linesDoneInEachSector[i] != sector->linecount) { I_Error ("P_GroupLines: miscounted"); } if (sector->linecount != 0) { bbox.ClearBox (); for (j = 0; j < sector->linecount; ++j) { li = sector->lines[j]; bbox.AddToBox (li->v1->x, li->v1->y); bbox.AddToBox (li->v2->x, li->v2->y); } } // set the soundorg to the middle of the bounding box sector->soundorg[0] = bbox.Right()/2 + bbox.Left()/2; sector->soundorg[1] = bbox.Top()/2 + bbox.Bottom()/2; sector->soundorg[2] = sector->floorplane.ZatPoint (sector->soundorg[0], sector->soundorg[1]); } delete[] linesDoneInEachSector; times[3].Unclock(); // [RH] Moved this here times[4].Clock(); P_InitTagLists(); // killough 1/30/98: Create xref tables for tags times[4].Unclock(); times[5].Clock(); if (!buildmap) { P_SetSlopes (); } times[5].Unclock(); if (showloadtimes) { Printf ("---Group Lines Times---\n"); for (i = 0; i < 7; ++i) { Printf (" time %d:%9.4f ms\n", i, times[i].TimeMS()); } } } // // P_LoadReject // void P_LoadReject (MapData * map, bool junk) { const int neededsize = (numsectors * numsectors + 7) >> 3; int rejectsize; if (strnicmp (map->MapLumps[ML_REJECT].Name, "REJECT", 8) != 0) { rejectsize = 0; } else { rejectsize = junk ? 0 : map->Size(ML_REJECT); } if (rejectsize < neededsize) { if (rejectsize > 0) { Printf ("REJECT is %d byte%s too small.\n", neededsize - rejectsize, neededsize-rejectsize==1?"":"s"); } rejectmatrix = NULL; } else { // Check if the reject has some actual content. If not, free it. rejectsize = MIN (rejectsize, neededsize); rejectmatrix = new BYTE[rejectsize]; map->Seek(ML_REJECT); map->file->Read (rejectmatrix, rejectsize); int qwords = rejectsize / 8; int i; if (qwords > 0) { const QWORD *qreject = (const QWORD *)rejectmatrix; i = 0; do { if (qreject[i] != 0) return; } while (++i < qwords); } rejectsize &= 7; qwords *= 8; for (i = 0; i < rejectsize; ++i) { if (rejectmatrix[qwords+rejectsize] != 0) return; } // Reject has no data, so pretend it isn't there. delete[] rejectmatrix; rejectmatrix = NULL; } } // // [RH] P_LoadBehavior // void P_LoadBehavior (MapData * map) { map->Seek(ML_BEHAVIOR); FBehavior::StaticLoadModule (-1, map->file, map->Size(ML_BEHAVIOR)); if (!FBehavior::StaticCheckAllGood ()) { Printf ("ACS scripts unloaded.\n"); FBehavior::StaticUnloadModules (); } } // Hash the sector tags across the sectors and linedefs. static void P_InitTagLists () { int i; for (i=numsectors; --i>=0; ) // Initially make all slots empty. sectors[i].firsttag = -1; for (i=numsectors; --i>=0; ) // Proceed from last to first sector { // so that lower sectors appear first int j = (unsigned) sectors[i].tag % (unsigned) numsectors; // Hash func sectors[i].nexttag = sectors[j].firsttag; // Prepend sector to chain sectors[j].firsttag = i; } // killough 4/17/98: same thing, only for linedefs for (i=numlines; --i>=0; ) // Initially make all slots empty. lines[i].firstid = -1; for (i=numlines; --i>=0; ) // Proceed from last to first linedef { // so that lower linedefs appear first int j = (unsigned) lines[i].id % (unsigned) numlines; // Hash func lines[i].nextid = lines[j].firstid; // Prepend linedef to chain lines[j].firstid = i; } } void P_GetPolySpots (MapData * map, TArray &spots, TArray &anchors) { if (map->HasBehavior) { int spot1, spot2, spot3, anchor; if (gameinfo.gametype == GAME_Hexen) { spot1 = PO_HEX_SPAWN_TYPE; spot2 = PO_HEX_SPAWNCRUSH_TYPE; anchor = PO_HEX_ANCHOR_TYPE; } else { spot1 = PO_SPAWN_TYPE; spot2 = PO_SPAWNCRUSH_TYPE; anchor = PO_ANCHOR_TYPE; } spot3 = PO_SPAWNHURT_TYPE; for (unsigned int i = 0; i < MapThingsConverted.Size(); ++i) { if (MapThingsConverted[i].type == spot1 || MapThingsConverted[i].type == spot2 || MapThingsConverted[i].type == spot3 || MapThingsConverted[i].type == anchor) { FNodeBuilder::FPolyStart newvert; newvert.x = MapThingsConverted[i].x; newvert.y = MapThingsConverted[i].y; newvert.polynum = MapThingsConverted[i].angle; if (MapThingsConverted[i].type == anchor) { anchors.Push (newvert); } else { spots.Push (newvert); } } } } } extern polyblock_t **PolyBlockMap; void P_FreeLevelData () { FPolyObj::ClearAllSubsectorLinks(); // can't be done as part of the polyobj deletion process. SN_StopAllSequences (); DThinker::DestroyAllThinkers (); level.total_monsters = level.total_items = level.total_secrets = level.killed_monsters = level.found_items = level.found_secrets = wminfo.maxfrags = 0; FBehavior::StaticUnloadModules (); if (vertexes != NULL) { delete[] vertexes; vertexes = NULL; } numvertexes = 0; if (segs != NULL) { delete[] segs; segs = NULL; } numsegs = 0; if (glsegextras != NULL) { delete[] glsegextras; glsegextras = NULL; } if (sectors != NULL) { delete[] sectors[0].e; delete[] sectors; sectors = NULL; } numsectors = 0; if (gamenodes != NULL && gamenodes != nodes) { delete[] gamenodes; } if (gamesubsectors != NULL && gamesubsectors != subsectors) { delete[] gamesubsectors; } if (subsectors != NULL) { for (int i = 0; i < numsubsectors; ++i) { if (subsectors[i].BSP != NULL) { delete subsectors[i].BSP; } } delete[] subsectors; } if (nodes != NULL) { delete[] nodes; } subsectors = gamesubsectors = NULL; numsubsectors = numgamesubsectors = 0; nodes = gamenodes = NULL; numnodes = numgamenodes = 0; if (lines != NULL) { delete[] lines; lines = NULL; } numlines = 0; if (sides != NULL) { delete[] sides; sides = NULL; } numsides = 0; if (blockmaplump != NULL) { delete[] blockmaplump; blockmaplump = NULL; } if (blocklinks != NULL) { delete[] blocklinks; blocklinks = NULL; } if (PolyBlockMap != NULL) { for (int i = bmapwidth*bmapheight-1; i >= 0; --i) { polyblock_t *link = PolyBlockMap[i]; while (link != NULL) { polyblock_t *next = link->next; delete link; link = next; } } delete[] PolyBlockMap; PolyBlockMap = NULL; } if (rejectmatrix != NULL) { delete[] rejectmatrix; rejectmatrix = NULL; } if (linebuffer != NULL) { delete[] linebuffer; linebuffer = NULL; } if (polyobjs != NULL) { delete[] polyobjs; polyobjs = NULL; } po_NumPolyobjs = 0; if (zones != NULL) { delete[] zones; zones = NULL; } numzones = 0; P_FreeStrifeConversations (); if (level.Scrolls != NULL) { delete[] level.Scrolls; level.Scrolls = NULL; } P_ClearUDMFKeys(); } extern msecnode_t *headsecnode; void P_FreeExtraLevelData() { // Free all blocknodes and msecnodes. // *NEVER* call this function without calling // P_FreeLevelData() first, or they might not all be freed. { FBlockNode *node = FBlockNode::FreeBlocks; while (node != NULL) { FBlockNode *next = node->NextBlock; delete node; node = next; } FBlockNode::FreeBlocks = NULL; } { msecnode_t *node = headsecnode; while (node != NULL) { msecnode_t *next = node->m_snext; M_Free (node); node = next; } headsecnode = NULL; } } // // P_SetupLevel // // [RH] position indicates the start spot to spawn at void P_SetupLevel (char *lumpname, int position) { cycle_t times[20]; FMapThing *buildthings; int numbuildthings; int i; bool buildmap; // This is motivated as follows: bool RequireGLNodes = am_textured; for (i = 0; i < (int)countof(times); ++i) { times[i].Reset(); } level.maptype = MAPTYPE_UNKNOWN; wminfo.partime = 180; MapThingsConverted.Clear(); linemap.Clear(); FCanvasTextureInfo::EmptyList (); R_FreePastViewers (); P_ClearUDMFKeys(); if (!savegamerestore) { for (i = 0; i < MAXPLAYERS; ++i) { players[i].killcount = players[i].secretcount = players[i].itemcount = 0; } } for (i = 0; i < MAXPLAYERS; ++i) { players[i].mo = NULL; } // [RH] Clear any scripted translation colors the previous level may have set. for (i = 0; i < int(translationtables[TRANSLATION_LevelScripted].Size()); ++i) { FRemapTable *table = translationtables[TRANSLATION_LevelScripted][i]; if (table != NULL) { delete table; translationtables[TRANSLATION_LevelScripted][i] = NULL; } } translationtables[TRANSLATION_LevelScripted].Clear(); // Initial height of PointOfView will be set by player think. players[consoleplayer].viewz = 1; // Make sure all sounds are stopped before Z_FreeTags. S_Start (); // [RH] Clear all ThingID hash chains. AActor::ClearTIDHashes (); // [RH] clear out the mid-screen message C_MidPrint (NULL, NULL); // Free all level data from the previous map P_FreeLevelData (); interpolator.ClearInterpolations(); // [RH] Nothing to interpolate on a fresh level. MapData *map = P_OpenMapData(lumpname); if (map == NULL) { I_Error("Unable to open map '%s'\n", lumpname); } // find map num level.lumpnum = map->lumpnum; hasglnodes = false; // [RH] Support loading Build maps (because I felt like it. :-) buildmap = false; if (map->MapLumps[0].Size > 0) { BYTE *mapdata = new BYTE[map->MapLumps[0].Size]; map->Seek(0); map->file->Read(mapdata, map->MapLumps[0].Size); times[0].Clock(); buildmap = P_LoadBuildMap (mapdata, map->MapLumps[0].Size, &buildthings, &numbuildthings); times[0].Unclock(); delete[] mapdata; } if (!buildmap) { // note: most of this ordering is important ForceNodeBuild = gennodes; // [RH] Load in the BEHAVIOR lump FBehavior::StaticUnloadModules (); if (map->HasBehavior) { P_LoadBehavior (map); level.maptype = MAPTYPE_HEXEN; } else { // We need translators only for Doom format maps. const char *translator; if (!level.info->Translator.IsEmpty()) { // The map defines its own translator. translator = level.info->Translator.GetChars(); } else { // Has the user overridden the game's default translator with a commandline parameter? translator = Args->CheckValue("-xlat"); if (translator == NULL) { // Use the game's default. translator = gameinfo.translator.GetChars(); } } P_LoadTranslator(translator); level.maptype = MAPTYPE_DOOM; } if (map->isText) { level.maptype = MAPTYPE_UDMF; } CheckCompatibility(map); T_LoadScripts(map); if (!map->HasBehavior || map->isText) { // Doom format and UDMF text maps get strict monster activation unless the mapinfo // specifies differently. if (!(level.flags2 & LEVEL2_LAXACTIVATIONMAPINFO)) { level.flags2 &= ~LEVEL2_LAXMONSTERACTIVATION; } } if (!map->HasBehavior && !map->isText) { // set compatibility flags if (gameinfo.gametype == GAME_Strife) { level.flags2 |= LEVEL2_RAILINGHACK; } level.flags2 |= LEVEL2_DUMMYSWITCHES; } FBehavior::StaticLoadDefaultModules (); P_LoadStrifeConversations (map, lumpname); FMissingTextureTracker missingtex; if (!map->isText) { times[0].Clock(); P_LoadVertexes (map); times[0].Unclock(); // Check for maps without any BSP data at all (e.g. SLIGE) times[1].Clock(); P_LoadSectors (map, missingtex); times[1].Unclock(); times[2].Clock(); P_LoadSideDefs (map); times[2].Unclock(); times[3].Clock(); if (!map->HasBehavior) P_LoadLineDefs (map); else P_LoadLineDefs2 (map); // [RH] Load Hexen-style linedefs times[3].Unclock(); times[4].Clock(); P_LoadSideDefs2 (map, missingtex); times[4].Unclock(); times[5].Clock(); P_FinishLoadingLineDefs (); times[5].Unclock(); if (!map->HasBehavior) P_LoadThings (map); else P_LoadThings2 (map); // [RH] Load Hexen-style things SetCompatibilityParams(); } else { P_ParseTextMap(map, missingtex); } times[6].Clock(); P_LoopSidedefs (true); times[6].Unclock(); linemap.Clear(); linemap.ShrinkToFit(); SummarizeMissingTextures(missingtex); } else { ForceNodeBuild = true; level.maptype = MAPTYPE_BUILD; } bool reloop = false; if (!ForceNodeBuild) { // Check for compressed nodes first, then uncompressed nodes FWadLump test; DWORD id = MAKE_ID('X','x','X','x'), idcheck = 0, idcheck2 = 0, idcheck3 = 0, idcheck4 = 0; if (map->MapLumps[ML_ZNODES].Size != 0) { // Test normal nodes first map->Seek(ML_ZNODES); idcheck = MAKE_ID('Z','N','O','D'); idcheck2 = MAKE_ID('X','N','O','D'); } else if (map->MapLumps[ML_GLZNODES].Size != 0) { map->Seek(ML_GLZNODES); idcheck = MAKE_ID('Z','G','L','N'); idcheck2 = MAKE_ID('Z','G','L','2'); idcheck3 = MAKE_ID('X','G','L','N'); idcheck4 = MAKE_ID('X','G','L','2'); } map->file->Read (&id, 4); if (id == idcheck || id == idcheck2 || id == idcheck3 || id == idcheck4) { try { P_LoadZNodes (*map->file, id); } catch (CRecoverableError &error) { Printf ("Error loading nodes: %s\n", error.GetMessage()); ForceNodeBuild = true; if (subsectors != NULL) { delete[] subsectors; subsectors = NULL; } if (segs != NULL) { delete[] segs; segs = NULL; } if (nodes != NULL) { delete[] nodes; nodes = NULL; } } } else if (!map->isText) // regular nodes are not supported for text maps { // If all 3 node related lumps are empty there's no need to output a message. // This just means that the map has no nodes and the engine is supposed to build them. if (map->Size(ML_SEGS) != 0 || map->Size(ML_SSECTORS) != 0 || map->Size(ML_NODES) != 0) { if (!P_CheckV4Nodes(map)) { times[7].Clock(); P_LoadSubsectors (map); times[7].Unclock(); times[8].Clock(); if (!ForceNodeBuild) P_LoadNodes (map); times[8].Unclock(); times[9].Clock(); if (!ForceNodeBuild) P_LoadSegs (map); times[9].Unclock(); } else { times[7].Clock(); P_LoadSubsectors (map); times[7].Unclock(); times[8].Clock(); if (!ForceNodeBuild) P_LoadNodes (map); times[8].Unclock(); times[9].Clock(); if (!ForceNodeBuild) P_LoadSegs (map); times[9].Unclock(); } } else ForceNodeBuild = true; } else ForceNodeBuild = true; // If loading the regular nodes failed try GL nodes before considering a rebuild if (ForceNodeBuild) { if (P_LoadGLNodes(map)) { ForceNodeBuild=false; reloop = true; } } } else reloop = true; unsigned int startTime=0, endTime=0; bool BuildGLNodes; if (ForceNodeBuild) { BuildGLNodes = am_textured || multiplayer || demoplayback || demorecording || genglnodes; startTime = I_FPSTime (); TArray polyspots, anchors; P_GetPolySpots (map, polyspots, anchors); FNodeBuilder::FLevel leveldata = { vertexes, numvertexes, sides, numsides, lines, numlines, 0, 0, 0, 0 }; leveldata.FindMapBounds (); // We need GL nodes if am_textured is on. // In case a sync critical game mode is started, also build GL nodes to avoid problems // if the different machines' am_textured setting differs. FNodeBuilder builder (leveldata, polyspots, anchors, BuildGLNodes); delete[] vertexes; builder.Extract (nodes, numnodes, segs, glsegextras, numsegs, subsectors, numsubsectors, vertexes, numvertexes); endTime = I_FPSTime (); DPrintf ("BSP generation took %.3f sec (%d segs)\n", (endTime - startTime) * 0.001, numsegs); reloop = true; } else { BuildGLNodes = false; // Older ZDBSPs had problems with compressed sidedefs and assigned wrong sides to the segs if both sides were the same sidedef. for(i=0;ibacksector == seg->frontsector && seg->linedef) { fixed_t d1=P_AproxDistance(seg->v1->x-seg->linedef->v1->x,seg->v1->y-seg->linedef->v1->y); fixed_t d2=P_AproxDistance(seg->v2->x-seg->linedef->v1->x,seg->v2->y-seg->linedef->v1->y); if (d2sidedef = seg->linedef->sidedef[1]; } else // front side { seg->sidedef = seg->linedef->sidedef[0]; } } } } // Copy pointers to the old nodes so that R_PointInSubsector can use them if (nodes && subsectors) { gamenodes = nodes; numgamenodes = numnodes; gamesubsectors = subsectors; numgamesubsectors = numsubsectors; } else { gamenodes=NULL; } if (RequireGLNodes) { // Build GL nodes if we want a textured automap or GL nodes are forced to be built. // If the original nodes being loaded are not GL nodes they will be kept around for // use in P_PointInSubsector to avoid problems with maps that depend on the specific // nodes they were built with (P:AR E1M3 is a good example for a map where this is the case.) reloop |= P_CheckNodes(map, BuildGLNodes, endTime - startTime); hasglnodes = true; } else { hasglnodes = P_CheckForGLNodes(); } times[10].Clock(); P_LoadBlockMap (map); times[10].Unclock(); times[11].Clock(); P_LoadReject (map, buildmap); times[11].Unclock(); times[12].Clock(); P_GroupLines (buildmap); times[12].Unclock(); times[13].Clock(); P_FloodZones (); times[13].Unclock(); if (hasglnodes) { P_SetRenderSector(); } bodyqueslot = 0; // phares 8/10/98: Clear body queue so the corpses from previous games are // not assumed to be from this one. for (i = 0; i < BODYQUESIZE; i++) bodyque[i] = NULL; deathmatchstarts.Clear (); if (!buildmap) { // [RH] Spawn slope creating things first. P_SpawnSlopeMakers (&MapThingsConverted[0], &MapThingsConverted[MapThingsConverted.Size()]); P_CopySlopes(); // Spawn 3d floors - must be done before spawning things so it can't be done in P_SpawnSpecials P_Spawn3DFloors(); times[14].Clock(); P_SpawnThings(position); for (i = 0; i < MAXPLAYERS; ++i) { if (playeringame[i] && players[i].mo != NULL) players[i].health = players[i].mo->health; } times[14].Unclock(); times[15].Clock(); if (!map->HasBehavior && !map->isText) P_TranslateTeleportThings (); // [RH] Assign teleport destination TIDs times[15].Unclock(); } else { for (i = 0; i < numbuildthings; ++i) { SpawnMapThing (i, &buildthings[i], 0); } delete[] buildthings; } delete map; // set up world state P_SpawnSpecials (); times[16].Clock(); if (reloop) P_LoopSidedefs (false); PO_Init (); // Initialize the polyobjs times[16].Unclock(); assert(sidetemp != NULL); delete[] sidetemp; sidetemp = NULL; // if deathmatch, randomly spawn the active players if (deathmatch) { for (i=0 ; i= numlines) { Printf ("No such line\n"); } Printf ("(%d,%d) -> (%d,%d)\n", lines[linenum].v1->x >> FRACBITS, lines[linenum].v1->y >> FRACBITS, lines[linenum].v2->x >> FRACBITS, lines[linenum].v2->y >> FRACBITS); } #endif