From 5c5fb523a49b31f80efe91536ff14cc9a650aae7 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 31 Jan 2024 19:32:59 +0200 Subject: [PATCH] Implemented transfer of artifacts held by non-transferred heroes --- lib/gameState/CGameStateCampaign.cpp | 138 +++++++++++++++++++-------- lib/gameState/CGameStateCampaign.h | 12 ++- 2 files changed, 107 insertions(+), 43 deletions(-) diff --git a/lib/gameState/CGameStateCampaign.cpp b/lib/gameState/CGameStateCampaign.cpp index d1a57624c..c706de1d0 100644 --- a/lib/gameState/CGameStateCampaign.cpp +++ b/lib/gameState/CGameStateCampaign.cpp @@ -60,30 +60,22 @@ std::optional CGameStateCampaign::getHeroesSourceScenario() return campaignState->lastScenario(); } -void CGameStateCampaign::trimCrossoverHeroesParameters(std::vector & campaignHeroReplacements, const CampaignTravel & travelOptions) +void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & travelOptions) { - // create heroes list for convenience iterating - std::vector crossoverHeroes; - crossoverHeroes.reserve(campaignHeroReplacements.size()); - for(auto & campaignHeroReplacement : campaignHeroReplacements) - { - crossoverHeroes.push_back(campaignHeroReplacement.hero); - } - // TODO this logic (what should be kept) should be part of CScenarioTravel and be exposed via some clean set of methods if(!travelOptions.whatHeroKeeps.experience) { //trimming experience - for(CGHeroInstance * cgh : crossoverHeroes) + for(auto & hero : campaignHeroReplacements) { - cgh->initExp(gameState->getRandomGenerator()); + hero.hero->initExp(gameState->getRandomGenerator()); } } if(!travelOptions.whatHeroKeeps.primarySkills) { //trimming prim skills - for(CGHeroInstance * cgh : crossoverHeroes) + for(auto & hero : campaignHeroReplacements) { for(auto g = PrimarySkill::BEGIN; g < PrimarySkill::END; ++g) { @@ -91,7 +83,7 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(std::vectorgetLocalBonus(sel)->val = cgh->type->heroClass->primarySkillInitial[g.getNum()]; + hero.hero->getLocalBonus(sel)->val = hero.hero->type->heroClass->primarySkillInitial[g.getNum()]; } } } @@ -99,32 +91,32 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(std::vectorsecSkills = cgh->type->secSkillsInit; - cgh->recreateSecondarySkillsBonuses(); + hero.hero->secSkills = hero.hero->type->secSkillsInit; + hero.hero->recreateSecondarySkillsBonuses(); } } if(!travelOptions.whatHeroKeeps.spells) { - for(CGHeroInstance * cgh : crossoverHeroes) + for(auto & hero : campaignHeroReplacements) { - cgh->removeSpellbook(); + hero.hero->removeSpellbook(); } } if(!travelOptions.whatHeroKeeps.artifacts) { //trimming artifacts - for(CGHeroInstance * hero : crossoverHeroes) + for(auto & hero : campaignHeroReplacements) { const auto & checkAndRemoveArtifact = [&](const ArtifactPosition & artifactPosition) { if(artifactPosition == ArtifactPosition::SPELLBOOK) return; // do not handle spellbook this way - const ArtSlotInfo *info = hero->getSlot(artifactPosition); + const ArtSlotInfo *info = hero.hero->getSlot(artifactPosition); if(!info) return; @@ -135,24 +127,27 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(std::vectorartType->getId()); - ArtifactLocation al(hero->id, artifactPosition); - if(!takeable && !hero->getSlot(al.slot)->locked) //don't try removing locked artifacts -> it crashes #1719 - hero->getArt(al.slot)->removeFrom(*hero, al.slot); + if (takeable) + hero.transferrableArtifacts.push_back(artifactPosition); + + ArtifactLocation al(hero.hero->id, artifactPosition); + if(!takeable && !hero.hero->getSlot(al.slot)->locked) //don't try removing locked artifacts -> it crashes #1719 + hero.hero->getArt(al.slot)->removeFrom(*hero.hero, al.slot); }; // process on copy - removal of artifact will invalidate container - auto artifactsWorn = hero->artifactsWorn; + auto artifactsWorn = hero.hero->artifactsWorn; for(const auto & art : artifactsWorn) checkAndRemoveArtifact(art.first); // process in reverse - removal of artifact will shift all artifacts after this one - for(int slotNumber = hero->artifactsInBackpack.size() - 1; slotNumber >= 0; slotNumber--) + for(int slotNumber = hero.hero->artifactsInBackpack.size() - 1; slotNumber >= 0; slotNumber--) checkAndRemoveArtifact(ArtifactPosition::BACKPACK_START + slotNumber); } } //trimming creatures - for(CGHeroInstance * cgh : crossoverHeroes) + for(auto & hero : campaignHeroReplacements) { auto shouldSlotBeErased = [&](const std::pair & j) -> bool { @@ -160,16 +155,16 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(std::vectorstacks; //copy of the map, so we can iterate iover it and remove stacks + auto stacksCopy = hero.hero->stacks; //copy of the map, so we can iterate iover it and remove stacks for(auto &slotPair : stacksCopy) if(shouldSlotBeErased(slotPair)) - cgh->eraseStack(slotPair.first); + hero.hero->eraseStack(slotPair.first); } // Removing short-term bonuses - for(CGHeroInstance * cgh : crossoverHeroes) + for(auto & hero : campaignHeroReplacements) { - cgh->removeBonusesRecursive(CSelector(Bonus::OneDay) + hero.hero->removeBonusesRecursive(CSelector(Bonus::OneDay) .Or(CSelector(Bonus::OneWeek)) .Or(CSelector(Bonus::NTurns)) .Or(CSelector(Bonus::NDays)) @@ -201,10 +196,10 @@ void CGameStateCampaign::placeCampaignHeroes() } logGlobal->debug("\tGenerate list of hero placeholders"); - auto campaignHeroReplacements = generateCampaignHeroesToReplace(); + generateCampaignHeroesToReplace(); logGlobal->debug("\tPrepare crossover heroes"); - trimCrossoverHeroesParameters(campaignHeroReplacements, campaignState->scenario(*campaignState->currentScenario()).travelOptions); + trimCrossoverHeroesParameters(campaignState->scenario(*campaignState->currentScenario()).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 @@ -221,8 +216,9 @@ void CGameStateCampaign::placeCampaignHeroes() heroesToRemove.insert(heroID); } - for(auto & campaignHeroReplacement : campaignHeroReplacements) - heroesToRemove.insert(campaignHeroReplacement.hero->getHeroType()); + for(auto & replacement : campaignHeroReplacements) + if (replacement.heroPlaceholderId.hasValue()) + heroesToRemove.insert(replacement.hero->getHeroType()); for(auto & heroID : heroesToRemove) { @@ -237,7 +233,7 @@ void CGameStateCampaign::placeCampaignHeroes() } logGlobal->debug("\tReplace placeholders with heroes"); - replaceHeroesPlaceholders(campaignHeroReplacements); + replaceHeroesPlaceholders(); // now add removed heroes again with unused type ID for(auto * hero : removedHeroes) @@ -337,10 +333,13 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero) } } -void CGameStateCampaign::replaceHeroesPlaceholders(const std::vector & campaignHeroReplacements) +void CGameStateCampaign::replaceHeroesPlaceholders() { for(const auto & campaignHeroReplacement : campaignHeroReplacements) { + if (!campaignHeroReplacement.heroPlaceholderId.hasValue()) + continue; + auto * heroPlaceholder = dynamic_cast(gameState->getObjInstance(campaignHeroReplacement.heroPlaceholderId)); CGHeroInstance *heroToPlace = campaignHeroReplacement.hero; @@ -364,14 +363,65 @@ void CGameStateCampaign::replaceHeroesPlaceholders(const std::vector CGameStateCampaign::generateCampaignHeroesToReplace() +void CGameStateCampaign::transferMissingArtifacts(const CampaignTravel & travelOptions) +{ + CGHeroInstance * receiver = nullptr; + + for(auto obj : gameState->map->objects) + { + if (!obj) + continue; + + if (obj->ID != Obj::HERO) + continue; + + auto * hero = dynamic_cast(obj.get()); + + if (gameState->getPlayerState(hero->getOwner())->isHuman()) + { + receiver = hero; + break; + } + } + assert(receiver); + + for(const auto & campaignHeroReplacement : campaignHeroReplacements) + { + if (campaignHeroReplacement.heroPlaceholderId.hasValue()) + continue; + + auto * donorHero = campaignHeroReplacement.hero; + + for (auto const & artLocation : campaignHeroReplacement.transferrableArtifacts) + { + auto * artifact = donorHero->getArt(artLocation); + artifact->removeFrom(*donorHero, artLocation); + + if (receiver) + { + const auto slot = ArtifactUtils::getArtAnyPosition(receiver, artifact->getTypeId()); + if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot)) + artifact->putAt(*receiver, slot); + else + logGlobal->error("Cannot transfer artifact - no free slots!"); + } + else + logGlobal->error("Cannot transfer artifact - no receiver hero!"); + } + + delete donorHero; + } +} + +void CGameStateCampaign::generateCampaignHeroesToReplace() { auto campaignState = gameState->scenarioOps->campState; - std::vector campaignHeroReplacements; std::vector placeholdersByPower; std::vector placeholdersByType; + campaignHeroReplacements.clear(); + // find all placeholders on map for(auto obj : gameState->map->objects) { @@ -412,7 +462,7 @@ std::vector CGameStateCampaign::generateCampaignHeroesT auto lastScenario = getHeroesSourceScenario(); - if (!placeholdersByPower.empty() && lastScenario) + if (lastScenario) { // sort hero placeholders descending power boost::range::sort(placeholdersByPower, [](const CGHeroPlaceholder * a, const CGHeroPlaceholder * b) @@ -435,8 +485,14 @@ std::vector CGameStateCampaign::generateCampaignHeroesT campaignHeroReplacements.emplace_back(hero, placeholder->id); } + + // Add remaining heroes without placeholders - to transfer their artifacts to placed heroes + for (;nodeListIter != nodeList.end(); ++nodeListIter) + { + CGHeroInstance * hero = campaignState->crossoverDeserialize(*nodeListIter, gameState->map); + campaignHeroReplacements.emplace_back(hero, ObjectInstanceID::NONE); + } } - return campaignHeroReplacements; } void CGameStateCampaign::initHeroes() @@ -493,6 +549,8 @@ void CGameStateCampaign::initHeroes() assert(yog->isCampaignYog()); gameState->giveHeroArtifact(yog, ArtifactID::ANGELIC_ALLIANCE); } + + transferMissingArtifacts(campaignState->scenario(*campaignState->currentScenario()).travelOptions); } void CGameStateCampaign::initStartingResources() diff --git a/lib/gameState/CGameStateCampaign.h b/lib/gameState/CGameStateCampaign.h index e9c4610bb..31cedd8b0 100644 --- a/lib/gameState/CGameStateCampaign.h +++ b/lib/gameState/CGameStateCampaign.h @@ -25,24 +25,30 @@ struct CampaignHeroReplacement CampaignHeroReplacement(CGHeroInstance * hero, const ObjectInstanceID & heroPlaceholderId); CGHeroInstance * hero; ObjectInstanceID heroPlaceholderId; + std::vector transferrableArtifacts; }; class CGameStateCampaign { CGameState * gameState; + /// Contains list of heroes that may be available in this scenario + /// temporary helper for game initialization, not serialized + std::vector campaignHeroReplacements; + /// Returns ID of scenario from which hero placeholders should be selected std::optional getHeroesSourceScenario() const; /// returns heroes and placeholders in where heroes will be put - std::vector generateCampaignHeroesToReplace(); + void generateCampaignHeroesToReplace(); std::optional currentBonus() const; /// Trims hero parameters that should not transfer between scenarios according to travelOptions flags - void trimCrossoverHeroesParameters(std::vector & campaignHeroReplacements, const CampaignTravel & travelOptions); + void trimCrossoverHeroesParameters(const CampaignTravel & travelOptions); - void replaceHeroesPlaceholders(const std::vector & campaignHeroReplacements); + void replaceHeroesPlaceholders(); + void transferMissingArtifacts(const CampaignTravel & travelOptions); void giveCampaignBonusToHero(CGHeroInstance * hero);