From 9e5e1aebbc501751f1a7cf0bb2e3e9c582349ef1 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 9 Apr 2023 00:16:11 +0400 Subject: [PATCH 01/26] Remove hardcoded campaign screens --- client/lobby/CBonusSelection.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index 104184418..a2ae41337 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -60,14 +60,10 @@ CBonusSelection::CBonusSelection() : CWindowObject(BORDERED) { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; - static const std::string bgNames[] = - { - "E1_BG.BMP", "G2_BG.BMP", "E2_BG.BMP", "G1_BG.BMP", "G3_BG.BMP", "N1_BG.BMP", - "S1_BG.BMP", "BR_BG.BMP", "IS_BG.BMP", "KR_BG.BMP", "NI_BG.BMP", "TA_BG.BMP", "AR_BG.BMP", "HS_BG.BMP", - "BB_BG.BMP", "NB_BG.BMP", "EL_BG.BMP", "RN_BG.BMP", "UA_BG.BMP", "SP_BG.BMP" - }; + loadPositionsOfGraphics(); - setBackground(bgNames[getCampaign()->camp->header.mapVersion]); + std::string bgName = campDescriptions[getCampaign()->camp->header.mapVersion].campPrefix + "_BG.BMP"; + setBackground(bgName); panelBackground = std::make_shared("CAMPBRF.BMP", 456, 6); From 307fb071a240f1c71245620fdbcbd07df477e30d Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 9 Apr 2023 03:24:40 +0400 Subject: [PATCH 02/26] VCMI campaign format support prototype --- client/lobby/SelectionTab.cpp | 3 +- lib/filesystem/ResourceID.cpp | 1 + lib/mapping/CCampaignHandler.cpp | 320 ++++++++++++++++++++++++++----- lib/mapping/CCampaignHandler.h | 15 +- lib/mapping/CMapInfo.cpp | 2 + 5 files changed, 292 insertions(+), 49 deletions(-) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index d3c22e77c..ece6f53f4 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -595,7 +595,8 @@ void SelectionTab::parseCampaigns(const std::unordered_set & files) //allItems[i].date = std::asctime(std::localtime(&files[i].date)); info->fileURI = file.getName(); info->campaignInit(); - allItems.push_back(info); + if(info->campaignHeader) + allItems.push_back(info); } } diff --git a/lib/filesystem/ResourceID.cpp b/lib/filesystem/ResourceID.cpp index 63f61afca..db7f2118b 100644 --- a/lib/filesystem/ResourceID.cpp +++ b/lib/filesystem/ResourceID.cpp @@ -159,6 +159,7 @@ EResType::Type EResTypeHelper::getTypeFromExtension(std::string extension) {".ERT", EResType::ERT}, {".ERS", EResType::ERS}, {".VMAP", EResType::MAP}, + {".VCMP", EResType::CAMPAIGN}, {".VERM", EResType::ERM}, {".LUA", EResType::LUA} }; diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index e28fa9d98..dde49311b 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -47,15 +47,22 @@ CCampaignHeader CCampaignHandler::getHeader( const std::string & name) std::string modName = VLC->modh->findResourceOrigin(resourceID); std::string language = VLC->modh->getModLanguage(modName); std::string encoding = Languages::getLanguageOptions(language).encoding; - auto fileStream = CResourceHandler::get(modName)->load(resourceID); - - std::vector cmpgn = getFile(std::move(fileStream), true)[0]; - - CMemoryStream stream(cmpgn.data(), cmpgn.size()); - CBinaryReader reader(&stream); - CCampaignHeader ret = readHeaderFromMemory(reader, resourceID.getName(), modName, encoding); - - return ret; + + JsonNode jsonCampaign(resourceID); + if(jsonCampaign.isNull()) + { + auto fileStream = CResourceHandler::get(modName)->load(resourceID); + std::vector cmpgn = getFile(std::move(fileStream), true)[0]; + CMemoryStream stream(cmpgn.data(), cmpgn.size()); + CBinaryReader reader(&stream); + CCampaignHeader ret = readHeaderFromMemory(reader, resourceID.getName(), modName, encoding); + return ret; + } + else + { + CCampaignHeader ret = readHeaderFromJson(jsonCampaign, resourceID.getName(), modName, encoding); + return ret; + } } std::unique_ptr CCampaignHandler::getCampaign( const std::string & name ) @@ -64,48 +71,69 @@ std::unique_ptr CCampaignHandler::getCampaign( const std::string & na std::string modName = VLC->modh->findResourceOrigin(resourceID); std::string language = VLC->modh->getModLanguage(modName); std::string encoding = Languages::getLanguageOptions(language).encoding; - auto fileStream = CResourceHandler::get(modName)->load(resourceID); - + auto ret = std::make_unique(); - - std::vector> file = getFile(std::move(fileStream), false); - - CMemoryStream stream(file[0].data(), file[0].size()); - CBinaryReader reader(&stream); - ret->header = readHeaderFromMemory(reader, resourceID.getName(), modName, encoding); - - int howManyScenarios = static_cast(VLC->generaltexth->getCampaignLength(ret->header.mapVersion)); - for(int g=0; gheader.version, ret->header.mapVersion); - ret->scenarios.push_back(sc); - } + auto fileStream = CResourceHandler::get(modName)->load(resourceID); - int scenarioID = 0; + std::vector> file = getFile(std::move(fileStream), false); - //first entry is campaign header. start loop from 1 - for (int g=1; gscenarios[scenarioID].isNotVoid()) //skip void scenarios + CMemoryStream stream(file[0].data(), file[0].size()); + CBinaryReader reader(&stream); + ret->header = readHeaderFromMemory(reader, resourceID.getName(), modName, encoding); + + int howManyScenarios = static_cast(VLC->generaltexth->getCampaignLength(ret->header.mapVersion)); + for(int g=0; gheader.version, ret->header.mapVersion); + ret->scenarios.push_back(sc); + } + + int scenarioID = 0; + + //first entry is campaign header. start loop from 1 + for (int g=1; gscenarios[scenarioID].isNotVoid()) //skip void scenarios + { + scenarioID++; + } + + std::string scenarioName = resourceID.getName(); + boost::to_lower(scenarioName); + scenarioName += ':' + std::to_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()); + CMapService mapService; + auto hdr = mapService.loadMapHeader( + reinterpret_cast(ret->mapPieces[scenarioID].c_str()), + static_cast(ret->mapPieces[scenarioID].size()), + scenarioName, + modName, + encoding); + ret->scenarios[scenarioID].scenarioName = hdr->name; scenarioID++; } - - std::string scenarioName = resourceID.getName(); - boost::to_lower(scenarioName); - scenarioName += ':' + std::to_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()); - CMapService mapService; - auto hdr = mapService.loadMapHeader( - reinterpret_cast(ret->mapPieces[scenarioID].c_str()), - static_cast(ret->mapPieces[scenarioID].size()), - scenarioName, - modName, - encoding); - ret->scenarios[scenarioID].scenarioName = hdr->name; - scenarioID++; + } + else + { + ret->header = readHeaderFromJson(jsonCampaign, resourceID.getName(), modName, encoding); + + for(auto & scenario : jsonCampaign["scenarios"].Vector()) + { + CCampaignScenario sc = readScenarioFromJson(scenario, resourceID.getName(), modName, encoding, ret->header.version, ret->header.mapVersion); + if(sc.isNotVoid()) + { + CMapService mapService; + auto hdr = mapService.loadMapHeader(ResourceID(sc.mapName, EResType::MAP)); + sc.scenarioName = hdr->name; + } + ret->scenarios.push_back(sc); + } } // handle campaign specific discrepancies @@ -151,6 +179,198 @@ std::string CCampaignHandler::readLocalizedString(CBinaryReader & reader, std::s return VLC->generaltexth->translate(stringID.get()); } +CCampaignHeader CCampaignHandler::readHeaderFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding ) +{ + CCampaignHeader ret; + + ret.version = reader["version"].Integer(); + if(ret.version < CampaignVersion::VCMI_MIN || ret.version > CampaignVersion::VCMI_MAX) + { + logGlobal->info("Unsupported campaign %s version %d", filename, ret.version); + return ret; + } + + ret.version = CampaignVersion::VCMI; + ret.mapVersion = reader["campaignId"].Integer(); + ret.name = reader["name"].String(); + ret.description = reader["description"].String(); + ret.difficultyChoosenByPlayer = reader["allowDifficultySelection"].Bool(); + //skip ret.music because it's unused in vcmi + ret.filename = filename; + ret.modName = modName; + ret.encoding = encoding; + ret.valid = true; + return ret; +} + +CCampaignScenario CCampaignHandler::readScenarioFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding, int version, int mapVersion) +{ + auto prologEpilogReader = [](JsonNode & identifier) -> CCampaignScenario::SScenarioPrologEpilog + { + CCampaignScenario::SScenarioPrologEpilog ret; + ret.hasPrologEpilog = !identifier.isNull(); + if(ret.hasPrologEpilog) + { + ret.prologVideo = identifier["video"].Integer(); + ret.prologMusic = identifier["music"].Integer(); + ret.prologText = identifier["text"].String(); + } + return ret; + }; + + CCampaignScenario ret; + ret.conquered = false; + ret.mapName = reader["map"].String(); + for(auto & g : reader["precoditions"].Vector()) + ret.preconditionRegions.insert(g.Integer()); + + ret.regionColor = reader["color"].Integer(); + ret.difficulty = reader["difficulty"].Integer(); + ret.regionText = reader["regionText"].String(); + ret.prolog = prologEpilogReader(reader["prolog"]); + ret.epilog = prologEpilogReader(reader["epilog"]); + + ret.travelOptions = readScenarioTravelFromJson(reader, version); + + return ret; +} + +CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader, int version ) +{ + CScenarioTravel ret; + + std::map heroKeepsMap = { + {"experience", 1}, + {"primary", 2}, + {"skills", 4}, + {"spells", 8}, + {"artifacts", 16} + }; + std::map startOptionsMap = { + {"none", 0}, + {"bonus", 1}, + {"crossover", 2}, + {"hero", 3} + }; + + std::map bonusTypeMap = { + {"spell", CScenarioTravel::STravelBonus::EBonusType::SPELL}, + {"creature", CScenarioTravel::STravelBonus::EBonusType::MONSTER}, + {"building", CScenarioTravel::STravelBonus::EBonusType::BUILDING}, + {"artifact", CScenarioTravel::STravelBonus::EBonusType::ARTIFACT}, + {"scroll", CScenarioTravel::STravelBonus::EBonusType::SPELL_SCROLL}, + {"primary", CScenarioTravel::STravelBonus::EBonusType::PRIMARY_SKILL}, + {"skill", CScenarioTravel::STravelBonus::EBonusType::SECONDARY_SKILL}, + {"resource", CScenarioTravel::STravelBonus::EBonusType::RESOURCE}, + //{"prevHero", CScenarioTravel::STravelBonus::EBonusType::HEROES_FROM_PREVIOUS_SCENARIO}, + //{"hero", CScenarioTravel::STravelBonus::EBonusType::HERO}, + }; + + std::map heroSpecialMap = { + {"strongest", 0xFFFD}, + {"generated", 0xFFFE}, + {"random", 0xFFFF} + }; + + std::map resourceTypeMap = { + //FD - wood+ore + //FE - mercury+sulfur+crystal+gem + {"wood", 0}, + {"ore", 1}, + {"mercury", 2}, + {"sulfur", 3}, + {"crystal", 4}, + {"gem", 5}, + {"gold", 6}, + {"common", 0xFD}, + {"rare", 0xFE} + }; + + for(auto & k : reader["heroKeeps"].Vector()) + ret.whatHeroKeeps |= heroKeepsMap[k.String()]; + + //reader.getStream()->read(ret.monstersKeptByHero.data(), ret.monstersKeptByHero.size()); + //reader.getStream()->read(ret.artifsKeptByHero.data(), ret.artifsKeptByHero.size()); + + ret.startOptions = startOptionsMap[reader["startOptions"].String()]; + + switch(ret.startOptions) + { + case 0: + //no bonuses. Seems to be OK + break; + case 1: //reading of bonuses player can choose + { + ret.playerColor = reader["playerColor"].Integer(); + for(auto & bjson : reader["bonuses"].Vector()) + { + CScenarioTravel::STravelBonus bonus; + bonus.type = bonusTypeMap[bjson["what"].String()]; + if(bonus.type == CScenarioTravel::STravelBonus::EBonusType::RESOURCE) + { + bonus.info1 = resourceTypeMap[bjson["type"].String()]; + bonus.info2 = bjson["amount"].Integer(); + } + else + { + if(int heroId = heroSpecialMap[bjson["hero"].String()]) + bonus.info1 = heroId; + else + bonus.info1 = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", bjson["hero"].String()).get(); + + if(bonus.type == CScenarioTravel::STravelBonus::EBonusType::SPELL + || bonus.type == CScenarioTravel::STravelBonus::EBonusType::MONSTER + || bonus.type == CScenarioTravel::STravelBonus::EBonusType::ARTIFACT) + bonus.info2 = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), bjson["what"].String(), bjson["type"].String()).get(); + else + bonus.info2 = bjson["type"].Integer(); + + bonus.info3 = bjson["amount"].Integer(); + } + ret.bonusesToChoose.push_back(bonus); + } + break; + } + case 2: //reading of players (colors / scenarios ?) player can choose + { + for(auto & bjson : reader["bonuses"].Vector()) + { + CScenarioTravel::STravelBonus bonus; + bonus.type = CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO; + bonus.info1 = bjson["playerColor"].Integer(); //player color + bonus.info2 = bjson["scenario"].Integer(); //from what scenario + ret.bonusesToChoose.push_back(bonus); + } + break; + } + case 3: //heroes player can choose between + { + for(auto & bjson : reader["bonuses"].Vector()) + { + CScenarioTravel::STravelBonus bonus; + bonus.type = CScenarioTravel::STravelBonus::HERO; + bonus.info1 = bjson["playerColor"].Integer(); //player color + + if(int heroId = heroSpecialMap[bjson["hero"].String()]) + bonus.info2 = heroId; + else + bonus.info2 = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", bjson["hero"].String()).get(); + + ret.bonusesToChoose.push_back(bonus); + } + break; + } + default: + { + logGlobal->warn("Corrupted h3c file"); + break; + } + } + + return ret; +} + + CCampaignHeader CCampaignHandler::readHeaderFromMemory( CBinaryReader & reader, std::string filename, std::string modName, std::string encoding ) { CCampaignHeader ret; @@ -167,6 +387,7 @@ CCampaignHeader CCampaignHandler::readHeaderFromMemory( CBinaryReader & reader, ret.filename = filename; ret.modName = modName; ret.encoding = encoding; + ret.valid = true; return ret; } @@ -490,12 +711,16 @@ CMap * CCampaignState::getMap(int scenarioId) const // FIXME: there is certainly better way to handle maps inside campaigns if(scenarioId == -1) scenarioId = currentMap.get(); + + CMapService mapService; + if(camp->header.version == CampaignVersion::Version::VCMI) + return mapService.loadMap(ResourceID(camp->scenarios.at(scenarioId).mapName, EResType::MAP)).release(); + std::string scenarioName = camp->header.filename.substr(0, camp->header.filename.find('.')); boost::to_lower(scenarioName); scenarioName += ':' + std::to_string(scenarioId); std::string & mapContent = camp->mapPieces.find(scenarioId)->second; const auto * buffer = reinterpret_cast(mapContent.data()); - CMapService mapService; return mapService.loadMap(buffer, static_cast(mapContent.size()), scenarioName, camp->header.modName, camp->header.encoding).release(); } @@ -503,13 +728,16 @@ std::unique_ptr CCampaignState::getHeader(int scenarioId) const { if(scenarioId == -1) scenarioId = currentMap.get(); + + CMapService mapService; + if(camp->header.version == CampaignVersion::Version::VCMI) + return mapService.loadMapHeader(ResourceID(camp->scenarios.at(scenarioId).mapName, EResType::MAP)); std::string scenarioName = camp->header.filename.substr(0, camp->header.filename.find('.')); boost::to_lower(scenarioName); scenarioName += ':' + std::to_string(scenarioId); std::string & mapContent = camp->mapPieces.find(scenarioId)->second; const auto * buffer = reinterpret_cast(mapContent.data()); - CMapService mapService; return mapService.loadMapHeader(buffer, static_cast(mapContent.size()), scenarioName, camp->header.modName, camp->header.encoding); } diff --git a/lib/mapping/CCampaignHandler.h b/lib/mapping/CCampaignHandler.h index 4b069ca00..2afeacdc4 100644 --- a/lib/mapping/CCampaignHandler.h +++ b/lib/mapping/CCampaignHandler.h @@ -29,8 +29,12 @@ namespace CampaignVersion RoE = 4, AB = 5, SoD = 6, - WoG = 6 + WoG = 6, + VCMI = 1 }; + + const int VCMI_MIN = 1; + const int VCMI_MAX = 1; } class DLL_LINKAGE CCampaignHeader @@ -40,7 +44,8 @@ public: ui8 mapVersion = 0; //CampText.txt's format std::string name, description; ui8 difficultyChoosenByPlayer = 0; - ui8 music = 0; //CmpMusic.txt, start from 0 + ui8 music = 0; //CmpMusic.txt, start from 0, field is unused in vcmi + bool valid = false; std::string filename; std::string modName; @@ -221,7 +226,13 @@ class DLL_LINKAGE CCampaignHandler std::vector scenariosCountPerCampaign; static std::string readLocalizedString(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, std::string identifier); + + //parsers for VCMI campaigns (*.vcmp) + static CCampaignHeader readHeaderFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding); + static CCampaignScenario readScenarioFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding, int version, int mapVersion ); + static CScenarioTravel readScenarioTravelFromJson(JsonNode & reader, int version); + //parsers for original H3C campaigns static CCampaignHeader readHeaderFromMemory(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding); static CCampaignScenario readScenarioFromMemory(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, int version, int mapVersion ); static CScenarioTravel readScenarioTravelFromMemory(CBinaryReader & reader, int version); diff --git a/lib/mapping/CMapInfo.cpp b/lib/mapping/CMapInfo.cpp index fe8864890..e956fc6c6 100644 --- a/lib/mapping/CMapInfo.cpp +++ b/lib/mapping/CMapInfo.cpp @@ -65,6 +65,8 @@ void CMapInfo::saveInit(const ResourceID & file) void CMapInfo::campaignInit() { campaignHeader = std::make_unique(CCampaignHandler::getHeader(fileURI)); + if(!campaignHeader->valid) + campaignHeader.reset(); } void CMapInfo::countPlayers() From 6ed8f748b3d42b322a79cc59dbe7f8b3a079b7d3 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 9 Apr 2023 04:26:04 +0400 Subject: [PATCH 03/26] Use string ids for most of bonuses --- lib/mapping/CCampaignHandler.cpp | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index dde49311b..195a18f45 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -259,13 +259,20 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader, {"building", CScenarioTravel::STravelBonus::EBonusType::BUILDING}, {"artifact", CScenarioTravel::STravelBonus::EBonusType::ARTIFACT}, {"scroll", CScenarioTravel::STravelBonus::EBonusType::SPELL_SCROLL}, - {"primary", CScenarioTravel::STravelBonus::EBonusType::PRIMARY_SKILL}, - {"skill", CScenarioTravel::STravelBonus::EBonusType::SECONDARY_SKILL}, + {"primarySkill", CScenarioTravel::STravelBonus::EBonusType::PRIMARY_SKILL}, + {"secondarySkill", CScenarioTravel::STravelBonus::EBonusType::SECONDARY_SKILL}, {"resource", CScenarioTravel::STravelBonus::EBonusType::RESOURCE}, //{"prevHero", CScenarioTravel::STravelBonus::EBonusType::HEROES_FROM_PREVIOUS_SCENARIO}, //{"hero", CScenarioTravel::STravelBonus::EBonusType::HERO}, }; + std::map primarySkillsMap = { + {"attack", 0}, + {"defence", 8}, + {"spellpower", 16}, + {"knowledge", 24}, + }; + std::map heroSpecialMap = { {"strongest", 0xFFFD}, {"generated", 0xFFFE}, @@ -276,11 +283,11 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader, //FD - wood+ore //FE - mercury+sulfur+crystal+gem {"wood", 0}, - {"ore", 1}, - {"mercury", 2}, + {"mercury", 1}, + {"ore", 2}, {"sulfur", 3}, {"crystal", 4}, - {"gem", 5}, + {"gems", 5}, {"gold", 6}, {"common", 0xFD}, {"rare", 0xFE} @@ -311,6 +318,8 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader, bonus.info1 = resourceTypeMap[bjson["type"].String()]; bonus.info2 = bjson["amount"].Integer(); } + else if(bonus.type == CScenarioTravel::STravelBonus::EBonusType::BUILDING) + bonus.info1 = bjson["type"].Integer(); //VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "buildings", bjson["hero"].String()).get(); else { if(int heroId = heroSpecialMap[bjson["hero"].String()]) @@ -320,8 +329,16 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader, if(bonus.type == CScenarioTravel::STravelBonus::EBonusType::SPELL || bonus.type == CScenarioTravel::STravelBonus::EBonusType::MONSTER + || bonus.type == CScenarioTravel::STravelBonus::EBonusType::SECONDARY_SKILL || bonus.type == CScenarioTravel::STravelBonus::EBonusType::ARTIFACT) bonus.info2 = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), bjson["what"].String(), bjson["type"].String()).get(); + else if(bonus.type == CScenarioTravel::STravelBonus::EBonusType::SPELL_SCROLL) + bonus.info2 = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "spell", bjson["type"].String()).get(); + else if(bonus.type == CScenarioTravel::STravelBonus::EBonusType::PRIMARY_SKILL) + { + for(auto & ps : primarySkillsMap) + bonus.info2 |= bjson[ps.first].Integer() << ps.second; + } else bonus.info2 = bjson["type"].Integer(); From 08809f1cdc3d074d7946a68068732c35d98ef0f1 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 9 Apr 2023 04:53:06 +0400 Subject: [PATCH 04/26] String building id --- client/lobby/CBonusSelection.cpp | 6 +++++- lib/CGameState.cpp | 9 +++++++-- lib/mapping/CCampaignHandler.cpp | 3 ++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index a2ae41337..1ba12e16d 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -192,7 +192,11 @@ void CBonusSelection::createBonusesIcons() } assert(faction != -1); - BuildingID buildID = CBuildingHandler::campToERMU(bonDescs[i].info1, faction, std::set()); + BuildingID buildID; + if(getCampaign()->camp->header.version == CampaignVersion::VCMI) + buildID = BuildingID(bonDescs[i].info1); + else + buildID = CBuildingHandler::campToERMU(bonDescs[i].info1, faction, std::set()); picName = graphics->ERMUtoPicture[faction][buildID]; picNumber = -1; diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 615d23283..2a50fea7b 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -1720,8 +1720,13 @@ void CGameState::initTowns() if (owner->human && //human-owned map->towns[g]->pos == pi.posOfMainTown) { - map->towns[g]->builtBuildings.insert( - CBuildingHandler::campToERMU(chosenBonus->info1, map->towns[g]->subID, map->towns[g]->builtBuildings)); + BuildingID buildingId; + if(scenarioOps->campState->camp->header.version == CampaignVersion::VCMI) + buildingId = BuildingID(chosenBonus->info1); + else + buildingId = CBuildingHandler::campToERMU(chosenBonus->info1, map->towns[g]->subID, map->towns[g]->builtBuildings); + + map->towns[g]->builtBuildings.insert(buildingId); break; } } diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index 195a18f45..a243b70cb 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -24,6 +24,7 @@ #include "../mapObjects/CGHeroInstance.h"//for hero crossover #include "../CHeroHandler.h" #include "../Languages.h" +#include "../StringConstants.h" #include "CMapService.h" #include "CMap.h" #include "CMapInfo.h" @@ -319,7 +320,7 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader, bonus.info2 = bjson["amount"].Integer(); } else if(bonus.type == CScenarioTravel::STravelBonus::EBonusType::BUILDING) - bonus.info1 = bjson["type"].Integer(); //VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "buildings", bjson["hero"].String()).get(); + bonus.info1 = vstd::find_pos(EBuildingType::names, bjson["type"].String()); else { if(int heroId = heroSpecialMap[bjson["hero"].String()]) From 7c132468515d33ab00d274717171dfe10733cab6 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 9 Apr 2023 05:02:34 +0400 Subject: [PATCH 05/26] Keep creatures and arts --- lib/mapping/CCampaignHandler.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index a243b70cb..4288c15c7 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -242,8 +242,8 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader, std::map heroKeepsMap = { {"experience", 1}, - {"primary", 2}, - {"skills", 4}, + {"primarySkill", 2}, + {"secondarySkill", 4}, {"spells", 8}, {"artifacts", 16} }; @@ -297,8 +297,16 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader, for(auto & k : reader["heroKeeps"].Vector()) ret.whatHeroKeeps |= heroKeepsMap[k.String()]; - //reader.getStream()->read(ret.monstersKeptByHero.data(), ret.monstersKeptByHero.size()); - //reader.getStream()->read(ret.artifsKeptByHero.data(), ret.artifsKeptByHero.size()); + for(auto & k : reader["keepCreatures"].Vector()) + { + int creId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "creature", k.String()).get(); + ret.monstersKeptByHero[creId / 8] |= (1 << creId % 8); + } + for(auto & k : reader["keepArtifacts"].Vector()) + { + int artId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "artifact", k.String()).get(); + ret.artifsKeptByHero[artId / 8] |= (1 << artId % 8); + } ret.startOptions = startOptionsMap[reader["startOptions"].String()]; From 23e411cacdc89d668aee45e8204a6ef44f056bfb Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 9 Apr 2023 13:29:07 +0400 Subject: [PATCH 06/26] Adding logging and error handling --- lib/mapping/CCampaignHandler.cpp | 112 ++++++++++++++++++++----------- 1 file changed, 73 insertions(+), 39 deletions(-) diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index 4288c15c7..186e62c6e 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -130,8 +130,8 @@ std::unique_ptr CCampaignHandler::getCampaign( const std::string & na if(sc.isNotVoid()) { CMapService mapService; - auto hdr = mapService.loadMapHeader(ResourceID(sc.mapName, EResType::MAP)); - sc.scenarioName = hdr->name; + if(auto hdr = mapService.loadMapHeader(ResourceID(sc.mapName, EResType::MAP))) + sc.scenarioName = hdr->name; } ret->scenarios.push_back(sc); } @@ -187,7 +187,7 @@ CCampaignHeader CCampaignHandler::readHeaderFromJson(JsonNode & reader, std::str ret.version = reader["version"].Integer(); if(ret.version < CampaignVersion::VCMI_MIN || ret.version > CampaignVersion::VCMI_MAX) { - logGlobal->info("Unsupported campaign %s version %d", filename, ret.version); + logGlobal->info("VCMP Loading: Unsupported campaign %s version %d", filename, ret.version); return ret; } @@ -299,17 +299,26 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader, for(auto & k : reader["keepCreatures"].Vector()) { - int creId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "creature", k.String()).get(); - ret.monstersKeptByHero[creId / 8] |= (1 << creId % 8); + if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "creature", k.String())) + { + int creId = identifier.get(); + ret.monstersKeptByHero[creId / 8] |= (1 << creId % 8); + } + else + logGlobal->warn("VCMP Loading: keepCreatures contains unresolved identifier %s", k.String()); } for(auto & k : reader["keepArtifacts"].Vector()) { - int artId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "artifact", k.String()).get(); - ret.artifsKeptByHero[artId / 8] |= (1 << artId % 8); + if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "artifact", k.String())) + { + int artId = identifier.get(); + ret.artifsKeptByHero[artId / 8] |= (1 << artId % 8); + } + else + logGlobal->warn("VCMP Loading: keepArtifacts contains unresolved identifier %s", k.String()); } ret.startOptions = startOptionsMap[reader["startOptions"].String()]; - switch(ret.startOptions) { case 0: @@ -322,36 +331,58 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader, { CScenarioTravel::STravelBonus bonus; bonus.type = bonusTypeMap[bjson["what"].String()]; - if(bonus.type == CScenarioTravel::STravelBonus::EBonusType::RESOURCE) + switch (bonus.type) { - bonus.info1 = resourceTypeMap[bjson["type"].String()]; - bonus.info2 = bjson["amount"].Integer(); - } - else if(bonus.type == CScenarioTravel::STravelBonus::EBonusType::BUILDING) - bonus.info1 = vstd::find_pos(EBuildingType::names, bjson["type"].String()); - else - { - if(int heroId = heroSpecialMap[bjson["hero"].String()]) - bonus.info1 = heroId; - else - bonus.info1 = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", bjson["hero"].String()).get(); - - if(bonus.type == CScenarioTravel::STravelBonus::EBonusType::SPELL - || bonus.type == CScenarioTravel::STravelBonus::EBonusType::MONSTER - || bonus.type == CScenarioTravel::STravelBonus::EBonusType::SECONDARY_SKILL - || bonus.type == CScenarioTravel::STravelBonus::EBonusType::ARTIFACT) - bonus.info2 = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), bjson["what"].String(), bjson["type"].String()).get(); - else if(bonus.type == CScenarioTravel::STravelBonus::EBonusType::SPELL_SCROLL) - bonus.info2 = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "spell", bjson["type"].String()).get(); - else if(bonus.type == CScenarioTravel::STravelBonus::EBonusType::PRIMARY_SKILL) - { - for(auto & ps : primarySkillsMap) - bonus.info2 |= bjson[ps.first].Integer() << ps.second; - } - else - bonus.info2 = bjson["type"].Integer(); - - bonus.info3 = bjson["amount"].Integer(); + case CScenarioTravel::STravelBonus::EBonusType::RESOURCE: + bonus.info1 = resourceTypeMap[bjson["type"].String()]; + bonus.info2 = bjson["amount"].Integer(); + break; + + case CScenarioTravel::STravelBonus::EBonusType::BUILDING: + bonus.info1 = vstd::find_pos(EBuildingType::names, bjson["type"].String()); + if(bonus.info1 == -1) + logGlobal->warn("VCMP Loading: unresolved building identifier %s", bjson["type"].String()); + break; + + default: + if(int heroId = heroSpecialMap[bjson["hero"].String()]) + bonus.info1 = heroId; + else + if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", bjson["hero"].String())) + bonus.info1 = identifier.get(); + else + logGlobal->warn("VCMP Loading: unresolved hero identifier %s", bjson["hero"].String()); + + bonus.info3 = bjson["amount"].Integer(); + + switch(bonus.type) + { + case CScenarioTravel::STravelBonus::EBonusType::SPELL: + case CScenarioTravel::STravelBonus::EBonusType::MONSTER: + case CScenarioTravel::STravelBonus::EBonusType::SECONDARY_SKILL: + case CScenarioTravel::STravelBonus::EBonusType::ARTIFACT: + if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), bjson["what"].String(), bjson["type"].String())) + bonus.info2 = identifier.get(); + else + logGlobal->warn("VCMP Loading: unresolved %s identifier %s", bjson["what"].String(), bjson["type"].String()); + break; + + case CScenarioTravel::STravelBonus::EBonusType::SPELL_SCROLL: + if(auto Identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "spell", bjson["type"].String())) + bonus.info2 = Identifier.get(); + else + logGlobal->warn("VCMP Loading: unresolved spell scroll identifier %s", bjson["type"].String()); + break; + + case CScenarioTravel::STravelBonus::EBonusType::PRIMARY_SKILL: + for(auto & ps : primarySkillsMap) + bonus.info2 |= bjson[ps.first].Integer() << ps.second; + break; + + default: + bonus.info2 = bjson["type"].Integer(); + } + break; } ret.bonusesToChoose.push_back(bonus); } @@ -380,7 +411,10 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader, if(int heroId = heroSpecialMap[bjson["hero"].String()]) bonus.info2 = heroId; else - bonus.info2 = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", bjson["hero"].String()).get(); + if (auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", bjson["hero"].String())) + bonus.info2 = identifier.get(); + else + logGlobal->warn("VCMP Loading: unresolved hero identifier %s", bjson["hero"].String()); ret.bonusesToChoose.push_back(bonus); } @@ -388,7 +422,7 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader, } default: { - logGlobal->warn("Corrupted h3c file"); + logGlobal->warn("VCMP Loading: Unsupported start options value"); break; } } From 3166fd05f49e08d01c654d8a66134ae0b35f2a45 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 9 Apr 2023 14:43:51 +0400 Subject: [PATCH 07/26] Handle array exceed limit potential crash --- lib/mapping/CCampaignHandler.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index 186e62c6e..3253a0d00 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -302,7 +302,10 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader, if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "creature", k.String())) { int creId = identifier.get(); - ret.monstersKeptByHero[creId / 8] |= (1 << creId % 8); + if(creId >= ret.monstersKeptByHero.size()) + logGlobal->warn("VCMP Loading: creature %s with id %d isn't supported yet", k.String(), creId); + else + ret.monstersKeptByHero[creId / 8] |= (1 << creId % 8); } else logGlobal->warn("VCMP Loading: keepCreatures contains unresolved identifier %s", k.String()); @@ -312,7 +315,10 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader, if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "artifact", k.String())) { int artId = identifier.get(); - ret.artifsKeptByHero[artId / 8] |= (1 << artId % 8); + if(artId >= ret.artifsKeptByHero.size()) + logGlobal->warn("VCMP Loading: artifact %s with id %d isn't supported yet", k.String(), artId); + else + ret.artifsKeptByHero[artId / 8] |= (1 << artId % 8); } else logGlobal->warn("VCMP Loading: keepArtifacts contains unresolved identifier %s", k.String()); From 94c6c0c26277f2feb28dbdd7998daeac6b727297 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 12 Apr 2023 02:13:53 +0400 Subject: [PATCH 08/26] Add hero limit checkbox in map editor --- mapeditor/mapsettings.cpp | 12 ++++++++++++ mapeditor/mapsettings.h | 2 ++ mapeditor/mapsettings.ui | 35 ++++++++++++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/mapeditor/mapsettings.cpp b/mapeditor/mapsettings.cpp index 7baefd12f..3f2f2922e 100644 --- a/mapeditor/mapsettings.cpp +++ b/mapeditor/mapsettings.cpp @@ -93,6 +93,8 @@ MapSettings::MapSettings(MapController & ctrl, QWidget *parent) : ui->mapNameEdit->setText(tr(controller.map()->name.c_str())); ui->mapDescriptionEdit->setPlainText(tr(controller.map()->description.c_str())); + ui->heroLevelLimit->setValue(controller.map()->levelLimit); + ui->heroLevelLimitCheck->setChecked(controller.map()->levelLimit); show(); @@ -432,6 +434,10 @@ void MapSettings::on_pushButton_clicked() { controller.map()->name = ui->mapNameEdit->text().toStdString(); controller.map()->description = ui->mapDescriptionEdit->toPlainText().toStdString(); + if(ui->heroLevelLimitCheck->isChecked()) + controller.map()->levelLimit = ui->heroLevelLimit->value(); + else + controller.map()->levelLimit = 0; controller.commitChangeWithoutRedraw(); for(int i = 0; i < controller.map()->allowedAbilities.size(); ++i) @@ -875,3 +881,9 @@ void MapSettings::on_loseComboBox_currentIndexChanged(int index) } } + +void MapSettings::on_heroLevelLimitCheck_toggled(bool checked) +{ + ui->heroLevelLimit->setEnabled(checked); +} + diff --git a/mapeditor/mapsettings.h b/mapeditor/mapsettings.h index de8edbe50..aff47e069 100644 --- a/mapeditor/mapsettings.h +++ b/mapeditor/mapsettings.h @@ -32,6 +32,8 @@ private slots: void on_loseComboBox_currentIndexChanged(int index); + void on_heroLevelLimitCheck_toggled(bool checked); + private: std::string getTownName(int townObjectIdx); diff --git a/mapeditor/mapsettings.ui b/mapeditor/mapsettings.ui index ebea4b77c..fe830d3b5 100644 --- a/mapeditor/mapsettings.ui +++ b/mapeditor/mapsettings.ui @@ -10,7 +10,7 @@ 0 0 470 - 480 + 494 @@ -60,6 +60,39 @@ + + + + 10 + + + + + false + + + + 48 + 0 + + + + + + + + + 0 + 0 + + + + Limit maximum heroes level + + + + + From 617a4385a2f798c89bcfb7b46041d488db13e3af Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 12 Apr 2023 02:14:22 +0400 Subject: [PATCH 09/26] Add mods fields into map header --- lib/mapping/CMap.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index 76a56c151..8bc3a8ec6 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -18,6 +18,7 @@ #include "../int3.h" #include "../GameConstants.h" #include "../LogicalExpression.h" +#include "../CModHandler.h" #include "CMapDefines.h" VCMI_LIB_NAMESPACE_BEGIN @@ -294,6 +295,8 @@ public: ui8 levels() const; EMapFormat::EMapFormat version; /// The default value is EMapFormat::SOD. + std::map mods; /// set of mods required to play a map + si32 height; /// The default value is 72. si32 width; /// The default value is 72. bool twoLevel; /// The default value is true. @@ -322,6 +325,8 @@ public: void serialize(Handler & h, const int Version) { h & version; + if(Version >= 821) + h & mods; h & name; h & description; h & width; From e669d31d33beb20685c5fd87148a3d37a0391afd Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 12 Apr 2023 02:44:17 +0400 Subject: [PATCH 10/26] Hidden maps --- client/lobby/SelectionTab.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index ece6f53f4..a9ecde719 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -534,6 +534,13 @@ void SelectionTab::parseMaps(const std::unordered_set & files) { try { + const std::string filename_prefix = file.getName().substr(file.getName().find_last_of("/\\") + 1, 2); + if(filename_prefix == "__") + { + logGlobal->trace("Map %s marked as hidden"); + continue; + } + auto mapInfo = std::make_shared(); mapInfo->mapInit(file.getName()); From d244702abcc57782147dca51ba0b844b2a2b965c Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 12 Apr 2023 02:59:55 +0400 Subject: [PATCH 11/26] Read and write mods into map --- lib/mapping/MapFormatH3M.cpp | 4 ++++ lib/mapping/MapFormatJson.cpp | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 685691a4e..bab8d0246 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -170,6 +170,10 @@ void CMapLoaderH3M::readHeader() { throw std::runtime_error("Invalid map format!"); } + + // include basic mod + if(mapHeader->version == EMapFormat::WOG) + mapHeader->mods["wake-of-gods"]; // Read map name, description, dimensions,... mapHeader->areAnyPlayers = reader->readBool(); diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index c630e98cb..e668380dd 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -339,7 +339,7 @@ namespace TerrainDetail ///CMapFormatJson const int CMapFormatJson::VERSION_MAJOR = 1; -const int CMapFormatJson::VERSION_MINOR = 0; +const int CMapFormatJson::VERSION_MINOR = 1; const std::string CMapFormatJson::HEADER_FILE_NAME = "header.json"; const std::string CMapFormatJson::OBJECTS_FILE_NAME = "objects.json"; @@ -947,6 +947,13 @@ void CMapLoaderJson::readHeader(const bool complete) JsonDeserializer handler(mapObjectResolver.get(), header); mapHeader->version = EMapFormat::VCMI;//todo: new version field + + //loading mods + if(!header["mods"].isNull()) + { + for(auto & mod : header["mods"].Vector()) + mapHeader->mods[mod["name"].String()] = CModInfo::Version::fromString(mod["version"].String()); + } //todo: multilevel map load support { @@ -1279,6 +1286,16 @@ void CMapSaverJson::writeHeader() header["versionMajor"].Float() = VERSION_MAJOR; header["versionMinor"].Float() = VERSION_MINOR; + + //write mods + JsonNode & mods = header["mods"]; + for(const auto & mod : mapHeader->mods) + { + JsonNode modWriter; + modWriter["name"].String() = mod.first; + modWriter["version"].String() = mod.second.toString(); + mods.Vector().push_back(modWriter); + } //todo: multilevel map save support JsonNode & levels = header["mapLevels"]; From 67e1b48d47b0ece50c393078c84d6ca899bb9a83 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 16 Apr 2023 14:00:01 +0400 Subject: [PATCH 12/26] Remove unused interface --- lib/CGameState.cpp | 4 ++-- lib/CGameState.h | 6 +++--- lib/mapping/CMapService.h | 41 +++++++++++++-------------------------- 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 4df4cd9b7..78905d616 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -722,7 +722,7 @@ void CGameState::preInit(Services * services) this->services = services; } -void CGameState::init(const IMapService * mapService, StartInfo * si, bool allowSavingRandomMap) +void CGameState::init(const CMapService * mapService, StartInfo * si, bool allowSavingRandomMap) { preInitAuto(); logGlobal->info("\tUsing random seed: %d", si->seedToBeUsed); @@ -851,7 +851,7 @@ void CGameState::preInitAuto() } } -void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRandomMap) +void CGameState::initNewGame(const CMapService * mapService, bool allowSavingRandomMap) { if(scenarioOps->createRandomMap()) { diff --git a/lib/CGameState.h b/lib/CGameState.h index 3877f6bc0..882c776be 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -57,7 +57,7 @@ class CQuest; class CCampaignScenario; struct EventCondition; class CScenarioTravel; -class IMapService; +class CMapService; template class CApplier; @@ -161,7 +161,7 @@ public: void preInit(Services * services); - void init(const IMapService * mapService, StartInfo * si, bool allowSavingRandomMap = false); + void init(const CMapService * mapService, StartInfo * si, bool allowSavingRandomMap = false); void updateOnLoad(StartInfo * si); ConstTransitivePtr scenarioOps, initialOpts; //second one is a copy of settings received from pregame (not randomized) @@ -253,7 +253,7 @@ private: // ----- initialization ----- void preInitAuto(); - void initNewGame(const IMapService * mapService, bool allowSavingRandomMap); + void initNewGame(const CMapService * mapService, bool allowSavingRandomMap); void initCampaign(); void checkMapChecksum(); void initGlobalBonuses(); diff --git a/lib/mapping/CMapService.h b/lib/mapping/CMapService.h index ab671c8ba..256fc8b3c 100644 --- a/lib/mapping/CMapService.h +++ b/lib/mapping/CMapService.h @@ -22,30 +22,30 @@ class IMapLoader; class IMapPatcher; /** - * The map service provides loading of VCMI/H3 map files. It can - * be extended to save maps later as well. + * The map service provides loading and saving of VCMI/H3 map files. */ -class DLL_LINKAGE IMapService +class DLL_LINKAGE CMapService { public: - IMapService() = default; - virtual ~IMapService() = default; + CMapService() = default; + virtual ~CMapService() = default; + /** * Loads the VCMI/H3 map file specified by the name. * * @param name the name of the map * @return a unique ptr to the loaded map class */ - virtual std::unique_ptr loadMap(const ResourceID & name) const = 0; - + std::unique_ptr loadMap(const ResourceID & name) const; + /** * Loads the VCMI/H3 map header specified by the name. * * @param name the name of the map * @return a unique ptr to the loaded map header class */ - virtual std::unique_ptr loadMapHeader(const ResourceID & name) const = 0; - + std::unique_ptr loadMapHeader(const ResourceID & name) const; + /** * Loads the VCMI/H3 map file from a buffer. This method is temporarily * in use to ease the transition to use the new map service. @@ -58,8 +58,8 @@ public: * @param name indicates name of file that will be used during map header patching * @return a unique ptr to the loaded map class */ - virtual std::unique_ptr loadMap(const ui8 * buffer, int size, const std::string & name, const std::string & modName, const std::string & encoding) const = 0; - + std::unique_ptr loadMap(const ui8 * buffer, int size, const std::string & name, const std::string & modName, const std::string & encoding) const; + /** * Loads the VCMI/H3 map header from a buffer. This method is temporarily * in use to ease the transition to use the new map service. @@ -72,22 +72,9 @@ public: * @param name indicates name of file that will be used during map header patching * @return a unique ptr to the loaded map class */ - virtual std::unique_ptr loadMapHeader(const ui8 * buffer, int size, const std::string & name, const std::string & modName, const std::string & encoding) const = 0; - - virtual void saveMap(const std::unique_ptr & map, boost::filesystem::path fullPath) const = 0; -}; - -class DLL_LINKAGE CMapService : public IMapService -{ -public: - CMapService() = default; - virtual ~CMapService() = default; - - std::unique_ptr loadMap(const ResourceID & name) const override; - std::unique_ptr loadMapHeader(const ResourceID & name) const override; - std::unique_ptr loadMap(const ui8 * buffer, int size, const std::string & name, const std::string & modName, const std::string & encoding) const override; - std::unique_ptr loadMapHeader(const ui8 * buffer, int size, const std::string & name, const std::string & modName, const std::string & encoding) const override; - void saveMap(const std::unique_ptr & map, boost::filesystem::path fullPath) const override; + std::unique_ptr loadMapHeader(const ui8 * buffer, int size, const std::string & name, const std::string & modName, const std::string & encoding) const; + + void saveMap(const std::unique_ptr & map, boost::filesystem::path fullPath) const; private: /** * Gets a map input stream object specified by a map name. From 954a2abb71d7831b646be1cd62abf05c37bc7433 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 16 Apr 2023 15:38:13 +0400 Subject: [PATCH 13/26] Verifying mods before starting map --- client/lobby/CLobbyScreen.cpp | 9 +++++++++ lib/CModHandler.cpp | 5 +++++ lib/CModHandler.h | 4 +++- lib/StartInfo.cpp | 11 ++++++++++- lib/mapping/CMap.h | 6 +++++- lib/mapping/CMapService.cpp | 18 ++++++++++++++++++ lib/mapping/CMapService.h | 10 ++++++++++ 7 files changed, 60 insertions(+), 3 deletions(-) diff --git a/client/lobby/CLobbyScreen.cpp b/client/lobby/CLobbyScreen.cpp index dbe54e0cb..4d03faa9d 100644 --- a/client/lobby/CLobbyScreen.cpp +++ b/client/lobby/CLobbyScreen.cpp @@ -131,6 +131,15 @@ void CLobbyScreen::startScenario(bool allowOnlyAI) CSH->sendStartGame(allowOnlyAI); buttonStart->block(true); } + catch(CModHandler::Incompatibility & e) + { + logGlobal->warn("Incompatibility exception during start scenario: %s", e.what()); + + auto errorMsg = VLC->generaltexth->translate("vcmi.server.errors.modsIncompatibility") + '\n'; + errorMsg += e.what(); + + CInfoWindow::showInfoDialog(errorMsg, CInfoWindow::TCompsInfo(), PlayerColor(1)); + } catch(std::exception & e) { logGlobal->error("Exception during startScenario: %s", e.what()); diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 66834fbe2..17ab1c134 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -967,6 +967,11 @@ std::vector CModHandler::getActiveMods() return activeMods; } +const CModInfo & CModHandler::getModInfo(const TModID & modId) const +{ + return allMods.at(modId); +} + static JsonNode genDefaultFS() { // default FS config for mods: directory "Content" that acts as H3 root directory diff --git a/lib/CModHandler.h b/lib/CModHandler.h index eb6ab5523..ae35841b3 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -174,7 +174,7 @@ public: const ContentTypeHandler & operator[] (const std::string & name) const; }; -typedef std::string TModID; +using TModID = std::string; class DLL_LINKAGE CModInfo { @@ -347,6 +347,8 @@ public: /// returns list of all (active) mods std::vector getAllMods(); std::vector getActiveMods(); + + const CModInfo & getModInfo(const TModID & modId) const; /// load content from all available mods void load(); diff --git a/lib/StartInfo.cpp b/lib/StartInfo.cpp index 353e6a495..57c35f4f2 100644 --- a/lib/StartInfo.cpp +++ b/lib/StartInfo.cpp @@ -15,6 +15,7 @@ #include "mapping/CMapInfo.h" #include "mapping/CCampaignHandler.h" #include "mapping/CMap.h" +#include "mapping/CMapService.h" VCMI_LIB_NAMESPACE_BEGIN @@ -67,8 +68,16 @@ std::string StartInfo::getCampaignName() const void LobbyInfo::verifyStateBeforeStart(bool ignoreNoHuman) const { - if(!mi) + if(!mi || !mi->mapHeader) throw std::domain_error("ExceptionMapMissing"); + + auto missingMods = CMapService::verifyMapHeaderMods(*mi->mapHeader); + CModHandler::Incompatibility::ModList modList; + for(const auto & m : missingMods) + modList.push_back({m.first, m.second.toString()}); + + if(!modList.empty()) + throw CModHandler::Incompatibility(std::move(modList)); //there must be at least one human player before game can be started std::map::const_iterator i; diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index 8bc3a8ec6..b96743f37 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -275,6 +275,10 @@ enum EMapFormat: ui8 }; } +// Inherit from container to enable forward declaration +class ModCompatibilityInfo: public std::map +{}; + /// The map header holds information about loss/victory condition,map format, version, players, height, width,... class DLL_LINKAGE CMapHeader { @@ -295,7 +299,7 @@ public: ui8 levels() const; EMapFormat::EMapFormat version; /// The default value is EMapFormat::SOD. - std::map mods; /// set of mods required to play a map + ModCompatibilityInfo mods; /// set of mods required to play a map si32 height; /// The default value is 72. si32 width; /// The default value is 72. diff --git a/lib/mapping/CMapService.cpp b/lib/mapping/CMapService.cpp index edfec1897..b06c15ff7 100644 --- a/lib/mapping/CMapService.cpp +++ b/lib/mapping/CMapService.cpp @@ -86,6 +86,24 @@ void CMapService::saveMap(const std::unique_ptr & map, boost::filesystem:: } } +ModCompatibilityInfo CMapService::verifyMapHeaderMods(const CMapHeader & map) +{ + ModCompatibilityInfo modCompatibilityInfo; + const auto & activeMods = VLC->modh->getActiveMods(); + for(const auto & mapMod : map.mods) + { + if(vstd::contains(activeMods, mapMod.first)) + { + const auto & modInfo = VLC->modh->getModInfo(mapMod.first); + if(modInfo.version.compatible(mapMod.second)) + continue; + } + + modCompatibilityInfo[mapMod.first] = mapMod.second; + } + return modCompatibilityInfo; +} + std::unique_ptr CMapService::getStreamFromFS(const ResourceID & name) { return CResourceHandler::get()->load(name); diff --git a/lib/mapping/CMapService.h b/lib/mapping/CMapService.h index 256fc8b3c..951d765b9 100644 --- a/lib/mapping/CMapService.h +++ b/lib/mapping/CMapService.h @@ -21,6 +21,8 @@ class CInputStream; class IMapLoader; class IMapPatcher; +class ModCompatibilityInfo; + /** * The map service provides loading and saving of VCMI/H3 map files. */ @@ -74,7 +76,15 @@ public: */ std::unique_ptr loadMapHeader(const ui8 * buffer, int size, const std::string & name, const std::string & modName, const std::string & encoding) const; + /** + * Tests if mods used in the map are currently loaded + * @param map const reference to map header + * @return data structure representing missing or incompatible mods (those which are needed from map but not loaded) + */ + static ModCompatibilityInfo verifyMapHeaderMods(const CMapHeader & map); + void saveMap(const std::unique_ptr & map, boost::filesystem::path fullPath) const; + private: /** * Gets a map input stream object specified by a map name. From 0c87d0a26c5b9d97f5b9bee1d0a4d9aa1eb8afda Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 16 Apr 2023 21:49:43 +0400 Subject: [PATCH 14/26] Add a comment --- lib/mapping/CMapService.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/mapping/CMapService.h b/lib/mapping/CMapService.h index 951d765b9..e408a46e0 100644 --- a/lib/mapping/CMapService.h +++ b/lib/mapping/CMapService.h @@ -83,6 +83,11 @@ public: */ static ModCompatibilityInfo verifyMapHeaderMods(const CMapHeader & map); + /** + * Saves map into VCMI format with name specified + * @param map to save + * @param fullPath full path to file to write, including extension + */ void saveMap(const std::unique_ptr & map, boost::filesystem::path fullPath) const; private: From e4c147db16666e1b838899a9e771d726983f88eb Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 17 Apr 2023 03:01:29 +0400 Subject: [PATCH 15/26] Add mod management into map editor --- mapeditor/mainwindow.cpp | 18 ++++- mapeditor/mapcontroller.cpp | 30 +++++++++ mapeditor/mapcontroller.h | 3 + mapeditor/mapsettings.cpp | 129 +++++++++++++++++++++++++++++++++++- mapeditor/mapsettings.h | 8 +++ mapeditor/mapsettings.ui | 89 ++++++++++++++++++++++--- mapeditor/validator.cpp | 10 +++ mapeditor/windownewmap.cpp | 2 +- 8 files changed, 277 insertions(+), 12 deletions(-) diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 329cb9176..5f3a141c5 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -333,7 +333,23 @@ bool MainWindow::openMap(const QString & filenameSelect) CMapService mapService; try { - controller.setMap(mapService.loadMap(resId)); + if(auto header = mapService.loadMapHeader(resId)) + { + auto missingMods = CMapService::verifyMapHeaderMods(*header); + CModHandler::Incompatibility::ModList modList; + for(const auto & m : missingMods) + modList.push_back({m.first, m.second.toString()}); + + if(!modList.empty()) + throw CModHandler::Incompatibility(std::move(modList)); + + controller.setMap(mapService.loadMap(resId)); + } + } + catch(const CModHandler::Incompatibility & e) + { + QMessageBox::warning(this, "Mods requiered", e.what()); + return false; } catch(const std::exception & e) { diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index e992e40c3..af14216f9 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -554,3 +554,33 @@ void MapController::redo() sceneForceUpdate(); //TODO: use smart invalidation (setDirty) main->mapChanged(); } + +ModCompatibilityInfo MapController::modAssessmentAll() +{ + ModCompatibilityInfo result; + for(auto primaryID : VLC->objtypeh->knownObjects()) + { + for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID)) + { + auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID); + auto modName = QString::fromStdString(handler->getJsonKey()).split(":").at(0).toStdString(); + if(modName != "core") + result[modName] = VLC->modh->getModInfo(modName).version; + } + } + return result; +} + +ModCompatibilityInfo MapController::modAssessmentMap(const CMap & map) +{ + ModCompatibilityInfo result; + for(auto obj : map.objects) + { + auto handler = VLC->objtypeh->getHandlerFor(obj->ID, obj->subID); + auto modName = QString::fromStdString(handler->getJsonKey()).split(":").at(0).toStdString(); + if(modName != "core") + result[modName] = VLC->modh->getModInfo(modName).version; + } + //TODO: terrains? + return result; +} diff --git a/mapeditor/mapcontroller.h b/mapeditor/mapcontroller.h index 338798201..892a89a1a 100644 --- a/mapeditor/mapcontroller.h +++ b/mapeditor/mapcontroller.h @@ -54,6 +54,9 @@ public: bool discardObject(int level) const; void createObject(int level, CGObjectInstance * obj) const; bool canPlaceObject(int level, CGObjectInstance * obj, QString & error) const; + + static ModCompatibilityInfo modAssessmentAll(); + static ModCompatibilityInfo modAssessmentMap(const CMap & map); void undo(); void redo(); diff --git a/mapeditor/mapsettings.cpp b/mapeditor/mapsettings.cpp index 3f2f2922e..c904bb5cb 100644 --- a/mapeditor/mapsettings.cpp +++ b/mapeditor/mapsettings.cpp @@ -17,8 +17,10 @@ #include "../lib/CArtHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CGeneralTextHandler.h" +#include "../lib/CModHandler.h" #include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/mapObjects/MiscObjects.h" +#include "../lib/mapping/CMapService.h" #include "../lib/StringConstants.h" #include "inspector/townbulidingswidget.h" //to convert BuildingID to string @@ -82,6 +84,14 @@ std::vector linearJsonArray(const JsonNode & json) return result; } +void traverseNode(QTreeWidgetItem * item, std::function action) +{ + // Do something with item + action(item); + for (int i = 0; i < item->childCount(); ++i) + traverseNode(item->child(i), action); +} + MapSettings::MapSettings(MapController & ctrl, QWidget *parent) : QDialog(parent), ui(new Ui::MapSettings), @@ -98,7 +108,6 @@ MapSettings::MapSettings(MapController & ctrl, QWidget *parent) : show(); - for(int i = 0; i < controller.map()->allowedAbilities.size(); ++i) { auto * item = new QListWidgetItem(QString::fromStdString(VLC->skillh->objects[i]->getNameTranslated())); @@ -387,6 +396,61 @@ MapSettings::MapSettings(MapController & ctrl, QWidget *parent) : } } } + + //mods management + //collect all active mods + QMap addedMods; + QSet modsToProcess; + ui->treeMods->blockSignals(true); + + auto createModTreeWidgetItem = [&](QTreeWidgetItem * parent, const CModInfo & modInfo) + { + auto item = new QTreeWidgetItem(parent, {QString::fromStdString(modInfo.name), QString::fromStdString(modInfo.version.toString())}); + item->setData(0, Qt::UserRole, QVariant(QString::fromStdString(modInfo.identifier))); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(0, controller.map()->mods.count(modInfo.identifier) ? Qt::Checked : Qt::Unchecked); + //set parent check + if(parent && item->checkState(0) == Qt::Checked) + parent->setCheckState(0, Qt::Checked); + return item; + }; + + for(const auto & modName : VLC->modh->getActiveMods()) + { + QString qmodName = QString::fromStdString(modName); + if(qmodName.split(".").size() == 1) + { + const auto & modInfo = VLC->modh->getModInfo(modName); + addedMods[qmodName] = createModTreeWidgetItem(nullptr, modInfo); + ui->treeMods->addTopLevelItem(addedMods[qmodName]); + } + else + { + modsToProcess.insert(qmodName); + } + } + + for(auto qmodIter = modsToProcess.begin(); qmodIter != modsToProcess.end();) + { + auto qmodName = *qmodIter; + auto pieces = qmodName.split("."); + assert(pieces.size() > 1); + + QString qs; + for(int i = 0; i < pieces.size() - 1; ++i) + qs += pieces[i]; + + if(addedMods.count(qs)) + { + const auto & modInfo = VLC->modh->getModInfo(qmodName.toStdString()); + addedMods[qmodName] = createModTreeWidgetItem(addedMods[qs], modInfo); + modsToProcess.erase(qmodIter); + qmodIter = modsToProcess.begin(); + } + else + ++qmodIter; + } + ui->treeMods->blockSignals(false); } MapSettings::~MapSettings() @@ -430,6 +494,22 @@ std::string MapSettings::getMonsterName(int monsterObjectIdx) return name; } +void MapSettings::updateModWidgetBasedOnMods(const ModCompatibilityInfo & mods) +{ + //Mod management + auto widgetAction = [&](QTreeWidgetItem * item) + { + auto modName = item->data(0, Qt::UserRole).toString().toStdString(); + item->setCheckState(0, mods.count(modName) ? Qt::Checked : Qt::Unchecked); + }; + + for (int i = 0; i < ui->treeMods->topLevelItemCount(); ++i) + { + QTreeWidgetItem *item = ui->treeMods->topLevelItem(i); + traverseNode(item, widgetAction); + } +} + void MapSettings::on_pushButton_clicked() { controller.map()->name = ui->mapNameEdit->text().toStdString(); @@ -705,6 +785,23 @@ void MapSettings::on_pushButton_clicked() controller.map()->triggeredEvents.push_back(specialDefeat); } + //Mod management + auto widgetAction = [&](QTreeWidgetItem * item) + { + if(item->checkState(0) == Qt::Checked) + { + auto modName = item->data(0, Qt::UserRole).toString().toStdString(); + controller.map()->mods[modName] = VLC->modh->getModInfo(modName).version; + } + }; + + controller.map()->mods.clear(); + for (int i = 0; i < ui->treeMods->topLevelItemCount(); ++i) + { + QTreeWidgetItem *item = ui->treeMods->topLevelItem(i); + traverseNode(item, widgetAction); + } + close(); } @@ -887,3 +984,33 @@ void MapSettings::on_heroLevelLimitCheck_toggled(bool checked) ui->heroLevelLimit->setEnabled(checked); } +void MapSettings::on_modResolution_map_clicked() +{ + updateModWidgetBasedOnMods(MapController::modAssessmentMap(*controller.map())); +} + + +void MapSettings::on_modResolution_full_clicked() +{ + updateModWidgetBasedOnMods(MapController::modAssessmentAll()); +} + +void MapSettings::on_treeMods_itemChanged(QTreeWidgetItem *item, int column) +{ + //set state for children + for (int i = 0; i < item->childCount(); ++i) + item->child(i)->setCheckState(0, item->checkState(0)); + + //set state for parent + ui->treeMods->blockSignals(true); + if(item->checkState(0) == Qt::Checked) + { + while(item->parent()) + { + item->parent()->setCheckState(0, Qt::Checked); + item = item->parent(); + } + } + ui->treeMods->blockSignals(false); +} + diff --git a/mapeditor/mapsettings.h b/mapeditor/mapsettings.h index aff47e069..bd7a79826 100644 --- a/mapeditor/mapsettings.h +++ b/mapeditor/mapsettings.h @@ -34,12 +34,20 @@ private slots: void on_heroLevelLimitCheck_toggled(bool checked); + void on_modResolution_map_clicked(); + + void on_modResolution_full_clicked(); + + void on_treeMods_itemChanged(QTreeWidgetItem *item, int column); + private: std::string getTownName(int townObjectIdx); std::string getHeroName(int townObjectIdx); std::string getMonsterName(int townObjectIdx); + void updateModWidgetBasedOnMods(const ModCompatibilityInfo & mods); + template std::vector getObjectIndexes() const { diff --git a/mapeditor/mapsettings.ui b/mapeditor/mapsettings.ui index fe830d3b5..5a314b917 100644 --- a/mapeditor/mapsettings.ui +++ b/mapeditor/mapsettings.ui @@ -9,7 +9,7 @@ 0 0 - 470 + 543 494 @@ -23,14 +23,7 @@ Map settings - - - - Ok - - - - + 0 @@ -139,6 +132,77 @@ + + + Mods + + + + + + Mandatory mods for playing this map + + + + + + + QAbstractScrollArea::AdjustIgnored + + + 320 + + + + Mod name + + + + + Version + + + + + + + + + + Automatic assignment + + + + + + + Set required mods based on objects placed on the map. This method may cause problems if you have customized rewards, garrisons, etc from mods + + + Map objects mods + + + false + + + + + + + Set all mods having a game content as mandatory + + + Full content mods + + + false + + + + + + + Events @@ -369,6 +433,13 @@ + + + + Ok + + + diff --git a/mapeditor/validator.cpp b/mapeditor/validator.cpp index 2fd622d69..c7a342542 100644 --- a/mapeditor/validator.cpp +++ b/mapeditor/validator.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "validator.h" +#include "mapcontroller.h" #include "ui_validator.h" #include "../lib/mapObjects/MapObjects.h" #include "../lib/CHeroHandler.h" @@ -158,6 +159,15 @@ std::list Validator::validate(const CMap * map) issues.emplace_back("Map name is not specified", false); if(map->description.empty()) issues.emplace_back("Map description is not specified", false); + + //verificationfor mods + for(auto & mod : MapController::modAssessmentMap(*map)) + { + if(!map->mods.count(mod.first)) + { + issues.emplace_back(QString("Map contains object from mod \"%1\", but doesn't require it").arg(QString::fromStdString(VLC->modh->getModInfo(mod.first).name)), true); + } + } } catch(const std::exception & e) { diff --git a/mapeditor/windownewmap.cpp b/mapeditor/windownewmap.cpp index 59fe1b2be..714012d9a 100644 --- a/mapeditor/windownewmap.cpp +++ b/mapeditor/windownewmap.cpp @@ -264,7 +264,7 @@ void WindowNewMap::on_okButton_clicked() nmap = f.get(); } - + nmap->mods = MapController::modAssessmentAll(); static_cast(parent())->controller.setMap(std::move(nmap)); static_cast(parent())->initializeMap(true); close(); From 70f4cc5e0fd4131d0581f11129f446bd0c67f840 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 17 Apr 2023 04:26:35 +0400 Subject: [PATCH 16/26] Refactor campaign regions --- client/lobby/CBonusSelection.cpp | 38 +++-------------- client/lobby/CBonusSelection.h | 20 +-------- client/lobby/SelectionTab.cpp | 6 +-- lib/mapping/CCampaignHandler.cpp | 73 +++++++++++++++++++++++++------- lib/mapping/CCampaignHandler.h | 61 ++++++++++++++++++++++---- 5 files changed, 122 insertions(+), 76 deletions(-) diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index 1ba12e16d..595cb50b2 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -51,6 +51,7 @@ #include "../../lib/mapObjects/CGHeroInstance.h" + std::shared_ptr CBonusSelection::getCampaign() { return CSH->si->campState; @@ -61,8 +62,7 @@ CBonusSelection::CBonusSelection() { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; - loadPositionsOfGraphics(); - std::string bgName = campDescriptions[getCampaign()->camp->header.mapVersion].campPrefix + "_BG.BMP"; + std::string bgName = getCampaign()->camp->header.campaignRegions.campPrefix + "_BG.BMP"; setBackground(bgName); panelBackground = std::make_shared("CAMPBRF.BMP", 456, 6); @@ -106,35 +106,9 @@ CBonusSelection::CBonusSelection() for(int g = 0; g < getCampaign()->camp->scenarios.size(); ++g) { if(getCampaign()->camp->conquerable(g)) - regions.push_back(std::make_shared(g, true, true, campDescriptions[getCampaign()->camp->header.mapVersion])); + regions.push_back(std::make_shared(g, true, true, getCampaign()->camp->header.campaignRegions)); else if(getCampaign()->camp->scenarios[g].conquered) //display as striped - regions.push_back(std::make_shared(g, false, false, campDescriptions[getCampaign()->camp->header.mapVersion])); - } -} - -void CBonusSelection::loadPositionsOfGraphics() -{ - const JsonNode config(ResourceID("config/campaign_regions.json")); - - for(const JsonNode & campaign : config["campaign_regions"].Vector()) - { - SCampPositions sc; - - sc.campPrefix = campaign["prefix"].String(); - sc.colorSuffixLength = static_cast(campaign["color_suffix_length"].Float()); - - for(const JsonNode & desc : campaign["desc"].Vector()) - { - SCampPositions::SRegionDesc rd; - - rd.infix = desc["infix"].String(); - rd.xpos = static_cast(desc["x"].Float()); - rd.ypos = static_cast(desc["y"].Float()); - sc.regions.push_back(rd); - } - - campDescriptions.push_back(sc); - + regions.push_back(std::make_shared(g, false, false, getCampaign()->camp->header.campaignRegions)); } } @@ -470,7 +444,7 @@ void CBonusSelection::decreaseDifficulty() CSH->setDifficulty(CSH->si->difficulty - 1); } -CBonusSelection::CRegion::CRegion(int id, bool accessible, bool selectable, const SCampPositions & campDsc) +CBonusSelection::CRegion::CRegion(int id, bool accessible, bool selectable, const CampaignRegions & campDsc) : CIntObject(LCLICK | RCLICK), idOfMapAndRegion(id), accessible(accessible), selectable(selectable) { OBJ_CONSTRUCTION; @@ -480,7 +454,7 @@ CBonusSelection::CRegion::CRegion(int id, bool accessible, bool selectable, cons {"Re", "Bl", "Br", "Gr", "Or", "Vi", "Te", "Pi"} }; - const SCampPositions::SRegionDesc & desc = campDsc.regions[idOfMapAndRegion]; + const CampaignRegions::RegionDescription & desc = campDsc.regions[idOfMapAndRegion]; pos.x += desc.xpos; pos.y += desc.ypos; diff --git a/client/lobby/CBonusSelection.h b/client/lobby/CBonusSelection.h index ecf322758..55b77cd9e 100644 --- a/client/lobby/CBonusSelection.h +++ b/client/lobby/CBonusSelection.h @@ -8,6 +8,7 @@ * */ #pragma once +#include "../../lib/mapping/CCampaignHandler.h" #include "../windows/CWindowObject.h" VCMI_LIB_NAMESPACE_BEGIN @@ -32,21 +33,6 @@ public: std::shared_ptr getCampaign(); CBonusSelection(); - struct SCampPositions - { - std::string campPrefix; - int colorSuffixLength; - - struct SRegionDesc - { - std::string infix; - int xpos, ypos; - }; - - std::vector regions; - - }; - class CRegion : public CIntObject { @@ -57,13 +43,12 @@ public: bool accessible; // false if region should be striped bool selectable; // true if region should be selectable public: - CRegion(int id, bool accessible, bool selectable, const SCampPositions & campDsc); + CRegion(int id, bool accessible, bool selectable, const CampaignRegions & campDsc); void updateState(); void clickLeft(tribool down, bool previousState) override; void clickRight(tribool down, bool previousState) override; }; - void loadPositionsOfGraphics(); void createBonusesIcons(); void updateAfterStateChange(); @@ -84,7 +69,6 @@ public: std::shared_ptr mapName; std::shared_ptr labelMapDescription; std::shared_ptr mapDescription; - std::vector campDescriptions; std::vector> regions; std::shared_ptr flagbox; diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index a9ecde719..16ab9a175 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -100,8 +100,8 @@ bool mapSorter::operator()(const std::shared_ptr aaa, const std::share switch(sortBy) { case _numOfMaps: //by number of maps in campaign - return CGI->generaltexth->getCampaignLength(aaa->campaignHeader->mapVersion) < - CGI->generaltexth->getCampaignLength(bbb->campaignHeader->mapVersion); + return aaa->campaignHeader->numberOfScenarios < + bbb->campaignHeader->numberOfScenarios; break; case _name: //by name return boost::ilexicographical_compare(aaa->campaignHeader->name, bbb->campaignHeader->name); @@ -665,7 +665,7 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr info, bool sel iconLossCondition->disable(); labelNumberOfCampaignMaps->enable(); std::ostringstream ostr(std::ostringstream::out); - ostr << CGI->generaltexth->getCampaignLength(info->campaignHeader->mapVersion); + ostr << info->campaignHeader->numberOfScenarios; labelNumberOfCampaignMaps->setText(ostr.str()); labelNumberOfCampaignMaps->setColor(color); } diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index 3253a0d00..a9064f285 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -36,12 +36,53 @@ VCMI_LIB_NAMESPACE_BEGIN +CampaignRegions::RegionDescription CampaignRegions::RegionDescription::fromJson(const JsonNode & node) +{ + CampaignRegions::RegionDescription rd; + rd.infix = node["infix"].String(); + rd.xpos = static_cast(node["x"].Float()); + rd.ypos = static_cast(node["y"].Float()); + return rd; +} + +CampaignRegions CampaignRegions::fromJson(const JsonNode & node) +{ + CampaignRegions cr; + cr.campPrefix = node["prefix"].String(); + cr.colorSuffixLength = static_cast(node["color_suffix_length"].Float()); + + for(const JsonNode & desc : node["desc"].Vector()) + cr.regions.push_back(CampaignRegions::RegionDescription::fromJson(desc)); + + return cr; +} + +CampaignRegions CampaignRegions::getLegacy(int campId) +{ + static std::vector campDescriptions; + if(campDescriptions.empty()) //read once + { + const JsonNode config(ResourceID("config/campaign_regions.json")); + for(const JsonNode & campaign : config["campaign_regions"].Vector()) + campDescriptions.push_back(CampaignRegions::fromJson(campaign)); + } + + return campDescriptions.at(campId); +} + + bool CScenarioTravel::STravelBonus::isBonusForHero() const { return type == SPELL || type == MONSTER || type == ARTIFACT || type == SPELL_SCROLL || type == PRIMARY_SKILL || type == SECONDARY_SKILL; } +void CCampaignHeader::loadLegacyData(ui8 campId) +{ + campaignRegions = CampaignRegions::getLegacy(campId); + numberOfScenarios = VLC->generaltexth->getCampaignLength(campId); +} + CCampaignHeader CCampaignHandler::getHeader( const std::string & name) { ResourceID resourceID(name, EResType::CAMPAIGN); @@ -86,10 +127,10 @@ std::unique_ptr CCampaignHandler::getCampaign( const std::string & na CBinaryReader reader(&stream); ret->header = readHeaderFromMemory(reader, resourceID.getName(), modName, encoding); - int howManyScenarios = static_cast(VLC->generaltexth->getCampaignLength(ret->header.mapVersion)); - for(int g=0; gheader.numberOfScenarios; + for(int g = 0; g < howManyScenarios; ++g) { - CCampaignScenario sc = readScenarioFromMemory(reader, resourceID.getName(), modName, encoding, ret->header.version, ret->header.mapVersion); + CCampaignScenario sc = readScenarioFromMemory(reader, ret->header); ret->scenarios.push_back(sc); } @@ -126,7 +167,7 @@ std::unique_ptr CCampaignHandler::getCampaign( const std::string & na for(auto & scenario : jsonCampaign["scenarios"].Vector()) { - CCampaignScenario sc = readScenarioFromJson(scenario, resourceID.getName(), modName, encoding, ret->header.version, ret->header.mapVersion); + CCampaignScenario sc = readScenarioFromJson(scenario); if(sc.isNotVoid()) { CMapService mapService; @@ -180,7 +221,7 @@ std::string CCampaignHandler::readLocalizedString(CBinaryReader & reader, std::s return VLC->generaltexth->translate(stringID.get()); } -CCampaignHeader CCampaignHandler::readHeaderFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding ) +CCampaignHeader CCampaignHandler::readHeaderFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding) { CCampaignHeader ret; @@ -192,7 +233,8 @@ CCampaignHeader CCampaignHandler::readHeaderFromJson(JsonNode & reader, std::str } ret.version = CampaignVersion::VCMI; - ret.mapVersion = reader["campaignId"].Integer(); + ret.campaignRegions = CampaignRegions::fromJson(reader["regions"]); + ret.numberOfScenarios = reader["scenarios"].Vector().size(); ret.name = reader["name"].String(); ret.description = reader["description"].String(); ret.difficultyChoosenByPlayer = reader["allowDifficultySelection"].Bool(); @@ -204,7 +246,7 @@ CCampaignHeader CCampaignHandler::readHeaderFromJson(JsonNode & reader, std::str return ret; } -CCampaignScenario CCampaignHandler::readScenarioFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding, int version, int mapVersion) +CCampaignScenario CCampaignHandler::readScenarioFromJson(JsonNode & reader) { auto prologEpilogReader = [](JsonNode & identifier) -> CCampaignScenario::SScenarioPrologEpilog { @@ -231,12 +273,12 @@ CCampaignScenario CCampaignHandler::readScenarioFromJson(JsonNode & reader, std: ret.prolog = prologEpilogReader(reader["prolog"]); ret.epilog = prologEpilogReader(reader["epilog"]); - ret.travelOptions = readScenarioTravelFromJson(reader, version); + ret.travelOptions = readScenarioTravelFromJson(reader); return ret; } -CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader, int version ) +CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader) { CScenarioTravel ret; @@ -442,7 +484,8 @@ CCampaignHeader CCampaignHandler::readHeaderFromMemory( CBinaryReader & reader, CCampaignHeader ret; ret.version = reader.readUInt32(); - ret.mapVersion = reader.readUInt8() - 1;//change range of it from [1, 20] to [0, 19] + ui8 campId = reader.readUInt8() - 1;//change range of it from [1, 20] to [0, 19] + ret.loadLegacyData(campId); ret.name = readLocalizedString(reader, filename, modName, encoding, "name"); ret.description = readLocalizedString(reader, filename, modName, encoding, "description"); if (ret.version > CampaignVersion::RoE) @@ -457,7 +500,7 @@ CCampaignHeader CCampaignHandler::readHeaderFromMemory( CBinaryReader & reader, return ret; } -CCampaignScenario CCampaignHandler::readScenarioFromMemory( CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, int version, int mapVersion ) +CCampaignScenario CCampaignHandler::readScenarioFromMemory( CBinaryReader & reader, const CCampaignHeader & header) { auto prologEpilogReader = [&](const std::string & identifier) -> CCampaignScenario::SScenarioPrologEpilog { @@ -467,7 +510,7 @@ CCampaignScenario CCampaignHandler::readScenarioFromMemory( CBinaryReader & read { ret.prologVideo = reader.readUInt8(); ret.prologMusic = reader.readUInt8(); - ret.prologText = readLocalizedString(reader, filename, modName, encoding, identifier); + ret.prologText = readLocalizedString(reader, header.filename, header.modName, header.encoding, identifier); } return ret; }; @@ -476,7 +519,7 @@ CCampaignScenario CCampaignHandler::readScenarioFromMemory( CBinaryReader & read ret.conquered = false; ret.mapName = reader.readBaseString(); ret.packedMapSize = reader.readUInt32(); - if(mapVersion == 18)//unholy alliance + if(header.numberOfScenarios > 0) //unholy alliance { ret.loadPreconditionRegions(reader.readUInt16()); } @@ -486,11 +529,11 @@ CCampaignScenario CCampaignHandler::readScenarioFromMemory( CBinaryReader & read } ret.regionColor = reader.readUInt8(); ret.difficulty = reader.readUInt8(); - ret.regionText = readLocalizedString(reader, filename, modName, encoding, ret.mapName + ".region"); + ret.regionText = readLocalizedString(reader, header.filename, header.modName, header.encoding, ret.mapName + ".region"); ret.prolog = prologEpilogReader(ret.mapName + ".prolog"); ret.epilog = prologEpilogReader(ret.mapName + ".epilog"); - ret.travelOptions = readScenarioTravelFromMemory(reader, version); + ret.travelOptions = readScenarioTravelFromMemory(reader, header.version); return ret; } diff --git a/lib/mapping/CCampaignHandler.h b/lib/mapping/CCampaignHandler.h index 2afeacdc4..6d093122a 100644 --- a/lib/mapping/CCampaignHandler.h +++ b/lib/mapping/CCampaignHandler.h @@ -37,11 +37,45 @@ namespace CampaignVersion const int VCMI_MAX = 1; } +struct DLL_LINKAGE CampaignRegions +{ + std::string campPrefix; + int colorSuffixLength; + + struct DLL_LINKAGE RegionDescription + { + std::string infix; + int xpos, ypos; + + template void serialize(Handler &h, const int formatVersion) + { + h & infix; + h & xpos; + h & ypos; + } + + static CampaignRegions::RegionDescription fromJson(const JsonNode & node); + }; + + std::vector regions; + + template void serialize(Handler &h, const int formatVersion) + { + h & campPrefix; + h & colorSuffixLength; + h & regions; + } + + static CampaignRegions fromJson(const JsonNode & node); + static CampaignRegions getLegacy(int campId); +}; + class DLL_LINKAGE CCampaignHeader { public: si32 version = 0; //4 - RoE, 5 - AB, 6 - SoD and WoG - ui8 mapVersion = 0; //CampText.txt's format + CampaignRegions campaignRegions; + int numberOfScenarios = 0; std::string name, description; ui8 difficultyChoosenByPlayer = 0; ui8 music = 0; //CmpMusic.txt, start from 0, field is unused in vcmi @@ -50,15 +84,28 @@ public: std::string filename; std::string modName; std::string encoding; + + void loadLegacyData(ui8 campId); template void serialize(Handler &h, const int formatVersion) { h & version; - h & mapVersion; + if(!h.saving && formatVersion < 821) + { + ui8 campId = 0; //legacy field + h & campId; + loadLegacyData(campId); + } + else + { + h & campaignRegions; + h & numberOfScenarios; + } h & name; h & description; h & difficultyChoosenByPlayer; - h & music; + if(formatVersion < 821) + h & music; //deprecated h & filename; h & modName; h & encoding; @@ -223,18 +270,16 @@ public: class DLL_LINKAGE CCampaignHandler { - std::vector scenariosCountPerCampaign; - static std::string readLocalizedString(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, std::string identifier); //parsers for VCMI campaigns (*.vcmp) static CCampaignHeader readHeaderFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding); - static CCampaignScenario readScenarioFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding, int version, int mapVersion ); - static CScenarioTravel readScenarioTravelFromJson(JsonNode & reader, int version); + static CCampaignScenario readScenarioFromJson(JsonNode & reader); + static CScenarioTravel readScenarioTravelFromJson(JsonNode & reader); //parsers for original H3C campaigns static CCampaignHeader readHeaderFromMemory(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding); - static CCampaignScenario readScenarioFromMemory(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, int version, int mapVersion ); + static CCampaignScenario readScenarioFromMemory(CBinaryReader & reader, const CCampaignHeader & header); static CScenarioTravel readScenarioTravelFromMemory(CBinaryReader & reader, int version); /// returns h3c split in parts. 0 = h3c header, 1-end - maps (binary h3m) /// headerOnly - only header will be decompressed, returned vector wont have any maps From 91b4782a2f6a256e55072783d7fdcf83d4881c8d Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 17 Apr 2023 04:47:54 +0400 Subject: [PATCH 17/26] Refactor more fields from campaign header --- client/mainmenu/CPrologEpilogVideo.cpp | 4 ++-- lib/mapping/CCampaignHandler.cpp | 10 +++++----- lib/mapping/CCampaignHandler.h | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/client/mainmenu/CPrologEpilogVideo.cpp b/client/mainmenu/CPrologEpilogVideo.cpp index 5161c74ed..e496b00f7 100644 --- a/client/mainmenu/CPrologEpilogVideo.cpp +++ b/client/mainmenu/CPrologEpilogVideo.cpp @@ -29,8 +29,8 @@ CPrologEpilogVideo::CPrologEpilogVideo(CCampaignScenario::SScenarioPrologEpilog pos = center(Rect(0, 0, 800, 600)); updateShadow(); - CCS->videoh->open(CCampaignHandler::prologVideoName(spe.prologVideo)); - CCS->musich->playMusic("Music/" + CCampaignHandler::prologMusicName(spe.prologMusic), true, true); + CCS->videoh->open(spe.prologVideo); + CCS->musich->playMusic("Music/" + spe.prologMusic, true, true); // MPTODO: Custom campaign crashing on this? // voiceSoundHandle = CCS->soundh->playSound(CCampaignHandler::prologVoiceName(spe.prologVideo)); diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index a9064f285..f7760d37c 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -254,8 +254,8 @@ CCampaignScenario CCampaignHandler::readScenarioFromJson(JsonNode & reader) ret.hasPrologEpilog = !identifier.isNull(); if(ret.hasPrologEpilog) { - ret.prologVideo = identifier["video"].Integer(); - ret.prologMusic = identifier["music"].Integer(); + ret.prologVideo = identifier["video"].String(); + ret.prologMusic = identifier["music"].String(); ret.prologText = identifier["text"].String(); } return ret; @@ -508,8 +508,8 @@ CCampaignScenario CCampaignHandler::readScenarioFromMemory( CBinaryReader & read ret.hasPrologEpilog = reader.readUInt8(); if(ret.hasPrologEpilog) { - ret.prologVideo = reader.readUInt8(); - ret.prologMusic = reader.readUInt8(); + ret.prologVideo = CCampaignHandler::prologVideoName(reader.readUInt8()); + ret.prologMusic = CCampaignHandler::prologMusicName(reader.readUInt8()); ret.prologText = readLocalizedString(reader, header.filename, header.modName, header.encoding, identifier); } return ret; @@ -519,7 +519,7 @@ CCampaignScenario CCampaignHandler::readScenarioFromMemory( CBinaryReader & read ret.conquered = false; ret.mapName = reader.readBaseString(); ret.packedMapSize = reader.readUInt32(); - if(header.numberOfScenarios > 0) //unholy alliance + if(header.numberOfScenarios > 8) //unholy alliance { ret.loadPreconditionRegions(reader.readUInt16()); } diff --git a/lib/mapping/CCampaignHandler.h b/lib/mapping/CCampaignHandler.h index 6d093122a..bb9e9e765 100644 --- a/lib/mapping/CCampaignHandler.h +++ b/lib/mapping/CCampaignHandler.h @@ -161,8 +161,8 @@ public: struct DLL_LINKAGE SScenarioPrologEpilog { bool hasPrologEpilog = false; - ui8 prologVideo = 0; // from CmpMovie.txt - ui8 prologMusic = 0; // from CmpMusic.txt + std::string prologVideo; // from CmpMovie.txt + std::string prologMusic; // from CmpMusic.txt std::string prologText; template void serialize(Handler &h, const int formatVersion) @@ -285,11 +285,11 @@ class DLL_LINKAGE CCampaignHandler /// headerOnly - only header will be decompressed, returned vector wont have any maps static std::vector> getFile(std::unique_ptr file, bool headerOnly); -public: static std::string prologVideoName(ui8 index); static std::string prologMusicName(ui8 index); static std::string prologVoiceName(ui8 index); +public: static CCampaignHeader getHeader( const std::string & name); //name - name of appropriate file static std::unique_ptr getCampaign(const std::string & name); //name - name of appropriate file From b41481c73ba0650a1dd6172c3b73cb76a2ac1503 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 17 Apr 2023 05:22:32 +0400 Subject: [PATCH 18/26] Fix(?) mingw --- lib/CModHandler.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/CModHandler.h b/lib/CModHandler.h index ae35841b3..92eb9748e 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -186,7 +186,7 @@ public: PASSED }; - struct Version + struct DLL_LINKAGE Version { int major = 0; int minor = 0; From ecf8b9934b82165609f6e02ef40fb2df1f36d6d3 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 17 Apr 2023 23:19:56 +0400 Subject: [PATCH 19/26] Further refactoring --- lib/CGameState.cpp | 16 ++++---- lib/mapping/CCampaignHandler.cpp | 68 ++++++++++++++++---------------- lib/mapping/CCampaignHandler.h | 49 +++++++++++++---------- 3 files changed, 68 insertions(+), 65 deletions(-) diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 27cf29b6c..4284c334f 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -1266,7 +1266,7 @@ void CGameState::prepareCrossoverHeroes(std::vectorartType->getId(); - assert( 8*18 > id );//number of arts that fits into h3m format - bool takeable = travelOptions.artifsKeptByHero[id / 8] & ( 1 << (id%8) ); + bool takeable = travelOptions.artifactsKeptByHero.count(art->artType->getId()); ArtifactLocation al(hero, artifactPosition); if(!takeable && !al.getSlot()->locked) //don't try removing locked artifacts -> it crashes #1719 @@ -1346,7 +1344,7 @@ void CGameState::prepareCrossoverHeroes(std::vector & j) -> bool { CreatureID::ECreatureID crid = j.second->getCreatureID().toEnum(); - return !(travelOptions.monstersKeptByHero[crid / 8] & (1 << (crid % 8))); + return !travelOptions.monstersKeptByHero.count(crid); }; auto stacksCopy = cgh->stacks; //copy of the map, so we can iterate iover it and remove stacks diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index f7760d37c..a91712494 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -282,13 +282,6 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader) { CScenarioTravel ret; - std::map heroKeepsMap = { - {"experience", 1}, - {"primarySkill", 2}, - {"secondarySkill", 4}, - {"spells", 8}, - {"artifacts", 16} - }; std::map startOptionsMap = { {"none", 0}, {"bonus", 1}, @@ -337,31 +330,25 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader) }; for(auto & k : reader["heroKeeps"].Vector()) - ret.whatHeroKeeps |= heroKeepsMap[k.String()]; + { + if(k.String() == "experience") ret.whatHeroKeeps.experience = true; + if(k.String() == "primarySkills") ret.whatHeroKeeps.primarySkills = true; + if(k.String() == "secondarySkills") ret.whatHeroKeeps.secondarySkills = true; + if(k.String() == "spells") ret.whatHeroKeeps.spells = true; + if(k.String() == "artifacts") ret.whatHeroKeeps.artifacts = true; + } for(auto & k : reader["keepCreatures"].Vector()) { if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "creature", k.String())) - { - int creId = identifier.get(); - if(creId >= ret.monstersKeptByHero.size()) - logGlobal->warn("VCMP Loading: creature %s with id %d isn't supported yet", k.String(), creId); - else - ret.monstersKeptByHero[creId / 8] |= (1 << creId % 8); - } + ret.monstersKeptByHero.insert(CreatureID(identifier.get())); else logGlobal->warn("VCMP Loading: keepCreatures contains unresolved identifier %s", k.String()); } for(auto & k : reader["keepArtifacts"].Vector()) { if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "artifact", k.String())) - { - int artId = identifier.get(); - if(artId >= ret.artifsKeptByHero.size()) - logGlobal->warn("VCMP Loading: artifact %s with id %d isn't supported yet", k.String(), artId); - else - ret.artifsKeptByHero[artId / 8] |= (1 << artId % 8); - } + ret.artifactsKeptByHero.insert(ArtifactID(identifier.get())); else logGlobal->warn("VCMP Loading: keepArtifacts contains unresolved identifier %s", k.String()); } @@ -491,8 +478,8 @@ CCampaignHeader CCampaignHandler::readHeaderFromMemory( CBinaryReader & reader, if (ret.version > CampaignVersion::RoE) ret.difficultyChoosenByPlayer = reader.readInt8(); else - ret.difficultyChoosenByPlayer = 0; - ret.music = reader.readInt8(); + ret.difficultyChoosenByPlayer = false; + reader.readInt8(); //music - skip as unused ret.filename = filename; ret.modName = modName; ret.encoding = encoding; @@ -518,7 +505,7 @@ CCampaignScenario CCampaignHandler::readScenarioFromMemory( CBinaryReader & read CCampaignScenario ret; ret.conquered = false; ret.mapName = reader.readBaseString(); - ret.packedMapSize = reader.readUInt32(); + reader.readUInt32(); //packedMapSize - not used if(header.numberOfScenarios > 8) //unholy alliance { ret.loadPreconditionRegions(reader.readUInt16()); @@ -551,18 +538,29 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromMemory(CBinaryReader & r { CScenarioTravel ret; - ret.whatHeroKeeps = reader.readUInt8(); - reader.getStream()->read(ret.monstersKeptByHero.data(), ret.monstersKeptByHero.size()); - - if (version < CampaignVersion::SoD) + ui8 whatHeroKeeps = reader.readUInt8(); + ret.whatHeroKeeps.experience = whatHeroKeeps & 1; + ret.whatHeroKeeps.primarySkills = whatHeroKeeps & 2; + ret.whatHeroKeeps.secondarySkills = whatHeroKeeps & 4; + ret.whatHeroKeeps.spells = whatHeroKeeps & 8; + ret.whatHeroKeeps.artifacts = whatHeroKeeps & 16; + + auto bitMaskToId = [&reader](std::set & container, int size) { - ret.artifsKeptByHero.fill(0); - reader.getStream()->read(ret.artifsKeptByHero.data(), ret.artifsKeptByHero.size() - 1); - } + for(int iId = 0, byte = 0; iId < size * 8; ++iId) + { + if(iId % 8 == 0) + byte = reader.readUInt8(); + if(byte & (1 << iId % 8)) + container.insert(T(iId)); + } + }; + + bitMaskToId(ret.monstersKeptByHero, 19); + if(version < CampaignVersion::SoD) + bitMaskToId(ret.artifactsKeptByHero, 17); else - { - reader.getStream()->read(ret.artifsKeptByHero.data(), ret.artifsKeptByHero.size()); - } + bitMaskToId(ret.artifactsKeptByHero, 18); ret.startOptions = reader.readUInt8(); diff --git a/lib/mapping/CCampaignHandler.h b/lib/mapping/CCampaignHandler.h index bb9e9e765..9dde5cae1 100644 --- a/lib/mapping/CCampaignHandler.h +++ b/lib/mapping/CCampaignHandler.h @@ -77,8 +77,7 @@ public: CampaignRegions campaignRegions; int numberOfScenarios = 0; std::string name, description; - ui8 difficultyChoosenByPlayer = 0; - ui8 music = 0; //CmpMusic.txt, start from 0, field is unused in vcmi + bool difficultyChoosenByPlayer = false; bool valid = false; std::string filename; @@ -90,34 +89,44 @@ public: template void serialize(Handler &h, const int formatVersion) { h & version; - if(!h.saving && formatVersion < 821) - { - ui8 campId = 0; //legacy field - h & campId; - loadLegacyData(campId); - } - else - { - h & campaignRegions; - h & numberOfScenarios; - } + h & campaignRegions; + h & numberOfScenarios; h & name; h & description; h & difficultyChoosenByPlayer; - if(formatVersion < 821) - h & music; //deprecated h & filename; h & modName; h & encoding; + h & valid; } }; class DLL_LINKAGE CScenarioTravel { public: - ui8 whatHeroKeeps = 0; //bitfield [0] - experience, [1] - prim skills, [2] - sec skills, [3] - spells, [4] - artifacts - std::array monstersKeptByHero; - std::array artifsKeptByHero; + + struct DLL_LINKAGE WhatHeroKeeps + { + bool experience = false; + bool primarySkills = false; + bool secondarySkills = false; + bool spells = false; + bool artifacts = false; + + template void serialize(Handler &h, const int formatVersion) + { + h & experience; + h & primarySkills; + h & secondarySkills; + h & spells; + h & artifacts; + } + }; + + WhatHeroKeeps whatHeroKeeps; + + std::set monstersKeptByHero; + std::set artifactsKeptByHero; ui8 startOptions = 0; //1 - start bonus, 2 - traveling hero, 3 - hero options @@ -147,7 +156,7 @@ public: { h & whatHeroKeeps; h & monstersKeptByHero; - h & artifsKeptByHero; + h & artifactsKeptByHero; h & startOptions; h & playerColor; h & bonusesToChoose; @@ -176,7 +185,6 @@ public: std::string mapName; //*.h3m std::string scenarioName; //from header. human-readble - ui32 packedMapSize = 0; //generally not used std::set preconditionRegions; //what we need to conquer to conquer this one (stored as bitfield in h3c) ui8 regionColor = 0; ui8 difficulty = 0; @@ -200,7 +208,6 @@ public: { h & mapName; h & scenarioName; - h & packedMapSize; h & preconditionRegions; h & regionColor; h & difficulty; From 5cf9fbbe02a75dbd23f10950327cea6670aa4965 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 19 Apr 2023 03:11:13 +0400 Subject: [PATCH 20/26] Fix compiling --- lib/mapping/CCampaignHandler.cpp | 24 +++++++++++------------- lib/mapping/CCampaignHandler.h | 5 +++-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index 8cfec9eed..779bb4efb 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -341,14 +341,14 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader) for(auto & k : reader["keepCreatures"].Vector()) { if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "creature", k.String())) - ret.monstersKeptByHero.insert(CreatureID(identifier.get())); + ret.monstersKeptByHero.insert(CreatureID(identifier.value())); else logGlobal->warn("VCMP Loading: keepCreatures contains unresolved identifier %s", k.String()); } for(auto & k : reader["keepArtifacts"].Vector()) { if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "artifact", k.String())) - ret.artifactsKeptByHero.insert(ArtifactID(identifier.get())); + ret.artifactsKeptByHero.insert(ArtifactID(identifier.value())); else logGlobal->warn("VCMP Loading: keepArtifacts contains unresolved identifier %s", k.String()); } @@ -384,7 +384,7 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader) bonus.info1 = heroId; else if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", bjson["hero"].String())) - bonus.info1 = identifier.get(); + bonus.info1 = identifier.value(); else logGlobal->warn("VCMP Loading: unresolved hero identifier %s", bjson["hero"].String()); @@ -397,14 +397,14 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader) case CScenarioTravel::STravelBonus::EBonusType::SECONDARY_SKILL: case CScenarioTravel::STravelBonus::EBonusType::ARTIFACT: if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), bjson["what"].String(), bjson["type"].String())) - bonus.info2 = identifier.get(); + bonus.info2 = identifier.value(); else logGlobal->warn("VCMP Loading: unresolved %s identifier %s", bjson["what"].String(), bjson["type"].String()); break; case CScenarioTravel::STravelBonus::EBonusType::SPELL_SCROLL: if(auto Identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "spell", bjson["type"].String())) - bonus.info2 = Identifier.get(); + bonus.info2 = Identifier.value(); else logGlobal->warn("VCMP Loading: unresolved spell scroll identifier %s", bjson["type"].String()); break; @@ -447,7 +447,7 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader) bonus.info2 = heroId; else if (auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", bjson["hero"].String())) - bonus.info2 = identifier.get(); + bonus.info2 = identifier.value(); else logGlobal->warn("VCMP Loading: unresolved hero identifier %s", bjson["hero"].String()); @@ -544,23 +544,21 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromMemory(CBinaryReader & r ret.whatHeroKeeps.secondarySkills = whatHeroKeeps & 4; ret.whatHeroKeeps.spells = whatHeroKeeps & 8; ret.whatHeroKeeps.artifacts = whatHeroKeeps & 16; - - auto bitMaskToId = [&reader](std::set & container, int size) + + //TODO: replace with template lambda form C++20 and make typed containers + auto bitMaskToId = [&reader](std::set & container, int size) { for(int iId = 0, byte = 0; iId < size * 8; ++iId) { if(iId % 8 == 0) byte = reader.readUInt8(); if(byte & (1 << iId % 8)) - container.insert(T(iId)); + container.insert(iId); } }; bitMaskToId(ret.monstersKeptByHero, 19); - if(version < CampaignVersion::SoD) - bitMaskToId(ret.artifactsKeptByHero, 17); - else - bitMaskToId(ret.artifactsKeptByHero, 18); + bitMaskToId(ret.artifactsKeptByHero, version < CampaignVersion::SoD ? 17 : 18); ret.startOptions = reader.readUInt8(); diff --git a/lib/mapping/CCampaignHandler.h b/lib/mapping/CCampaignHandler.h index 7b9c1631e..ca6c2fbfc 100644 --- a/lib/mapping/CCampaignHandler.h +++ b/lib/mapping/CCampaignHandler.h @@ -125,8 +125,9 @@ public: WhatHeroKeeps whatHeroKeeps; - std::set monstersKeptByHero; - std::set artifactsKeptByHero; + //TODO: use typed containers + std::set monstersKeptByHero; + std::set artifactsKeptByHero; ui8 startOptions = 0; //1 - start bonus, 2 - traveling hero, 3 - hero options From 4e18a3d57984a63b307503a7abfe9edbb391b189 Mon Sep 17 00:00:00 2001 From: Konstantin P Date: Wed, 19 Apr 2023 13:00:51 +0300 Subject: [PATCH 21/26] CSerializer: fix debug assertion Fix compilation in debug mode --- lib/serializer/CSerializer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/serializer/CSerializer.h b/lib/serializer/CSerializer.h index 407c5bcc3..2e0d83731 100644 --- a/lib/serializer/CSerializer.h +++ b/lib/serializer/CSerializer.h @@ -99,7 +99,7 @@ public: return nullptr; else { - assert(!i->second.empty()); + assert(i->second.has_value()); #ifndef __APPLE__ assert(i->second.type() == typeid(VectorizedObjectInfo)); #endif From 9ecf16ca234cc5ad1b0e237aa476ebad9173596c Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 20 Apr 2023 03:20:00 +0400 Subject: [PATCH 22/26] Fix minor bugs --- lib/mapObjects/CGHeroInstance.cpp | 8 +++++++- lib/mapping/CCampaignHandler.cpp | 2 +- mapeditor/mapcontroller.cpp | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 949023718..05674ee12 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1479,7 +1479,10 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler) if(portrait >= 0) { if(portrait < legacyHeroes || portrait >= moddedStart) - handler.serializeId("portrait", portrait, -1); + { + int tempPortrait = portrait - GameConstants::HERO_PORTRAIT_SHIFT; + handler.serializeId("portrait", tempPortrait, -1); + } else handler.serializeInt("portrait", portrait, -1); } @@ -1489,7 +1492,10 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler) const JsonNode & portraitNode = handler.getCurrent()["portrait"]; if(portraitNode.getType() == JsonNode::JsonType::DATA_STRING) + { handler.serializeId("portrait", portrait, -1); + portrait += GameConstants::HERO_PORTRAIT_SHIFT; + } else handler.serializeInt("portrait", portrait, -1); } diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index 779bb4efb..c66fc965e 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -264,7 +264,7 @@ CCampaignScenario CCampaignHandler::readScenarioFromJson(JsonNode & reader) CCampaignScenario ret; ret.conquered = false; ret.mapName = reader["map"].String(); - for(auto & g : reader["precoditions"].Vector()) + for(auto & g : reader["preconditions"].Vector()) ret.preconditionRegions.insert(g.Integer()); ret.regionColor = reader["color"].Integer(); diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index af14216f9..e4faf8d5f 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -576,6 +576,9 @@ ModCompatibilityInfo MapController::modAssessmentMap(const CMap & map) ModCompatibilityInfo result; for(auto obj : map.objects) { + if(obj->ID == Obj::HERO) + continue; //stub! + auto handler = VLC->objtypeh->getHandlerFor(obj->ID, obj->subID); auto modName = QString::fromStdString(handler->getJsonKey()).split(":").at(0).toStdString(); if(modName != "core") From be6667acf5e7873889faed815371666aabe895ca Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 20 Apr 2023 19:21:37 +0400 Subject: [PATCH 23/26] Most questionable workaround --- lib/filesystem/MinizipExtensions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/filesystem/MinizipExtensions.cpp b/lib/filesystem/MinizipExtensions.cpp index c98eccdfd..e58fb3a9d 100644 --- a/lib/filesystem/MinizipExtensions.cpp +++ b/lib/filesystem/MinizipExtensions.cpp @@ -67,7 +67,7 @@ inline long streamSeek(voidpf opaque, voidpf stream, ZPOS64_T offset, int origin } if(ret == -1) logGlobal->error("Stream seek failed"); - return ret; + return 0; } template From db33558abcd79c1fc9e72c4057ea19a2bf645351 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 20 Apr 2023 19:22:27 +0400 Subject: [PATCH 24/26] Load custom campaigns from gzip --- lib/mapping/CCampaignHandler.cpp | 105 ++++++++++++------------------- 1 file changed, 41 insertions(+), 64 deletions(-) diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index c66fc965e..ce642e147 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -90,21 +90,19 @@ CCampaignHeader CCampaignHandler::getHeader( const std::string & name) std::string language = VLC->modh->getModLanguage(modName); std::string encoding = Languages::getLanguageOptions(language).encoding; - JsonNode jsonCampaign(resourceID); + auto fileStream = CResourceHandler::get(modName)->load(resourceID); + std::vector cmpgn = getFile(std::move(fileStream), true)[0]; + JsonNode jsonCampaign((const char*)cmpgn.data(), cmpgn.size()); if(jsonCampaign.isNull()) { - auto fileStream = CResourceHandler::get(modName)->load(resourceID); - std::vector cmpgn = getFile(std::move(fileStream), true)[0]; + //legacy OH3 campaign (*.h3c) CMemoryStream stream(cmpgn.data(), cmpgn.size()); CBinaryReader reader(&stream); - CCampaignHeader ret = readHeaderFromMemory(reader, resourceID.getName(), modName, encoding); - return ret; - } - else - { - CCampaignHeader ret = readHeaderFromJson(jsonCampaign, resourceID.getName(), modName, encoding); - return ret; + return readHeaderFromMemory(reader, resourceID.getName(), modName, encoding); } + + //VCMI (*.vcmp) + return readHeaderFromJson(jsonCampaign, resourceID.getName(), modName, encoding); } std::unique_ptr CCampaignHandler::getCampaign( const std::string & name ) @@ -116,66 +114,48 @@ std::unique_ptr CCampaignHandler::getCampaign( const std::string & na auto ret = std::make_unique(); - JsonNode jsonCampaign(resourceID); + auto fileStream = CResourceHandler::get(modName)->load(resourceID); + + std::vector> files = getFile(std::move(fileStream), false); + + JsonNode jsonCampaign((const char*)files[0].data(), files[0].size()); if(jsonCampaign.isNull()) { - auto fileStream = CResourceHandler::get(modName)->load(resourceID); - - std::vector> file = getFile(std::move(fileStream), false); - - CMemoryStream stream(file[0].data(), file[0].size()); + CMemoryStream stream(files[0].data(), files[0].size()); CBinaryReader reader(&stream); ret->header = readHeaderFromMemory(reader, resourceID.getName(), modName, encoding); - - int howManyScenarios = ret->header.numberOfScenarios; - for(int g = 0; g < howManyScenarios; ++g) - { - CCampaignScenario sc = readScenarioFromMemory(reader, ret->header); - ret->scenarios.push_back(sc); - } - int scenarioID = 0; - - //first entry is campaign header. start loop from 1 - for (int g=1; gscenarios[scenarioID].isNotVoid()) //skip void scenarios - { - scenarioID++; - } - - std::string scenarioName = resourceID.getName(); - boost::to_lower(scenarioName); - scenarioName += ':' + std::to_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()); - CMapService mapService; - auto hdr = mapService.loadMapHeader( - reinterpret_cast(ret->mapPieces[scenarioID].c_str()), - static_cast(ret->mapPieces[scenarioID].size()), - scenarioName, - modName, - encoding); - ret->scenarios[scenarioID].scenarioName = hdr->name; - scenarioID++; - } + for(int g = 0; g < ret->header.numberOfScenarios; ++g) + ret->scenarios.emplace_back(readScenarioFromMemory(reader, ret->header)); } else { ret->header = readHeaderFromJson(jsonCampaign, resourceID.getName(), modName, encoding); - for(auto & scenario : jsonCampaign["scenarios"].Vector()) - { - CCampaignScenario sc = readScenarioFromJson(scenario); - if(sc.isNotVoid()) - { - CMapService mapService; - if(auto hdr = mapService.loadMapHeader(ResourceID(sc.mapName, EResType::MAP))) - sc.scenarioName = hdr->name; - } - ret->scenarios.push_back(sc); - } + ret->scenarios.emplace_back(readScenarioFromJson(scenario)); + } + + //first entry is campaign header. start loop from 1 + for(int scenarioID = 0, g = 1; g < files.size() && scenarioID < ret->header.numberOfScenarios; ++g) + { + while(!ret->scenarios[scenarioID].isNotVoid()) //skip void scenarios + scenarioID++; + + std::string scenarioName = resourceID.getName(); + boost::to_lower(scenarioName); + scenarioName += ':' + std::to_string(g - 1); + + //set map piece appropriately, convert vector to string + ret->mapPieces[scenarioID].assign(reinterpret_cast(files[g].data()), files[g].size()); + CMapService mapService; + auto hdr = mapService.loadMapHeader( + reinterpret_cast(ret->mapPieces[scenarioID].c_str()), + static_cast(ret->mapPieces[scenarioID].size()), + scenarioName, + modName, + encoding); + ret->scenarios[scenarioID].scenarioName = hdr->name; + scenarioID++; } // handle campaign specific discrepancies @@ -817,10 +797,7 @@ CMap * CCampaignState::getMap(int scenarioId) const if(scenarioId == -1) scenarioId = currentMap.value(); - CMapService mapService; - if(camp->header.version == CampaignVersion::Version::VCMI) - return mapService.loadMap(ResourceID(camp->scenarios.at(scenarioId).mapName, EResType::MAP)).release(); - + CMapService mapService; std::string scenarioName = camp->header.filename.substr(0, camp->header.filename.find('.')); boost::to_lower(scenarioName); scenarioName += ':' + std::to_string(scenarioId); From de22132cdf35d002a6c65a566d701d58af43824c Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 20 Apr 2023 19:46:27 +0400 Subject: [PATCH 25/26] Fix loading --- lib/mapping/CCampaignHandler.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index ce642e147..9513a5320 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -797,7 +797,7 @@ CMap * CCampaignState::getMap(int scenarioId) const if(scenarioId == -1) scenarioId = currentMap.value(); - CMapService mapService; + CMapService mapService; std::string scenarioName = camp->header.filename.substr(0, camp->header.filename.find('.')); boost::to_lower(scenarioName); scenarioName += ':' + std::to_string(scenarioId); @@ -812,9 +812,6 @@ std::unique_ptr CCampaignState::getHeader(int scenarioId) const scenarioId = currentMap.value(); CMapService mapService; - if(camp->header.version == CampaignVersion::Version::VCMI) - return mapService.loadMapHeader(ResourceID(camp->scenarios.at(scenarioId).mapName, EResType::MAP)); - std::string scenarioName = camp->header.filename.substr(0, camp->header.filename.find('.')); boost::to_lower(scenarioName); scenarioName += ':' + std::to_string(scenarioId); From 753b72f96de7410dee3789c56529eb0fc03fc5cf Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 20 Apr 2023 19:50:00 +0400 Subject: [PATCH 26/26] Revert "Hidden maps" This reverts commit e669d31d33beb20685c5fd87148a3d37a0391afd. --- client/lobby/SelectionTab.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 5b12e2354..7a300d315 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -534,13 +534,6 @@ void SelectionTab::parseMaps(const std::unordered_set & files) { try { - const std::string filename_prefix = file.getName().substr(file.getName().find_last_of("/\\") + 1, 2); - if(filename_prefix == "__") - { - logGlobal->trace("Map %s marked as hidden"); - continue; - } - auto mapInfo = std::make_shared(); mapInfo->mapInit(file.getName());