diff --git a/lib/filesystem/CZipSaver.cpp b/lib/filesystem/CZipSaver.cpp index 41a6bbf4e..3475f9b7f 100644 --- a/lib/filesystem/CZipSaver.cpp +++ b/lib/filesystem/CZipSaver.cpp @@ -32,16 +32,27 @@ CZipOutputStream::CZipOutputStream(CZipSaver * owner_, zipFile archive, const st fileInfo.external_fa = 0; //??? fileInfo.internal_fa = 0; - int status = zipOpenNewFileInZip(handle, - archiveFilename.c_str(), - &fileInfo, - nullptr, - 0, - nullptr, - 0, - nullptr, - Z_DEFLATED, - Z_DEFAULT_COMPRESSION); + int status = zipOpenNewFileInZip4_64( + handle, + archiveFilename.c_str(), + &fileInfo, + nullptr,//extrafield_local + 0, + nullptr,//extrafield_global + 0, + nullptr,//comment + Z_DEFLATED, + Z_DEFAULT_COMPRESSION, + 0,//raw + -15,//windowBits + 9,//memLevel + Z_DEFAULT_STRATEGY,//strategy + nullptr,//password + 0,//crcForCrypting + 20,//versionMadeBy + 0,//flagBase + 0//zip64 + ); if(status != ZIP_OK) throw new std::runtime_error("CZipOutputStream: zipOpenNewFileInZip failed"); @@ -51,7 +62,9 @@ CZipOutputStream::CZipOutputStream(CZipSaver * owner_, zipFile archive, const st CZipOutputStream::~CZipOutputStream() { - zipCloseFileInZip(handle); + int status = zipCloseFileInZip(handle); + if (status != ZIP_OK) + logGlobal->errorStream() << "CZipOutputStream: stream finalize failed: "<activeStream = nullptr; } @@ -88,7 +101,12 @@ CZipSaver::~CZipSaver() if(handle != nullptr) - zipClose(handle, nullptr); + { + int status = zipClose(handle, nullptr); + if (status != ZIP_OK) + logGlobal->errorStream() << "CZipSaver: archive finalize failed: "< CZipSaver::addFile(const std::string & archiveFilename) diff --git a/lib/filesystem/MinizipExtensions.cpp b/lib/filesystem/MinizipExtensions.cpp index 5810babe9..d4c694fe7 100644 --- a/lib/filesystem/MinizipExtensions.cpp +++ b/lib/filesystem/MinizipExtensions.cpp @@ -165,7 +165,7 @@ CInputOutputStream * CProxyIOApi::openFile(const std::string& filename, int mode logGlobal->traceStream() << "CProxyIOApi: stream opened for " <seek(0); - return data;//todo: check that only one "copy" is opened + return data; } void CProxyIOApi::closeFile(CInputOutputStream * stream) const diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 0419462ad..7523efce2 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -17,6 +17,8 @@ #include "../CModHandler.h" #include "../VCMI_Lib.h" +namespace TriggeredEventsDetail{ + static const std::array conditionNames = { "haveArtifact", "haveCreatures", "haveResources", "haveBuilding", "control", "destroy", "transport", "daysPassed", @@ -82,6 +84,30 @@ static JsonNode ConditionToJson(const EventCondition& event) return std::move(json); } +}//namespace TriggeredEventsDetail + +namespace TerrainDetail{ + static const std::array terrainCodes = + { + "dt", "sa", "gr", "sn", "sw", "rg", "sb", "lv", "wt", "rc" + }; + static const std::array roadCodes = + { + "", "pd", "pg", "pc" + }; + + static const std::array riverCodes = + { + "", "rw", "ri", "rm", "rl" + }; + + static const std::array flipCodes = + { + '_', '-', '|', '+' + }; + +} + ///CMapFormatJson const int CMapFormatJson::VERSION_MAJOR = 1; const int CMapFormatJson::VERSION_MINOR = 0; @@ -109,6 +135,8 @@ void CMapFormatJson::readTriggeredEvents(const JsonNode & input) void CMapFormatJson::readTriggeredEvent(TriggeredEvent & event, const JsonNode & source) { + using namespace TriggeredEventsDetail; + event.onFulfill = source["message"].String(); event.description = source["description"].String(); event.effect.type = vstd::find_pos(typeNames, source["effect"]["type"].String()); @@ -132,6 +160,8 @@ void CMapFormatJson::writeTriggeredEvents(JsonNode& output) void CMapFormatJson::writeTriggeredEvent(const TriggeredEvent& event, JsonNode& dest) { + using namespace TriggeredEventsDetail; + dest["message"].String() = event.onFulfill; dest["description"].String() = event.description; @@ -194,24 +224,33 @@ std::unique_ptr CMapLoaderJson::loadMapHeader() return std::move(mapHeader); } +const JsonNode CMapLoaderJson::readJson(const std::string & archiveFilename) +{ + ResourceID resource(archiveFilename, EResType::TEXT); + + if(!loader.existsResource(resource)) + throw new std::runtime_error(archiveFilename+" not found"); + + auto data = loader.load(resource)->readAll(); + + JsonNode res(reinterpret_cast(data.first.get()), data.second); + + return std::move(res); +} + + void CMapLoaderJson::readMap() { readHeader(); map->initTerrain(); + readTerrain(); //TODO:readMap } void CMapLoaderJson::readHeader() { //do not use map field here, use only mapHeader - ResourceID headerID(HEADER_FILE_NAME, EResType::TEXT); - - if(!loader.existsResource(headerID)) - throw new std::runtime_error(HEADER_FILE_NAME+" not found"); - - auto headerData = loader.load(headerID)->readAll(); - - const JsonNode header(reinterpret_cast(headerData.first.get()), headerData.second); + const JsonNode header = readJson(HEADER_FILE_NAME); //TODO: read such data like map name & size //mapHeader->version = ??? //todo: new version field @@ -255,6 +294,146 @@ void CMapLoaderJson::readPlayerInfo() //TODO: readPlayerInfo } +void CMapLoaderJson::readTerrainTile(const std::string& src, TerrainTile& tile) +{ + using namespace TerrainDetail; + {//terrain type + const std::string typeCode = src.substr(0,2); + + int rawType = vstd::find_pos(terrainCodes, typeCode); + + if(rawType < 0) + throw new std::runtime_error("Invalid terrain type code in "+src); + + tile.terType = ETerrainType(rawType); + } + int startPos = 2; //0+typeCode fixed length + {//terrain view + int pos = startPos; + while(isdigit(src.at(pos))) + pos++; + int len = pos - startPos; + if(len<=0) + throw new std::runtime_error("Invalid terrain view in "+src); + const std::string rawCode = src.substr(startPos,len); + tile.terView = atoi(rawCode.c_str()); + startPos+=len; + } + {//terrain flip + int terrainFlip = vstd::find_pos(flipCodes, src.at(startPos++)); + if(terrainFlip < 0) + throw new std::runtime_error("Invalid terrain flip in "+src); + else + tile.extTileFlags = terrainFlip; + } + if(startPos >= src.size()) + return; + bool hasRoad = true; + {//road type + const std::string typeCode = src.substr(startPos,2); + startPos+=2; + int rawType = vstd::find_pos(roadCodes, typeCode); + if(rawType < 0) + { + rawType = vstd::find_pos(riverCodes, typeCode); + if(rawType < 0) + throw new std::runtime_error("Invalid river type in "+src); + else + { + tile.riverType = ERiverType::ERiverType(rawType); + hasRoad = false; + } + } + else + tile.roadType = ERoadType::ERoadType(rawType); + } + if(hasRoad) + {//road dir + int pos = startPos; + while(isdigit(src.at(pos))) + pos++; + int len = pos - startPos; + if(len<=0) + throw new std::runtime_error("Invalid road dir in "+src); + const std::string rawCode = src.substr(startPos,len); + tile.roadDir = atoi(rawCode.c_str()); + startPos+=len; + } + if(hasRoad) + {//road flip + int flip = vstd::find_pos(flipCodes, src.at(startPos++)); + if(flip < 0) + throw new std::runtime_error("Invalid road flip in "+src); + else + tile.extTileFlags |= (flip<<4); + } + if(startPos >= src.size()) + return; + if(hasRoad) + {//river type + const std::string typeCode = src.substr(startPos,2); + startPos+=2; + int rawType = vstd::find_pos(riverCodes, typeCode); + if(rawType < 0) + throw new std::runtime_error("Invalid river type in "+src); + tile.riverType = ERiverType::ERiverType(rawType); + } + {//river dir + int pos = startPos; + while(isdigit(src.at(pos))) + pos++; + int len = pos - startPos; + if(len<=0) + throw new std::runtime_error("Invalid river dir in "+src); + const std::string rawCode = src.substr(startPos,len); + tile.riverDir = atoi(rawCode.c_str()); + startPos+=len; + } + {//river flip + int flip = vstd::find_pos(flipCodes, src.at(startPos++)); + if(flip < 0) + throw new std::runtime_error("Invalid road flip in "+src); + else + tile.extTileFlags |= (flip<<2); + } +} + +void CMapLoaderJson::readTerrainLevel(const JsonNode& src, const int index) +{ + int3 pos(0,0,index); + + const JsonVector & rows = src.Vector(); + + if(rows.size() != map->height) + throw new std::runtime_error("Invalid terrain data"); + + for(pos.y = 0; pos.y < map->height; pos.y++) + { + const JsonVector & tiles = rows[pos.y].Vector(); + + if(tiles.size() != map->width) + throw new std::runtime_error("Invalid terrain data"); + + for(pos.x = 0; pos.x < map->width; pos.x++) + readTerrainTile(tiles[pos.x].String(), map->getTile(pos)); + } +} + + +void CMapLoaderJson::readTerrain() +{ + { + const JsonNode surface = readJson("surface.json"); + readTerrainLevel(surface, 0); + } + if(map->twoLevel) + { + const JsonNode underground = readJson("underground.json"); + readTerrainLevel(underground, 1); + } + +} + ///CMapSaverJson CMapSaverJson::CMapSaverJson(CInputOutputStream * stream): CMapFormatZip(stream), @@ -268,15 +447,30 @@ CMapSaverJson::~CMapSaverJson() } +void CMapSaverJson::addToArchive(const JsonNode& data, const std::string& filename) +{ + std::ostringstream out; + out << data; + out.flush(); + + { + auto s = out.str(); + std::unique_ptr stream = saver.addFile(filename); + + if (stream->write((const ui8*)s.c_str(), s.size()) != s.size()) + throw new std::runtime_error("CMapSaverJson::saveHeader() zip compression failed."); + } +} + void CMapSaverJson::saveMap(const std::unique_ptr& map) { //TODO: saveMap this->map = map.get(); - saveHeader(); - + writeHeader(); + writeTerrain(); } -void CMapSaverJson::saveHeader() +void CMapSaverJson::writeHeader() { JsonNode header; header["versionMajor"].Float() = VERSION_MAJOR; @@ -319,15 +513,62 @@ void CMapSaverJson::saveHeader() //todo: allowedHeroes; //todo: placeholdedHeroes; - std::ostringstream out; - out << header; - out.flush(); - - { - auto s = out.str(); - std::unique_ptr stream = saver.addFile(HEADER_FILE_NAME); - - if (stream->write((const ui8*)s.c_str(), s.size()) != s.size()) - throw new std::runtime_error("CMapSaverJson::saveHeader() zip compression failed."); - } + addToArchive(header, HEADER_FILE_NAME); +} + +const std::string CMapSaverJson::writeTerrainTile(const TerrainTile & tile) +{ + using namespace TerrainDetail; + + std::ostringstream out; + out.setf(std::ios::dec, std::ios::basefield); + out.unsetf(std::ios::showbase); + + out << terrainCodes.at(int(tile.terType)) << (int)tile.terView << flipCodes[tile.extTileFlags % 4]; + + if(tile.roadType != ERoadType::NO_ROAD) + { + out << roadCodes.at(int(tile.roadType)) << (int)tile.roadDir << flipCodes[(tile.extTileFlags >> 4) % 4]; + } + + if(tile.riverType != ERiverType::NO_RIVER) + { + out << riverCodes.at(int(tile.riverType)) << (int)tile.riverDir << flipCodes[(tile.extTileFlags >> 2) % 4]; + } + + return out.str(); +} + +JsonNode CMapSaverJson::writeTerrainLevel(const int index) +{ + JsonNode data; + int3 pos(0,0,index); + + data.Vector().resize(map->height); + + for(pos.y = 0; pos.y < map->height; pos.y++) + { + JsonNode & row = data.Vector()[pos.y]; + + row.Vector().resize(map->width); + + for(pos.x = 0; pos.x < map->width; pos.x++) + row.Vector()[pos.x].String() = writeTerrainTile(map->getTile(pos)); + } + + return std::move(data); +} + +void CMapSaverJson::writeTerrain() +{ + //todo: multilevel map save support + + JsonNode surface = writeTerrainLevel(0); + addToArchive(surface, "surface.json"); + + if(map->twoLevel) + { + JsonNode underground = writeTerrainLevel(1); + addToArchive(underground, "underground.json"); + } } diff --git a/lib/mapping/MapFormatJson.h b/lib/mapping/MapFormatJson.h index 63d8a591a..2107723ca 100644 --- a/lib/mapping/MapFormatJson.h +++ b/lib/mapping/MapFormatJson.h @@ -17,7 +17,7 @@ #include "../filesystem/CZipLoader.h" class TriggeredEvent; - +struct TerrainTile; class DLL_LINKAGE CMapFormatJson { @@ -135,6 +135,14 @@ private: */ void readPlayerInfo(); + void readTerrainTile(const std::string & src, TerrainTile & tile); + + void readTerrainLevel(const JsonNode & src, const int index); + + void readTerrain(); + + const JsonNode readJson(const std::string & archiveFilename); + CZipLoader loader; }; @@ -156,7 +164,19 @@ public: */ void saveMap(const std::unique_ptr & map) override; private: - void saveHeader(); + /** + * Save @data as json file with specified @filename + * + */ + void addToArchive(const JsonNode & data, const std::string & filename); + + void writeHeader(); + + const std::string writeTerrainTile(const TerrainTile & tile); + + JsonNode writeTerrainLevel(const int index); + + void writeTerrain(); CZipSaver saver; }; diff --git a/test/CMapFormatTest.cpp b/test/CMapFormatTest.cpp index 758812635..6aa901bff 100644 --- a/test/CMapFormatTest.cpp +++ b/test/CMapFormatTest.cpp @@ -35,9 +35,9 @@ public: { CMapGenOptions opt; - opt.setHeight(72); - opt.setWidth(72); - opt.setHasTwoLevels(false); + opt.setHeight(CMapHeader::MAP_SIZE_SMALL); + opt.setWidth(CMapHeader::MAP_SIZE_SMALL); + opt.setHasTwoLevels(true); opt.setPlayerCount(2); CMapGenerator gen; @@ -66,7 +66,8 @@ BOOST_AUTO_TEST_CASE(CMapFormatVCMI_Simple) { auto path = VCMIDirs::get().userDataPath()/"temp.zip"; boost::filesystem::remove(path); - boost::filesystem::ofstream tmp(path); + boost::filesystem::ofstream tmp(path, boost::filesystem::ofstream::binary); + tmp.write((const char *)serializeBuffer.getBuffer().data(),serializeBuffer.getSize()); tmp.flush(); tmp.close();