Mercurial > hg > forks > yadex
view src/levels.cc @ 107:20aa5a515896
Reformat one line /* */ comments to //
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 06 Oct 2014 12:42:55 +0300 |
parents | 093497110727 |
children | f05330267c66 |
line wrap: on
line source
/* * levels.cc * Level loading and saving routines, * global variables used to hold the level data. * BW & RQ sometime in 1993 or 1994. */ /* This file is part of Yadex. Yadex incorporates code from DEU 5.21 that was put in the public domain in 1994 by Raphaël Quinet and Brendon Wyber. The rest of Yadex is Copyright © 1997-2003 André Majorel and others. 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. */ #include "yadex.h" #include "bitvec.h" #include "dialog.h" #include "game.h" #include "levels.h" #include "objid.h" #include "wstructs.h" #include "things.h" #include "wadfile.h" #include "wads.h" #include "wads2.h" /* FIXME All these variables should be turned into members of a "Level" class. */ MDirPtr Level; // master dictionary entry for the level int NumThings; // number of things TPtr Things; // things data int NumLineDefs; // number of line defs LDPtr LineDefs; // line defs data int NumSideDefs; // number of side defs SDPtr SideDefs; // side defs data int NumVertices; // number of vertexes VPtr Vertices; // vertex data int NumSectors; // number of sectors SPtr Sectors; // sectors data // FIXME should be somewhere else int NumWTexture; // number of wall textures char **WTexture; // array of wall texture names // FIXME all the flat list stuff should be put in a separate class size_t NumFTexture; // number of floor/ceiling textures flat_list_entry_t *flat_list; // List of all flats in the directory int MapMaxX = -32767; // maximum X value of map int MapMaxY = -32767; // maximum Y value of map int MapMinX = 32767; // minimum X value of map int MapMinY = 32767; // minimum Y value of map bool MadeChanges; // made changes? bool MadeMapChanges; // made changes that need rebuilding? unsigned long things_angles; // See levels.h for description. unsigned long things_types; // See levels.h for description. char Level_name[WAD_NAME + 1]; /* The name of the level (E.G. "MAP01" or "E1M1"), followed by a NUL. If the Level has been created as the result of a "c" command with no argument, an empty string. The name is not necesarily in upper case but it always a valid lump name, not a command line shortcut like "17". */ y_file_name_t Level_file_name; /* The name of the file in which the level would be saved. If the level has been created as the result of a "c" command, with or without argument, an empty string. */ y_file_name_t Level_file_name_saved; /* The name of the file in which the level was last saved. If the Level has never been saved yet, an empty string. */ void EmptyLevelData(const char *levelname) { Things = 0; NumThings = 0; things_angles++; things_types++; LineDefs = 0; NumLineDefs = 0; SideDefs = 0; NumSideDefs = 0; Sectors = 0; NumSectors = 0; Vertices = 0; NumVertices = 0; } /* * texno_texname * A convenience function when loading Doom alpha levels */ static char *tex_list = 0; static size_t ntex = 0; static char tex_name[WAD_TEX_NAME + 1]; inline const char *texno_texname(i16 texno) { if (texno < 0) return "-"; else if (yg_texture_format == YGTF_NAMELESS) { sprintf(tex_name, "TEX%04u", (unsigned) texno); return tex_name; } else { if (texno < (i16) ntex) return tex_list + WAD_TEX_NAME * texno; else return "unknown"; } } /* read in the level data */ int ReadLevelData(const char *levelname) // SWAP! { int rc = 0; MDirPtr dir; int OldNumVertices; // Find the various level information from the master directory DisplayMessage(-1, -1, "Reading data for level %s...", levelname); Level = FindMasterDir(cfg.MasterDir, levelname); if (!Level) fatal_error("level data not found"); // Get the number of vertices i32 v_offset = 42; i32 v_length = 42; { const char *lump_name = "BUG"; if (yg_level_format == YGLF_ALPHA) // Doom alpha lump_name = "POINTS"; else lump_name = "VERTEXES"; dir = FindMasterDir(Level, lump_name); if (dir == 0) OldNumVertices = 0; else { v_offset = dir->dir.start; v_length = dir->dir.size; if (yg_level_format == YGLF_ALPHA) // Doom alpha: skip leading count { v_offset += 4; v_length -= 4; } OldNumVertices = (int) (v_length / WAD_VERTEX_BYTES); if ((i32) (OldNumVertices * WAD_VERTEX_BYTES) != v_length) warn("the %s lump has a weird size." " The wad might be corrupt.\n", lump_name); } } // Read THINGS { const char *lump_name = "THINGS"; verbmsg("Reading %s things", levelname); i32 offset = 42; i32 length; dir = FindMasterDir(Level, lump_name); if (dir == 0) NumThings = 0; else { offset = dir->dir.start; length = dir->dir.size; if (cfg.GameId == IWAD_HEXEN) // Hexen mode { NumThings = (int) (length / WAD_HEXEN_THING_BYTES); if ((i32) (NumThings * WAD_HEXEN_THING_BYTES) != length) warn("the %s lump has a weird size." " The wad might be corrupt.\n", lump_name); } else // Doom/Heretic/Strife mode { if (yg_level_format == YGLF_ALPHA) // Doom alpha: skip leading count { offset += 4; length -= 4; } size_t thing_size = yg_level_format == YGLF_ALPHA ? 12 : WAD_THING_BYTES; NumThings = (int) (length / thing_size); if ((i32) (NumThings * thing_size) != length) warn("the %s lump has a weird size." " The wad might be corrupt.\n", lump_name); } } things_angles++; things_types++; if (NumThings > 0) { Things = (TPtr) GetMemory((unsigned long) NumThings * sizeof(struct Thing)); const Wad_file *wf = dir->wadfile; wf->seek(offset); if (wf->error()) { err("%s: seek error", lump_name); rc = 1; if (cfg.error_res <= 3) goto byebye; } if (cfg.GameId == IWAD_HEXEN) // Hexen mode for (long n = 0; n < NumThings; n++) { u8 dummy2[6]; wf->read_i16(); // Tid wf->read_i16(&Things[n].xpos); wf->read_i16(&Things[n].ypos); wf->read_i16(); // Height wf->read_i16(&Things[n].angle); wf->read_i16(&Things[n].type); wf->read_i16(&Things[n].when); wf->read_bytes(dummy2, sizeof dummy2); if (wf->error()) { err("%s: error reading thing #%ld", lump_name, n); rc = 1; if (cfg.error_res <= 3) goto byebye; } } else // Doom/Heretic/Strife mode for (long n = 0; n < NumThings; n++) { wf->read_i16(&Things[n].xpos); wf->read_i16(&Things[n].ypos); wf->read_i16(&Things[n].angle); wf->read_i16(&Things[n].type); if (yg_level_format == YGLF_ALPHA) wf->read_i16(); // Alpha. Don't know what it's for. wf->read_i16(&Things[n].when); if (wf->error()) { err("%s: error reading thing #%ld", lump_name, n); rc = 1; if (cfg.error_res <= 3) goto byebye; } } } } // Read LINEDEFS if (yg_level_format != YGLF_ALPHA) { const char *lump_name = "LINEDEFS"; verbmsg(" linedefs"); dir = FindMasterDir(Level, lump_name); if (dir == 0) NumLineDefs = 0; else { if (cfg.GameId == IWAD_HEXEN) // Hexen mode { NumLineDefs = (int) (dir->dir.size / WAD_HEXEN_LINEDEF_BYTES); if ((i32) (NumLineDefs * WAD_HEXEN_LINEDEF_BYTES) != dir->dir.size) warn("the %s lump has a weird size." " The wad might be corrupt.\n", lump_name); } else // Doom/Heretic/Strife mode { NumLineDefs = (int) (dir->dir.size / WAD_LINEDEF_BYTES); if ((i32) (NumLineDefs * WAD_LINEDEF_BYTES) != dir->dir.size) warn("the %s lump has a weird size." " The wad might be corrupt.\n", lump_name); } } if (NumLineDefs > 0) { LineDefs = (LDPtr) GetMemory((unsigned long) NumLineDefs * sizeof(struct LineDef)); const Wad_file *wf = dir->wadfile; wf->seek(dir->dir.start); if (wf->error()) { err("%s: seek error", lump_name); rc = 1; if (cfg.error_res <= 3) goto byebye; } if (cfg.GameId == IWAD_HEXEN) // Hexen mode for (long n = 0; n < NumLineDefs; n++) { u8 dummy[6]; wf->read_i16(&LineDefs[n].start); wf->read_i16(&LineDefs[n].end); wf->read_i16(&LineDefs[n].flags); wf->read_bytes(dummy, sizeof dummy); wf->read_i16(&LineDefs[n].sidedef1); wf->read_i16(&LineDefs[n].sidedef2); LineDefs[n].type = dummy[0]; LineDefs[n].tag = dummy[1]; // arg1 often contains a tag if (wf->error()) { err("%s: error reading linedef #%ld", lump_name, n); rc = 1; if (cfg.error_res <= 3) goto byebye; } } else // Doom/Heretic/Strife mode for (long n = 0; n < NumLineDefs; n++) { wf->read_i16(&LineDefs[n].start); wf->read_i16(&LineDefs[n].end); wf->read_i16(&LineDefs[n].flags); wf->read_i16(&LineDefs[n].type); wf->read_i16(&LineDefs[n].tag); wf->read_i16(&LineDefs[n].sidedef1); wf->read_i16(&LineDefs[n].sidedef2); if (wf->error()) { err("%s: error reading linedef #%ld", lump_name, n); rc = 1; if (cfg.error_res <= 3) goto byebye; } } } } // Read SIDEDEFS { const char *lump_name = "SIDEDEFS"; verbmsg(" sidedefs"); dir = FindMasterDir(Level, lump_name); if (dir) { NumSideDefs = (int) (dir->dir.size / WAD_SIDEDEF_BYTES); if ((i32) (NumSideDefs * WAD_SIDEDEF_BYTES) != dir->dir.size) warn("the SIDEDEFS lump has a weird size." " The wad might be corrupt.\n"); } else NumSideDefs = 0; if (NumSideDefs > 0) { SideDefs = (SDPtr) GetMemory((unsigned long) NumSideDefs * sizeof(struct SideDef)); const Wad_file *wf = dir->wadfile; wf->seek(dir->dir.start); if (wf->error()) { err("%s: seek error", lump_name); rc = 1; if (cfg.error_res <= 3) goto byebye; } for (long n = 0; n < NumSideDefs; n++) { wf->read_i16(&SideDefs[n].xoff); wf->read_i16(&SideDefs[n].yoff); wf->read_bytes(&SideDefs[n].tex1, WAD_TEX_NAME); wf->read_bytes(&SideDefs[n].tex2, WAD_TEX_NAME); wf->read_bytes(&SideDefs[n].tex3, WAD_TEX_NAME); wf->read_i16(&SideDefs[n].sector); if (wf->error()) { err("%s: error reading sidedef #%ld", lump_name, n); rc = 1; if (cfg.error_res <= 3) goto byebye; } } } } /* Sanity checkings on linedefs: the 1st and 2nd vertices must exist. The 1st and 2nd sidedefs must exist or be set to -1. */ for (long n = 0; n < NumLineDefs; n++) { if (LineDefs[n].sidedef1 != -1 && outside(LineDefs[n].sidedef1, 0, NumSideDefs - 1)) { err("linedef %ld has bad 1st sidedef number %d, giving up", n, LineDefs[n].sidedef1); rc = 1; if (cfg.error_res <= 2) goto byebye; LineDefs[n].sidedef1 = 0; } if (LineDefs[n].sidedef2 != -1 && outside(LineDefs[n].sidedef2, 0, NumSideDefs - 1)) { err("linedef %ld has bad 2nd sidedef number %d, giving up", n, LineDefs[n].sidedef2); rc = 1; if (cfg.error_res <= 2) goto byebye; LineDefs[n].sidedef2 = 0; } if (outside(LineDefs[n].start, 0, OldNumVertices - 1)) { err("linedef %ld has bad 1st vertex number %d, giving up", n, LineDefs[n].start); rc = 1; if (cfg.error_res <= 2) goto byebye; LineDefs[n].start = 0; } if (outside(LineDefs[n].end, 0, OldNumVertices - 1)) { err("linedef %ld has bad 2nd vertex number %d, giving up", n, LineDefs[n].end); rc = 1; if (cfg.error_res <= 2) goto byebye; LineDefs[n].end = 0; } } // Read LINES (Doom alpha only) if (yg_level_format == YGLF_ALPHA) { const char *lump_name = "LINES"; verbmsg(" lines"); dir = FindMasterDir(Level, lump_name); if (dir) { if ((dir->dir.size - 4) % 36) warn("the %s lump has a weird size. The wad might be corrupt.\n", lump_name); const size_t nlines = dir->dir.size / 36; NumLineDefs = nlines; NumSideDefs = 2 * nlines; // Worst case. We'll adjust later. LineDefs = (LDPtr) GetMemory((unsigned long) NumLineDefs * sizeof(struct LineDef)); SideDefs = (SDPtr) GetMemory((unsigned long) NumSideDefs * sizeof(struct SideDef)); // Read TEXTURES if (yg_texture_format != YGTF_NAMELESS) { const char *lump_name = "TEXTURES"; bool success = false; ntex = 0; i32 *offset_table = 0; MDirPtr d = FindMasterDir(cfg.MasterDir, lump_name); if (!d) { warn("%s: lump not found in directory\n", lump_name); goto textures_done; } { const Wad_file *wf = d->wadfile; wf->seek(d->dir.start); if (wf->error()) { warn("%s: seek error\n", lump_name); goto textures_done; } i32 num; wf->read_i32(&num); if (wf->error()) { warn("%s: error reading texture count\n", lump_name); } if (num < 0 || num > 32767) { warn("%s: bad texture count, giving up\n", lump_name); goto textures_done; } ntex = num; offset_table = new i32[ntex]; for (size_t n = 0; n < ntex; n++) { wf->read_i32(offset_table + n); if (wf->error()) { warn("%s: error reading offsets table\n"); goto textures_done; } } tex_list = (char *) GetMemory(ntex * WAD_TEX_NAME); for (size_t n = 0; n < ntex; n++) { const long offset = d->dir.start + offset_table[n]; wf->seek(offset); if (wf->error()) { warn("%s: seek error\n", lump_name); goto textures_done; } wf->read_bytes(tex_list + WAD_TEX_NAME * n, WAD_TEX_NAME); if (wf->error()) { warn("%s: error reading texture names\n", lump_name); goto textures_done; } } success = true; } textures_done: if (offset_table != 0) delete[]offset_table; if (!success) warn("%s: errors found, won't be able to import texture names\n", lump_name); } const Wad_file *wf = dir->wadfile; wf->seek(dir->dir.start + 4); if (wf->error()) { err("%s: seek error", lump_name); rc = 1; if (cfg.error_res <= 3) goto byebye; } size_t s = 0; for (size_t n = 0; n < nlines; n++) { LDPtr ld = LineDefs + n; ld->start = wf->read_i16(); ld->end = wf->read_i16(); ld->flags = wf->read_i16(); wf->read_i16(); // Unused ? ld->type = wf->read_i16(); ld->tag = wf->read_i16(); wf->read_i16(); // Unused ? i16 sector1 = wf->read_i16(); i16 xofs1 = wf->read_i16(); i16 tex1m = wf->read_i16(); i16 tex1u = wf->read_i16(); i16 tex1l = wf->read_i16(); wf->read_i16(); // Unused ? i16 sector2 = wf->read_i16(); i16 xofs2 = wf->read_i16(); i16 tex2m = wf->read_i16(); i16 tex2u = wf->read_i16(); i16 tex2l = wf->read_i16(); if (sector1 >= 0) // Create first sidedef { ld->sidedef1 = s; SDPtr sd = SideDefs + s; sd->xoff = xofs1; sd->yoff = 0; memcpy(sd->tex1, texno_texname(tex1u), sizeof sd->tex1); memcpy(sd->tex2, texno_texname(tex1l), sizeof sd->tex2); memcpy(sd->tex3, texno_texname(tex1m), sizeof sd->tex3); sd->sector = sector1; s++; } else // No first sidedef ! ld->sidedef1 = -1; if (ld->flags & 0x04) // Create second sidedef { ld->sidedef2 = s; SDPtr sd = SideDefs + s; sd->xoff = xofs2; sd->yoff = 0; memcpy(sd->tex1, texno_texname(tex2u), sizeof sd->tex1); memcpy(sd->tex2, texno_texname(tex2l), sizeof sd->tex2); memcpy(sd->tex3, texno_texname(tex2m), sizeof sd->tex3); sd->sector = sector2; s++; } else ld->sidedef2 = -1; if (wf->error()) { err("%s: error reading line #%d", lump_name, int (n)); rc = 1; if (cfg.error_res <= 3) goto byebye; } } // (size_t) to silence GCC warning if ((size_t) NumSideDefs > s) // Almost always true. { NumSideDefs = s; SideDefs = (SDPtr) ResizeMemory(SideDefs, (unsigned long) NumSideDefs * sizeof(struct SideDef)); } if (tex_list) FreeMemory(tex_list); tex_list = 0; ntex = 0; } } /* Read the vertices. If the wad has been run through a nodes builder, there is a bunch of vertices at the end that are not used by any linedefs. Those vertices have been created by the nodes builder for the segs. We ignore them, because they're useless to the level designer. However, we do NOT ignore unused vertices in the middle because that's where the "string art" bug came from. Note that there is absolutely no guarantee that the nodes builders add their own vertices at the end, instead of at the beginning or right in the middle, AFAIK. It's just that they all seem to do that (1). What if some don't ? Well, we would end up with many unwanted vertices in the level data. Nothing that a good CheckCrossReferences() couldn't take care of. */ { verbmsg(" vertices"); int last_used_vertex = -1; for (long n = 0; n < NumLineDefs; n++) { last_used_vertex = y_max(last_used_vertex, LineDefs[n].start); last_used_vertex = y_max(last_used_vertex, LineDefs[n].end); } NumVertices = last_used_vertex + 1; // This block is only here to warn me if (1) is false. { bitvec_c vertex_used(OldNumVertices); for (long n = 0; n < NumLineDefs; n++) if (LineDefs[n].start != -1 && LineDefs[n].end != -1) { vertex_used.set(LineDefs[n].start); vertex_used.set(LineDefs[n].end); } int unused = 0; for (long n = 0; n <= last_used_vertex; n++) { if (!vertex_used.get(n)) unused++; } if (unused > 0) { warn("this level has unused vertices in the middle.\n"); warn("total %d, tail %d (%d%%), unused %d (", OldNumVertices, OldNumVertices - NumVertices, NumVertices - unused ? 100 * (OldNumVertices - NumVertices) / (NumVertices - unused) : 0, unused); int first = 1; for (int n = 0; n <= last_used_vertex; n++) { if (!vertex_used.get(n)) { if (n == 0 || vertex_used.get(n - 1)) { if (first) first = 0; else warn(", "); warn("%d", n); } else if (n == last_used_vertex || vertex_used.get(n + 1)) warn("-%d", n); } } warn(")\n"); } } // Now load all the vertices except the unused ones at the end. if (NumVertices > 0) { const char *lump_name = "BUG"; Vertices = (VPtr) GetMemory((unsigned long) NumVertices * sizeof(struct Vertex)); if (yg_level_format == YGLF_ALPHA) // Doom alpha lump_name = "POINTS"; else lump_name = "VERTEXES"; dir = FindMasterDir(Level, lump_name); if (dir == 0) goto vertexes_done; // FIXME isn't that fatal ? { const Wad_file *wf = dir->wadfile; wf->seek(v_offset); if (wf->error()) { err("%s: seek error", lump_name); rc = 1; if (cfg.error_res <= 3) goto byebye; } MapMaxX = -32767; MapMaxY = -32767; MapMinX = 32767; MapMinY = 32767; for (long n = 0; n < NumVertices; n++) { i16 val; wf->read_i16(&val); if (val < MapMinX) MapMinX = val; if (val > MapMaxX) MapMaxX = val; Vertices[n].x = val; wf->read_i16(&val); if (val < MapMinY) MapMinY = val; if (val > MapMaxY) MapMaxY = val; Vertices[n].y = val; if (wf->error()) { err("%s: error reading vertex #%ld", lump_name, n); rc = 1; if (cfg.error_res <= 3) goto byebye; } } } vertexes_done: ; } } // Ignore SEGS, SSECTORS and NODES // Read SECTORS { const char *lump_name = "SECTORS"; verbmsg(" sectors\n"); dir = FindMasterDir(Level, lump_name); if (yg_level_format != YGLF_ALPHA) { if (dir) { NumSectors = (int) (dir->dir.size / WAD_SECTOR_BYTES); if ((i32) (NumSectors * WAD_SECTOR_BYTES) != dir->dir.size) warn("the %s lump has a weird size." " The wad might be corrupt.\n", lump_name); } else NumSectors = 0; if (NumSectors > 0) { Sectors = (SPtr) GetMemory((unsigned long) NumSectors * sizeof(struct Sector)); const Wad_file *wf = dir->wadfile; wf->seek(dir->dir.start); if (wf->error()) { err("%s: seek error", lump_name); rc = 1; if (cfg.error_res <= 3) goto byebye; } for (long n = 0; n < NumSectors; n++) { wf->read_i16(&Sectors[n].floorh); wf->read_i16(&Sectors[n].ceilh); wf->read_bytes(&Sectors[n].floort, WAD_FLAT_NAME); wf->read_bytes(&Sectors[n].ceilt, WAD_FLAT_NAME); wf->read_i16(&Sectors[n].light); wf->read_i16(&Sectors[n].special); wf->read_i16(&Sectors[n].tag); if (wf->error()) { err("%s: error reading sector #%ld", lump_name, n); rc = 1; if (cfg.error_res <= 3) goto byebye; } } } } else // Doom alpha--a wholly different SECTORS format { i32 *offset_table = 0; i32 nsectors = 0; i32 nflatnames = 0; char *flatnames = 0; if (dir == 0) { warn("%s: lump not found in directory\n", lump_name); // FIXME fatal ? goto sectors_alpha_done; } { const Wad_file *wf = dir->wadfile; wf->seek(dir->dir.start); if (wf->error()) { err("%s: seek error", lump_name); rc = 1; if (cfg.error_res <= 3) goto byebye; } wf->read_i32(&nsectors); if (wf->error()) { err("%s: error reading sector count", lump_name); rc = 1; if (cfg.error_res <= 3) goto byebye; } if (nsectors < 0) { warn("Negative sector count. Clamping to 0.\n"); nsectors = 0; } NumSectors = nsectors; Sectors = (SPtr) GetMemory((unsigned long) NumSectors * sizeof(struct Sector)); offset_table = new i32[nsectors]; for (size_t n = 0; n < (size_t) nsectors; n++) wf->read_i32(offset_table + n); if (wf->error()) { err("%s: error reading offsets table", lump_name); rc = 1; goto sectors_alpha_done; } // Load FLATNAME { const char *lump_name = "FLATNAME"; bool success = false; MDirPtr dir2 = FindMasterDir(Level, lump_name); if (dir2 == 0) { warn("%s: lump not found in directory\n", lump_name); goto flatname_done; // FIXME warn ? } { const Wad_file *wf = dir2->wadfile; wf->seek(dir2->dir.start); if (wf->error()) { warn("%s: seek error\n", lump_name); goto flatname_done; } wf->read_i32(&nflatnames); if (wf->error()) { warn("%s: error reading flat name count\n", lump_name); nflatnames = 0; goto flatname_done; } if (nflatnames < 0 || nflatnames > 32767) { warn("%s: bad flat name count, giving up\n", lump_name); nflatnames = 0; goto flatname_done; } else { flatnames = new char[WAD_FLAT_NAME * nflatnames]; wf->read_bytes(flatnames, WAD_FLAT_NAME * nflatnames); if (wf->error()) { warn("%s: error reading flat names\n", lump_name); nflatnames = 0; goto flatname_done; } success = true; } } flatname_done: if (!success) warn("%s: errors found, you'll have to do without flat names\n", lump_name); } for (size_t n = 0; n < (size_t) nsectors; n++) { wf->seek(dir->dir.start + offset_table[n]); if (wf->error()) { err("%s: seek error", lump_name); rc = 1; goto sectors_alpha_done; } i16 index; wf->read_i16(&Sectors[n].floorh); wf->read_i16(&Sectors[n].ceilh); wf->read_i16(&index); if (nflatnames && flatnames && index >= 0 && index < nflatnames) memcpy(Sectors[n].floort, flatnames + WAD_FLAT_NAME * index, WAD_FLAT_NAME); else strcpy(Sectors[n].floort, "unknown"); wf->read_i16(&index); if (nflatnames && flatnames && index >= 0 && index < nflatnames) memcpy(Sectors[n].ceilt, flatnames + WAD_FLAT_NAME * index, WAD_FLAT_NAME); else strcpy(Sectors[n].ceilt, "unknown"); wf->read_i16(&Sectors[n].light); wf->read_i16(&Sectors[n].special); wf->read_i16(&Sectors[n].tag); // Don't know what the tail is for. Ignore it. if (wf->error()) { err("%s: error reading sector #%ld", lump_name, long (n)); rc = 1; goto sectors_alpha_done; } } } sectors_alpha_done: if (offset_table != 0) delete[]offset_table; if (flatnames != 0) delete[]flatnames; if (rc != 0 && cfg.error_res <= 3) goto byebye; } } /* Sanity checking on sidedefs: the sector must exist. I don't make this a fatal error, though, because it's not exceptional to find wads with unused sidedefs with a sector# of -1. Well known ones include dyst3 (MAP06, MAP07, MAP08), mm (MAP16), mm2 (MAP13, MAP28) and requiem (MAP03, MAP08, ...). */ for (long n = 0; n < NumSideDefs; n++) { if (outside(SideDefs[n].sector, 0, NumSectors - 1)) warn("sidedef %ld has bad sector number %d\n", n, SideDefs[n].sector); } // Ignore REJECT and BLOCKMAP // Silly statistics verbmsg (" %d things, %d vertices, %d linedefs, %d sidedefs, %d sectors\n", (int) NumThings, (int) NumVertices, (int) NumLineDefs, (int) NumSideDefs, (int) NumSectors); verbmsg(" Map: (%d,%d)-(%d,%d)\n", MapMinX, MapMinY, MapMaxX, MapMaxY); byebye: if (rc != 0) err("%s: errors found, giving up", levelname); return (cfg.error_res > 0) ? 0 : rc; } /* forget the level data */ void ForgetLevelData() // SWAP! { // forget the things NumThings = 0; if (Things != 0) FreeMemory(Things); Things = 0; things_angles++; things_types++; // forget the vertices NumVertices = 0; if (Vertices != 0) FreeMemory(Vertices); Vertices = 0; // forget the linedefs NumLineDefs = 0; if (LineDefs != 0) FreeMemory(LineDefs); LineDefs = 0; // forget the sidedefs NumSideDefs = 0; if (SideDefs != 0) FreeMemory(SideDefs); SideDefs = 0; // forget the sectors NumSectors = 0; if (Sectors != 0) FreeMemory(Sectors); Sectors = 0; } /* * Save the level data to a pwad file * The name of the level is always obtained from * <level_name>, whether or not the level was created from * scratch. * * The previous contents of the pwad file are lost. Yes, it * sucks but it's not easy to fix. * * The lumps are always written in the same order, the same * as the one in the Doom iwad. The length field of the * marker lump is always set to 0. Its offset field is * always set to the offset of the first lump of the level * (THINGS). * * If the level has been created by editing an existing * level and has not been changed in a way that calls for a * rebuild of the nodes, the VERTEXES, SEGS, SSECTORS, * NODES, REJECT and BLOCKMAP lumps are copied from the * original level. Otherwise, they are created with a * length of 0 bytes and an offset equal to the offset of * the previous lump plus its length. * * Returns 0 on success and non-zero on failure (see errno). */ int SaveLevelData(const char *outfile, const char *level_name) // SWAP! { FILE *file; MDirPtr dir; int n; long lump_offset[WAD_LL__]; size_t lump_size[WAD_LL__]; wad_level_lump_no_t l; if (yg_level_format == YGLF_HEXEN || cfg.GameId == IWAD_HEXEN) { Notify(-1, -1, "I refuse to save. Hexen mode is still", "too badly broken. You would lose data."); return 1; } if (!level_name || !levelname2levelno(level_name)) { nf_bug ("SaveLevelData: bad level_name \"%s\", using \"E1M1\" instead.", level_name); level_name = "E1M1"; } DisplayMessage(-1, -1, "Saving data to \"%s\"...", outfile); LogMessage(": Saving data to \"%s\"...\n", outfile); if ((file = fopen(outfile, "wb")) == NULL) { char buf1[81]; char buf2[81]; y_snprintf(buf1, sizeof buf1, "Can't open \"%.64s\"", outfile); y_snprintf(buf2, sizeof buf1, "for writing (%.64s)", strerror(errno)); Notify(-1, -1, buf1, buf2); return 1; } /* Can we reuse the old nodes ? Not if this is a new level from scratch or if the structure of the level has changed. If the level comes from an alpha version of Doom, we can't either because that version of Doom didn't have SEGS, NODES, etc. */ bool reuse_nodes = Level && !MadeMapChanges && yg_level_format != YGLF_ALPHA; // Write the pwad header WriteBytes(file, "PWAD", 4); // Pwad file file_write_i32(file, WAD_LL__); // Number of entries = 11 file_write_i32(file, 0); // Fix this up later if (Level) dir = Level->next; else dir = 0; // Useless except to trap accidental dereferences // The label (EnMm or MAPnm) l = WAD_LL_LABEL; lump_offset[l] = ftell(file); // By definition lump_size[l] = 0; // By definition // Write the THINGS lump l = WAD_LL_THINGS; lump_offset[l] = ftell(file); for (n = 0; n < NumThings; n++) { file_write_i16(file, Things[n].xpos); file_write_i16(file, Things[n].ypos); file_write_i16(file, Things[n].angle); file_write_i16(file, Things[n].type); file_write_i16(file, Things[n].when); } lump_size[l] = ftell(file) - lump_offset[l]; if (Level) dir = dir->next; // Write the LINEDEFS lump l = WAD_LL_LINEDEFS; lump_offset[WAD_LL_LINEDEFS] = ftell(file); for (n = 0; n < NumLineDefs; n++) { file_write_i16(file, LineDefs[n].start); file_write_i16(file, LineDefs[n].end); file_write_i16(file, LineDefs[n].flags); file_write_i16(file, LineDefs[n].type); file_write_i16(file, LineDefs[n].tag); file_write_i16(file, LineDefs[n].sidedef1); file_write_i16(file, LineDefs[n].sidedef2); } lump_size[l] = ftell(file) - lump_offset[l]; if (Level) dir = dir->next; // Write the SIDEDEFS lump l = WAD_LL_SIDEDEFS; lump_offset[l] = ftell(file); for (n = 0; n < NumSideDefs; n++) { file_write_i16(file, SideDefs[n].xoff); file_write_i16(file, SideDefs[n].yoff); WriteBytes(file, &(SideDefs[n].tex1), WAD_TEX_NAME); WriteBytes(file, &(SideDefs[n].tex2), WAD_TEX_NAME); WriteBytes(file, &(SideDefs[n].tex3), WAD_TEX_NAME); file_write_i16(file, SideDefs[n].sector); } lump_size[l] = ftell(file) - lump_offset[l]; if (Level) dir = dir->next; // Write the VERTEXES lump l = WAD_LL_VERTEXES; lump_offset[WAD_LL_VERTEXES] = ftell(file); if (reuse_nodes) { // Copy the vertices const Wad_file *wf = dir->wadfile; wf->seek(dir->dir.start); if (wf->error()) { warn("%s: seek error\n", wad_level_lump[l]); } copy_bytes(file, wf->fp, dir->dir.size); } else { // Write the vertices for (n = 0; n < NumVertices; n++) { file_write_i16(file, Vertices[n].x); file_write_i16(file, Vertices[n].y); } } lump_size[l] = ftell(file) - lump_offset[l]; if (Level) dir = dir->next; // Write the SEGS, SSECTORS and NODES lumps for (n = 0; n < 3; n++) { if (n == 0) l = WAD_LL_SEGS; else if (n == 1) l = WAD_LL_SSECTORS; else if (n == 2) l = WAD_LL_NODES; lump_offset[l] = ftell(file); if (reuse_nodes) { const Wad_file *wf = dir->wadfile; wf->seek(dir->dir.start); if (wf->error()) { warn("%s: seek error\n", wad_level_lump[l]); } copy_bytes(file, wf->fp, dir->dir.size); } lump_size[l] = ftell(file) - lump_offset[l]; if (Level) dir = dir->next; } // Write the SECTORS lump l = WAD_LL_SECTORS; lump_offset[l] = ftell(file); for (n = 0; n < NumSectors; n++) { file_write_i16(file, Sectors[n].floorh); file_write_i16(file, Sectors[n].ceilh); WriteBytes(file, Sectors[n].floort, WAD_FLAT_NAME); WriteBytes(file, Sectors[n].ceilt, WAD_FLAT_NAME); file_write_i16(file, Sectors[n].light); file_write_i16(file, Sectors[n].special); file_write_i16(file, Sectors[n].tag); } lump_size[l] = ftell(file) - lump_offset[l]; if (Level) dir = dir->next; // Write the REJECT lump l = WAD_LL_REJECT; lump_offset[l] = ftell(file); if (reuse_nodes) { // Copy the REJECT data const Wad_file *wf = dir->wadfile; wf->seek(dir->dir.start); if (wf->error()) { warn("%s: seek error\n", wad_level_lump[l]); } copy_bytes(file, wf->fp, dir->dir.size); } lump_size[l] = ftell(file) - lump_offset[l]; if (Level) dir = dir->next; // Write the BLOCKMAP lump l = WAD_LL_BLOCKMAP; lump_offset[l] = ftell(file); if (reuse_nodes) { const Wad_file *wf = dir->wadfile; wf->seek(dir->dir.start); if (wf->error()) { warn("%s: seek error\n", wad_level_lump[l]); } copy_bytes(file, wf->fp, dir->dir.size); } lump_size[l] = ftell(file) - lump_offset[l]; if (Level) dir = dir->next; // Write the actual directory long dir_offset = ftell(file); for (int L = 0; L < (int) WAD_LL__; L++) { file_write_i32(file, lump_offset[L]); file_write_i32(file, lump_size[L]); if (L == (int) WAD_LL_LABEL) file_write_name(file, level_name); else file_write_name(file, wad_level_lump[L].name); } // Fix up the directory start information if (fseek(file, 8, SEEK_SET)) { char buf1[81]; char buf2[81]; y_snprintf(buf1, sizeof buf1, "%.64s: seek error", outfile); y_snprintf(buf2, sizeof buf2, "(%.64s)", strerror(errno)); Notify(-1, -1, buf1, buf2); fclose(file); return 1; } file_write_i32(file, dir_offset); // Close the file if (fclose(file)) { char buf1[81]; char buf2[81]; y_snprintf(buf1, sizeof buf1, "%.64s: write error", outfile); y_snprintf(buf2, sizeof buf2, "(%.64s)", strerror(errno)); Notify(-1, -1, buf1, buf2); return 1; } // The file is now up to date if (!Level || MadeMapChanges) cfg.remind_to_build_nodes = 1; MadeChanges = 0; MadeMapChanges = 0; // Update pointers in Master Directory OpenPatchWad(outfile); // This should free the old "*.bak" file CloseUnusedWadFiles(); // Update MapMinX, MapMinY, MapMaxX, MapMaxY // Probably not necessary anymore -- AYM 1999-04-05 update_level_bounds(); return 0; } /* * flat_list_entry_cmp * Function used by qsort() to sort the flat_list_entry array * by ascending flat name. */ static int flat_list_entry_cmp(const void *a, const void *b) { return y_strnicmp(((const flat_list_entry_t *) a)->name, ((const flat_list_entry_t *) b)->name, WAD_FLAT_NAME); } /* function used by qsort to sort the texture names */ static int SortTextures(const void *a, const void *b) { return y_strnicmp(*((const char *const *) a), *((const char *const *) b), WAD_TEX_NAME); } /* read the texture names */ void ReadWTextureNames() { MDirPtr dir; int n; i32 val; verbmsg("Reading texture names\n"); // Doom alpha 0.4 : "TEXTURES", no names if (yg_texture_lumps == YGTL_TEXTURES && yg_texture_format == YGTF_NAMELESS) { const char *lump_name = "TEXTURES"; dir = FindMasterDir(cfg.MasterDir, lump_name); if (dir == NULL) { warn("%s: lump not found in directory\n", lump_name); goto textures04_done; } { const Wad_file *wf = dir->wadfile; wf->seek(dir->dir.start); if (wf->error()) { warn("%s: seek error\n", lump_name); goto textures04_done; } wf->read_i32(&val); if (wf->error()) { warn("%s: error reading texture count\n", lump_name); goto textures04_done; } NumWTexture = (int) val + 1; WTexture = (char **) GetMemory((long) NumWTexture * sizeof *WTexture); WTexture[0] = (char *) GetMemory(WAD_TEX_NAME + 1); strcpy(WTexture[0], "-"); if (WAD_TEX_NAME < 7) nf_bug("WAD_TEX_NAME too small"); // Sanity for (long n = 0; n < val; n++) { WTexture[n + 1] = (char *) GetMemory(WAD_TEX_NAME + 1); if (n > 9999) { warn("more than 10,000 textures. Ignoring excess.\n"); break; } sprintf(WTexture[n + 1], "TEX%04ld", n); } } textures04_done: ; } // Doom alpha 0.5 : only "TEXTURES" else if (yg_texture_lumps == YGTL_TEXTURES && (yg_texture_format == YGTF_NORMAL || yg_texture_format == YGTF_STRIFE11)) { const char *lump_name = "TEXTURES"; i32 *offsets = 0; dir = FindMasterDir(cfg.MasterDir, lump_name); if (dir == NULL) // In theory it always exists, though { warn("%s: lump not found in directory\n", lump_name); goto textures05_done; } { const Wad_file *wf = dir->wadfile; wf->seek(dir->dir.start); if (wf->error()) { warn("%s: seek error\n", lump_name); goto textures05_done; } wf->read_i32(&val); if (wf->error()) { warn("%s: error reading texture count\n", lump_name); goto textures05_done; } NumWTexture = (int) val + 1; // read in the offsets for texture1 names offsets = (i32 *) GetMemory((long) NumWTexture * 4); wf->read_i32(offsets + 1, NumWTexture - 1); if (wf->error()) { warn("%s: error reading offsets table\n", lump_name); goto textures05_done; } // read in the actual names WTexture = (char **) GetMemory((long) NumWTexture * sizeof(char *)); WTexture[0] = (char *) GetMemory(WAD_TEX_NAME + 1); strcpy(WTexture[0], "-"); for (n = 1; n < NumWTexture; n++) { WTexture[n] = (char *) GetMemory(WAD_TEX_NAME + 1); long offset = dir->dir.start + offsets[n]; wf->seek(offset); if (wf->error()) { warn("%s: error seeking to error\n", lump_name); goto textures05_done; // FIXME cleanup } wf->read_bytes(WTexture[n], WAD_TEX_NAME); if (wf->error()) { warn("%s: error reading texture names\n", lump_name); goto textures05_done; // FIXME cleanup } WTexture[n][WAD_TEX_NAME] = '\0'; } } textures05_done: if (offsets != 0) FreeMemory(offsets); } // Other iwads : "TEXTURE1" and possibly "TEXTURE2" else if (yg_texture_lumps == YGTL_NORMAL && (yg_texture_format == YGTF_NORMAL || yg_texture_format == YGTF_STRIFE11)) { const char *lump_name = "TEXTURE1"; i32 *offsets = 0; dir = FindMasterDir(cfg.MasterDir, lump_name); if (dir != NULL) // In theory it always exists, though { const Wad_file *wf = dir->wadfile; wf->seek(dir->dir.start); if (wf->error()) { warn("%s: seek error\n", lump_name); // FIXME } wf->read_i32(&val); if (wf->error()) { // FIXME } NumWTexture = (int) val + 1; // read in the offsets for texture1 names offsets = (i32 *) GetMemory((long) NumWTexture * 4); wf->read_i32(offsets + 1, NumWTexture - 1); { // FIXME } // read in the actual names WTexture = (char **) GetMemory((long) NumWTexture * sizeof(char *)); WTexture[0] = (char *) GetMemory(WAD_TEX_NAME + 1); strcpy(WTexture[0], "-"); for (n = 1; n < NumWTexture; n++) { WTexture[n] = (char *) GetMemory(WAD_TEX_NAME + 1); wf->seek(dir->dir.start + offsets[n]); if (wf->error()) { warn("%s: seek error\n", lump_name); // FIXME } wf->read_bytes(WTexture[n], WAD_TEX_NAME); if (wf->error()) { // FIXME } WTexture[n][WAD_TEX_NAME] = '\0'; } FreeMemory(offsets); } { dir = FindMasterDir(cfg.MasterDir, "TEXTURE2"); if (dir) // Doom II has no TEXTURE2 { const Wad_file *wf = dir->wadfile; wf->seek(dir->dir.start); if (wf->error()) { warn("%s: seek error\n", lump_name); // FIXME } wf->read_i32(&val); if (wf->error()) { // FIXME } // read in the offsets for texture2 names offsets = (i32 *) GetMemory((long) val * 4); wf->read_i32(offsets, val); if (wf->error()) { // FIXME } // read in the actual names WTexture = (char **) ResizeMemory(WTexture, (NumWTexture + val) * sizeof(char *)); for (n = 0; n < val; n++) { WTexture[NumWTexture + n] = (char *) GetMemory(WAD_TEX_NAME + 1); wf->seek(dir->dir.start + offsets[n]); if (wf->error()) { warn("%s: seek error\n", lump_name); // FIXME } wf->read_bytes(WTexture[NumWTexture + n], WAD_TEX_NAME); if (wf->error()) { warn("%s: read error for texture %d / %d\n", lump_name, n, NumWTexture + n); } WTexture[NumWTexture + n][WAD_TEX_NAME] = '\0'; } NumWTexture += val; FreeMemory(offsets); } } } else nf_bug("Invalid texture_format/texture_lumps combination."); // sort the names qsort(WTexture, NumWTexture, sizeof(char *), SortTextures); } /* forget the texture names */ void ForgetWTextureNames() { int n; // forget all names for (n = 0; n < NumWTexture; n++) FreeMemory(WTexture[n]); // forget the array NumWTexture = 0; FreeMemory(WTexture); } /* read the flat names */ void ReadFTextureNames() { MDirPtr dir; int n; verbmsg("Reading flat names"); NumFTexture = 0; for (dir = cfg.MasterDir; (dir = FindMasterDir(dir, "F_START", "FF_START"));) { bool ff_start = !y_strnicmp(dir->dir.name, "FF_START", WAD_NAME); MDirPtr dir0; // count the names dir = dir->next; dir0 = dir; for (n = 0; dir && y_strnicmp(dir->dir.name, "F_END", WAD_NAME) && (!ff_start || y_strnicmp(dir->dir.name, "FF_END", WAD_NAME)); dir = dir->next) { if (dir->dir.start == 0 || dir->dir.size == 0) { if (!(toupper(dir->dir.name[0]) == 'F' && (dir->dir.name[1] == '1' || dir->dir.name[1] == '2' || dir->dir.name[1] == '3' || toupper(dir->dir.name[1]) == 'F') && dir->dir.name[2] == '_' && (!y_strnicmp(dir->dir.name + 3, "START", WAD_NAME - 3) || !y_strnicmp(dir->dir.name + 3, "END", WAD_NAME - 3)))) warn("unexpected label \"%.*s\" among flats.\n", WAD_NAME, dir->dir.name); continue; } if (dir->dir.size != 4096) warn("flat \"%.*s\" has weird size %lu." " Using 4096 instead.\n", WAD_NAME, dir->dir.name, (unsigned long) dir->dir.size); n++; } /* If FF_START/FF_END followed by F_END (mm2.wad), advance past F_END. In fact, this does not work because the F_END that follows has been snatched by OpenPatchWad(), that thinks it replaces the F_END from the iwad. OpenPatchWad() needs to be kludged to take this special case into account. Fortunately, the only consequence is a useless "this wad uses FF_END" warning. -- AYM 1999-07-10 */ if (ff_start && dir && !y_strnicmp(dir->dir.name, "FF_END", WAD_NAME)) if (dir->next && !y_strnicmp(dir->next->dir.name, "F_END", WAD_NAME)) dir = dir->next; verbmsg(" FF_START/%s %d", dir->dir.name, n); if (dir && !y_strnicmp(dir->dir.name, "FF_END", WAD_NAME)) warn("this wad uses FF_END. That won't work with Doom." " Use F_END instead.\n"); // get the actual names from master dir. flat_list = (flat_list_entry_t *) ResizeMemory(flat_list, (NumFTexture + n) * sizeof *flat_list); for (size_t m = NumFTexture; m < NumFTexture + n; dir0 = dir0->next) { // Skip all labels. if (dir0->dir.start == 0 || dir0->dir.size == 0 || (toupper(dir0->dir.name[0]) == 'F' && (dir0->dir.name[1] == '1' || dir0->dir.name[1] == '2' || dir0->dir.name[1] == '3' || toupper(dir0->dir.name[1]) == 'F') && dir0->dir.name[2] == '_' && (!y_strnicmp(dir0->dir.name + 3, "START", WAD_NAME - 3) || !y_strnicmp(dir0->dir.name + 3, "END", WAD_NAME - 3)))) continue; *flat_list[m].name = '\0'; strncat(flat_list[m].name, dir0->dir.name, sizeof flat_list[m].name - 1); flat_list[m].wadfile = dir0->wadfile; flat_list[m].offset = dir0->dir.start; m++; } NumFTexture += n; } verbmsg("\n"); // sort the flats by names qsort(flat_list, NumFTexture, sizeof *flat_list, flat_list_entry_cmp); /* Eliminate all but the last duplicates of a flat. Suboptimal. Would be smarter to start by the end. */ for (size_t n = 0; n < NumFTexture; n++) { size_t m = n; while (m + 1 < NumFTexture && !flat_list_entry_cmp(flat_list + n, flat_list + m + 1)) m++; // m now contains the index of the last duplicate int nduplicates = m - n; if (nduplicates > 0) { memmove(flat_list + n, flat_list + m, (NumFTexture - m) * sizeof *flat_list); NumFTexture -= nduplicates; // Note that I'm too lazy to resize flat_list... } } } /* * is_flat_name_in_list * FIXME should use bsearch() */ int is_flat_name_in_list(const char *name) { if (!flat_list) return 0; for (size_t n = 0; n < NumFTexture; n++) if (!y_strnicmp(name, flat_list[n].name, WAD_FLAT_NAME)) return 1; return 0; } /* forget the flat names */ void ForgetFTextureNames() { NumFTexture = 0; FreeMemory(flat_list); flat_list = 0; } /* * update_level_bounds - update Map{Min,Max}{X,Y} */ void update_level_bounds() { MapMaxX = -32767; MapMaxY = -32767; MapMinX = 32767; MapMinY = 32767; for (obj_no_t n = 0; n < NumVertices; n++) { int x = Vertices[n].x; if (x < MapMinX) MapMinX = x; if (x > MapMaxX) MapMaxX = x; int y = Vertices[n].y; if (y < MapMinY) MapMinY = y; if (y > MapMaxY) MapMaxY = y; } }