diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index fed90b912..69b9e9b26 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -526,7 +526,7 @@ void CTownHandler::loadClientData(CTown &town, const JsonNode & source) info.tavernVideo = source["tavernVideo"].String(); else info.tavernVideo = "TAVERN.BIK"; - //end of legacy assignment + //end of legacy assignment loadTownHall(town, source["hallSlots"]); loadStructures(town, source["structures"]); @@ -795,3 +795,33 @@ std::set CTownHandler::getAllowedFactions(bool withTown /*=true*/) con return allowedFactions; } + +si32 CTownHandler::decodeFaction(const std::string & identifier) +{ + auto rawId = VLC->modh->identifiers.getIdentifier("core", "faction", identifier); + if(rawId) + return rawId.get(); + else + return -1; +} + +std::string CTownHandler::encodeFaction(const si32 index) +{ + return VLC->townh->factions[index]->identifier; +} + +si32 CTownHandler::decodeBuilding(const std::string & identifier) +{ + //FIXME: CTownHandler::decodeBuilding + auto rawId = VLC->modh->identifiers.getIdentifier("core", "building", identifier); //??? + if(rawId) + return rawId.get(); + else + return -1; +} + +std::string CTownHandler::encodeBuilding(const si32 index) +{ + //FIXME: CTownHandler::encodeBuilding + return ""; +} diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index 79cef9e10..c04ad2bf9 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -122,8 +122,8 @@ public: std::string creatureBg120; std::string creatureBg130; - - + + std::vector puzzleMap; @@ -142,7 +142,7 @@ public: static std::vector defaultMoatHexes(); CFaction * faction; - + std::vector names; //names of the town instances /// level -> list of creatures on this tier @@ -282,6 +282,18 @@ public: std::vector getDefaultAllowed() const override; std::set getAllowedFactions(bool withTown = true) const; + //json serialization helper + static si32 decodeFaction(const std::string & identifier); + + //json serialization helper + static std::string encodeFaction(const si32 index); + + //json serialization helper + static si32 decodeBuilding(const std::string & identifier); + + //json serialization helper + static std::string encodeBuilding(const si32 index); + template void serialize(Handler &h, const int version) { h & factions; diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 0d7c21d3a..cd78cc6a0 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -29,23 +29,15 @@ namespace HeaderDetail { - static const std::map difficultyReverseMap = - { - {"", 1}, - {"EASY", 0}, - {"NORMAL", 1}, - {"HARD", 2}, - {"EXPERT", 3}, - {"IMPOSSIBLE", 4} - }; + static const ui8 difficultyDefault = 1;//normal - static const std::map difficultyForwardMap = + static const std::vector difficultyMap = { - {0, "EASY"}, - {1, "NORMAL"}, - {2, "HARD"}, - {3, "EXPERT"}, - {4, "IMPOSSIBLE"} + "EASY", + "NORMAL", + "HARD", + "EXPERT", + "IMPOSSIBLE" }; } @@ -179,14 +171,218 @@ const int CMapFormatJson::VERSION_MINOR = 0; const std::string CMapFormatJson::HEADER_FILE_NAME = "header.json"; const std::string CMapFormatJson::OBJECTS_FILE_NAME = "objects.json"; +void CMapFormatJson::serializeAllowedFactions(JsonSerializeFormat & handler, std::set & value) +{ + //TODO: unify allowed factions with others - make them std::vector + + std::vector temp; + temp.resize(VLC->townh->factions.size(), false); + auto standard = VLC->townh->getDefaultAllowed(); + + if(handler.saving) + { + for(auto faction : VLC->townh->factions) + if(faction->town && vstd::contains(value, faction->index)) + temp[std::size_t(faction->index)] = true; + } + + handler.serializeLIC("allowedFactions", &CTownHandler::decodeFaction, &CTownHandler::encodeFaction, standard, temp); + + if(!handler.saving) + { + value.clear(); + for (std::size_t i=0; iname); + handler.serializeString("description", mapHeader->description); + handler.serializeNumeric("heroLevelLimit", mapHeader->levelLimit); + + //todo: support arbitrary percentage + handler.serializeNumericEnum("difficulty", HeaderDetail::difficultyMap, HeaderDetail::difficultyDefault, mapHeader->difficulty); + + serializePlayerInfo(handler); +} + +void CMapFormatJson::serializePlayerInfo(JsonSerializeFormat & handler) +{ + auto playersData = handler.enterStruct("players"); + + for(int player = 0; player < PlayerColor::PLAYER_LIMIT_I; player++) + { + PlayerInfo & info = mapHeader->players[player]; + + auto playerData = playersData.enterStruct(GameConstants::PLAYER_COLOR_NAMES[player]); + + if(handler.saving) + { + if(!info.canAnyonePlay()) + continue; + } + else + { + if(playerData.get().isNull()) + { + info.canComputerPlay = false; + info.canHumanPlay = false; + continue; + } + info.canComputerPlay = true; + } + + serializeAllowedFactions(handler, info.allowedFactions); + + handler.serializeBoolEnum("canPlay", "PlayerOrAI", "AIOnly", info.canHumanPlay); + + //mainTown + if(handler.saving) + { + + } + else + { + + } + + handler.serializeBool("generateHeroAtMainTown", info.generateHeroAtMainTown); + + + //mainHero + + //mainHeroPortrait + + //mainCustomHeroName + + //towns + + //heroes + { + auto heroes = playersData.enterStruct("heroes"); + + } + + if(!handler.saving) + { + //isFactionRandom indicates that player may select faction, depends on towns & heroes + // true if main town is random and generateHeroAtMainTown==true + // or main hero is random + //TODO: recheck mechanics + // info.isFactionRandom = + } + + + } +} + +void CMapFormatJson::readTeams(JsonDeserializer & handler) +{ + auto teams = handler.enterStruct("teams"); + const JsonNode & src = teams.get(); + + if(src.getType() != JsonNode::DATA_VECTOR) + { + // No alliances + if(src.getType() != JsonNode::DATA_NULL) + logGlobal->errorStream() << "Invalid teams field type"; + + mapHeader->howManyTeams = 0; + for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++) + { + if(mapHeader->players[i].canComputerPlay || mapHeader->players[i].canHumanPlay) + { + mapHeader->players[i].team = TeamID(mapHeader->howManyTeams++); + } + } + } + else + { + const JsonVector & srcVector = src.Vector(); + mapHeader->howManyTeams = srcVector.size(); + + for(int team = 0; team < mapHeader->howManyTeams; team++) + { + for(const JsonNode & playerData : srcVector[team].Vector()) + { + PlayerColor player = PlayerColor(vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, playerData.String())); + if(player.isValidPlayer()) + { + if(mapHeader->players[player.getNum()].canAnyonePlay()) + { + mapHeader->players[player.getNum()].team = TeamID(team); + } + } + } + } + + for(PlayerInfo & player : mapHeader->players) + { + if(player.canAnyonePlay() && player.team == TeamID::NO_TEAM) + player.team = TeamID(mapHeader->howManyTeams++); + } + + } +} + + +void CMapFormatJson::writeTeams(JsonSerializer & handler) +{ + auto teams = handler.enterStruct("teams"); + JsonNode & dest = teams.get(); + std::vector> teamsData; + + teamsData.resize(mapHeader->howManyTeams); + + //get raw data + for(int idx = 0; idx < mapHeader->players.size(); idx++) + { + const PlayerInfo & player = mapHeader->players.at(idx); + int team = player.team.getNum(); + if(vstd::iswithin(team, 0, mapHeader->howManyTeams-1) && player.canAnyonePlay()) + teamsData.at(team).insert(PlayerColor(idx)); + } + + //remove single-member teams + vstd::erase_if(teamsData, [](std::set & elem) -> bool + { + return elem.size() <= 1; + }); + + //construct output + dest.setType(JsonNode::DATA_VECTOR); + + for(const std::set & teamData : teamsData) + { + JsonNode team(JsonNode::DATA_VECTOR); + for(const PlayerColor & player : teamData) + { + JsonNode member(JsonNode::DATA_STRING); + member.String() = GameConstants::PLAYER_COLOR_NAMES[player.getNum()]; + team.Vector().push_back(std::move(member)); + } + dest.Vector().push_back(std::move(team)); + } +} + +void CMapFormatJson::serializeTriggeredEvents(JsonSerializeFormat & handler) +{ + handler.serializeString("victoryString", mapHeader->victoryMessage); + handler.serializeNumeric("victoryIconIndex", mapHeader->victoryIconIndex); + + handler.serializeString("defeatString", mapHeader->defeatMessage); + handler.serializeNumeric("defeatIconIndex", mapHeader->defeatIconIndex); +} + + void CMapFormatJson::readTriggeredEvents(JsonDeserializer & handler) { const JsonNode & input = handler.getCurrent(); - mapHeader->victoryMessage = input["victoryString"].String(); - mapHeader->victoryIconIndex = input["victoryIconIndex"].Float(); - mapHeader->defeatMessage = input["defeatString"].String(); - mapHeader->defeatIconIndex = input["defeatIconIndex"].Float(); + serializeTriggeredEvents(handler); mapHeader->triggeredEvents.clear(); @@ -210,13 +406,11 @@ void CMapFormatJson::readTriggeredEvent(TriggeredEvent & event, const JsonNode & event.trigger = EventExpression(source["condition"], JsonToCondition); // logical expression } -void CMapFormatJson::writeTriggeredEvents(JsonNode& output) +void CMapFormatJson::writeTriggeredEvents(JsonSerializer & handler) { - output["victoryString"].String() = mapHeader->victoryMessage; - output["victoryIconIndex"].Float() = mapHeader->victoryIconIndex; + JsonNode & output = handler.getCurrent(); - output["defeatString"].String() = mapHeader->defeatMessage; - output["defeatIconIndex"].Float() = mapHeader->defeatIconIndex; + serializeTriggeredEvents(handler); JsonMap & triggeredEvents = output["triggeredEvents"].Struct(); @@ -237,7 +431,6 @@ void CMapFormatJson::writeTriggeredEvent(const TriggeredEvent& event, JsonNode& dest["condition"] = event.trigger.toJson(ConditionToJson); } - ///CMapPatcher CMapPatcher::CMapPatcher(JsonNode stream): input(stream) @@ -335,6 +528,22 @@ void CMapLoaderJson::readHeader() { //do not use map field here, use only mapHeader JsonNode header = getFromArchive(HEADER_FILE_NAME); + + int versionMajor = header["versionMajor"].Float(); + + if(versionMajor != VERSION_MAJOR) + { + logGlobal->errorStream() << "Unsupported map format version: " << versionMajor; + throw std::runtime_error("Unsupported map format version"); + } + + int versionMinor = header["versionMinor"].Float(); + + if(versionMinor > VERSION_MINOR) + { + logGlobal->traceStream() << "Too new map format revision: " << versionMinor << ". This map should work but some of map features may be ignored."; + } + JsonDeserializer handler(header); mapHeader->version = EMapFormat::VCMI;//todo: new version field @@ -354,13 +563,7 @@ void CMapLoaderJson::readHeader() } } - mapHeader->name = header["name"].String(); - mapHeader->description = header["description"].String(); - - //todo: support arbitrary percentage - - mapHeader->difficulty = HeaderDetail::difficultyReverseMap.at(header["difficulty"].String()); - mapHeader->levelLimit = header["heroLevelLimit"].Float(); + serializeHeader(handler); // std::vector allowedHeroes; @@ -368,99 +571,11 @@ void CMapLoaderJson::readHeader() readTriggeredEvents(handler); - readPlayerInfo(handler); readTeams(handler); //TODO: readHeader } -void CMapLoaderJson::readPlayerInfo(JsonDeserializer & handler) -{ - auto playersData = handler.enterStruct("players"); - - for(int player = 0; player < PlayerColor::PLAYER_LIMIT_I; player++) - { - PlayerInfo & info = mapHeader->players.at(player); - - auto playerData = playersData.enterStruct(GameConstants::PLAYER_COLOR_NAMES[player]); - - if(playerData.get().isNull()) - { - info.canComputerPlay = false; - info.canHumanPlay = false; - } - else - { - //allowed factions - - // info.isFactionRandom = - - info.canComputerPlay = true; - info.canHumanPlay = playerData.get()["canPlay"].String() != "AIOnly"; - - //placedHeroes - - //mainTown - - info.generateHeroAtMainTown = playerData.get()["generateHeroAtMainTown"].Bool(); - - //mainHero - - //mainHeroPortrait - - //mainCustomHeroName - } - } -} - -void CMapLoaderJson::readTeams(JsonDeserializer & handler) -{ - auto teamsData = handler.enterStruct("teams"); - const JsonNode & src = teamsData.get(); - - if(src.getType() != JsonNode::DATA_VECTOR) - { - // No alliances - if(src.getType() != JsonNode::DATA_NULL) - logGlobal->errorStream() << "Invalid teams field type"; - - mapHeader->howManyTeams = 0; - for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++) - { - if(mapHeader->players[i].canComputerPlay || mapHeader->players[i].canHumanPlay) - { - mapHeader->players[i].team = TeamID(mapHeader->howManyTeams++); - } - } - } - else - { - const JsonVector & srcVector = src.Vector(); - mapHeader->howManyTeams = srcVector.size(); - - for(int team = 0; team < mapHeader->howManyTeams; team++) - { - for(const JsonNode & playerData : srcVector[team].Vector()) - { - PlayerColor player = PlayerColor(vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, playerData.String())); - if(player.isValidPlayer()) - { - if(mapHeader->players[player.getNum()].canAnyonePlay()) - { - mapHeader->players[player.getNum()].team = TeamID(team); - } - } - } - } - - for(PlayerInfo & player : mapHeader->players) - { - if(player.canAnyonePlay() && player.team == TeamID::NO_TEAM) - player.team = TeamID(mapHeader->howManyTeams++); - } - - } -} void CMapLoaderJson::readTerrainTile(const std::string& src, TerrainTile& tile) { @@ -775,20 +890,11 @@ void CMapSaverJson::writeHeader() levels["underground"]["index"].Float() = 1; } - header["name"].String() = mapHeader->name; - header["description"].String() = mapHeader->description; + serializeHeader(handler); + writeTriggeredEvents(handler); - //todo: support arbitrary percentage - - header["difficulty"].String() = HeaderDetail::difficultyForwardMap.at(mapHeader->difficulty); - header["heroLevelLimit"].Float() = mapHeader->levelLimit; - - writeTriggeredEvents(header); - - writePlayerInfo(header); - - writeTeams(header); + writeTeams(handler); //todo: allowedHeroes; //todo: placeholdedHeroes; @@ -796,73 +902,6 @@ void CMapSaverJson::writeHeader() addToArchive(header, HEADER_FILE_NAME); } -void CMapSaverJson::writePlayerInfo(JsonNode & output) -{ - JsonNode & dest = output["players"]; - dest.setType(JsonNode::DATA_STRUCT); - - for(int player = 0; player < PlayerColor::PLAYER_LIMIT_I; player++) - { - const PlayerInfo & info = mapHeader->players[player]; - - if(info.canAnyonePlay()) - writePlayerInfo(info, dest[GameConstants::PLAYER_COLOR_NAMES[player]]); - } -} - -void CMapSaverJson::writePlayerInfo(const PlayerInfo & info, JsonNode & output) -{ - //allowed factions - - output["canPlay"].String() = info.canHumanPlay ? "PlayerOrAI" : "AIOnly"; - - //mainTown - output["generateHeroAtMainTown"].Bool() = info.generateHeroAtMainTown; - - //mainHero - - //towns - //heroes -} - -void CMapSaverJson::writeTeams(JsonNode& output) -{ - JsonNode & dest = output["teams"]; - std::vector> teamsData; - - teamsData.resize(mapHeader->howManyTeams); - - //get raw data - for(int idx = 0; idx < mapHeader->players.size(); idx++) - { - const PlayerInfo & player = mapHeader->players.at(idx); - int team = player.team.getNum(); - if(vstd::iswithin(team, 0, mapHeader->howManyTeams-1) && player.canAnyonePlay()) - teamsData.at(team).insert(PlayerColor(idx)); - } - - //remove single-member teams - vstd::erase_if(teamsData, [](std::set & elem) -> bool - { - return elem.size() <= 1; - }); - - //construct output - dest.setType(JsonNode::DATA_VECTOR); - - for(const std::set & teamData : teamsData) - { - JsonNode team(JsonNode::DATA_VECTOR); - for(const PlayerColor & player : teamData) - { - JsonNode member(JsonNode::DATA_STRING); - member.String() = GameConstants::PLAYER_COLOR_NAMES[player.getNum()]; - team.Vector().push_back(std::move(member)); - } - dest.Vector().push_back(std::move(team)); - } -} - const std::string CMapSaverJson::writeTerrainTile(const TerrainTile & tile) { using namespace TerrainDetail; diff --git a/lib/mapping/MapFormatJson.h b/lib/mapping/MapFormatJson.h index 50488c70b..d7fa2fc8c 100644 --- a/lib/mapping/MapFormatJson.h +++ b/lib/mapping/MapFormatJson.h @@ -22,6 +22,8 @@ struct TerrainTile; struct PlayerInfo; class CGObjectInstance; class AObjectTypeHandler; + +class JsonSerializeFormat; class JsonDeserializer; class JsonSerializer; @@ -44,6 +46,30 @@ protected: */ CMapHeader * mapHeader; + void serializeAllowedFactions(JsonSerializeFormat & handler, std::set & value); + + ///common part of header saving/loading + void serializeHeader(JsonSerializeFormat & handler); + + ///player information saving/loading + void serializePlayerInfo(JsonSerializeFormat & handler); + + /** + * Reads team settings to header + * @param input serialized header + */ + void readTeams(JsonDeserializer & handler); + + /** + * Saves team settings to header + * @param output serialized header + */ + void writeTeams(JsonSerializer & handler); + + + ///common part triggered events of saving/loading + void serializeTriggeredEvents(JsonSerializeFormat & handler); + /** * Reads triggered events, including victory/loss conditions */ @@ -52,7 +78,7 @@ protected: /** * Writes triggered events, including victory/loss conditions */ - void writeTriggeredEvents(JsonNode & output); + void writeTriggeredEvents(JsonSerializer & handler); /** * Reads one of triggered events @@ -137,26 +163,15 @@ private: si32 getIdentifier(const std::string & type, const std::string & name); - /** - * Reads complete map. - */ - void readMap(); - /** * Reads the map header. */ void readHeader(); /** - * Reads player information. + * Reads complete map. */ - void readPlayerInfo(JsonDeserializer & handler); - - /** - * Reads team settings to header - * @param input serialized header - */ - void readTeams(JsonDeserializer & handler); + void readMap(); void readTerrainTile(const std::string & src, TerrainTile & tile); @@ -205,24 +220,6 @@ private: */ void writeHeader(); - /** - * Saves all players info to header - * @param output serialized header - */ - void writePlayerInfo(JsonNode & output); - - /** - * Saves one player info - * @param output empty object - */ - void writePlayerInfo(const PlayerInfo & info, JsonNode & output); - - /** - * Saves team settings to header - * @param output serialized header - */ - void writeTeams(JsonNode & output); - /** * Encodes one tile into string * @param tile tile to serialize diff --git a/lib/serializer/JsonDeserializer.cpp b/lib/serializer/JsonDeserializer.cpp index 5f1737434..ba07f50c1 100644 --- a/lib/serializer/JsonDeserializer.cpp +++ b/lib/serializer/JsonDeserializer.cpp @@ -15,9 +15,94 @@ #include "../JsonNode.h" JsonDeserializer::JsonDeserializer(JsonNode & root_): - JsonSerializeFormat(root_) + JsonSerializeFormat(root_, false) { } +void JsonDeserializer::serializeBool(const std::string & fieldName, bool & value) +{ + value = current->operator[](fieldName).Bool(); +} + +void JsonDeserializer::serializeBoolEnum(const std::string & fieldName, const std::string & trueValue, const std::string & falseValue, bool & value) +{ + const JsonNode & tmp = current->operator[](fieldName); + + value = tmp.String() == trueValue; +} + +void JsonDeserializer::serializeFloat(const std::string & fieldName, double & value) +{ + value = current->operator[](fieldName).Float(); +} + +void JsonDeserializer::serializeIntEnum(const std::string & fieldName, const std::vector & enumMap, const si32 defaultValue, si32 & value) +{ + const std::string & valueName = current->operator[](fieldName).String(); + + si32 rawValue = vstd::find_pos(enumMap, valueName); + if(rawValue < 0) + value = defaultValue; + else + value = rawValue; +} + +void JsonDeserializer::serializeLIC(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const std::vector & standard, std::vector & value) +{ + const JsonNode & field = current->operator[](fieldName); + if(field.isNull()) + return; + + const JsonVector & anyOf = field["anyOf"].Vector(); + const JsonVector & allOf = field["allOf"].Vector(); + const JsonVector & noneOf = field["noneOf"].Vector(); + + + if(anyOf.empty() && allOf.empty()) + { + //permissive mode + value = standard; + } + else + { + //restrictive mode + value.clear(); + value.resize(standard.size(), false); + + for(size_t index = 0; index < anyOf.size(); index++) + { + const std::string & identifier = anyOf[index].String(); + + si32 rawId = decoder(identifier); + if(rawId >= 0) + value[rawId] = true; + } + + + for(size_t index = 0; index < allOf.size(); index++) + { + const std::string & identifier = allOf[index].String(); + + si32 rawId = decoder(identifier); + if(rawId >=0) + value[rawId] = true; + } + } + + for(size_t index = 0; index < noneOf.size(); index++) + { + const std::string & identifier = noneOf[index].String(); + + si32 rawId = decoder(identifier); + if(rawId >=0 ) + value[rawId] = false; + } + +} + +void JsonDeserializer::serializeString(const std::string & fieldName, std::string & value) +{ + value = current->operator[](fieldName).String(); +} diff --git a/lib/serializer/JsonDeserializer.h b/lib/serializer/JsonDeserializer.h index 91bbae0bc..b05c9582a 100644 --- a/lib/serializer/JsonDeserializer.h +++ b/lib/serializer/JsonDeserializer.h @@ -17,7 +17,14 @@ class JsonNode; class JsonDeserializer: public JsonSerializeFormat { public: - static const bool saving = false; - JsonDeserializer(JsonNode & root_); + + void serializeBool(const std::string & fieldName, bool & value) override; + void serializeBoolEnum(const std::string & fieldName, const std::string & trueValue, const std::string & falseValue, bool & value) override; + void serializeLIC(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const std::vector & standard, std::vector & value) override; + void serializeString(const std::string & fieldName, std::string & value) override; + +protected: + void serializeFloat(const std::string & fieldName, double & value) override; + void serializeIntEnum(const std::string & fieldName, const std::vector & enumMap, const si32 defaultValue, si32 & value) override; }; diff --git a/lib/serializer/JsonSerializeFormat.cpp b/lib/serializer/JsonSerializeFormat.cpp index 68d7c0e71..11b4f947d 100644 --- a/lib/serializer/JsonSerializeFormat.cpp +++ b/lib/serializer/JsonSerializeFormat.cpp @@ -66,7 +66,8 @@ JsonSerializeFormat * JsonStructSerializer::operator->() //JsonSerializeFormat -JsonSerializeFormat::JsonSerializeFormat(JsonNode & root_): +JsonSerializeFormat::JsonSerializeFormat(JsonNode & root_, const bool saving_): + saving(saving_), root(&root_), current(root) { diff --git a/lib/serializer/JsonSerializeFormat.h b/lib/serializer/JsonSerializeFormat.h index 59b278fce..6b84308de 100644 --- a/lib/serializer/JsonSerializeFormat.h +++ b/lib/serializer/JsonSerializeFormat.h @@ -36,27 +36,77 @@ private: friend class JsonSerializeFormat; }; -class JsonSerializeFormat +class JsonSerializeFormat: public boost::noncopyable { public: - JsonSerializeFormat(JsonNode & root_); + ///user-provided callback to resolve string identifier + ///returns resolved identifier or -1 on error + typedef std::function TDecoder; + + ///user-provided callback to get string identifier + ///may assume that object index is valid + typedef std::function TEncoder; + + const bool saving; + + JsonSerializeFormat() = delete; virtual ~JsonSerializeFormat() = default; JsonNode & getRoot() { - return *root; + return * root; }; JsonNode & getCurrent() { - return *current; + return * current; }; JsonStructSerializer enterStruct(const std::string & fieldName); + virtual void serializeBool(const std::string & fieldName, bool & value) = 0; + virtual void serializeBoolEnum(const std::string & fieldName, const std::string & trueValue, const std::string & falseValue, bool & value) = 0; + + /** @brief Restrictive serialization of Logical identifier condition (only "anyOf" used), full deserialization + * + * @param fieldName + * @param decoder resolve callback, should report errors itself and do not throw + * @param encoder encode callback, should report errors itself and do not throw + * @param value target value, must be resized properly + * + */ + virtual void serializeLIC(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const std::vector & standard, std::vector & value) = 0; + + + template + void serializeNumericEnum(const std::string & fieldName, const std::vector & enumMap, const T defaultValue, T & value) + { + si32 temp = value; + serializeIntEnum(fieldName, enumMap, defaultValue, temp); + if(!saving) + value = temp; + }; + + template + void serializeNumeric(const std::string & fieldName, T & value) + { + double temp = value; + serializeFloat(fieldName, temp); + if(!saving) + value = temp; + }; + + virtual void serializeString(const std::string & fieldName, std::string & value) = 0; + protected: JsonNode * root; JsonNode * current; + + JsonSerializeFormat(JsonNode & root_, const bool saving_); + + virtual void serializeFloat(const std::string & fieldName, double & value) = 0; + + virtual void serializeIntEnum(const std::string & fieldName, const std::vector & enumMap, const si32 defaultValue, si32 & value) = 0; private: friend class JsonStructSerializer; }; diff --git a/lib/serializer/JsonSerializer.cpp b/lib/serializer/JsonSerializer.cpp index 21755321c..8fff1f7af 100644 --- a/lib/serializer/JsonSerializer.cpp +++ b/lib/serializer/JsonSerializer.cpp @@ -15,9 +15,51 @@ #include "../JsonNode.h" JsonSerializer::JsonSerializer(JsonNode & root_): - JsonSerializeFormat(root_) + JsonSerializeFormat(root_, true) { } +void JsonSerializer::serializeBool(const std::string & fieldName, bool & value) +{ + current->operator[](fieldName).Bool() = value; +} + +void JsonSerializer::serializeBoolEnum(const std::string & fieldName, const std::string & trueValue, const std::string & falseValue, bool & value) +{ + current->operator[](fieldName).String() = value ? trueValue : falseValue; +} + +void JsonSerializer::serializeFloat(const std::string & fieldName, double & value) +{ + current->operator[](fieldName).Float() = value; +} + +void JsonSerializer::serializeIntEnum(const std::string & fieldName, const std::vector & enumMap, const si32 defaultValue, si32 & value) +{ + current->operator[](fieldName).String() = enumMap.at(value); +} + +void JsonSerializer::serializeLIC(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const std::vector & standard, std::vector & value) +{ + assert(standard.size() == value.size()); + if(standard == value) + return; + auto & target = current->operator[](fieldName)["anyOf"].Vector(); + for(si32 idx = 0; idx < value.size(); idx ++) + { + if(value[idx]) + { + JsonNode val(JsonNode::DATA_STRING); + val.String() = encoder(idx); + target.push_back(std::move(val)); + } + } +} + + +void JsonSerializer::serializeString(const std::string & fieldName, std::string & value) +{ + current->operator[](fieldName).String() = value; +} diff --git a/lib/serializer/JsonSerializer.h b/lib/serializer/JsonSerializer.h index 0f4a9e925..6eca2cce0 100644 --- a/lib/serializer/JsonSerializer.h +++ b/lib/serializer/JsonSerializer.h @@ -17,9 +17,14 @@ class JsonNode; class JsonSerializer: public JsonSerializeFormat { public: - static const bool saving = true; - JsonSerializer(JsonNode & root_); + void serializeBool(const std::string & fieldName, bool & value) override; + void serializeBoolEnum(const std::string & fieldName, const std::string & trueValue, const std::string & falseValue, bool & value) override; + void serializeLIC(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const std::vector & standard, std::vector & value) override; + void serializeString(const std::string & fieldName, std::string & value) override; +protected: + void serializeFloat(const std::string & fieldName, double & value) override; + void serializeIntEnum(const std::string & fieldName, const std::vector & enumMap, const si32 defaultValue, si32 & value) override; }; diff --git a/test/CMapFormatTest.cpp b/test/CMapFormatTest.cpp index f022e1862..f0b1b1070 100644 --- a/test/CMapFormatTest.cpp +++ b/test/CMapFormatTest.cpp @@ -61,12 +61,13 @@ BOOST_GLOBAL_FIXTURE(CMapTestFixture); BOOST_AUTO_TEST_CASE(CMapFormatVCMI_Simple) { logGlobal->info("CMapFormatVCMI_Simple start"); + BOOST_TEST_CHECKPOINT("CMapFormatVCMI_Simple start"); CMemoryBuffer serializeBuffer; { CMapSaverJson saver(&serializeBuffer); saver.saveMap(initialMap); } - + BOOST_TEST_CHECKPOINT("CMapFormatVCMI_Simple serialized"); #if 1 { auto path = VCMIDirs::get().userDataPath()/"test.vmap"; @@ -79,7 +80,7 @@ BOOST_AUTO_TEST_CASE(CMapFormatVCMI_Simple) logGlobal->infoStream() << "Test map has been saved to " << path; } - + BOOST_TEST_CHECKPOINT("CMapFormatVCMI_Simple saved"); #endif // 1