فهرست منبع

Support for overriding victory/defeat conditions from h3m map or
campaign:
- new file MapFormatJson that implements small subset of Json map
format, as described on wiki
- vcmi will read overrides from file config/mapOverrides.json (currently
empty)
- Json writer for logical expressions

TODO: write data for map overrides

Ivan Savenko 11 سال پیش
والد
کامیت
7e02f6b670

+ 5 - 1
client/CPreGame.cpp

@@ -3306,11 +3306,15 @@ void CBonusSelection::selectMap(int mapNr, bool initialSelect)
 		selectedMap = mapNr;
 		selectedBonus = boost::none;
 
+		std::string scenarioName = ourCampaign->camp->header.filename.substr(0, ourCampaign->camp->header.filename.find('.'));
+		boost::to_lower(scenarioName);
+		scenarioName += ':' + boost::lexical_cast<std::string>(selectedMap);
+
 		//get header
 		delete ourHeader;
 		std::string & headerStr = ourCampaign->camp->mapPieces.find(mapNr)->second;
 		auto buffer = reinterpret_cast<const ui8 *>(headerStr.data());
-		ourHeader = CMapService::loadMapHeader(buffer, headerStr.size()).release();
+		ourHeader = CMapService::loadMapHeader(buffer, headerStr.size(), scenarioName).release();
 
 		std::map<ui8, std::string> names;
 		names[1] = settings["general"]["playerName"].String();

+ 2 - 0
config/mapOverrides.json

@@ -0,0 +1,2 @@
+{
+}

+ 6 - 2
lib/CGameState.cpp

@@ -869,9 +869,13 @@ void CGameState::initCampaign()
 	auto campaign = scenarioOps->campState;
 	assert(vstd::contains(campaign->camp->mapPieces, *scenarioOps->campState->currentMap));
 
-	std::string & mapContent = campaign->camp->mapPieces[*scenarioOps->campState->currentMap];
+	std::string scenarioName = scenarioOps->mapname.substr(0, scenarioOps->mapname.find('.'));
+	boost::to_lower(scenarioName);
+	scenarioName += ':' + boost::lexical_cast<std::string>(*campaign->currentMap);
+
+	std::string & mapContent = campaign->camp->mapPieces[*campaign->currentMap];
 	auto buffer = reinterpret_cast<const ui8 *>(mapContent.data());
-	map = CMapService::loadMap(buffer, mapContent.size()).release();
+	map = CMapService::loadMap(buffer, mapContent.size(), scenarioName).release();
 }
 
 void CGameState::initDuel()

+ 1 - 0
lib/CMakeLists.txt

@@ -33,6 +33,7 @@ set(lib_SRCS
 		mapping/CMapInfo.cpp
 		mapping/CMapService.cpp
 		mapping/MapFormatH3M.cpp
+		mapping/MapFormatJson.cpp
 
 		rmg/CMapGenerator.cpp
 		rmg/CMapGenOptions.cpp

+ 18 - 4
lib/CModHandler.cpp

@@ -122,7 +122,7 @@ void CIdentifierStorage::tryRequestIdentifier(std::string type, const JsonNode &
 boost::optional<si32> CIdentifierStorage::getIdentifier(std::string type, const JsonNode & name, bool silent)
 {
 	auto pair = splitString(name.String(), ':'); // remoteScope:name
-	auto idList = getIdentifier(ObjectCallback(name.meta, pair.first, type, pair.second, std::function<void(si32)>(), silent));
+	auto idList = getPossibleIdentifiers(ObjectCallback(name.meta, pair.first, type, pair.second, std::function<void(si32)>(), silent));
 
 	if (idList.size() == 1)
 		return idList.front().id;
@@ -132,6 +132,20 @@ boost::optional<si32> CIdentifierStorage::getIdentifier(std::string type, const
 	return boost::optional<si32>();
 }
 
+boost::optional<si32> CIdentifierStorage::getIdentifier(const JsonNode & name, bool silent)
+{
+	auto pair  = splitString(name.String(), ':'); // remoteScope:<type.name>
+	auto pair2 = splitString(pair.second,   '.'); // type.name
+	auto idList = getPossibleIdentifiers(ObjectCallback(name.meta, pair.first, pair2.first, pair2.second, std::function<void(si32)>(), silent));
+
+	if (idList.size() == 1)
+		return idList.front().id;
+	if (!silent)
+		logGlobal->errorStream() << "Failed to resolve identifier " << name.String() << " from mod " << pair2.first;
+
+	return boost::optional<si32>();
+}
+
 void CIdentifierStorage::registerObject(std::string scope, std::string type, std::string name, si32 identifier)
 {
 	ObjectData data;
@@ -144,14 +158,14 @@ void CIdentifierStorage::registerObject(std::string scope, std::string type, std
 	registeredObjects.insert(std::make_pair(fullID, data));
 }
 
-std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getIdentifier(const ObjectCallback & request)
+std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getPossibleIdentifiers(const ObjectCallback & request)
 {
 	std::set<std::string> allowedScopes;
 
 	if (request.remoteScope.empty())
 	{
 		// normally ID's from all required mods, own mod and virtual "core" mod are allowed
-		if (request.localScope != "core")
+		if (request.localScope != "core" && request.localScope != "")
 			allowedScopes = VLC->modh->getModData(request.localScope).dependencies;
 
 		allowedScopes.insert(request.localScope);
@@ -187,7 +201,7 @@ std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getIdentifier(co
 
 bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request)
 {
-	auto identifiers = getIdentifier(request);
+	auto identifiers = getPossibleIdentifiers(request);
 	if (identifiers.size() == 1) // normally resolved ID
 	{
 		request.callback(identifiers.front().id);

+ 2 - 1
lib/CModHandler.h

@@ -51,7 +51,7 @@ class CIdentifierStorage
 
 	void requestIdentifier(ObjectCallback callback);
 	bool resolveIdentifier(const ObjectCallback & callback);
-	std::vector<ObjectData> getIdentifier(const ObjectCallback & callback);
+	std::vector<ObjectData> getPossibleIdentifiers(const ObjectCallback & callback);
 public:
 	/// request identifier for specific object name.
 	/// Function callback will be called during ID resolution phase of loading
@@ -65,6 +65,7 @@ public:
 
 	/// get identifier immediately. If identifier is not know and not silent call will result in error message
 	boost::optional<si32> getIdentifier(std::string type, const JsonNode & name, bool silent = false);
+	boost::optional<si32> getIdentifier(const JsonNode & name, bool silent = false);
 
 	/// registers new object
 	void registerObject(std::string scope, std::string type, std::string name, si32 identifier);

+ 49 - 0
lib/LogicalExpression.h

@@ -223,6 +223,49 @@ namespace LogicalExpressionDetail
 		}
 	};
 
+	/// Prints expression in human-readable format
+	template <typename ContainedClass>
+	class Writer : public boost::static_visitor<JsonNode>
+	{
+		typedef ExpressionBase<ContainedClass> Base;
+
+		std::function<JsonNode(const typename Base::Value &)> classPrinter;
+
+		JsonNode printExpressionList(std::string name, const std::vector<typename Base::Variant> & element) const
+		{
+			JsonNode ret;
+			ret.Vector().resize(1);
+			ret.Vector().back().String() = name;
+			for (auto & expr : element)
+				ret.Vector().push_back(boost::apply_visitor(*this, expr));
+			return ret;
+		}
+	public:
+		Writer(std::function<JsonNode(const typename Base::Value &)> classPrinter):
+			classPrinter(classPrinter)
+		{}
+
+		JsonNode operator()(const typename Base::OperatorAny & element) const
+		{
+			return printExpressionList("anyOf", element.expressions);
+		}
+
+		JsonNode operator()(const typename Base::OperatorAll & element) const
+		{
+			return printExpressionList("allOf", element.expressions);
+		}
+
+		JsonNode operator()(const typename Base::OperatorNone & element) const
+		{
+			return printExpressionList("noneOf", element.expressions);
+		}
+
+		JsonNode operator()(const typename Base::Value & element) const
+		{
+			return classPrinter(element);
+		}
+	};
+
 	std::string DLL_LINKAGE getTextForOperator(std::string operation);
 
 	/// Prints expression in human-readable format
@@ -368,6 +411,12 @@ public:
 		return boost::apply_visitor(printVisitor, data);
 	}
 
+	JsonNode toJson(std::function<JsonNode(const Value &)> toJson) const
+	{
+		LogicalExpressionDetail::Writer<Value> writeVisitor(toJson);
+		return boost::apply_visitor(writeVisitor, data);
+	}
+
 	template <typename Handler>
 	void serialize(Handler & h, const int version)
 	{

+ 5 - 1
lib/mapping/CCampaignHandler.cpp

@@ -64,9 +64,13 @@ unique_ptr<CCampaign> CCampaignHandler::getCampaign( const std::string & name )
 			scenarioID++;
 		}
 
+		std::string scenarioName = name.substr(0, name.find('.'));
+		boost::to_lower(scenarioName);
+		scenarioName += ':' + boost::lexical_cast<std::string>(g-1);
+
 		//set map piece appropriately, convert vector to string
 		ret->mapPieces[scenarioID].assign(reinterpret_cast< const char* >(file[g].data()), file[g].size());
-		ret->scenarios[scenarioID].scenarioName = CMapService::loadMapHeader((const ui8*)ret->mapPieces[scenarioID].c_str(), ret->mapPieces[scenarioID].size())->name;
+		ret->scenarios[scenarioID].scenarioName = CMapService::loadMapHeader((const ui8*)ret->mapPieces[scenarioID].c_str(), ret->mapPieces[scenarioID].size(), scenarioName)->name;
 		scenarioID++;
 	}
 

+ 31 - 6
lib/mapping/CMapService.cpp

@@ -8,30 +8,47 @@
 #include "CMap.h"
 
 #include "MapFormatH3M.h"
+#include "MapFormatJson.h"
 
 
 std::unique_ptr<CMap> CMapService::loadMap(const std::string & name)
 {
 	auto stream = getStreamFromFS(name);
-	return getMapLoader(stream)->loadMap();
+	std::unique_ptr<CMap> map(getMapLoader(stream)->loadMap());
+	std::unique_ptr<CMapHeader> header(map.get());
+
+	getMapPatcher(name)->patchMapHeader(header);
+	header.release();
+
+	return std::move(map);
 }
 
 std::unique_ptr<CMapHeader> CMapService::loadMapHeader(const std::string & name)
 {
 	auto stream = getStreamFromFS(name);
-	return getMapLoader(stream)->loadMapHeader();
+	std::unique_ptr<CMapHeader> header = getMapLoader(stream)->loadMapHeader();
+	getMapPatcher(name)->patchMapHeader(header);
+	return std::move(header);
 }
 
-std::unique_ptr<CMap> CMapService::loadMap(const ui8 * buffer, int size)
+std::unique_ptr<CMap> CMapService::loadMap(const ui8 * buffer, int size, const std::string & name)
 {
 	auto stream = getStreamFromMem(buffer, size);
-	return getMapLoader(stream)->loadMap();
+	std::unique_ptr<CMap> map(getMapLoader(stream)->loadMap());
+	std::unique_ptr<CMapHeader> header(map.get());
+
+	getMapPatcher(name)->patchMapHeader(header);
+	header.release();
+
+	return std::move(map);
 }
 
-std::unique_ptr<CMapHeader> CMapService::loadMapHeader(const ui8 * buffer, int size)
+std::unique_ptr<CMapHeader> CMapService::loadMapHeader(const ui8 * buffer, int size, const std::string & name)
 {
 	auto stream = getStreamFromMem(buffer, size);
-	return getMapLoader(stream)->loadMapHeader();
+	std::unique_ptr<CMapHeader> header = getMapLoader(stream)->loadMapHeader();
+	getMapPatcher(name)->patchMapHeader(header);
+	return std::move(header);
 }
 
 std::unique_ptr<CInputStream> CMapService::getStreamFromFS(const std::string & name)
@@ -68,3 +85,11 @@ std::unique_ptr<IMapLoader> CMapService::getMapLoader(std::unique_ptr<CInputStre
 			throw std::runtime_error("Unknown map format");
 	}
 }
+
+std::unique_ptr<IMapPatcher> CMapService::getMapPatcher(std::string scenarioName)
+{
+	boost::to_lower(scenarioName);
+	logGlobal->debugStream() << "Request to patch map " << scenarioName;
+	JsonNode node = JsonUtils::assembleFromFiles("config/mapOverrides.json");
+	return std::unique_ptr<IMapPatcher>(new CMapLoaderJson(node[scenarioName]));
+}

+ 22 - 3
lib/mapping/CMapService.h

@@ -16,6 +16,7 @@ class CMapHeader;
 class CInputStream;
 
 class IMapLoader;
+class IMapPatcher;
 
 /**
  * The map service provides loading of VCMI/H3 map files. It can
@@ -49,9 +50,10 @@ public:
 	 *
 	 * @param buffer a pointer to a buffer containing the map data
 	 * @param size the size of the buffer
+	 * @param name indicates name of file that will be used during map header patching
 	 * @return a unique ptr to the loaded map class
 	 */
-	static std::unique_ptr<CMap> loadMap(const ui8 * buffer, int size);
+	static std::unique_ptr<CMap> loadMap(const ui8 * buffer, int size, const std::string & name);
 
 	/**
 	 * Loads the VCMI/H3 map header from a buffer. This method is temporarily
@@ -62,9 +64,10 @@ public:
 	 *
 	 * @param buffer a pointer to a buffer containing the map header data
 	 * @param size the size of the buffer
+	 * @param name indicates name of file that will be used during map header patching
 	 * @return a unique ptr to the loaded map class
 	 */
-	static std::unique_ptr<CMapHeader> loadMapHeader(const ui8 * buffer, int size);
+	static std::unique_ptr<CMapHeader> loadMapHeader(const ui8 * buffer, int size, const std::string & name);
 
 private:
 	/**
@@ -92,6 +95,14 @@ private:
 	 * @return the constructed map loader
 	 */
 	static std::unique_ptr<IMapLoader> getMapLoader(std::unique_ptr<CInputStream> & stream);
+
+	/**
+	 * Gets a map patcher for specified scenario
+	 *
+	 * @param scenarioName for patcher
+	 * @return the constructed map patcher
+	 */
+	static std::unique_ptr<IMapPatcher> getMapPatcher(std::string scenarioName);
 };
 
 /**
@@ -115,4 +126,12 @@ public:
 	virtual std::unique_ptr<CMapHeader> loadMapHeader() = 0;
 };
 
-
+class DLL_LINKAGE IMapPatcher : public IMapLoader
+{
+public:
+	/**
+	 * Modifies supplied map header using Json data
+	 *
+	 */
+	virtual void patchMapHeader(std::unique_ptr<CMapHeader> & header) = 0;
+};

+ 3 - 0
lib/mapping/MapFormatH3M.cpp

@@ -1563,9 +1563,11 @@ void CMapLoaderH3M::readObjects()
 				if(htid == 0xff)
 				{
 					hp->power = reader.readUInt8();
+					logGlobal->infoStream() << "Hero placeholder: by power at " << objPos;
 				}
 				else
 				{
+					logGlobal->infoStream() << "Hero placeholder: " << VLC->heroh->heroes[htid]->name << " at " << objPos;
 					hp->power = 0;
 				}
 
@@ -1684,6 +1686,7 @@ void CMapLoaderH3M::readObjects()
 		}
 		if(nobj->ID == Obj::HERO)
 		{
+			logGlobal->infoStream() << "Hero: " << VLC->heroh->heroes[nobj->subID]->name << " at " << objPos;
 			map->heroesOnMap.push_back(static_cast<CGHeroInstance*>(nobj));
 		}
 	}

+ 171 - 0
lib/mapping/MapFormatJson.cpp

@@ -0,0 +1,171 @@
+/*
+* MapFormatJson.cpp, part of VCMI engine
+*
+* Authors: listed in file AUTHORS in main folder
+*
+* License: GNU General Public License v2.0 or later
+* Full text of license available in license.txt file, in main folder
+*
+*/
+
+#include "StdInc.h"
+#include "MapFormatJson.h"
+
+#include "CMap.h"
+#include "../CModHandler.h"
+#include "../VCMI_Lib.h"
+
+static const std::string conditionNames[] = {
+"haveArtifact", "haveCreatures", "haveResources",   "haveBuilding",
+"control",      "destroy",       "transport",
+"daysPassed",   "isHuman",       "daysWithoutTown", "standardWin"
+};
+
+static const std::string typeNames[] = { "victory", "defeat" };
+
+CMapLoaderJson::CMapLoaderJson(JsonNode stream):
+	input(stream)
+{
+
+}
+
+std::unique_ptr<CMap> CMapLoaderJson::loadMap()
+{
+	map = new CMap();
+	mapHeader.reset(map);
+	readMap();
+	mapHeader.reset();
+	return std::unique_ptr<CMap>(map);
+}
+
+std::unique_ptr<CMapHeader> CMapLoaderJson::loadMapHeader()
+{
+	mapHeader.reset(new CMapHeader);
+	readHeader();
+	return std::move(mapHeader);
+}
+
+/*
+	//This code can be used to write map header to console or file in its Json representation
+
+	JsonNode out;
+	JsonNode data;
+	data["victoryString"].String() = mapHeader->victoryMessage;
+	data["defeatString"].String() = mapHeader->defeatMessage;
+
+	data["victoryIconIndex"].Float() = mapHeader->victoryIconIndex;
+	data["defeatIconIndex"].Float() = mapHeader->defeatIconIndex;
+
+	for (const TriggeredEvent & entry : mapHeader->triggeredEvents)
+	{
+		JsonNode event;
+		event["message"].String() = entry.onFulfill;
+		event["effect"]["messageToSend"].String() = entry.effect.toOtherMessage;
+		event["effect"]["type"].String() = typeNames[entry.effect.type];
+		event["condition"] = entry.trigger.toJson(eventToJson);
+		data["triggeredEvents"][entry.identifier] = event;
+	}
+
+	out[mapHeader->name] = data;
+	logGlobal->errorStream() << out;
+
+JsonNode eventToJson(const EventCondition & cond)
+{
+	JsonNode ret;
+	ret.Vector().resize(2);
+	ret.Vector()[0].String() = conditionNames[size_t(cond.condition)];
+	JsonNode & data = ret.Vector()[1];
+	data["type"].Float() = cond.objectType;
+	data["value"].Float() = cond.value;
+	data["position"].Vector().resize(3);
+	data["position"].Vector()[0].Float() = cond.position.x;
+	data["position"].Vector()[1].Float() = cond.position.y;
+	data["position"].Vector()[2].Float() = cond.position.z;
+
+	return ret;
+}
+*/
+void CMapLoaderJson::patchMapHeader(std::unique_ptr<CMapHeader> & header)
+{
+	header.swap(mapHeader);
+	if (!input.isNull())
+		readPatchData();
+	header.swap(mapHeader);
+}
+
+void CMapLoaderJson::readMap()
+{
+	readHeader();
+	assert(0); // Not implemented, vcmi does not have its own map format right now
+}
+
+void CMapLoaderJson::readHeader()
+{
+	//TODO: read such data like map name & size
+	readPatchData();
+	readPlayerInfo();
+	assert(0); // Not implemented
+}
+
+void CMapLoaderJson::readPatchData()
+{
+	mapHeader->victoryMessage = input["victoryString"].String();
+	mapHeader->victoryIconIndex = input["victoryIconIndex"].Float();
+
+	mapHeader->defeatMessage = input["defeatString"].String();
+	mapHeader->defeatIconIndex = input["defeatIconIndex"].Float();
+
+	readTriggeredEvents();
+}
+
+void CMapLoaderJson::readTriggeredEvents()
+{
+	mapHeader->triggeredEvents.clear();
+
+	for (auto & entry : input["triggeredEvents"].Struct())
+	{
+		TriggeredEvent event;
+		event.identifier = entry.first;
+		readTriggeredEvent(event, entry.second);
+		mapHeader->triggeredEvents.push_back(event);
+	}
+}
+
+static EventCondition JsonToCondition(const JsonNode & node)
+{
+	EventCondition event;
+	event.condition = EventCondition::EWinLoseType(vstd::find_pos(conditionNames, node.Vector()[0].String()));
+	if (node.Vector().size() > 1)
+	{
+		const JsonNode & data = node.Vector()[1];
+		if (data["type"].getType() == JsonNode::DATA_STRING)
+			event.objectType = VLC->modh->identifiers.getIdentifier(data["type"]).get();
+		if (data["type"].getType() == JsonNode::DATA_FLOAT)
+			event.objectType = data["type"].Float();
+
+		if (!data["value"].isNull())
+			event.value = data["value"].Float();
+
+		if (!data["position"].isNull())
+		{
+			event.position.x = data["position"].Vector()[0].Float();
+			event.position.y = data["position"].Vector()[1].Float();
+			event.position.z = data["position"].Vector()[2].Float();
+		}
+	}
+	return event;
+}
+
+void CMapLoaderJson::readTriggeredEvent(TriggeredEvent & event, const JsonNode & source)
+{
+	event.onFulfill = source["message"].String();
+	event.description = source["description"].String();
+	event.effect.type = vstd::find_pos(typeNames, source["effect"]["type"].String());
+	event.effect.toOtherMessage = source["effect"]["messageToSend"].String();
+	event.trigger = EventExpression(source["condition"], JsonToCondition); // logical expression
+}
+
+void CMapLoaderJson::readPlayerInfo()
+{
+	assert(0); // Not implemented
+}

+ 91 - 0
lib/mapping/MapFormatJson.h

@@ -0,0 +1,91 @@
+
+/*
+ * MapFormatH3M.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "CMapService.h"
+#include "../JsonNode.h"
+
+class TriggeredEvent;
+
+class DLL_LINKAGE CMapLoaderJson : public IMapPatcher
+{
+public:
+	/**
+	 * Default constructor.
+	 *
+	 * @param stream a stream containing the map data
+	 */
+	CMapLoaderJson(JsonNode stream);
+
+	/**
+	 * Loads the VCMI/Json map file.
+	 *
+	 * @return a unique ptr of the loaded map class
+	 */
+	std::unique_ptr<CMap> loadMap();
+
+	/**
+	 * Loads the VCMI/Json map header.
+	 *
+	 * @return a unique ptr of the loaded map header class
+	 */
+	std::unique_ptr<CMapHeader> loadMapHeader();
+
+	/**
+	 * Modifies supplied map header using Json data
+	 *
+	 */
+	void patchMapHeader(std::unique_ptr<CMapHeader> & header);
+
+private:
+	/**
+	 * Reads complete map.
+	 */
+	void readMap();
+
+	/**
+	 * Reads the map header.
+	 */
+	void readHeader();
+
+	/**
+	 * Reads subset of header that can be replaced by patching.
+	 */
+	void readPatchData();
+
+	/**
+	 * Reads player information.
+	 */
+	void readPlayerInfo();
+
+	/**
+	 * Reads triggered events, including victory/loss conditions
+	 */
+	void readTriggeredEvents();
+
+	/**
+	 * Reads one of triggered events
+	 */
+	void readTriggeredEvent(TriggeredEvent & event, const JsonNode & source);
+
+
+	/** ptr to the map object which gets filled by data from the buffer */
+	CMap * map;
+
+	/**
+	 * ptr to the map header object which gets filled by data from the buffer.
+	 * (when loading map and mapHeader point to the same object)
+	 */
+	std::unique_ptr<CMapHeader> mapHeader;
+
+	const JsonNode input;
+};