/* ** maploader.cpp ** ** Map loader for non-Blood maps ** **--------------------------------------------------------------------------- ** Copyright 2020 Christoph Oelckers ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** */ #include #include "build.h" #include "files.h" #include "automap.h" #include "printf.h" #include "inputstate.h" #include "md4.h" #include "coreactor.h" #include "gamecontrol.h" #include "gamefuncs.h" #include "sectorgeometry.h" #include "render.h" #include "hw_sections.h" #include "interpolate.h" #include "tiletexture.h" #include "games/blood/src/mapstructs.h" #include "buildtiles.h" #include "m_swap.h" extern BitArray clipsectormap; TArray sector; TArray wall; // for differential savegames. TArray sectorbackup; TArray wallbackup; void walltype::calcLength() { lengthflags &= ~1; point2Wall()->lengthflags &= ~2; length = delta().Length(); } // needed for skipping over to get the map size first. enum { sectorsize5 = 37, sectorsize6 = 37, sectorsize7 = 40, wallsize5 = 35, wallsize6 = 32, wallsize7 = 32, }; // This arena stores the larger allocated game-specific extension data. Since this can be freed in bulk a memory arena is better suited than malloc. static FMemArena mapDataArena; void walltype::allocX() { using XWALL = BLD_NS::XWALL; _xw = (XWALL*)mapDataArena.Alloc(sizeof(XWALL)); memset(_xw, 0, sizeof(XWALL)); } void sectortype::allocX() { using XSECTOR = BLD_NS::XSECTOR; _xs = (XSECTOR*)mapDataArena.Alloc(sizeof(XSECTOR)); memset(_xs, 0, sizeof(XSECTOR)); } static void ReadSectorV7(FileReader& fr, sectortype& sect) { int wallptr = fr.ReadInt16(); int wallnum = fr.ReadInt16(); sect.walls.Set(&wall[wallptr], wallnum); int c = fr.ReadInt32(); int f = fr.ReadInt32(); sect.setzfrommap(c, f); sect.ceilingstat = ESectorFlags::FromInt(fr.ReadUInt16()); sect.floorstat = ESectorFlags::FromInt(fr.ReadUInt16()); sect.setceilingtexture(tileGetTextureID(fr.ReadUInt16())); sect.ceilingheinum = fr.ReadInt16(); sect.ceilingshade = fr.ReadInt8(); sect.ceilingpal = fr.ReadUInt8(); sect.ceilingxpan_ = fr.ReadUInt8(); sect.ceilingypan_ = fr.ReadUInt8(); sect.setfloortexture(tileGetTextureID(fr.ReadUInt16())); sect.floorheinum = fr.ReadInt16(); sect.floorshade = fr.ReadInt8(); sect.floorpal = fr.ReadUInt8(); sect.floorxpan_ = fr.ReadUInt8(); sect.floorypan_ = fr.ReadUInt8(); sect.visibility = fr.ReadUInt8(); sect.fogpal = fr.ReadUInt8(); // note: currently unused, except for Blood. sect.lotag = fr.ReadInt16(); sect.hitag = fr.ReadInt16(); sect.extra = fr.ReadInt16(); } static void ReadSectorV6(FileReader& fr, sectortype& sect) { int wallptr = fr.ReadInt16(); int wallnum = fr.ReadInt16(); sect.walls.Set(&wall[wallptr], wallnum); sect.setceilingtexture(tileGetTextureID(fr.ReadUInt16())); sect.setfloortexture(tileGetTextureID(fr.ReadUInt16())); sect.ceilingheinum = clamp(fr.ReadInt16() << 5, -32768, 32767); sect.floorheinum = clamp(fr.ReadInt16() << 5, -32768, 32767); int c = fr.ReadInt32(); int f = fr.ReadInt32(); sect.setzfrommap(c, f); sect.ceilingshade = fr.ReadInt8(); sect.floorshade = fr.ReadInt8(); sect.ceilingxpan_ = fr.ReadUInt8(); sect.floorxpan_ = fr.ReadUInt8(); sect.ceilingypan_ = fr.ReadUInt8(); sect.floorypan_ = fr.ReadUInt8(); sect.ceilingstat = ESectorFlags::FromInt(fr.ReadUInt8()); sect.floorstat = ESectorFlags::FromInt(fr.ReadUInt8()); sect.ceilingpal = fr.ReadUInt8(); sect.floorpal = fr.ReadUInt8(); sect.visibility = fr.ReadUInt8(); sect.lotag = fr.ReadInt16(); sect.hitag = fr.ReadInt16(); sect.extra = fr.ReadInt16(); } static void ReadSectorV5(FileReader& fr, sectortype& sect) { int wallptr = fr.ReadInt16(); int wallnum = fr.ReadInt16(); sect.walls.Set(&wall[wallptr], wallnum); sect.setceilingtexture(tileGetTextureID(fr.ReadUInt16())); sect.setfloortexture(tileGetTextureID(fr.ReadUInt16())); sect.ceilingheinum = clamp(fr.ReadInt16() << 5, -32768, 32767); sect.floorheinum = clamp(fr.ReadInt16() << 5, -32768, 32767); int c = fr.ReadInt32(); int f = fr.ReadInt32(); sect.setzfrommap(c, f); sect.ceilingshade = fr.ReadInt8(); sect.floorshade = fr.ReadInt8(); sect.ceilingxpan_ = fr.ReadUInt8(); sect.floorxpan_ = fr.ReadUInt8(); sect.ceilingypan_ = fr.ReadUInt8(); sect.floorypan_ = fr.ReadUInt8(); sect.ceilingstat = ESectorFlags::FromInt(fr.ReadUInt8()); sect.floorstat = ESectorFlags::FromInt(fr.ReadUInt8()); sect.ceilingpal = fr.ReadUInt8(); sect.floorpal = fr.ReadUInt8(); sect.visibility = fr.ReadUInt8(); sect.lotag = fr.ReadInt16(); sect.hitag = fr.ReadInt16(); sect.extra = fr.ReadInt16(); if ((sect.ceilingstat & CSTAT_SECTOR_SLOPE) == 0) sect.ceilingheinum = 0; if ((sect.floorstat & CSTAT_SECTOR_SLOPE) == 0) sect.floorheinum = 0; } static void ReadWallV7(FileReader& fr, walltype& wall) { int x = fr.ReadInt32(); int y = fr.ReadInt32(); wall.setPosFromMap(x, y); wall.point2 = fr.ReadInt16(); wall.nextwall = fr.ReadInt16(); wall.nextsector = fr.ReadInt16(); wall.cstat = EWallFlags::FromInt(fr.ReadUInt16()); wall.setwalltexture(tileGetTextureID(fr.ReadInt16())); int overpic = fr.ReadInt16(); if (overpic == 0) overpic = -1; wall.setovertexture(tileGetTextureID(overpic)); wall.shade = fr.ReadInt8(); wall.pal = fr.ReadUInt8(); wall.xrepeat = fr.ReadUInt8(); wall.yrepeat = fr.ReadUInt8(); wall.xpan_ = fr.ReadUInt8(); wall.ypan_ = fr.ReadUInt8(); wall.lotag = fr.ReadInt16(); wall.hitag = fr.ReadInt16(); wall.extra = fr.ReadInt16(); } static void ReadWallV6(FileReader& fr, walltype& wall) { int x = fr.ReadInt32(); int y = fr.ReadInt32(); wall.setPosFromMap(x, y); wall.point2 = fr.ReadInt16(); wall.nextsector = fr.ReadInt16(); wall.nextwall = fr.ReadInt16(); wall.setwalltexture(tileGetTextureID(fr.ReadInt16())); int overpic = fr.ReadInt16(); if (overpic == 0) overpic = -1; wall.setovertexture(tileGetTextureID(overpic)); wall.shade = fr.ReadInt8(); wall.pal = fr.ReadUInt8(); wall.cstat = EWallFlags::FromInt(fr.ReadUInt16()); wall.xrepeat = fr.ReadUInt8(); wall.yrepeat = fr.ReadUInt8(); wall.xpan_ = fr.ReadUInt8(); wall.ypan_ = fr.ReadUInt8(); wall.lotag = fr.ReadInt16(); wall.hitag = fr.ReadInt16(); wall.extra = fr.ReadInt16(); } static void ReadWallV5(FileReader& fr, walltype& wall) { int x = fr.ReadInt32(); int y = fr.ReadInt32(); wall.setPosFromMap(x, y); wall.point2 = fr.ReadInt16(); wall.setwalltexture(tileGetTextureID(fr.ReadInt16())); int overpic = fr.ReadInt16(); if (overpic == 0) overpic = -1; wall.setovertexture(tileGetTextureID(overpic)); wall.shade = fr.ReadInt8(); wall.cstat = EWallFlags::FromInt(fr.ReadUInt16()); wall.xrepeat = fr.ReadUInt8(); wall.yrepeat = fr.ReadUInt8(); wall.xpan_ = fr.ReadUInt8(); wall.ypan_ = fr.ReadUInt8(); wall.nextsector = fr.ReadInt16(); wall.nextwall = fr.ReadInt16(); fr.Seek(4, FileReader::SeekSet); // skip over 2 unused 16 bit values wall.lotag = fr.ReadInt16(); wall.hitag = fr.ReadInt16(); wall.extra = fr.ReadInt16(); } static void SetWallPalV5() { for (auto& sect : sector) { for(auto& wal : sect.walls) { wal.pal = sect.floorpal; } } } void validateSprite(spritetype& spri, int sectnum, int index) { sectortype* sectp = §or[sectnum]; auto pos = spri.pos; bool bugged = false; if ((unsigned)spri.statnum >= MAXSTATUS) { Printf("Sprite #%d (%.0f,%.0f) has invalid statnum %d.\n", index, pos.X, pos.Y, spri.statnum); bugged = true; } else if ((unsigned)spri.picnum >= MAXTILES) { Printf("Sprite #%d (%.0f,%.0f) has invalid picnum %d.\n", index, pos.X, pos.Y, spri.picnum); bugged = true; } else if (!validSectorIndex(sectnum)) { sectp = nullptr; updatesector(pos.XY(), §p); bugged = sectp == nullptr; if (!DPrintf(DMSG_WARNING, "Sprite #%d (%.0f,%.0f) with invalid sector %d was corrected to sector %d\n", index, pos.X, pos.Y, sectnum, ::sectindex(sectp))) { if (bugged) Printf("Sprite #%d (%.0f,%.0f) with invalid sector %d\n", index, pos.X, pos.Y, sectnum); } } if (bugged) { spri = {}; spri.statnum = MAXSTATUS; sectp = nullptr; } spri.sectp = sectp; } static void ReadSpriteV7(FileReader& fr, spritetype& spr, int& secno) { int x = fr.ReadInt32(); int y = fr.ReadInt32(); int z = fr.ReadInt32(); spr.SetMapPos(x, y, z); spr.cstat = ESpriteFlags::FromInt(fr.ReadUInt16()); spr.picnum = fr.ReadInt16(); spr.shade = fr.ReadInt8(); spr.pal = fr.ReadUInt8(); spr.clipdist = fr.ReadUInt8(); spr.blend = fr.ReadUInt8(); x = fr.ReadUInt8(); y = fr.ReadUInt8(); spr.scale = DVector2(x * REPEAT_SCALE, y * REPEAT_SCALE); spr.xoffset = fr.ReadInt8(); spr.yoffset = fr.ReadInt8(); secno = fr.ReadInt16(); spr.statnum = fr.ReadInt16(); spr.intangle = fr.ReadInt16(); spr.Angles.Yaw = mapangle(spr.intangle); spr.intowner = fr.ReadInt16(); spr.xint = fr.ReadInt16(); spr.yint = fr.ReadInt16(); spr.inittype = fr.ReadInt16(); spr.lotag = fr.ReadInt16(); spr.hitag = fr.ReadInt16(); spr.extra = fr.ReadInt16(); spr.detail = 0; } static void ReadSpriteV6(FileReader& fr, spritetype& spr, int& secno) { int x = fr.ReadInt32(); int y = fr.ReadInt32(); int z = fr.ReadInt32(); spr.SetMapPos(x, y, z); spr.cstat = ESpriteFlags::FromInt(fr.ReadUInt16()); spr.shade = fr.ReadInt8(); spr.pal = fr.ReadUInt8(); spr.clipdist = fr.ReadUInt8(); x = fr.ReadUInt8(); y = fr.ReadUInt8(); spr.scale = DVector2(x * REPEAT_SCALE, y * REPEAT_SCALE); spr.xoffset = fr.ReadInt8(); spr.yoffset = fr.ReadInt8(); spr.picnum = fr.ReadInt16(); spr.intangle = fr.ReadInt16(); spr.Angles.Yaw = mapangle(spr.intangle); spr.xint = fr.ReadInt16(); spr.yint = fr.ReadInt16(); spr.inittype = fr.ReadInt16(); spr.intowner = fr.ReadInt16(); secno = fr.ReadInt16(); spr.statnum = fr.ReadInt16(); spr.lotag = fr.ReadInt16(); spr.hitag = fr.ReadInt16(); spr.extra = fr.ReadInt16(); spr.blend = 0; spr.detail = 0; if ((spr.cstat & CSTAT_SPRITE_ALIGNMENT_MASK) == CSTAT_SPRITE_ALIGNMENT_SLOPE) spr.cstat &= ~CSTAT_SPRITE_ALIGNMENT_MASK; } static void ReadSpriteV5(FileReader& fr, spritetype& spr, int& secno) { int x = fr.ReadInt32(); int y = fr.ReadInt32(); int z = fr.ReadInt32(); spr.SetMapPos(x, y, z); spr.cstat = ESpriteFlags::FromInt(fr.ReadUInt16()); spr.shade = fr.ReadInt8(); x = fr.ReadUInt8(); y = fr.ReadUInt8(); spr.scale = DVector2(x * REPEAT_SCALE, y * REPEAT_SCALE); spr.picnum = fr.ReadInt16(); spr.intangle = fr.ReadInt16(); spr.Angles.Yaw = mapangle(spr.intangle); spr.xint = fr.ReadInt16(); spr.yint = fr.ReadInt16(); spr.inittype = fr.ReadInt16(); spr.intowner = fr.ReadInt16(); secno = fr.ReadInt16(); spr.statnum = fr.ReadInt16(); spr.lotag = fr.ReadInt16(); spr.hitag = fr.ReadInt16(); spr.extra = fr.ReadInt16(); if ((spr.cstat & CSTAT_SPRITE_ALIGNMENT_MASK) == CSTAT_SPRITE_ALIGNMENT_SLOPE) spr.cstat &= ~CSTAT_SPRITE_ALIGNMENT_MASK; auto sec = spr.sectp; if ((sec->ceilingstat & CSTAT_SECTOR_SKY) > 0) spr.pal = sec->ceilingpal; else spr.pal = sec->floorpal; spr.blend = 0; spr.clipdist = 32; spr.xoffset = 0; spr.yoffset = 0; spr.detail = 0; } // allocates global map storage. Blood will also call this. void allocateMapArrays(int numwall, int numsector, int numsprites) { ClearInterpolations(); show2dsector.Resize(numsector); show2dwall.Resize(numwall); gotsector.Resize(numsector); clipsectormap.Resize(numsector); mapDataArena.FreeAll(); sector.Resize(numsector); memset(sector.Data(), 0, sizeof(sectortype) * numsector); wall.Resize(numwall); memset(wall.Data(), 0, sizeof(walltype) * wall.Size()); ClearAutomap(); } void fixSectors() { for(auto& sect: sector) { // Fix maps which do not set their wall index to the first wall of the sector. Lo Wang In Time's map 11 is such a case. auto wp = sect.walls.Data(); // Note: we do not have the 'sector' index initialized here, it would not be helpful anyway for this fix. while (wp != wall.Data() && wp[-1].twoSided() && wp[-1].nextWall()->nextWall() == &wp[-1] && wp[-1].nextWall()->nextSector() == §) { sect.walls.Set(sect.walls.Data() - 1, sect.walls.Size() + 1); wp--; } } } void validateStartSector(const char* filename, const DVector3& pos, sectortype** cursect, unsigned numsectors, bool noabort) { if (*cursect == nullptr) { sectortype* sect = nullptr; updatesectorz(pos, §); if (!sect) updatesector(pos, §); if (sect || noabort) { Printf(PRINT_HIGH, "Error in map %s: Start sector %d out of range. Max. sector is %d\n", filename, sectindex(*cursect), numsectors); *cursect = sect? sect : §or[0]; } else { I_Error("Unable to start map %s: Start sector %d out of range. Max. sector is %d. No valid location at start spot\n", filename, sectindex(*cursect), numsectors); } } } void loadMap(const char* filename, int flags, DVector3* pos, int16_t* ang, sectortype** cursect, SpawnSpriteDef& sprites) { FileReader fr = fileSystem.OpenFileReader(filename); if (!fr.isOpen()) I_Error("Unable to open map %s", filename); int mapversion = fr.ReadInt32(); if (mapversion < 5 || mapversion > 9) // 9 is most likely useless but let's try anyway. { I_Error("%s: Invalid map format, expected 5-9, got %d", filename, mapversion); } pos->X = fr.ReadInt32() * maptoworld; pos->Y = fr.ReadInt32() * maptoworld; pos->Z = fr.ReadInt32() * zmaptoworld; *ang = fr.ReadInt16() & 2047; int cursectnum = fr.ReadUInt16(); // Get the basics out before loading the data so that we can set up the global storage. unsigned numsectors = fr.ReadUInt16(); auto sectorpos = fr.Tell(); fr.Seek((mapversion == 5 ? sectorsize5 : mapversion == 6 ? sectorsize6 : sectorsize7) * numsectors, FileReader::SeekCur); unsigned numwalls = fr.ReadUInt16(); auto wallpos = fr.Tell(); fr.Seek((mapversion == 5 ? wallsize5 : mapversion == 6 ? wallsize6 : wallsize7)* numwalls, FileReader::SeekCur); int numsprites = fr.ReadUInt16(); auto spritepos = fr.Tell(); // Now that we know the map's size, set up the globals. allocateMapArrays(numwalls, numsectors, numsprites); sprites.sprites.Resize(numsprites); memset(sprites.sprites.Data(), 0, numsprites * sizeof(spritetype)); // Now load the actual data. fr.Seek(sectorpos, FileReader::SeekSet); for (unsigned i = 0; i < sector.Size(); i++) { switch (mapversion) { case 5: ReadSectorV5(fr, sector[i]); break; case 6: ReadSectorV6(fr, sector[i]); break; default: ReadSectorV7(fr, sector[i]); break; } // If we do not do this here, we need to do a lot more contortions to exclude this default from getting written out to savegames. if (isExhumed()) { sector[i].Sound = -1; } } fr.Seek(wallpos, FileReader::SeekSet); for (unsigned i = 0; i < wall.Size(); i++) { switch (mapversion) { case 5: ReadWallV5(fr, wall[i]); break; case 6: ReadWallV6(fr, wall[i]); break; default: ReadWallV7(fr, wall[i]); break; } } fr.Seek(spritepos, FileReader::SeekSet); for (int i = 0; i < numsprites; i++) { int secno = -1; switch (mapversion) { case 5: ReadSpriteV5(fr, sprites.sprites[i], secno); break; case 6: ReadSpriteV6(fr, sprites.sprites[i], secno); break; default: ReadSpriteV7(fr, sprites.sprites[i], secno); break; } validateSprite(sprites.sprites[i], secno, i); } //Must be last. fixSectors(); *cursect = validSectorIndex(cursectnum) ? §or[cursectnum] : nullptr; updatesector(*pos, cursect); fr.Seek(0, FileReader::SeekSet); auto buffer = fr.Read(); uint8_t md4[16]; md4once(buffer.bytes(), (unsigned)buffer.size(), md4); PostProcessLevel(md4, filename, sprites); loadMapHack(filename, md4, sprites); setWallSectors(); hw_CreateSections(); sectionGeometry.SetSize(sections.Size()); wallbackup = wall; sectorbackup = sector; validateStartSector(filename, *pos, cursect, numsectors); } //========================================================================== // // Decrypt // // Note that this is different from the general RFF encryption. // //========================================================================== static void Decrypt(void* to_, const void* from_, int len, int key) { uint8_t* to = (uint8_t*)to_; const uint8_t* from = (const uint8_t*)from_; for (int i = 0; i < len; ++i, ++key) { to[i] = from[i] ^ key; } } //========================================================================== // // P_LoadBloodMap // // This was adapted from ZDoom's old Build map loader. // //========================================================================== static void P_LoadBloodMapWalls(const uint8_t* data, size_t len, TArray& lwalls) { uint8_t infoBlock[37]; int mapver = data[5]; uint32_t matt; int i; int k; if (mapver != 6 && mapver != 7) { return; } matt = *(uint32_t*)(data + 28); if (matt != 0 && matt != MAKE_ID('M', 'a', 't', 't') && matt != MAKE_ID('t', 't', 'a', 'M')) { Decrypt(infoBlock, data + 6, 37, 0x7474614d); } else { memcpy(infoBlock, data + 6, 37); } int numRevisions = *(uint32_t*)(infoBlock + 27); int numSectors = *(uint16_t*)(infoBlock + 31); int numWalls = *(uint16_t*)(infoBlock + 33); int numSprites = *(uint16_t*)(infoBlock + 35); int skyLen = 2 << *(uint16_t*)(infoBlock + 16); if (mapver == 7) { // Version 7 has some extra stuff after the info block. This // includes a copyright, and I have no idea what the rest of // it is. data += 171; } else { data += 43; } // Skip the sky info. data += skyLen; lwalls.Reserve(numWalls); // Read sectors k = numRevisions * sizeof(sectortypedisk); for (i = 0; i < numSectors; ++i) { sectortypedisk bsec; if (mapver == 7) { Decrypt(&bsec, data, sizeof(sectortypedisk), k); } else { memcpy(&bsec, data, sizeof(sectortypedisk)); } data += sizeof(sectortypedisk); if (bsec.extra > 0) // skip Xsector { data += 60; } } // Read walls k |= 0x7474614d; for (i = 0; i < numWalls; ++i) { walltypedisk load; if (mapver == 7) { Decrypt(&load, data, sizeof(walltypedisk), k); } else { memcpy(&load, data, sizeof(walltypedisk)); } // only copy what we need to draw the map preview. auto pWall = &lwalls[i]; int x = LittleLong(load.x); int y = LittleLong(load.y); pWall->setPosFromMap(x, y); pWall->point2 = LittleShort(load.point2); pWall->nextwall = LittleShort(load.nextwall); pWall->nextsector = LittleShort(load.nextsector); data += sizeof(walltypedisk); if (load.extra > 0) // skip Xwall { data += 24; } } } TArray loadMapWalls(const char* filename) { TArray lwalls; FileReader fr = fileSystem.OpenFileReader(filename); if (!fr.isOpen()) return lwalls; if (isBlood()) { auto data = fr.Read(); P_LoadBloodMapWalls(data.bytes(), data.size(), lwalls); return lwalls; } int mapversion = fr.ReadInt32(); if (mapversion < 5 || mapversion > 9) return lwalls; fr.Seek(16, FileReader::SeekCur); // Get the basics out before loading the data so that we can set up the global storage. unsigned numsectors = fr.ReadUInt16(); fr.Seek((mapversion == 5 ? sectorsize5 : mapversion == 6 ? sectorsize6 : sectorsize7) * numsectors, FileReader::SeekCur); unsigned numwalls = fr.ReadUInt16(); lwalls.Resize(numwalls); for (unsigned i = 0; i < lwalls.Size(); i++) { switch (mapversion) { case 5: ReadWallV5(fr, lwalls[i]); break; case 6: ReadWallV6(fr, lwalls[i]); break; default: ReadWallV7(fr, lwalls[i]); break; } } return lwalls; } void qloadboard(const char* filename, uint8_t flags, DVector3* dapos, int16_t* daang); // loads a map into the backup buffer. void loadMapBackup(const char* filename) { DVector3 fpos; int16_t scratch; sectortype* scratch2; SpawnSpriteDef scratch3; if (isBlood()) { qloadboard(filename, 0, &fpos, &scratch); } else { loadMap(filename, 0, &fpos, &scratch, &scratch2, scratch3); } } // Sets the sector reference for each wall. We need this for the triangulation cache. void setWallSectors() { for (auto& wal : wall) { wal.sector = -1; wal.lengthflags = 3; } for (unsigned i = 0; i < sector.Size() - 1; i++) { auto sect = §or[i]; auto nextsect = §or[i + 1]; unsigned int sectstart = wallindex(sect->walls.Data()); unsigned int nextsectstart = wallindex(sect->walls.Data()); if (sectstart < nextsectstart && sectstart + sect->walls.Size() > nextsectstart) { // We have overlapping wall ranges for two sectors. Do some analysis to see where these walls belong int checkstart = nextsectstart; int checkend = sectstart + sect->walls.Size(); // for now assign the walls to the first sector. Final decisions are made below. nextsectstart = checkend; nextsect->walls.Set(&wall[nextsectstart], nextsect->walls.Size() - (checkend - checkstart)); auto belongs = [](int wal, int first, int last, int firstwal) { bool point2ok = wall[wal].point2 >= first && wall[wal].point2 < last; bool refok = false; for (int i = first; i < last; i++) if (wall[i].point2 >= firstwal && wall[i].point2 <= wal) { refok = true; break; } return refok && point2ok; }; while (checkstart < checkend && belongs(checkstart, sectstart, checkstart, checkstart)) checkstart++; sect->walls.Set(sect->walls.Data(), checkstart - sectstart); while (checkstart < checkend && belongs(checkend - 1, checkend, nextsectstart + nextsect->walls.Size(), checkstart)) checkend--; int cnt = nextsect->walls.Size() - (nextsectstart - checkend); nextsectstart = checkend; nextsect->walls.Set(&wall[nextsectstart], cnt); if (nextsectstart > sectstart + sect->walls.Size()) { // If there's a gap, assign to the first sector. In this case we may only guess. Printf("Wall range %d - %d referenced by sectors %d and %d\n", sectstart + sect->walls.Size(), nextsectstart - 1, i, i + 1); sect->walls.Set(sect->walls.Data(), nextsectstart - sectstart); } } } int i = 0; for(auto& sect: sector) { sect.dirty = EDirty::AllDirty; for (auto& wal : sect.walls) { if (wal.sector == -1) wal.sector = i; } i++; } // for (unsigned ii = 1; ii < wall.Size() - 1; ii++) { // two maps in RRRA have this error. Delete one of those 2 walls. if (wall[ii].point2 == wall[ii + 1].point2) { auto w1 = wall[ii].lastWall(false); auto w2 = wall[ii + 1].lastWall(false); // Neutralize the bad one of the two walls. if (w1 == nullptr) { wall[ii].nextwall = -1; wall[ii].nextsector = -1; wall[ii].point2 = ii; } else if (w2 == nullptr) { wall[ii+1].nextwall = -1; wall[ii+1].nextsector = -1; wall[ii+1].point2 = ii; } } } // validate 'nextsector' fields. Some maps have these wrong which can cause render glitches and occasionally even crashes. for (auto& wal : wall) { if (validWallIndex(wal.nextwall)) { if (wal.nextsector != wal.nextWall()->sector) { DPrintf(DMSG_ERROR, "Bad 'nextsector' reference %d on wall %d\n", wal.nextsector, wall.IndexOf(&wal)); wal.nextsector = wal.nextWall()->sector; } } else { wal.nextwall = -1; wal.nextsector = -1; } } } void MarkMap() { for (auto& stat : statList) { GC::Mark(stat.firstEntry); GC::Mark(stat.lastEntry); } for (auto& sect: sector) { GC::Mark(sect.firstEntry); GC::Mark(sect.lastEntry); if (isDukeEngine()) GC::Mark(sect.hitagactor); else if (isBlood()) { GC::Mark(sect.upperLink); GC::Mark(sect.lowerLink); } } }