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();
}
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
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::vector<CampaignHeroR
.And(Selector::subtype()(BonusSubtypeID(g)))
.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)
{
//trimming sec skills
for(CGHeroInstance * cgh : crossoverHeroes)
for(auto & hero : campaignHeroReplacements)
{
cgh->secSkills = 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::vector<CampaignHeroR
bool takeable = travelOptions.artifactsKeptByHero.count(art->artType->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<SlotID, CStackInstance *> & j) -> bool
{
@@ -160,16 +155,16 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(std::vector<CampaignHeroR
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)
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<CampaignHeroReplacement> & campaignHeroReplacements)
void CGameStateCampaign::replaceHeroesPlaceholders()
{
for(const auto & campaignHeroReplacement : campaignHeroReplacements)
{
if (!campaignHeroReplacement.heroPlaceholderId.hasValue())
continue;
auto * heroPlaceholder = dynamic_cast<CGHeroPlaceholder *>(gameState->getObjInstance(campaignHeroReplacement.heroPlaceholderId));
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;
std::vector<CampaignHeroReplacement> campaignHeroReplacements;
std::vector<CGHeroPlaceholder *> placeholdersByPower;
std::vector<CGHeroPlaceholder *> placeholdersByType;
campaignHeroReplacements.clear();
// find all placeholders on map
for(auto obj : gameState->map->objects)
{
@@ -412,7 +462,7 @@ std::vector<CampaignHeroReplacement> 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<CampaignHeroReplacement> 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()

View File

@@ -25,24 +25,30 @@ struct CampaignHeroReplacement
CampaignHeroReplacement(CGHeroInstance * hero, const ObjectInstanceID & heroPlaceholderId);
CGHeroInstance * hero;
ObjectInstanceID heroPlaceholderId;
std::vector<ArtifactPosition> 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<CampaignHeroReplacement> campaignHeroReplacements;
/// Returns ID of scenario from which hero placeholders should be selected
std::optional<CampaignScenarioID> getHeroesSourceScenario() const;
/// returns heroes and placeholders in where heroes will be put
std::vector<CampaignHeroReplacement> generateCampaignHeroesToReplace();
void generateCampaignHeroesToReplace();
std::optional<CampaignBonus> currentBonus() const;
/// 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);