From 7e02f6b670215373b72181518b4c5af2baed621f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 30 Dec 2013 23:09:58 +0000 Subject: [PATCH] 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 --- client/CPreGame.cpp | 6 +- config/mapOverrides.json | 2 + lib/CGameState.cpp | 8 +- lib/CMakeLists.txt | 1 + lib/CModHandler.cpp | 22 +++- lib/CModHandler.h | 3 +- lib/LogicalExpression.h | 49 +++++++++ lib/mapping/CCampaignHandler.cpp | 6 +- lib/mapping/CMapService.cpp | 37 +++++-- lib/mapping/CMapService.h | 25 ++++- lib/mapping/MapFormatH3M.cpp | 3 + lib/mapping/MapFormatJson.cpp | 171 +++++++++++++++++++++++++++++++ lib/mapping/MapFormatJson.h | 91 ++++++++++++++++ 13 files changed, 406 insertions(+), 18 deletions(-) create mode 100644 config/mapOverrides.json create mode 100644 lib/mapping/MapFormatJson.cpp create mode 100644 lib/mapping/MapFormatJson.h diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index 4caeba3fc..b47b5b399 100644 --- a/client/CPreGame.cpp +++ b/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(selectedMap); + //get header delete ourHeader; std::string & headerStr = ourCampaign->camp->mapPieces.find(mapNr)->second; auto buffer = reinterpret_cast(headerStr.data()); - ourHeader = CMapService::loadMapHeader(buffer, headerStr.size()).release(); + ourHeader = CMapService::loadMapHeader(buffer, headerStr.size(), scenarioName).release(); std::map names; names[1] = settings["general"]["playerName"].String(); diff --git a/config/mapOverrides.json b/config/mapOverrides.json new file mode 100644 index 000000000..2c63c0851 --- /dev/null +++ b/config/mapOverrides.json @@ -0,0 +1,2 @@ +{ +} diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 4d829ab5a..bb2dd4bef 100644 --- a/lib/CGameState.cpp +++ b/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(*campaign->currentMap); + + std::string & mapContent = campaign->camp->mapPieces[*campaign->currentMap]; auto buffer = reinterpret_cast(mapContent.data()); - map = CMapService::loadMap(buffer, mapContent.size()).release(); + map = CMapService::loadMap(buffer, mapContent.size(), scenarioName).release(); } void CGameState::initDuel() diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 7d7111e8e..e23b37072 100644 --- a/lib/CMakeLists.txt +++ b/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 diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 8b0ab39ce..704905c68 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -122,7 +122,7 @@ void CIdentifierStorage::tryRequestIdentifier(std::string type, const JsonNode & boost::optional 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(), silent)); + auto idList = getPossibleIdentifiers(ObjectCallback(name.meta, pair.first, type, pair.second, std::function(), silent)); if (idList.size() == 1) return idList.front().id; @@ -132,6 +132,20 @@ boost::optional CIdentifierStorage::getIdentifier(std::string type, const return boost::optional(); } +boost::optional CIdentifierStorage::getIdentifier(const JsonNode & name, bool silent) +{ + auto pair = splitString(name.String(), ':'); // remoteScope: + auto pair2 = splitString(pair.second, '.'); // type.name + auto idList = getPossibleIdentifiers(ObjectCallback(name.meta, pair.first, pair2.first, pair2.second, std::function(), 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(); +} + 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::getIdentifier(const ObjectCallback & request) +std::vector CIdentifierStorage::getPossibleIdentifiers(const ObjectCallback & request) { std::set 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::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); diff --git a/lib/CModHandler.h b/lib/CModHandler.h index 6fd2395a7..9c36f0385 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -51,7 +51,7 @@ class CIdentifierStorage void requestIdentifier(ObjectCallback callback); bool resolveIdentifier(const ObjectCallback & callback); - std::vector getIdentifier(const ObjectCallback & callback); + std::vector 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 getIdentifier(std::string type, const JsonNode & name, bool silent = false); + boost::optional getIdentifier(const JsonNode & name, bool silent = false); /// registers new object void registerObject(std::string scope, std::string type, std::string name, si32 identifier); diff --git a/lib/LogicalExpression.h b/lib/LogicalExpression.h index 0639a6a3b..13dbc6e0b 100644 --- a/lib/LogicalExpression.h +++ b/lib/LogicalExpression.h @@ -223,6 +223,49 @@ namespace LogicalExpressionDetail } }; + /// Prints expression in human-readable format + template + class Writer : public boost::static_visitor + { + typedef ExpressionBase Base; + + std::function classPrinter; + + JsonNode printExpressionList(std::string name, const std::vector & 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 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 toJson) const + { + LogicalExpressionDetail::Writer writeVisitor(toJson); + return boost::apply_visitor(writeVisitor, data); + } + template void serialize(Handler & h, const int version) { diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index cd6d840f8..832a4c22c 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -64,9 +64,13 @@ unique_ptr CCampaignHandler::getCampaign( const std::string & name ) scenarioID++; } + std::string scenarioName = name.substr(0, name.find('.')); + boost::to_lower(scenarioName); + scenarioName += ':' + boost::lexical_cast(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++; } diff --git a/lib/mapping/CMapService.cpp b/lib/mapping/CMapService.cpp index 83c50dfc1..1f274200a 100644 --- a/lib/mapping/CMapService.cpp +++ b/lib/mapping/CMapService.cpp @@ -8,30 +8,47 @@ #include "CMap.h" #include "MapFormatH3M.h" +#include "MapFormatJson.h" std::unique_ptr CMapService::loadMap(const std::string & name) { auto stream = getStreamFromFS(name); - return getMapLoader(stream)->loadMap(); + std::unique_ptr map(getMapLoader(stream)->loadMap()); + std::unique_ptr header(map.get()); + + getMapPatcher(name)->patchMapHeader(header); + header.release(); + + return std::move(map); } std::unique_ptr CMapService::loadMapHeader(const std::string & name) { auto stream = getStreamFromFS(name); - return getMapLoader(stream)->loadMapHeader(); + std::unique_ptr header = getMapLoader(stream)->loadMapHeader(); + getMapPatcher(name)->patchMapHeader(header); + return std::move(header); } -std::unique_ptr CMapService::loadMap(const ui8 * buffer, int size) +std::unique_ptr CMapService::loadMap(const ui8 * buffer, int size, const std::string & name) { auto stream = getStreamFromMem(buffer, size); - return getMapLoader(stream)->loadMap(); + std::unique_ptr map(getMapLoader(stream)->loadMap()); + std::unique_ptr header(map.get()); + + getMapPatcher(name)->patchMapHeader(header); + header.release(); + + return std::move(map); } -std::unique_ptr CMapService::loadMapHeader(const ui8 * buffer, int size) +std::unique_ptr CMapService::loadMapHeader(const ui8 * buffer, int size, const std::string & name) { auto stream = getStreamFromMem(buffer, size); - return getMapLoader(stream)->loadMapHeader(); + std::unique_ptr header = getMapLoader(stream)->loadMapHeader(); + getMapPatcher(name)->patchMapHeader(header); + return std::move(header); } std::unique_ptr CMapService::getStreamFromFS(const std::string & name) @@ -68,3 +85,11 @@ std::unique_ptr CMapService::getMapLoader(std::unique_ptr 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(new CMapLoaderJson(node[scenarioName])); +} diff --git a/lib/mapping/CMapService.h b/lib/mapping/CMapService.h index 177218ffd..82333e9ae 100644 --- a/lib/mapping/CMapService.h +++ b/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 loadMap(const ui8 * buffer, int size); + static std::unique_ptr 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 loadMapHeader(const ui8 * buffer, int size); + static std::unique_ptr 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 getMapLoader(std::unique_ptr & stream); + + /** + * Gets a map patcher for specified scenario + * + * @param scenarioName for patcher + * @return the constructed map patcher + */ + static std::unique_ptr getMapPatcher(std::string scenarioName); }; /** @@ -115,4 +126,12 @@ public: virtual std::unique_ptr loadMapHeader() = 0; }; - +class DLL_LINKAGE IMapPatcher : public IMapLoader +{ +public: + /** + * Modifies supplied map header using Json data + * + */ + virtual void patchMapHeader(std::unique_ptr & header) = 0; +}; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 5b9e44eeb..7faf6d597 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/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(nobj)); } } diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp new file mode 100644 index 000000000..70147d3e6 --- /dev/null +++ b/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 CMapLoaderJson::loadMap() +{ + map = new CMap(); + mapHeader.reset(map); + readMap(); + mapHeader.reset(); + return std::unique_ptr(map); +} + +std::unique_ptr 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 & 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 +} diff --git a/lib/mapping/MapFormatJson.h b/lib/mapping/MapFormatJson.h new file mode 100644 index 000000000..80751cfd2 --- /dev/null +++ b/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 loadMap(); + + /** + * Loads the VCMI/Json map header. + * + * @return a unique ptr of the loaded map header class + */ + std::unique_ptr loadMapHeader(); + + /** + * Modifies supplied map header using Json data + * + */ + void patchMapHeader(std::unique_ptr & 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 mapHeader; + + const JsonNode input; +};