/* * CCampaignHandler.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * * License: GNU General Public License v2.0 or later * Full text of license available in license.txt file, in main folder * */ #include "StdInc.h" #include "CampaignState.h" #include "../Point.h" #include "../filesystem/ResourcePath.h" #include "../VCMI_Lib.h" #include "../texts/CGeneralTextHandler.h" #include "../mapping/CMapService.h" #include "../mapping/CMapInfo.h" #include "../mapping/CMap.h" #include "../mapObjects/CGHeroInstance.h" #include "../serializer/JsonDeserializer.h" #include "../serializer/JsonSerializer.h" #include "../json/JsonUtils.h" VCMI_LIB_NAMESPACE_BEGIN void CampaignScenario::loadPreconditionRegions(ui32 regions) { for (int i=0; i<32; i++) //for each bit in region. h3c however can only hold up to 16 { if ( (1 << i) & regions) preconditionRegions.insert(static_cast(i)); } } CampaignRegions::RegionDescription CampaignRegions::RegionDescription::fromJson(const JsonNode & node) { CampaignRegions::RegionDescription rd; rd.infix = node["infix"].String(); rd.pos = Point(static_cast(node["x"].Float()), static_cast(node["y"].Float())); if(!node["labelPos"].isNull()) rd.labelPos = Point(static_cast(node["labelPos"]["x"].Float()), static_cast(node["labelPos"]["y"].Float())); else rd.labelPos = std::nullopt; return rd; } CampaignRegions CampaignRegions::fromJson(const JsonNode & node) { CampaignRegions cr; cr.campPrefix = node["prefix"].String(); cr.colorSuffixLength = static_cast(node["colorSuffixLength"].Float()); cr.campSuffix = node["suffix"].isNull() ? std::vector() : std::vector{node["suffix"].Vector()[0].String(), node["suffix"].Vector()[1].String(), node["suffix"].Vector()[2].String()}; cr.campBackground = node["background"].isNull() ? "" : node["background"].String(); 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(JsonPath::builtin("config/campaign_regions.json")); for(const JsonNode & campaign : config["campaign_regions"].Vector()) campDescriptions.push_back(CampaignRegions::fromJson(campaign)); } return campDescriptions.at(campId); } ImagePath CampaignRegions::getBackgroundName() const { if(campBackground.empty()) return ImagePath::builtin(campPrefix + "_BG.BMP"); else return ImagePath::builtin(campBackground); } Point CampaignRegions::getPosition(CampaignScenarioID which) const { auto const & region = regions[which.getNum()]; return region.pos; } std::optional CampaignRegions::getLabelPosition(CampaignScenarioID which) const { auto const & region = regions[which.getNum()]; return region.labelPos; } ImagePath CampaignRegions::getNameFor(CampaignScenarioID which, int colorIndex, std::string type) const { auto const & region = regions[which.getNum()]; static const std::array, 3> colors = {{ { "", "", "", "", "", "", "", "" }, { "R", "B", "N", "G", "O", "V", "T", "P" }, { "Re", "Bl", "Br", "Gr", "Or", "Vi", "Te", "Pi" } }}; std::string color = colors[colorSuffixLength][colorIndex]; return ImagePath::builtin(campPrefix + region.infix + "_" + type + color + ".BMP"); } ImagePath CampaignRegions::getAvailableName(CampaignScenarioID which, int color) const { if(campSuffix.empty()) return getNameFor(which, color, "En"); else return getNameFor(which, color, campSuffix[0]); } ImagePath CampaignRegions::getSelectedName(CampaignScenarioID which, int color) const { if(campSuffix.empty()) return getNameFor(which, color, "Se"); else return getNameFor(which, color, campSuffix[1]); } ImagePath CampaignRegions::getConqueredName(CampaignScenarioID which, int color) const { if(campSuffix.empty()) return getNameFor(which, color, "Co"); else return getNameFor(which, color, campSuffix[2]); } bool CampaignBonus::isBonusForHero() const { return type == CampaignBonusType::SPELL || type == CampaignBonusType::MONSTER || type == CampaignBonusType::ARTIFACT || type == CampaignBonusType::SPELL_SCROLL || type == CampaignBonusType::PRIMARY_SKILL || type == CampaignBonusType::SECONDARY_SKILL; } void CampaignHeader::loadLegacyData(ui8 campId) { campaignRegions = CampaignRegions::getLegacy(campId); numberOfScenarios = VLC->generaltexth->getCampaignLength(campId); } void CampaignHeader::loadLegacyData(CampaignRegions regions, int numOfScenario) { campaignRegions = regions; numberOfScenarios = numOfScenario; } bool CampaignHeader::playerSelectedDifficulty() const { return difficultyChosenByPlayer; } bool CampaignHeader::formatVCMI() const { return version == CampaignVersion::VCMI; } std::string CampaignHeader::getDescriptionTranslated() const { return description.toString(); } std::string CampaignHeader::getNameTranslated() const { return name.toString(); } std::string CampaignHeader::getAuthor() const { return authorContact.toString(); } std::string CampaignHeader::getAuthorContact() const { return authorContact.toString(); } std::string CampaignHeader::getCampaignVersion() const { return campaignVersion.toString(); } time_t CampaignHeader::getCreationDateTime() const { return creationDateTime; } std::string CampaignHeader::getFilename() const { return filename; } std::string CampaignHeader::getModName() const { return modName; } std::string CampaignHeader::getEncoding() const { return encoding; } AudioPath CampaignHeader::getMusic() const { return music; } ImagePath CampaignHeader::getLoadingBackground() const { return loadingBackground; } ImagePath CampaignHeader::getVideoRim() const { return videoRim; } VideoPath CampaignHeader::getIntroVideo() const { return introVideo; } VideoPath CampaignHeader::getOutroVideo() const { return outroVideo; } const CampaignRegions & CampaignHeader::getRegions() const { return campaignRegions; } TextContainerRegistrable & CampaignHeader::getTexts() { return textContainer; } bool CampaignState::isConquered(CampaignScenarioID whichScenario) const { return vstd::contains(mapsConquered, whichScenario); } bool CampaignState::isAvailable(CampaignScenarioID whichScenario) const { //check for void scenraio if (!scenario(whichScenario).isNotVoid()) { return false; } if (vstd::contains(mapsConquered, whichScenario)) { return false; } //check preconditioned regions for (auto const & it : scenario(whichScenario).preconditionRegions) { if (!vstd::contains(mapsConquered, it)) return false; } return true; } bool CampaignScenario::isNotVoid() const { return !mapName.empty(); } std::set CampaignState::getReservedHeroes() const { std::set result; for (auto const & scenarioID : allScenarios()) { if (isConquered(scenarioID)) continue; auto header = getMapHeader(scenarioID); result.insert(header->reservedCampaignHeroes.begin(), header->reservedCampaignHeroes.end()); } return result; } const CGHeroInstance * CampaignState::strongestHero(CampaignScenarioID scenarioId, const PlayerColor & owner) const { std::function isOwned = [&](const JsonNode & node) { auto * h = CampaignState::crossoverDeserialize(node, nullptr); bool result = h->tempOwner == owner; vstd::clear_pointer(h); return result; }; auto ownedHeroes = scenarioHeroPool.at(scenarioId) | boost::adaptors::filtered(isOwned); if (ownedHeroes.empty()) return nullptr; return CampaignState::crossoverDeserialize(ownedHeroes.front(), nullptr); } /// Returns heroes that can be instantiated as hero placeholders by power const std::vector & CampaignState::getHeroesByPower(CampaignScenarioID scenarioId) const { static const std::vector emptyVector; if (scenarioHeroPool.count(scenarioId)) return scenarioHeroPool.at(scenarioId); return emptyVector; } /// Returns hero for instantiation as placeholder by type /// May return empty JsonNode if such hero was not found const JsonNode & CampaignState::getHeroByType(HeroTypeID heroID) const { static const JsonNode emptyNode; if (!getReservedHeroes().count(heroID)) return emptyNode; if (!globalHeroPool.count(heroID)) return emptyNode; return globalHeroPool.at(heroID); } void CampaignState::setCurrentMapAsConquered(std::vector heroes) { range::sort(heroes, [](const CGHeroInstance * a, const CGHeroInstance * b) { return a->getHeroStrength() > b->getHeroStrength(); }); logGlobal->info("Scenario %d of campaign %s (%s) has been completed", currentMap->getNum(), getFilename(), getNameTranslated()); mapsConquered.push_back(*currentMap); auto reservedHeroes = getReservedHeroes(); for (auto * hero : heroes) { JsonNode node = CampaignState::crossoverSerialize(hero); if (reservedHeroes.count(hero->getHeroType())) { logGlobal->info("Hero crossover: %d (%s) exported to global pool", hero->getHeroType(), hero->getNameTranslated()); globalHeroPool[hero->getHeroType()] = node; } else { logGlobal->info("Hero crossover: %d (%s) exported to scenario pool", hero->getHeroType(), hero->getNameTranslated()); scenarioHeroPool[*currentMap].push_back(node); } } } std::optional CampaignState::getBonus(CampaignScenarioID which) const { auto bonuses = scenario(which).travelOptions.bonusesToChoose; assert(chosenCampaignBonuses.count(*currentMap) || bonuses.empty()); if(bonuses.empty()) return std::optional(); if (!getBonusID(which)) return std::optional(); return bonuses[getBonusID(which).value()]; } std::optional CampaignState::getBonusID(CampaignScenarioID which) const { if (!chosenCampaignBonuses.count(which)) return std::nullopt; return chosenCampaignBonuses.at(which); } std::unique_ptr CampaignState::getMap(CampaignScenarioID scenarioId, IGameCallback * cb) { // FIXME: there is certainly better way to handle maps inside campaigns if(scenarioId == CampaignScenarioID::NONE) scenarioId = currentMap.value(); CMapService mapService; std::string scenarioName = getFilename().substr(0, getFilename().find('.')); boost::to_lower(scenarioName); scenarioName += ':' + std::to_string(scenarioId.getNum()); const auto & mapContent = mapPieces.find(scenarioId)->second; auto result = mapService.loadMap(mapContent.data(), mapContent.size(), scenarioName, getModName(), getEncoding(), cb); mapTranslations[scenarioId] = result->texts; return result; } std::unique_ptr CampaignState::getMapHeader(CampaignScenarioID scenarioId) const { if(scenarioId == CampaignScenarioID::NONE) scenarioId = currentMap.value(); CMapService mapService; std::string scenarioName = getFilename().substr(0, getFilename().find('.')); boost::to_lower(scenarioName); scenarioName += ':' + std::to_string(scenarioId.getNum()); const auto & mapContent = mapPieces.find(scenarioId)->second; return mapService.loadMapHeader(mapContent.data(), mapContent.size(), scenarioName, getModName(), getEncoding()); } std::shared_ptr CampaignState::getMapInfo(CampaignScenarioID scenarioId) const { if(scenarioId == CampaignScenarioID::NONE) scenarioId = currentMap.value(); auto mapInfo = std::make_shared(); mapInfo->fileURI = getFilename(); mapInfo->mapHeader = getMapHeader(scenarioId); mapInfo->countPlayers(); return mapInfo; } JsonNode CampaignState::crossoverSerialize(CGHeroInstance * hero) const { JsonNode node; JsonSerializer handler(nullptr, node); hero->serializeJsonOptions(handler); return node; } CGHeroInstance * CampaignState::crossoverDeserialize(const JsonNode & node, CMap * map) const { JsonDeserializer handler(nullptr, const_cast(node)); auto * hero = new CGHeroInstance(map ? map->cb : nullptr); hero->ID = Obj::HERO; hero->serializeJsonOptions(handler); if (map) { hero->serializeJsonArtifacts(handler, "artifacts"); map->addNewArtifactInstance(*hero); } return hero; } void CampaignState::setCurrentMap(CampaignScenarioID which) { assert(scenario(which).isNotVoid()); currentMap = which; } void CampaignState::setCurrentMapBonus(ui8 which) { chosenCampaignBonuses[*currentMap] = which; } std::optional CampaignState::currentScenario() const { return currentMap; } std::optional CampaignState::lastScenario() const { if (mapsConquered.empty()) return std::nullopt; return mapsConquered.back(); } std::set CampaignState::conqueredScenarios() const { std::set result; result.insert(mapsConquered.begin(), mapsConquered.end()); return result; } std::set Campaign::allScenarios() const { std::set result; for (auto const & entry : scenarios) { if (entry.second.isNotVoid()) result.insert(entry.first); } return result; } void Campaign::overrideCampaign() { const JsonNode node = JsonUtils::assembleFromFiles("config/campaignOverrides.json"); for (auto & entry : node.Struct()) if(filename == entry.first) { if(!entry.second["regions"].isNull() && !entry.second["scenarioCount"].isNull()) loadLegacyData(CampaignRegions::fromJson(entry.second["regions"]), entry.second["scenarioCount"].Integer()); if(!entry.second["loadingBackground"].isNull()) loadingBackground = ImagePath::builtin(entry.second["loadingBackground"].String()); if(!entry.second["videoRim"].isNull()) videoRim = ImagePath::builtin(entry.second["videoRim"].String()); if(!entry.second["introVideo"].isNull()) introVideo = VideoPath::builtin(entry.second["introVideo"].String()); if(!entry.second["outroVideo"].isNull()) outroVideo = VideoPath::builtin(entry.second["outroVideo"].String()); } } void Campaign::overrideCampaignScenarios() { const JsonNode node = JsonUtils::assembleFromFiles("config/campaignOverrides.json"); for (auto & entry : node.Struct()) if(filename == entry.first) { if(!entry.second["scenarios"].isNull()) { auto sc = entry.second["scenarios"].Vector(); for(int i = 0; i < sc.size(); i++) { auto it = scenarios.begin(); std::advance(it, i); if(!sc.at(i)["voiceProlog"].isNull()) it->second.prolog.prologVoice = AudioPath::builtin(sc.at(i)["voiceProlog"].String()); if(!sc.at(i)["voiceEpilog"].isNull()) it->second.epilog.prologVoice = AudioPath::builtin(sc.at(i)["voiceEpilog"].String()); } } } } int Campaign::scenariosCount() const { return allScenarios().size(); } const CampaignScenario & Campaign::scenario(CampaignScenarioID which) const { assert(scenarios.count(which)); assert(scenarios.at(which).isNotVoid()); return scenarios.at(which); } bool CampaignState::isCampaignFinished() const { return conqueredScenarios() == allScenarios(); } VCMI_LIB_NAMESPACE_END