mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Implemented terrain serialisation
* with test
This commit is contained in:
		| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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;	 | ||||
| 	 | ||||
| 	std::ostringstream out; | ||||
| 	out << header; | ||||
| 	out.flush(); | ||||
| 	 | ||||
| 	{ | ||||
| 		auto s = out.str(); | ||||
| 		std::unique_ptr<COutputStream> 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");		 | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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;	 | ||||
| }; | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user