From a08fe0951773fdf642672273da10a181ff07ea1b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 26 Jun 2023 00:01:25 +0300 Subject: [PATCH] Added encapsulation for CampaignState class --- client/ClientCommandManager.cpp | 4 +- client/eventsSDL/UserEventHandler.cpp | 4 +- client/lobby/CBonusSelection.cpp | 42 ++++---- client/windows/CCastleInterface.cpp | 2 +- lib/NetPacksLib.cpp | 14 --- lib/StartInfo.cpp | 4 +- lib/campaign/CampaignHandler.cpp | 10 -- lib/campaign/CampaignState.cpp | 133 ++++++++++++++++++++------ lib/campaign/CampaignState.h | 74 ++++++++++---- lib/gameState/CGameStateCampaign.cpp | 19 ++-- server/CVCMIServer.cpp | 10 +- server/NetPacksLobbyServer.cpp | 14 +-- 12 files changed, 203 insertions(+), 127 deletions(-) diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index 0650be71a..23f23e4f4 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -210,8 +210,8 @@ void ClientCommandManager::handleConvertTextCommand() for (auto const & campaignName : campaignList) { auto state = CampaignHandler::getCampaign(campaignName.getName()); - for (auto const & part : state->mapPieces) - state->getMap(part.first); + for (auto const & part : state->allScenarios()) + state->getMap(part); } VLC->generaltexth->dumpAllTexts(); diff --git a/client/eventsSDL/UserEventHandler.cpp b/client/eventsSDL/UserEventHandler.cpp index ece51c6de..6d9cec47d 100644 --- a/client/eventsSDL/UserEventHandler.cpp +++ b/client/eventsSDL/UserEventHandler.cpp @@ -51,10 +51,10 @@ void UserEventHandler::handleUserEvent(const SDL_UserEvent & user) CSH->campaignServerRestartLock.set(true); CSH->endGameplay(); auto ourCampaign = std::shared_ptr(reinterpret_cast(user.data1)); - auto & epilogue = ourCampaign->scenarios[ourCampaign->mapsConquered.back()].epilog; + auto & epilogue = ourCampaign->scenario(*ourCampaign->lastScenario()).epilog; auto finisher = [=]() { - if(!ourCampaign->mapsRemaining.empty()) + if(!ourCampaign->isCampaignFinished()) { GH.windows().pushWindow(CMM); GH.windows().pushWindow(CMM->menu); diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index 3435d752d..a00478b94 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -64,7 +64,7 @@ CBonusSelection::CBonusSelection() { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; - std::string bgName = getCampaign()->header.campaignRegions.campPrefix + "_BG.BMP"; + std::string bgName = getCampaign()->getHeader().campaignRegions.campPrefix + "_BG.BMP"; setBackground(bgName); panelBackground = std::make_shared("CAMPBRF.BMP", 456, 6); @@ -78,7 +78,7 @@ CBonusSelection::CBonusSelection() iconsMapSizes = std::make_shared("SCNRMPSZ", 4, 0, 735, 26); labelCampaignDescription = std::make_shared(481, 63, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]); - campaignDescription = std::make_shared(getCampaign()->header.description, Rect(480, 86, 286, 117), 1); + campaignDescription = std::make_shared(getCampaign()->getHeader().description, Rect(480, 86, 286, 117), 1); mapName = std::make_shared(481, 219, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getName()); labelMapDescription = std::make_shared(481, 253, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]); @@ -99,27 +99,25 @@ CBonusSelection::CBonusSelection() difficultyIcons[b] = std::make_shared("GSPBUT" + std::to_string(b + 3) + ".DEF", 0, 0, 709, 455); } - if(getCampaign()->header.difficultyChoosenByPlayer) + if(getCampaign()->getHeader().difficultyChoosenByPlayer) { buttonDifficultyLeft = std::make_shared(Point(694, 508), "SCNRBLF.DEF", CButton::tooltip(), std::bind(&CBonusSelection::decreaseDifficulty, this)); buttonDifficultyRight = std::make_shared(Point(738, 508), "SCNRBRT.DEF", CButton::tooltip(), std::bind(&CBonusSelection::increaseDifficulty, this)); } - for(int g = 0; g < getCampaign()->scenarios.size(); ++g) + for(auto scenarioID : getCampaign()->allScenarios()) { - auto scenarioID = static_cast(g); - - if(getCampaign()->conquerable(scenarioID)) - regions.push_back(std::make_shared(scenarioID, true, true, getCampaign()->header.campaignRegions)); - else if(getCampaign()->scenarios[scenarioID].conquered) //display as striped - regions.push_back(std::make_shared(scenarioID, false, false, getCampaign()->header.campaignRegions)); + if(getCampaign()->isAvailable(scenarioID)) + regions.push_back(std::make_shared(scenarioID, true, true, getCampaign()->getHeader().campaignRegions)); + else if(getCampaign()->isConquered(scenarioID)) //display as striped + regions.push_back(std::make_shared(scenarioID, false, false, getCampaign()->getHeader().campaignRegions)); } } void CBonusSelection::createBonusesIcons() { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; - const CampaignScenario & scenario = getCampaign()->scenarios[CSH->campaignMap]; + const CampaignScenario & scenario = getCampaign()->scenario(CSH->campaignMap); const std::vector & bonDescs = scenario.travelOptions.bonusesToChoose; groupBonuses = std::make_shared(std::bind(&IServerAPI::setCampaignBonus, CSH, _1)); @@ -172,7 +170,7 @@ void CBonusSelection::createBonusesIcons() assert(faction != -1); BuildingID buildID; - if(getCampaign()->header.version == CampaignVersion::VCMI) + if(getCampaign()->getHeader().version == CampaignVersion::VCMI) buildID = BuildingID(bonDescs[i].info1); else buildID = CBuildingHandler::campToERMU(bonDescs[i].info1, faction, std::set()); @@ -272,13 +270,13 @@ void CBonusSelection::createBonusesIcons() } case CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO: { - auto superhero = getCampaign()->scenarios[static_cast(bonDescs[i].info2)].strongestHero(PlayerColor(bonDescs[i].info1)); + auto superhero = getCampaign()->strongestHero(static_cast(bonDescs[i].info2), PlayerColor(bonDescs[i].info1)); if(!superhero) logGlobal->warn("No superhero! How could it be transferred?"); picNumber = superhero ? superhero->portrait : 0; desc = CGI->generaltexth->allTexts[719]; - boost::algorithm::replace_first(desc, "%s", getCampaign()->scenarios[static_cast(bonDescs[i].info2)].scenarioName); + boost::algorithm::replace_first(desc, "%s", getCampaign()->scenario(static_cast(bonDescs[i].info2)).scenarioName); break; } @@ -313,10 +311,8 @@ void CBonusSelection::createBonusesIcons() groupBonuses->addToggle(i, bonusButton); } - if(vstd::contains(getCampaign()->chosenCampaignBonuses, CSH->campaignMap)) - { - groupBonuses->setSelected(getCampaign()->chosenCampaignBonuses[CSH->campaignMap]); - } + if(getCampaign()->getBonusID(CSH->campaignMap)) + groupBonuses->setSelected(*getCampaign()->getBonusID(CSH->campaignMap)); } void CBonusSelection::updateAfterStateChange() @@ -325,7 +321,7 @@ void CBonusSelection::updateAfterStateChange() { buttonRestart->disable(); buttonStart->enable(); - if(!getCampaign()->mapsConquered.empty()) + if(!getCampaign()->conqueredScenarios().empty()) buttonBack->block(true); else buttonBack->block(false); @@ -342,7 +338,7 @@ void CBonusSelection::updateAfterStateChange() } if(CSH->campaignBonus == -1) { - buttonStart->block(getCampaign()->scenarios[CSH->campaignMap].travelOptions.bonusesToChoose.size()); + buttonStart->block(getCampaign()->scenario(CSH->campaignMap).travelOptions.bonusesToChoose.size()); } else if(buttonStart->isBlocked()) { @@ -398,7 +394,7 @@ void CBonusSelection::startMap() CSH->sendStartGame(); }; - const CampaignScenario & scenario = getCampaign()->scenarios[CSH->campaignMap]; + const CampaignScenario & scenario = getCampaign()->scenario(CSH->campaignMap); if(scenario.prolog.hasPrologEpilog) { GH.windows().createAndPushWindow(scenario.prolog, exitCb); @@ -464,7 +460,7 @@ CBonusSelection::CRegion::CRegion(CampaignScenarioID id, bool accessible, bool s pos.y += desc.ypos; std::string prefix = campDsc.campPrefix + desc.infix + "_"; - std::string suffix = colors[campDsc.colorSuffixLength - 1][CSH->si->campState->scenarios[idOfMapAndRegion].regionColor]; + std::string suffix = colors[campDsc.colorSuffixLength - 1][CSH->si->campState->scenario(idOfMapAndRegion).regionColor]; graphicsNotSelected = std::make_shared(prefix + "En" + suffix + ".BMP"); graphicsNotSelected->disable(); graphicsSelected = std::make_shared(prefix + "Se" + suffix + ".BMP"); @@ -513,7 +509,7 @@ void CBonusSelection::CRegion::clickLeft(tribool down, bool previousState) void CBonusSelection::CRegion::showPopupWindow() { // FIXME: For some reason "down" is only ever contain indeterminate_value - auto text = CSH->si->campState->scenarios[idOfMapAndRegion].regionText; + auto text = CSH->si->campState->scenario(idOfMapAndRegion).regionText; if(!graphicsNotSelected->getSurface()->isTransparent(GH.getCursorPosition() - pos.topLeft()) && text.size()) { CRClickPopup::createAndPush(text); diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 1df5a0db0..e281ef567 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -923,7 +923,7 @@ void CCastleBuildings::enterMagesGuild() const StartInfo *si = LOCPLINT->cb->getStartInfo(); // it would be nice to find a way to move this hack to config/mapOverrides.json if(si && si->campState && si->campState && // We're in campaign, - (si->campState->header.filename == "DATA/YOG.H3C") && // which is "Birth of a Barbarian", + (si->campState->getHeader().filename == "DATA/YOG.H3C") && // which is "Birth of a Barbarian", (hero->subID == 45)) // and the hero is Yog (based on Solmyr) { // "Yog has given up magic in all its forms..." diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index bf917c9da..c6aa1e38e 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -1076,21 +1076,7 @@ void PlayerEndsGame::applyGs(CGameState * gs) const // keep all heroes from the winning player crossoverHeroes.push_back(hero); } - else if (vstd::contains(gs->scenarioOps->campState->getCurrentScenario().keepHeroes, HeroTypeID(hero->subID))) - { - // keep hero whether lost or won (like Xeron in AB campaign) - crossoverHeroes.push_back(hero); - } } - // keep lost heroes which are in heroes pool - for (auto & heroPair : gs->hpool.heroesPool) - { - if (vstd::contains(gs->scenarioOps->campState->getCurrentScenario().keepHeroes, HeroTypeID(heroPair.first))) - { - crossoverHeroes.push_back(heroPair.second.get()); - } - } - gs->scenarioOps->campState->setCurrentMapAsConquered(crossoverHeroes); } } diff --git a/lib/StartInfo.cpp b/lib/StartInfo.cpp index 51e045027..856e7f70f 100644 --- a/lib/StartInfo.cpp +++ b/lib/StartInfo.cpp @@ -62,8 +62,8 @@ PlayerSettings * StartInfo::getPlayersSettings(const ui8 connectedPlayerId) std::string StartInfo::getCampaignName() const { - if(campState->header.name.length()) - return campState->header.name; + if(campState->getHeader().name.empty()) + return campState->getHeader().name; else return VLC->generaltexth->allTexts[508]; } diff --git a/lib/campaign/CampaignHandler.cpp b/lib/campaign/CampaignHandler.cpp index 58d6c6d23..e01e94983 100644 --- a/lib/campaign/CampaignHandler.cpp +++ b/lib/campaign/CampaignHandler.cpp @@ -111,14 +111,6 @@ std::shared_ptr CampaignHandler::getCampaign( const std::string & scenarioID++; } - for(int i = 0; i < ret->scenarios.size(); i++) - { - auto scenarioID = static_cast(i); - - if(vstd::contains(ret->mapPieces, scenarioID)) //not all maps must be present in a campaign - ret->mapsRemaining.push_back(scenarioID); - } - return ret; } @@ -188,7 +180,6 @@ CampaignScenario CampaignHandler::readScenarioFromJson(JsonNode & reader) }; CampaignScenario ret; - ret.conquered = false; ret.mapName = reader["map"].String(); for(auto & g : reader["preconditions"].Vector()) ret.preconditionRegions.insert(static_cast(g.Integer())); @@ -428,7 +419,6 @@ CampaignScenario CampaignHandler::readScenarioFromMemory( CBinaryReader & reader }; CampaignScenario ret; - ret.conquered = false; ret.mapName = reader.readBaseString(); reader.readUInt32(); //packedMapSize - not used if(header.numberOfScenarios > 8) //unholy alliance diff --git a/lib/campaign/CampaignState.cpp b/lib/campaign/CampaignState.cpp index fb1cf8224..5b2dc4c60 100644 --- a/lib/campaign/CampaignState.cpp +++ b/lib/campaign/CampaignState.cpp @@ -83,7 +83,12 @@ void CampaignHeader::loadLegacyData(ui8 campId) numberOfScenarios = VLC->generaltexth->getCampaignLength(campId); } -bool CampaignState::conquerable(CampaignScenarioID whichScenario) const +bool CampaignState::isConquered(CampaignScenarioID whichScenario) const +{ + return vstd::contains(mapsConquered, whichScenario); +} + +bool CampaignState::isAvailable(CampaignScenarioID whichScenario) const { //check for void scenraio if (!scenarios.at(whichScenario).isNotVoid()) @@ -91,14 +96,14 @@ bool CampaignState::conquerable(CampaignScenarioID whichScenario) const return false; } - if (scenarios.at(whichScenario).conquered) + if (vstd::contains(mapsConquered, whichScenario)) { return false; } //check preconditioned regions for (auto const & it : scenarios.at(whichScenario).preconditionRegions) { - if (!scenarios.at(it).conquered) + if (!vstd::contains(mapsConquered, it)) return false; } return true; @@ -109,18 +114,18 @@ bool CampaignScenario::isNotVoid() const return !mapName.empty(); } -const CGHeroInstance * CampaignScenario::strongestHero(const PlayerColor & owner) +const CGHeroInstance * CampaignState::strongestHero(CampaignScenarioID scenarioId, const PlayerColor & owner) const { - std::function isOwned = [owner](JsonNode & node) + std::function isOwned = [owner](const JsonNode & node) { auto * h = CampaignState::crossoverDeserialize(node); bool result = h->tempOwner == owner; vstd::clear_pointer(h); return result; }; - auto ownedHeroes = crossoverHeroes | boost::adaptors::filtered(isOwned); + auto ownedHeroes = crossover.placedHeroes.at(scenarioId) | boost::adaptors::filtered(isOwned); - auto i = vstd::maxElementByFun(ownedHeroes, [](JsonNode & node) + auto i = vstd::maxElementByFun(ownedHeroes, [](const JsonNode & node) { auto * h = CampaignState::crossoverDeserialize(node); double result = h->getHeroStrength(); @@ -130,41 +135,43 @@ const CGHeroInstance * CampaignScenario::strongestHero(const PlayerColor & owner return i == ownedHeroes.end() ? nullptr : CampaignState::crossoverDeserialize(*i); } -std::vector CampaignScenario::getLostCrossoverHeroes() +std::vector CampaignState::getLostCrossoverHeroes(CampaignScenarioID scenarioId) const { std::vector lostCrossoverHeroes; - if(conquered) + + for(auto node2 : crossover.placedHeroes.at(scenarioId)) { - for(auto node2 : placedCrossoverHeroes) + auto * hero = CampaignState::crossoverDeserialize(node2); + auto it = range::find_if(crossover.crossoverHeroes.at(scenarioId), [hero](JsonNode node) { - auto * hero = CampaignState::crossoverDeserialize(node2); - auto it = range::find_if(crossoverHeroes, [hero](JsonNode node) - { - auto * h = CampaignState::crossoverDeserialize(node); - bool result = hero->subID == h->subID; - vstd::clear_pointer(h); - return result; - }); - if(it == crossoverHeroes.end()) - { - lostCrossoverHeroes.push_back(hero); - } + auto * h = CampaignState::crossoverDeserialize(node); + bool result = hero->subID == h->subID; + vstd::clear_pointer(h); + return result; + }); + if(it == crossover.crossoverHeroes.at(scenarioId).end()) + { + lostCrossoverHeroes.push_back(hero); } } + return lostCrossoverHeroes; } +std::vector CampaignState::getCrossoverHeroes(CampaignScenarioID scenarioId) const +{ + return crossover.crossoverHeroes.at(scenarioId); +} + void CampaignState::setCurrentMapAsConquered(const std::vector & heroes) { - scenarios.at(*currentMap).crossoverHeroes.clear(); + crossover.crossoverHeroes[*currentMap].clear(); for(CGHeroInstance * hero : heroes) { - scenarios.at(*currentMap).crossoverHeroes.push_back(crossoverSerialize(hero)); + crossover.crossoverHeroes[*currentMap].push_back(crossoverSerialize(hero)); } mapsConquered.push_back(*currentMap); - mapsRemaining -= *currentMap; - scenarios.at(*currentMap).conquered = true; } std::optional CampaignState::getBonusForCurrentMap() const @@ -183,9 +190,12 @@ const CampaignScenario & CampaignState::getCurrentScenario() const return scenarios.at(*currentMap); } -CampaignScenario & CampaignState::getCurrentScenario() +std::optional CampaignState::getBonusID(CampaignScenarioID & which) const { - return scenarios.at(*currentMap); + if (!chosenCampaignBonuses.count(which)) + return std::nullopt; + + return chosenCampaignBonuses.at(which); } ui8 CampaignState::currentBonusID() const @@ -242,11 +252,74 @@ JsonNode CampaignState::crossoverSerialize(CGHeroInstance * hero) return node; } -CGHeroInstance * CampaignState::crossoverDeserialize(JsonNode & node) +CGHeroInstance * CampaignState::crossoverDeserialize(const JsonNode & node) { - JsonDeserializer handler(nullptr, node); + JsonDeserializer handler(nullptr, const_cast(node)); auto * hero = new CGHeroInstance(); hero->ID = Obj::HERO; hero->serializeJsonOptions(handler); return hero; } + +void CampaignState::setCurrentMap(CampaignScenarioID which) +{ + assert(scenarios.count(which)); + assert(scenarios.at(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 CampaignState::allScenarios() const +{ + std::set result; + + for (auto const & entry : scenarios) + { + if (entry.second.isNotVoid()) + result.insert(entry.first); + } + + return result; +} + +const CampaignScenario & CampaignState::scenario(CampaignScenarioID which) const +{ + assert(scenarios.count(which)); + assert(scenarios.at(which).isNotVoid()); + + return scenarios.at(which); +} + +bool CampaignState::isCampaignFinished() const +{ + return conqueredScenarios() == allScenarios(); +} + +const CampaignHeader & CampaignState::getHeader() const +{ + return header; +} diff --git a/lib/campaign/CampaignState.h b/lib/campaign/CampaignState.h index 032d00748..0a9a80cca 100644 --- a/lib/campaign/CampaignState.h +++ b/lib/campaign/CampaignState.h @@ -156,22 +156,15 @@ public: std::set preconditionRegions; //what we need to conquer to conquer this one (stored as bitfield in h3c) ui8 regionColor = 0; ui8 difficulty = 0; - bool conquered = false; std::string regionText; CampaignScenarioPrologEpilog prolog; CampaignScenarioPrologEpilog epilog; CampaignTravel travelOptions; - std::vector keepHeroes; // contains list of heroes which should be kept for next scenario (doesn't matter if they lost) - std::vector crossoverHeroes; // contains all heroes with the same state when the campaign scenario was finished - std::vector placedCrossoverHeroes; // contains all placed crossover heroes defined by hero placeholders when the scenario was started void loadPreconditionRegions(ui32 regions); bool isNotVoid() const; - // FIXME: due to usage of JsonNode I can't make these methods const - const CGHeroInstance * strongestHero(const PlayerColor & owner); - std::vector getLostCrossoverHeroes(); /// returns a list of crossover heroes which started the scenario, but didn't complete it template void serialize(Handler &h, const int formatVersion) { @@ -180,44 +173,85 @@ public: h & preconditionRegions; h & regionColor; h & difficulty; - h & conquered; h & regionText; h & prolog; h & epilog; h & travelOptions; + } +}; + +struct DLL_LINKAGE CampaignHeroes +{ + using ScenarioHeroesList = std::vector; + using CampaignHeroesList = std::map; + + CampaignHeroesList crossoverHeroes; // contains all heroes with the same state when the campaign scenario was finished + CampaignHeroesList placedHeroes; // contains all placed crossover heroes defined by hero placeholders when the scenario was started + + template void serialize(Handler &h, const int formatVersion) + { h & crossoverHeroes; - h & placedCrossoverHeroes; - h & keepHeroes; + h & placedHeroes; } }; class DLL_LINKAGE CampaignState { -public: - CampaignHeader header; + friend class CampaignHandler; + + /// List of all maps completed by player, in order of their completion + std::vector mapsConquered; + std::map scenarios; std::map mapPieces; //binary h3ms, scenario number -> map data - - std::vector mapsConquered; - std::vector mapsRemaining; + std::map chosenCampaignBonuses; std::optional currentMap; - std::map chosenCampaignBonuses; + CampaignHeader header; + CampaignHeroes crossover; public: + std::optional lastScenario() const; + std::optional currentScenario() const; + std::set allScenarios() const; + std::set conqueredScenarios() const; + + const CampaignScenario & scenario(CampaignScenarioID which) const; + std::optional getBonusForCurrentMap() const; const CampaignScenario & getCurrentScenario() const; + + std::optional getBonusID(CampaignScenarioID & which) const; ui8 currentBonusID() const; - bool conquerable(CampaignScenarioID whichScenario) const; + + /// Returns true if selected scenario can be selected and started by player + bool isAvailable(CampaignScenarioID whichScenario) const; + + /// Returns true if selected scenario has been already completed by player + bool isConquered(CampaignScenarioID whichScenario) const; + + /// Returns true if all available scenarios have been completed and campaign is finished + bool isCampaignFinished() const; + + const CampaignHeader & getHeader() const; std::unique_ptr getMap(CampaignScenarioID scenarioId) const; std::unique_ptr getMapHeader(CampaignScenarioID scenarioId) const; std::shared_ptr getMapInfo(CampaignScenarioID scenarioId) const; - CampaignScenario & getCurrentScenario(); + void setCurrentMap(CampaignScenarioID which); + void setCurrentMapBonus(ui8 which); void setCurrentMapAsConquered(const std::vector & heroes); + + const CGHeroInstance * strongestHero(CampaignScenarioID scenarioId, const PlayerColor & owner) const; + + /// returns a list of crossover heroes which started the scenario, but didn't complete it + std::vector getLostCrossoverHeroes(CampaignScenarioID scenarioId) const; + + std::vector getCrossoverHeroes(CampaignScenarioID scenarioId) const; + static JsonNode crossoverSerialize(CGHeroInstance * hero); - static CGHeroInstance * crossoverDeserialize(JsonNode & node); + static CGHeroInstance * crossoverDeserialize(const JsonNode & node); CampaignState() = default; @@ -225,8 +259,8 @@ public: { h & header; h & scenarios; + h & crossover; h & mapPieces; - h & mapsRemaining; h & mapsConquered; h & currentMap; h & chosenCampaignBonuses; diff --git a/lib/gameState/CGameStateCampaign.cpp b/lib/gameState/CGameStateCampaign.cpp index 8318740db..f32251282 100644 --- a/lib/gameState/CGameStateCampaign.cpp +++ b/lib/gameState/CGameStateCampaign.cpp @@ -65,7 +65,7 @@ CrossoverHeroesList CGameStateCampaign::getCrossoverHeroesFromPreviousScenarios( auto scenarioID = static_cast(bonus->info2); std::vector heroes; - for(auto & node : campaignState->scenarios.at(scenarioID).crossoverHeroes) + for(auto & node : campaignState->getCrossoverHeroes(scenarioID)) { auto * h = CampaignState::crossoverDeserialize(node); heroes.push_back(h); @@ -76,14 +76,13 @@ CrossoverHeroesList CGameStateCampaign::getCrossoverHeroesFromPreviousScenarios( return crossoverHeroes; } - if(campaignState->mapsConquered.empty()) + if(!campaignState->lastScenario()) return crossoverHeroes; - for(auto mapNr : campaignState->mapsConquered) + for(auto mapNr : campaignState->conqueredScenarios()) { // create a list of deleted heroes - auto & scenario = campaignState->scenarios[mapNr]; - auto lostCrossoverHeroes = scenario.getLostCrossoverHeroes(); + auto lostCrossoverHeroes = campaignState->getLostCrossoverHeroes(mapNr); // remove heroes which didn't reached the end of the scenario, but were available at the start for(auto * hero : lostCrossoverHeroes) @@ -96,7 +95,7 @@ CrossoverHeroesList CGameStateCampaign::getCrossoverHeroesFromPreviousScenarios( } // now add heroes which completed the scenario - for(auto node : scenario.crossoverHeroes) + for(auto node : campaignState->getCrossoverHeroes(mapNr)) { auto * hero = CampaignState::crossoverDeserialize(node); // add new heroes and replace old heroes with newer ones @@ -116,7 +115,7 @@ CrossoverHeroesList CGameStateCampaign::getCrossoverHeroesFromPreviousScenarios( crossoverHeroes.heroesFromAnyPreviousScenarios.push_back(hero); } - if(mapNr == campaignState->mapsConquered.back()) + if(mapNr == campaignState->lastScenario()) { crossoverHeroes.heroesFromPreviousScenario.push_back(hero); } @@ -392,7 +391,7 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero) continue; } auto bb = std::make_shared( - BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::CAMPAIGN_BONUS, val, static_cast(*gameState->scenarioOps->campState->currentMap), g + BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::CAMPAIGN_BONUS, val, static_cast(*gameState->scenarioOps->campState->currentScenario()), g ); hero->addNewBonus(bb); } @@ -445,8 +444,6 @@ void CGameStateCampaign::replaceHeroesPlaceholders(const std::vectormap->instanceNames[heroToPlace->instanceName] = heroToPlace; delete heroPlaceholder; - - gameState->scenarioOps->campState->getCurrentScenario().placedCrossoverHeroes.push_back(CampaignState::crossoverSerialize(heroToPlace)); } } @@ -628,7 +625,7 @@ void CGameStateCampaign::initTowns() gameState->map->towns[g]->pos == pi.posOfMainTown) { BuildingID buildingId; - if(gameState->scenarioOps->campState->header.version == CampaignVersion::VCMI) + if(gameState->scenarioOps->campState->getHeader().version == CampaignVersion::VCMI) buildingId = BuildingID(chosenBonus->info1); else buildingId = CBuildingHandler::campToERMU(chosenBonus->info1, gameState->map->towns[g]->subID, gameState->map->towns[g]->builtBuildings); diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 8789d33fc..0d922dec1 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -296,8 +296,8 @@ bool CVCMIServer::prepareToStartGame() { case StartInfo::CAMPAIGN: logNetwork->info("Preparing to start new campaign"); - si->campState->currentMap = std::make_optional(campaignMap); - si->campState->chosenCampaignBonuses[campaignMap] = campaignBonus; + si->campState->setCurrentMap(campaignMap); + si->campState->setCurrentMapBonus(campaignBonus); gh->init(si.get()); break; @@ -668,7 +668,7 @@ void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr mapInfo, si = CMemorySerializer::deepCopy(*mi->scenarioOptionsOfSave); si->mode = StartInfo::LOAD_GAME; if(si->campState) - campaignMap = si->campState->currentMap.value(); + campaignMap = si->campState->currentScenario().value(); for(auto & ps : si->playerInfos) { @@ -873,7 +873,7 @@ void CVCMIServer::optionNextCastle(PlayerColor player, int dir) void CVCMIServer::setCampaignMap(CampaignScenarioID mapId) { campaignMap = mapId; - si->difficulty = si->campState->scenarios[mapId].difficulty; + si->difficulty = si->campState->scenario(mapId).difficulty; campaignBonus = -1; updateStartInfoOnMapChange(si->campState->getMapInfo(mapId)); } @@ -882,7 +882,7 @@ void CVCMIServer::setCampaignBonus(int bonusId) { campaignBonus = bonusId; - const CampaignScenario & scenario = si->campState->scenarios[campaignMap]; + const CampaignScenario & scenario = si->campState->scenario(campaignMap); const std::vector & bonDescs = scenario.travelOptions.bonusesToChoose; if(bonDescs[bonusId].type == CampaignBonusType::HERO) { diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index 39c5a43fd..33f8fef61 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -211,18 +211,18 @@ void ApplyOnServerNetPackVisitor::visitLobbySetMap(LobbySetMap & pack) void ApplyOnServerNetPackVisitor::visitLobbySetCampaign(LobbySetCampaign & pack) { - srv.si->mapname = pack.ourCampaign->header.filename; + srv.si->mapname = pack.ourCampaign->getHeader().filename; srv.si->mode = StartInfo::CAMPAIGN; srv.si->campState = pack.ourCampaign; srv.si->turnTime = 0; - bool isCurrentMapConquerable = pack.ourCampaign->currentMap && pack.ourCampaign->conquerable(*pack.ourCampaign->currentMap); - for(int i = 0; i < pack.ourCampaign->scenarios.size(); i++) - { - auto scenarioID = static_cast(i); - if(pack.ourCampaign->conquerable(scenarioID)) + bool isCurrentMapConquerable = pack.ourCampaign->currentScenario() && pack.ourCampaign->isAvailable(*pack.ourCampaign->currentScenario()); + + for(auto scenarioID : pack.ourCampaign->allScenarios()) + { + if(pack.ourCampaign->isAvailable(scenarioID)) { - if(!isCurrentMapConquerable || (isCurrentMapConquerable && scenarioID == *pack.ourCampaign->currentMap)) + if(!isCurrentMapConquerable || (isCurrentMapConquerable && scenarioID == *pack.ourCampaign->currentScenario())) { srv.setCampaignMap(scenarioID); }