1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-13 19:54:17 +02:00

Implemented transfer of artifacts held by non-transferred heroes

This commit is contained in:
Ivan Savenko
2024-01-31 19:32:59 +02:00
parent 6d0803dab6
commit 5c5fb523a4
2 changed files with 107 additions and 43 deletions

View File

@@ -60,30 +60,22 @@ std::optional<CampaignScenarioID> CGameStateCampaign::getHeroesSourceScenario()
return campaignState->lastScenario(); return campaignState->lastScenario();
} }
void CGameStateCampaign::trimCrossoverHeroesParameters(std::vector<CampaignHeroReplacement> & campaignHeroReplacements, const CampaignTravel & travelOptions) void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & travelOptions)
{ {
// create heroes list for convenience iterating
std::vector<CGHeroInstance *> 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 // 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) if(!travelOptions.whatHeroKeeps.experience)
{ {
//trimming experience //trimming experience
for(CGHeroInstance * cgh : crossoverHeroes) for(auto & hero : campaignHeroReplacements)
{ {
cgh->initExp(gameState->getRandomGenerator()); hero.hero->initExp(gameState->getRandomGenerator());
} }
} }
if(!travelOptions.whatHeroKeeps.primarySkills) if(!travelOptions.whatHeroKeeps.primarySkills)
{ {
//trimming prim skills //trimming prim skills
for(CGHeroInstance * cgh : crossoverHeroes) for(auto & hero : campaignHeroReplacements)
{ {
for(auto g = PrimarySkill::BEGIN; g < PrimarySkill::END; ++g) for(auto g = PrimarySkill::BEGIN; g < PrimarySkill::END; ++g)
{ {
@@ -91,7 +83,7 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(std::vector<CampaignHeroR
.And(Selector::subtype()(BonusSubtypeID(g))) .And(Selector::subtype()(BonusSubtypeID(g)))
.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)); .And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL));
cgh->getLocalBonus(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::vector<CampaignHeroR
if(!travelOptions.whatHeroKeeps.secondarySkills) if(!travelOptions.whatHeroKeeps.secondarySkills)
{ {
//trimming sec skills //trimming sec skills
for(CGHeroInstance * cgh : crossoverHeroes) for(auto & hero : campaignHeroReplacements)
{ {
cgh->secSkills = cgh->type->secSkillsInit; hero.hero->secSkills = hero.hero->type->secSkillsInit;
cgh->recreateSecondarySkillsBonuses(); hero.hero->recreateSecondarySkillsBonuses();
} }
} }
if(!travelOptions.whatHeroKeeps.spells) if(!travelOptions.whatHeroKeeps.spells)
{ {
for(CGHeroInstance * cgh : crossoverHeroes) for(auto & hero : campaignHeroReplacements)
{ {
cgh->removeSpellbook(); hero.hero->removeSpellbook();
} }
} }
if(!travelOptions.whatHeroKeeps.artifacts) if(!travelOptions.whatHeroKeeps.artifacts)
{ {
//trimming artifacts //trimming artifacts
for(CGHeroInstance * hero : crossoverHeroes) for(auto & hero : campaignHeroReplacements)
{ {
const auto & checkAndRemoveArtifact = [&](const ArtifactPosition & artifactPosition) const auto & checkAndRemoveArtifact = [&](const ArtifactPosition & artifactPosition)
{ {
if(artifactPosition == ArtifactPosition::SPELLBOOK) if(artifactPosition == ArtifactPosition::SPELLBOOK)
return; // do not handle spellbook this way return; // do not handle spellbook this way
const ArtSlotInfo *info = hero->getSlot(artifactPosition); const ArtSlotInfo *info = hero.hero->getSlot(artifactPosition);
if(!info) if(!info)
return; return;
@@ -135,24 +127,27 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(std::vector<CampaignHeroR
bool takeable = travelOptions.artifactsKeptByHero.count(art->artType->getId()); bool takeable = travelOptions.artifactsKeptByHero.count(art->artType->getId());
ArtifactLocation al(hero->id, artifactPosition); if (takeable)
if(!takeable && !hero->getSlot(al.slot)->locked) //don't try removing locked artifacts -> it crashes #1719 hero.transferrableArtifacts.push_back(artifactPosition);
hero->getArt(al.slot)->removeFrom(*hero, al.slot);
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 // process on copy - removal of artifact will invalidate container
auto artifactsWorn = hero->artifactsWorn; auto artifactsWorn = hero.hero->artifactsWorn;
for(const auto & art : artifactsWorn) for(const auto & art : artifactsWorn)
checkAndRemoveArtifact(art.first); checkAndRemoveArtifact(art.first);
// process in reverse - removal of artifact will shift all artifacts after this one // 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); checkAndRemoveArtifact(ArtifactPosition::BACKPACK_START + slotNumber);
} }
} }
//trimming creatures //trimming creatures
for(CGHeroInstance * cgh : crossoverHeroes) for(auto & hero : campaignHeroReplacements)
{ {
auto shouldSlotBeErased = [&](const std::pair<SlotID, CStackInstance *> & j) -> bool auto shouldSlotBeErased = [&](const std::pair<SlotID, CStackInstance *> & j) -> bool
{ {
@@ -160,16 +155,16 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(std::vector<CampaignHeroR
return !travelOptions.monstersKeptByHero.count(crid); return !travelOptions.monstersKeptByHero.count(crid);
}; };
auto stacksCopy = cgh->stacks; //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) for(auto &slotPair : stacksCopy)
if(shouldSlotBeErased(slotPair)) if(shouldSlotBeErased(slotPair))
cgh->eraseStack(slotPair.first); hero.hero->eraseStack(slotPair.first);
} }
// Removing short-term bonuses // 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::OneWeek))
.Or(CSelector(Bonus::NTurns)) .Or(CSelector(Bonus::NTurns))
.Or(CSelector(Bonus::NDays)) .Or(CSelector(Bonus::NDays))
@@ -201,10 +196,10 @@ void CGameStateCampaign::placeCampaignHeroes()
} }
logGlobal->debug("\tGenerate list of hero placeholders"); logGlobal->debug("\tGenerate list of hero placeholders");
auto campaignHeroReplacements = generateCampaignHeroesToReplace(); generateCampaignHeroesToReplace();
logGlobal->debug("\tPrepare crossover heroes"); 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 // 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 // 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); heroesToRemove.insert(heroID);
} }
for(auto & campaignHeroReplacement : campaignHeroReplacements) for(auto & replacement : campaignHeroReplacements)
heroesToRemove.insert(campaignHeroReplacement.hero->getHeroType()); if (replacement.heroPlaceholderId.hasValue())
heroesToRemove.insert(replacement.hero->getHeroType());
for(auto & heroID : heroesToRemove) for(auto & heroID : heroesToRemove)
{ {
@@ -237,7 +233,7 @@ void CGameStateCampaign::placeCampaignHeroes()
} }
logGlobal->debug("\tReplace placeholders with heroes"); logGlobal->debug("\tReplace placeholders with heroes");
replaceHeroesPlaceholders(campaignHeroReplacements); replaceHeroesPlaceholders();
// now add removed heroes again with unused type ID // now add removed heroes again with unused type ID
for(auto * hero : removedHeroes) for(auto * hero : removedHeroes)
@@ -337,10 +333,13 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
} }
} }
void CGameStateCampaign::replaceHeroesPlaceholders(const std::vector<CampaignHeroReplacement> & campaignHeroReplacements) void CGameStateCampaign::replaceHeroesPlaceholders()
{ {
for(const auto & campaignHeroReplacement : campaignHeroReplacements) for(const auto & campaignHeroReplacement : campaignHeroReplacements)
{ {
if (!campaignHeroReplacement.heroPlaceholderId.hasValue())
continue;
auto * heroPlaceholder = dynamic_cast<CGHeroPlaceholder *>(gameState->getObjInstance(campaignHeroReplacement.heroPlaceholderId)); auto * heroPlaceholder = dynamic_cast<CGHeroPlaceholder *>(gameState->getObjInstance(campaignHeroReplacement.heroPlaceholderId));
CGHeroInstance *heroToPlace = campaignHeroReplacement.hero; CGHeroInstance *heroToPlace = campaignHeroReplacement.hero;
@@ -364,14 +363,65 @@ void CGameStateCampaign::replaceHeroesPlaceholders(const std::vector<CampaignHer
} }
} }
std::vector<CampaignHeroReplacement> 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<CGHeroInstance *>(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; auto campaignState = gameState->scenarioOps->campState;
std::vector<CampaignHeroReplacement> campaignHeroReplacements;
std::vector<CGHeroPlaceholder *> placeholdersByPower; std::vector<CGHeroPlaceholder *> placeholdersByPower;
std::vector<CGHeroPlaceholder *> placeholdersByType; std::vector<CGHeroPlaceholder *> placeholdersByType;
campaignHeroReplacements.clear();
// find all placeholders on map // find all placeholders on map
for(auto obj : gameState->map->objects) for(auto obj : gameState->map->objects)
{ {
@@ -412,7 +462,7 @@ std::vector<CampaignHeroReplacement> CGameStateCampaign::generateCampaignHeroesT
auto lastScenario = getHeroesSourceScenario(); auto lastScenario = getHeroesSourceScenario();
if (!placeholdersByPower.empty() && lastScenario) if (lastScenario)
{ {
// sort hero placeholders descending power // sort hero placeholders descending power
boost::range::sort(placeholdersByPower, [](const CGHeroPlaceholder * a, const CGHeroPlaceholder * b) boost::range::sort(placeholdersByPower, [](const CGHeroPlaceholder * a, const CGHeroPlaceholder * b)
@@ -435,8 +485,14 @@ std::vector<CampaignHeroReplacement> CGameStateCampaign::generateCampaignHeroesT
campaignHeroReplacements.emplace_back(hero, placeholder->id); 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() void CGameStateCampaign::initHeroes()
@@ -493,6 +549,8 @@ void CGameStateCampaign::initHeroes()
assert(yog->isCampaignYog()); assert(yog->isCampaignYog());
gameState->giveHeroArtifact(yog, ArtifactID::ANGELIC_ALLIANCE); gameState->giveHeroArtifact(yog, ArtifactID::ANGELIC_ALLIANCE);
} }
transferMissingArtifacts(campaignState->scenario(*campaignState->currentScenario()).travelOptions);
} }
void CGameStateCampaign::initStartingResources() void CGameStateCampaign::initStartingResources()

View File

@@ -25,24 +25,30 @@ struct CampaignHeroReplacement
CampaignHeroReplacement(CGHeroInstance * hero, const ObjectInstanceID & heroPlaceholderId); CampaignHeroReplacement(CGHeroInstance * hero, const ObjectInstanceID & heroPlaceholderId);
CGHeroInstance * hero; CGHeroInstance * hero;
ObjectInstanceID heroPlaceholderId; ObjectInstanceID heroPlaceholderId;
std::vector<ArtifactPosition> transferrableArtifacts;
}; };
class CGameStateCampaign class CGameStateCampaign
{ {
CGameState * gameState; CGameState * gameState;
/// Contains list of heroes that may be available in this scenario
/// temporary helper for game initialization, not serialized
std::vector<CampaignHeroReplacement> campaignHeroReplacements;
/// Returns ID of scenario from which hero placeholders should be selected /// Returns ID of scenario from which hero placeholders should be selected
std::optional<CampaignScenarioID> getHeroesSourceScenario() const; std::optional<CampaignScenarioID> getHeroesSourceScenario() const;
/// returns heroes and placeholders in where heroes will be put /// returns heroes and placeholders in where heroes will be put
std::vector<CampaignHeroReplacement> generateCampaignHeroesToReplace(); void generateCampaignHeroesToReplace();
std::optional<CampaignBonus> currentBonus() const; std::optional<CampaignBonus> currentBonus() const;
/// Trims hero parameters that should not transfer between scenarios according to travelOptions flags /// Trims hero parameters that should not transfer between scenarios according to travelOptions flags
void trimCrossoverHeroesParameters(std::vector<CampaignHeroReplacement> & campaignHeroReplacements, const CampaignTravel & travelOptions); void trimCrossoverHeroesParameters(const CampaignTravel & travelOptions);
void replaceHeroesPlaceholders(const std::vector<CampaignHeroReplacement> & campaignHeroReplacements); void replaceHeroesPlaceholders();
void transferMissingArtifacts(const CampaignTravel & travelOptions);
void giveCampaignBonusToHero(CGHeroInstance * hero); void giveCampaignBonusToHero(CGHeroInstance * hero);