2
0
Эх сурвалжийг харах

Implemented terrain serialisation
* with test

AlexVinS 10 жил өмнө
parent
commit
3af255d1b9

+ 30 - 12
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: "<<status;
 	owner->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: "<<status;		
+	}
+		
 }
 
 std::unique_ptr<COutputStream> CZipSaver::addFile(const std::string & archiveFilename)

+ 1 - 1
lib/filesystem/MinizipExtensions.cpp

@@ -165,7 +165,7 @@ CInputOutputStream * CProxyIOApi::openFile(const std::string& filename, int mode
 	logGlobal->traceStream() << "CProxyIOApi: stream opened for " <<filename<<" with mode "<<mode; 
 	
 	data->seek(0);
-	return data;//todo: check that only one "copy" is opened
+	return data;
 }
 
 void CProxyIOApi::closeFile(CInputOutputStream * stream) const

+ 259 - 18
lib/mapping/MapFormatJson.cpp

@@ -17,6 +17,8 @@
 #include "../CModHandler.h"
 #include "../VCMI_Lib.h"
 
+namespace TriggeredEventsDetail{
+
 static const std::array<std::string, 12> 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<std::string, 10> terrainCodes = 
+	{
+		"dt", "sa", "gr", "sn", "sw", "rg", "sb", "lv", "wt", "rc"
+	};
+	static const std::array<std::string, 4> roadCodes = 
+	{
+		"", "pd", "pg", "pc"
+	};
+	
+	static const std::array<std::string, 5> riverCodes = 
+	{
+		"", "rw", "ri", "rm", "rl"
+	};
+	
+	static const std::array<char, 10> 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<CMapHeader> 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<char*>(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<char*>(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<COutputStream> 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<CMap>& 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;	
 	
+	addToArchive(header, HEADER_FILE_NAME);
+}
+
+const std::string CMapSaverJson::writeTerrainTile(const TerrainTile & tile)
+{
+	using namespace TerrainDetail;
+
 	std::ostringstream out;
-	out << header;
-	out.flush();
+	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++)
 	{
-		auto s = out.str();
-		std::unique_ptr<COutputStream> stream = saver.addFile(HEADER_FILE_NAME);
+		JsonNode & row = data.Vector()[pos.y];
 		
-		if (stream->write((const ui8*)s.c_str(), s.size()) != s.size())
-			throw new std::runtime_error("CMapSaverJson::saveHeader() zip compression failed.");
-	}	
+		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");		
+	}
 }

+ 22 - 2
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<CMap> & 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;	
 };

+ 5 - 4
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();