diff --git a/client/Client.cpp b/client/Client.cpp index 0ba80fb0f..fea87d2a0 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -326,10 +326,7 @@ void CClient::newGame( CConnection *con, StartInfo *si ) c << ui8(2) << ui8(1); //new game; one client c << *si; c >> pom8; - if(pom8) - throw std::runtime_error("Server cannot open the map!"); - else - logNetwork->infoStream() << "Server opened map properly."; + if(pom8) throw std::runtime_error("Server cannot open the map!"); } c >> si; diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 73e1c601d..df27ec9c8 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -432,6 +432,23 @@ CGHeroInstance * CGameState::HeroesPool::pickHeroFor(bool native, PlayerColor pl return ret; } +void CGameState::CrossoverHeroesList::addHeroToBothLists(CGHeroInstance * hero) +{ + heroesFromPreviousScenario.push_back(hero); + heroesFromAnyPreviousScenarios.push_back(hero); +} + +void CGameState::CrossoverHeroesList::removeHeroFromBothLists(CGHeroInstance * hero) +{ + heroesFromPreviousScenario -= hero; + heroesFromAnyPreviousScenarios -= hero; +} + +CGameState::CampaignHeroReplacement::CampaignHeroReplacement(CGHeroInstance * hero, ObjectInstanceID heroPlaceholderId) : hero(hero), heroPlaceholderId(heroPlaceholderId) +{ + +} + int CGameState::pickNextHeroType(PlayerColor owner) const { const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner); @@ -1104,15 +1121,15 @@ void CGameState::placeCampaignHeroes() } // replace heroes placeholders - auto sourceCrossoverHeroes = getCrossoverHeroesFromPreviousScenario(); + auto crossoverHeroes = getCrossoverHeroesFromPreviousScenarios(); - if(!sourceCrossoverHeroes.empty()) + if(!crossoverHeroes.heroesFromAnyPreviousScenarios.empty()) { - logGlobal->debugStream() << "\tPrepare crossover heroes"; - auto crossoverHeroes = prepareCrossoverHeroes(sourceCrossoverHeroes, scenarioOps->campState->getCurrentScenario().travelOptions); - logGlobal->debugStream() << "\tGenerate list of hero placeholders"; - const auto campaignHeroReplacements = generateCampaignHeroesToReplace(crossoverHeroes); + auto campaignHeroReplacements = generateCampaignHeroesToReplace(crossoverHeroes); + + logGlobal->debugStream() << "\tPrepare crossover heroes"; + prepareCrossoverHeroes(campaignHeroReplacements, scenarioOps->campState->getCurrentScenario().travelOptions); // remove same heroes on the map which will be added through crossover heroes // INFO: we will remove heroes because later it may be possible that the API doesn't allow having heroes @@ -1121,7 +1138,7 @@ void CGameState::placeCampaignHeroes() for(auto & campaignHeroReplacement : campaignHeroReplacements) { - auto hero = getUsedHero(HeroTypeID(campaignHeroReplacement.first->subID)); + auto hero = getUsedHero(HeroTypeID(campaignHeroReplacement.hero->subID)); if(hero) { removedHeroes.push_back(hero); @@ -1176,30 +1193,58 @@ void CGameState::placeStartingHero(PlayerColor playerColor, HeroTypeID heroTypeI map->getEditManager()->insertObject(hero, townPos); } -std::vector CGameState::getCrossoverHeroesFromPreviousScenario() const +CGameState::CrossoverHeroesList CGameState::getCrossoverHeroesFromPreviousScenarios() const { - std::vector crossoverHeroes; + CrossoverHeroesList crossoverHeroes; auto campaignState = scenarioOps->campState; auto bonus = campaignState->getBonusForCurrentMap(); if (bonus->type == CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO) { - crossoverHeroes = campaignState->camp->scenarios[bonus->info2].crossoverHeroes; + crossoverHeroes.heroesFromAnyPreviousScenarios = crossoverHeroes.heroesFromPreviousScenario = campaignState->camp->scenarios[bonus->info2].crossoverHeroes; } else { if(!campaignState->mapsConquered.empty()) { - crossoverHeroes = campaignState->camp->scenarios[campaignState->mapsConquered.back()].crossoverHeroes; + crossoverHeroes.heroesFromPreviousScenario = campaignState->camp->scenarios[campaignState->mapsConquered.back()].crossoverHeroes; + + for(auto mapNr : campaignState->mapsConquered) + { + // create a list of deleted heroes + auto & scenario = campaignState->camp->scenarios[mapNr]; + auto lostCrossoverHeroes = scenario.getLostCrossoverHeroes(); + + // remove heroes which didn't reached the end of the scenario, but were available at the start + for(auto hero : lostCrossoverHeroes) + { + range::remove_if(crossoverHeroes.heroesFromAnyPreviousScenarios, CGObjectInstanceBySubIdFinder(hero)); + } + + // now add heroes which completed the scenario + for(auto hero : scenario.crossoverHeroes) + { + // add new heroes and replace old heroes with newer ones + range::remove_if(crossoverHeroes.heroesFromAnyPreviousScenarios, CGObjectInstanceBySubIdFinder(hero)); + crossoverHeroes.heroesFromAnyPreviousScenarios.push_back(hero); + } + } } } return std::move(crossoverHeroes); } -std::vector CGameState::prepareCrossoverHeroes(const std::vector & sourceCrossoverHeroes, const CScenarioTravel & travelOptions) +void CGameState::prepareCrossoverHeroes(std::vector & campaignHeroReplacements, const CScenarioTravel & travelOptions) const { - auto crossoverHeroes = sourceCrossoverHeroes; //TODO deep copy hero instances + //TODO deep copy hero instances + + // create heroes list for convenience iterating + std::vector crossoverHeroes; + for(auto & campaignHeroReplacement : campaignHeroReplacements) + { + crossoverHeroes.push_back(campaignHeroReplacement.hero); + } if(!(travelOptions.whatHeroKeeps & 1)) { @@ -1280,8 +1325,6 @@ std::vector CGameState::prepareCrossoverHeroes(const std::vect return !(travelOptions.monstersKeptByHero[crid / 8] & (1 << (crid % 8)) ); }); } - - return std::move(crossoverHeroes); } void CGameState::placeStartingHeroes() @@ -2696,9 +2739,9 @@ std::set CGameState::getUnusedAllowedHeroes(bool alsoIncludeNotAllow return ret; } -std::vector > CGameState::generateCampaignHeroesToReplace(std::vector & crossoverHeroes) +std::vector CGameState::generateCampaignHeroesToReplace(CrossoverHeroesList & crossoverHeroes) { - std::vector > campaignHeroReplacements; + std::vector campaignHeroReplacements; auto removeHeroPlaceholder = [this](CGHeroPlaceholder * heroPlaceholder) { @@ -2708,56 +2751,50 @@ std::vector > CGameState::generateC }; //selecting heroes by type - for(int g = 0; g < map->objects.size(); ++g) + for(auto obj : map->objects) { - CGObjectInstance * obj = map->objects[g]; - if (obj && obj->ID == Obj::HERO_PLACEHOLDER) + if(obj && obj->ID == Obj::HERO_PLACEHOLDER) { - CGHeroPlaceholder * hp = static_cast(obj); - - const ObjectInstanceID gid = ObjectInstanceID(g); - if(hp->subID != 0xFF) //select by type + auto heroPlaceholder = dynamic_cast(obj.get()); + if(heroPlaceholder->subID != 0xFF) //select by type { - bool found = false; - for(auto ghi : crossoverHeroes) + auto it = range::find_if(crossoverHeroes.heroesFromAnyPreviousScenarios, [heroPlaceholder](CGHeroInstance * hero) { - if (ghi->subID == hp->subID) - { - found = true; - campaignHeroReplacements.push_back(std::make_pair(ghi, gid)); - crossoverHeroes -= ghi; - break; - } + return hero->subID == heroPlaceholder->subID; + }); + if(it == crossoverHeroes.heroesFromAnyPreviousScenarios.end()) + { + removeHeroPlaceholder(heroPlaceholder); } - if(!found) + else { - removeHeroPlaceholder(hp); + campaignHeroReplacements.push_back(CampaignHeroReplacement(*it, obj->id)); + crossoverHeroes.removeHeroFromBothLists(*it); } } } } //selecting heroes by power - std::sort(crossoverHeroes.begin(), crossoverHeroes.end(), [](const CGHeroInstance * a, const CGHeroInstance * b) + range::sort(crossoverHeroes.heroesFromPreviousScenario, [](const CGHeroInstance * a, const CGHeroInstance * b) { return a->getHeroStrength() > b->getHeroStrength(); }); //sort, descending strength // sort hero placeholders descending power std::vector heroPlaceholders; - for(int g = 0; g < map->objects.size(); ++g) + for(auto obj : map->objects) { - CGObjectInstance * obj = map->objects[g]; if(obj && obj->ID == Obj::HERO_PLACEHOLDER) { - CGHeroPlaceholder * hp = dynamic_cast(obj); - if(hp->subID == 0xFF) //select by power + auto heroPlaceholder = dynamic_cast(obj.get()); + if(heroPlaceholder->subID == 0xFF) //select by power { - heroPlaceholders.push_back(hp); + heroPlaceholders.push_back(heroPlaceholder); } } } - std::sort(heroPlaceholders.begin(), heroPlaceholders.end(), [](const CGHeroPlaceholder * a, const CGHeroPlaceholder * b) + range::sort(heroPlaceholders, [](const CGHeroPlaceholder * a, const CGHeroPlaceholder * b) { return a->power > b->power; }); @@ -2765,9 +2802,9 @@ std::vector > CGameState::generateC for(int i = 0; i < heroPlaceholders.size(); ++i) { auto heroPlaceholder = heroPlaceholders[i]; - if(crossoverHeroes.size() > i) + if(crossoverHeroes.heroesFromPreviousScenario.size() > i) { - campaignHeroReplacements.push_back(std::make_pair(crossoverHeroes[i], heroPlaceholder->id)); + campaignHeroReplacements.push_back(CampaignHeroReplacement(crossoverHeroes.heroesFromPreviousScenario[i], heroPlaceholder->id)); } else { @@ -2778,16 +2815,16 @@ std::vector > CGameState::generateC return campaignHeroReplacements; } -void CGameState::replaceHeroesPlaceholders(const std::vector > &campHeroReplacements) +void CGameState::replaceHeroesPlaceholders(const std::vector & campaignHeroReplacements) { - for(auto obj : campHeroReplacements) + for(auto campaignHeroReplacement : campaignHeroReplacements) { - CGHeroPlaceholder *placeholder = dynamic_cast(getObjInstance(obj.second)); + auto heroPlaceholder = dynamic_cast(getObjInstance(campaignHeroReplacement.heroPlaceholderId)); - CGHeroInstance *heroToPlace = obj.first; - heroToPlace->id = obj.second; - heroToPlace->tempOwner = placeholder->tempOwner; - heroToPlace->pos = placeholder->pos; + CGHeroInstance *heroToPlace = campaignHeroReplacement.hero; + heroToPlace->id = campaignHeroReplacement.heroPlaceholderId; + heroToPlace->tempOwner = heroPlaceholder->tempOwner; + heroToPlace->pos = heroPlaceholder->pos; heroToPlace->type = VLC->heroh->heroes[heroToPlace->subID]; for(auto &&i : heroToPlace->stacks) @@ -2808,6 +2845,8 @@ void CGameState::replaceHeroesPlaceholders(const std::vectorheroesOnMap.push_back(heroToPlace); map->objects[heroToPlace->id.getNum()] = heroToPlace; map->addBlockVisTiles(heroToPlace); + + scenarioOps->campState->getCurrentScenario().placedCrossoverHeroes.push_back(heroToPlace); } } diff --git a/lib/CGameState.h b/lib/CGameState.h index ff94976b8..1a344fd59 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -460,6 +460,20 @@ public: } private: + struct CrossoverHeroesList + { + std::vector heroesFromPreviousScenario, heroesFromAnyPreviousScenarios; + void addHeroToBothLists(CGHeroInstance * hero); + void removeHeroFromBothLists(CGHeroInstance * hero); + }; + + struct CampaignHeroReplacement + { + CampaignHeroReplacement(CGHeroInstance * hero, ObjectInstanceID heroPlaceholderId); + CGHeroInstance * hero; + ObjectInstanceID heroPlaceholderId; + }; + // ----- initialization ----- void initNewGame(); @@ -472,15 +486,15 @@ private: void randomizeObject(CGObjectInstance *cur); void initPlayerStates(); void placeCampaignHeroes(); - std::vector getCrossoverHeroesFromPreviousScenario() const; - - /// gets prepared and copied hero instances with crossover heroes from prev. scenario and travel options from current scenario - std::vector prepareCrossoverHeroes(const std::vector & sourceCrossoverHeroes, const CScenarioTravel & travelOptions); + CrossoverHeroesList getCrossoverHeroesFromPreviousScenarios() const; /// returns heroes and placeholders in where heroes will be put - std::vector > generateCampaignHeroesToReplace(std::vector & crossoverHeroes); + std::vector generateCampaignHeroesToReplace(CrossoverHeroesList & crossoverHeroes); - void replaceHeroesPlaceholders(const std::vector > &campHeroReplacements); + /// gets prepared and copied hero instances with crossover heroes from prev. scenario and travel options from current scenario + void prepareCrossoverHeroes(std::vector & campaignHeroReplacements, const CScenarioTravel & travelOptions) const; + + void replaceHeroesPlaceholders(const std::vector & campaignHeroReplacements); void placeStartingHeroes(); void placeStartingHero(PlayerColor playerColor, HeroTypeID heroTypeId, int3 townPos); void initStartingResources(); diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index e23b37072..d7843719f 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -94,6 +94,7 @@ set(lib_HEADERS int3.h Interprocess.h NetPacks.h + NetPacksBase.h RegisterTypes.h StartInfo.h UnlockGuard.h diff --git a/lib/CObjectHandler.cpp b/lib/CObjectHandler.cpp index 702a32fe1..c281c1488 100644 --- a/lib/CObjectHandler.cpp +++ b/lib/CObjectHandler.cpp @@ -519,6 +519,16 @@ bool CGObjectInstance::passableFor(PlayerColor color) const return getPassableness() & 1<obj->subID == obj->subID; +} + static int lowestSpeed(const CGHeroInstance * chi) { if(!chi->Slots().size()) diff --git a/lib/CObjectHandler.h b/lib/CObjectHandler.h index a063492be..4c2c2339b 100644 --- a/lib/CObjectHandler.h +++ b/lib/CObjectHandler.h @@ -228,6 +228,18 @@ protected: void getNameVis(std::string &hname) const; void giveDummyBonus(ObjectInstanceID heroID, ui8 duration = Bonus::ONE_DAY) const; }; + +/// function object which can be used to find an object with an specific sub ID +class CGObjectInstanceBySubIdFinder +{ +public: + CGObjectInstanceBySubIdFinder(CGObjectInstance * obj); + bool operator()(CGObjectInstance * obj) const; + +private: + CGObjectInstance * obj; +}; + class CGHeroPlaceholder : public CGObjectInstance { public: diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index 832a4c22c..0e31cfe90 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -337,6 +337,23 @@ const CGHeroInstance * CCampaignScenario::strongestHero( PlayerColor owner ) con return i == ownedHeroes.end() ? nullptr : *i; } +std::vector CCampaignScenario::getLostCrossoverHeroes() const +{ + std::vector lostCrossoverHeroes; + if(conquered) + { + for(auto hero : placedCrossoverHeroes) + { + auto it = range::find_if(crossoverHeroes, CGObjectInstanceBySubIdFinder(hero)); + if(it == crossoverHeroes.end()) + { + lostCrossoverHeroes.push_back(hero); + } + } + } + return std::move(lostCrossoverHeroes); +} + bool CScenarioTravel::STravelBonus::isBonusForHero() const { return type == SPELL || type == MONSTER || type == ARTIFACT || type == SPELL_SCROLL || type == PRIMARY_SKILL @@ -376,6 +393,11 @@ const CCampaignScenario & CCampaignState::getCurrentScenario() const return camp->scenarios[*currentMap]; } +CCampaignScenario & CCampaignState::getCurrentScenario() +{ + return camp->scenarios[*currentMap]; +} + ui8 CCampaignState::currentBonusID() const { return chosenCampaignBonuses.at(*currentMap); diff --git a/lib/mapping/CCampaignHandler.h b/lib/mapping/CCampaignHandler.h index 945096769..169e36d72 100644 --- a/lib/mapping/CCampaignHandler.h +++ b/lib/mapping/CCampaignHandler.h @@ -109,15 +109,17 @@ public: CScenarioTravel travelOptions; 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 const CGHeroInstance * strongestHero(PlayerColor owner) const; void loadPreconditionRegions(ui32 regions); bool isNotVoid() const; + std::vector getLostCrossoverHeroes() const; /// returns a list of crossover heroes which started the scenario, but didn't complete it template void serialize(Handler &h, const int formatVersion) { h & mapName & scenarioName & packedMapSize & preconditionRegions & regionColor & difficulty & conquered & regionText & - prolog & epilog & travelOptions & crossoverHeroes; + prolog & epilog & travelOptions & crossoverHeroes & placedCrossoverHeroes; } }; @@ -151,7 +153,8 @@ public: //void initNewCampaign(const StartInfo &si); void setCurrentMapAsConquered(const std::vector & heroes); boost::optional getBonusForCurrentMap() const; - const CCampaignScenario &getCurrentScenario() const; + const CCampaignScenario & getCurrentScenario() const; + CCampaignScenario & getCurrentScenario(); ui8 currentBonusID() const; CCampaignState(); diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 27154b9cc..e26731981 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -9,23 +9,21 @@ */ #include "StdInc.h" -#include "MapFormatH3M.h" - #include -#include "../CStopWatch.h" - -#include "../filesystem/Filesystem.h" +#include "MapFormatH3M.h" #include "CMap.h" +#include "../CStopWatch.h" +#include "../filesystem/Filesystem.h" #include "../CSpellHandler.h" #include "../CCreatureHandler.h" #include "../CGeneralTextHandler.h" #include "../CHeroHandler.h" #include "../CObjectHandler.h" #include "../CDefObjInfoHandler.h" - #include "../VCMI_Lib.h" +#include "../NetPacksBase.h" const bool CMapLoaderH3M::IS_PROFILING_ENABLED = false; @@ -442,7 +440,6 @@ void CMapLoaderH3M::readVictoryLossConditions() { EventCondition cond(EventCondition::CONTROL); cond.objectType = Obj::CREATURE_GENERATOR1; // FIXME: generators 2-4? - cond.position = readInt3(); specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[289]; specialVictory.onFulfill = VLC->generaltexth->allTexts[288]; @@ -473,7 +470,7 @@ void CMapLoaderH3M::readVictoryLossConditions() default: assert(0); } - //bool allowNormalVictory = reader.readBool(); + // if condition is human-only turn it into following construction: AllOf(human, condition) if (!appliesToAI) { @@ -869,7 +866,17 @@ bool CMapLoaderH3M::loadArtifactToSlot(CGHeroInstance * hero, int slot) slot = ArtifactPosition::SPELLBOOK; } - hero->putArtifact(ArtifactPosition(slot), createArtifact(aid)); + // this is needed, because some H3M maps (last scenario of ROE map) contain invalid data like misplaced artifacts + auto artifact = createArtifact(aid); + auto artifactPos = ArtifactPosition(slot); + if (artifact->canBePutAt(ArtifactLocation(hero, artifactPos))) + { + hero->putArtifact(artifactPos, artifact); + } + else + { + logGlobal->debugStream() << "Artifact can't be put at the specified location."; //TODO add more debugging information + } } return isArt;