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:
@@ -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()
|
||||
|
@@ -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);
|
||||
|
||||
|
Reference in New Issue
Block a user