1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Replaced hero crossover logic with one that actually matches H3

This commit is contained in:
Ivan Savenko 2023-06-26 16:25:34 +03:00
parent e2bd98e21e
commit 48ac84110b
9 changed files with 265 additions and 335 deletions

View File

@ -1066,17 +1066,14 @@ void PlayerEndsGame::applyGs(CGameState * gs) const
p->status = EPlayerStatus::WINNER;
// TODO: Campaign-specific code might as well go somewhere else
// keep all heroes from the winning player
if(p->human && gs->scenarioOps->campState)
{
std::vector<CGHeroInstance *> crossoverHeroes;
for (CGHeroInstance * hero : gs->map->heroesOnMap)
{
if (hero->tempOwner == player)
{
// keep all heroes from the winning player
crossoverHeroes.push_back(hero);
}
}
gs->scenarioOps->campState->setCurrentMapAsConquered(crossoverHeroes);
}
}

View File

@ -65,7 +65,6 @@ std::unique_ptr<Campaign> CampaignHandler::getHeader( const std::string & name)
auto ret = std::make_unique<Campaign>();
auto fileStream = CResourceHandler::get(modName)->load(resourceID);
std::vector<ui8> cmpgn = getFile(std::move(fileStream), true)[0];
JsonNode jsonCampaign((const char*)cmpgn.data(), cmpgn.size());
readCampaign(ret.get(), cmpgn, resourceID.getName(), modName, encoding);
@ -436,7 +435,7 @@ CampaignScenario CampaignHandler::readScenarioFromMemory( CBinaryReader & reader
}
template<typename Identifier>
static void readContainer(std::set<Identifier> container, CBinaryReader & reader, int sizeBytes)
static void readContainer(std::set<Identifier> & container, CBinaryReader & reader, int sizeBytes)
{
for(int iId = 0, byte = 0; iId < sizeBytes * 8; ++iId)
{

View File

@ -185,70 +185,100 @@ bool CampaignScenario::isNotVoid() const
return !mapName.empty();
}
std::set<HeroTypeID> CampaignState::getReservedHeroes() const
{
std::set<HeroTypeID> result;
for (auto const & scenarioID : allScenarios())
{
if (isConquered(scenarioID))
continue;
auto header = getMapHeader(scenarioID);
result.insert(header->reservedCampaignHeroes.begin(), header->reservedCampaignHeroes.end());
}
return result;
}
const CGHeroInstance * CampaignState::strongestHero(CampaignScenarioID scenarioId, const PlayerColor & owner) const
{
std::function<bool(const JsonNode & node)> isOwned = [owner](const JsonNode & node)
{
auto * h = CampaignState::crossoverDeserialize(node);
auto * h = CampaignState::crossoverDeserialize(node, nullptr);
bool result = h->tempOwner == owner;
vstd::clear_pointer(h);
return result;
};
auto ownedHeroes = crossover.placedHeroes.at(scenarioId) | boost::adaptors::filtered(isOwned);
auto ownedHeroes = scenarioHeroPool.at(scenarioId) | boost::adaptors::filtered(isOwned);
auto i = vstd::maxElementByFun(ownedHeroes, [](const JsonNode & node)
if (ownedHeroes.empty())
return nullptr;
return CampaignState::crossoverDeserialize(ownedHeroes.front(), nullptr);
}
/// Returns heroes that can be instantiated as hero placeholders by power
const std::vector<JsonNode> & CampaignState::getHeroesByPower(CampaignScenarioID scenarioId) const
{
static const std::vector<JsonNode> emptyVector;
if (scenarioHeroPool.count(scenarioId))
return scenarioHeroPool.at(scenarioId);
return emptyVector;
}
/// Returns hero for instantiation as placeholder by type
/// May return empty JsonNode if such hero was not found
const JsonNode & CampaignState::getHeroByType(HeroTypeID heroID) const
{
static const JsonNode emptyNode;
if (!getReservedHeroes().count(heroID))
return emptyNode;
if (!globalHeroPool.count(heroID))
return emptyNode;
return globalHeroPool.at(heroID);
}
void CampaignState::setCurrentMapAsConquered(std::vector<CGHeroInstance *> heroes)
{
range::sort(heroes, [](const CGHeroInstance * a, const CGHeroInstance * b)
{
auto * h = CampaignState::crossoverDeserialize(node);
double result = h->getHeroStrength();
vstd::clear_pointer(h);
return result;
return a->getHeroStrength() > b->getHeroStrength();
});
return i == ownedHeroes.end() ? nullptr : CampaignState::crossoverDeserialize(*i);
}
std::vector<CGHeroInstance *> CampaignState::getLostCrossoverHeroes(CampaignScenarioID scenarioId) const
{
std::vector<CGHeroInstance *> lostCrossoverHeroes;
for(auto node2 : crossover.placedHeroes.at(scenarioId))
{
auto * hero = CampaignState::crossoverDeserialize(node2);
auto it = range::find_if(crossover.crossoverHeroes.at(scenarioId), [hero](JsonNode node)
{
auto * h = CampaignState::crossoverDeserialize(node);
bool result = hero->subID == h->subID;
vstd::clear_pointer(h);
return result;
});
if(it == crossover.crossoverHeroes.at(scenarioId).end())
{
lostCrossoverHeroes.push_back(hero);
}
}
return lostCrossoverHeroes;
}
std::vector<JsonNode> CampaignState::getCrossoverHeroes(CampaignScenarioID scenarioId) const
{
return crossover.crossoverHeroes.at(scenarioId);
}
void CampaignState::setCurrentMapAsConquered(const std::vector<CGHeroInstance *> & heroes)
{
crossover.crossoverHeroes[*currentMap].clear();
for(CGHeroInstance * hero : heroes)
{
crossover.crossoverHeroes[*currentMap].push_back(crossoverSerialize(hero));
}
logGlobal->info("Scenario %d of campaign %s (%s) has been completed", static_cast<int>(*currentMap), getFilename(), getName());
mapsConquered.push_back(*currentMap);
auto reservedHeroes = getReservedHeroes();
for (auto * hero : heroes)
{
HeroTypeID heroType(hero->subID);
JsonNode node = CampaignState::crossoverSerialize(hero);
if (reservedHeroes.count(heroType))
{
logGlobal->info("Hero crossover: %d (%s) exported to global pool", hero->subID, hero->getNameTranslated());
globalHeroPool[heroType] = node;
}
else
{
logGlobal->info("Hero crossover: %d (%s) exported to scenario pool", hero->subID, hero->getNameTranslated());
scenarioHeroPool[*currentMap].push_back(node);
}
}
}
std::optional<CampaignBonus> CampaignState::getBonus(CampaignScenarioID which) const
{
auto bonuses = scenario(which).travelOptions.bonusesToChoose;
assert(chosenCampaignBonuses.count(*currentMap) || bonuses.size() == 0);
assert(chosenCampaignBonuses.count(*currentMap) || bonuses.empty());
if(bonuses.empty())
return std::optional<CampaignBonus>();
@ -316,12 +346,14 @@ JsonNode CampaignState::crossoverSerialize(CGHeroInstance * hero)
return node;
}
CGHeroInstance * CampaignState::crossoverDeserialize(const JsonNode & node)
CGHeroInstance * CampaignState::crossoverDeserialize(const JsonNode & node, CMap * map)
{
JsonDeserializer handler(nullptr, const_cast<JsonNode&>(node));
auto * hero = new CGHeroInstance();
hero->ID = Obj::HERO;
hero->serializeJsonOptions(handler);
if (map)
hero->serializeJsonArtifacts(handler, "artifacts", map);
return hero;
}

View File

@ -199,21 +199,6 @@ struct DLL_LINKAGE CampaignScenario
}
};
struct DLL_LINKAGE CampaignHeroes
{
using ScenarioHeroesList = std::vector<JsonNode>;
using CampaignHeroesList = std::map<CampaignScenarioID, ScenarioHeroesList>;
CampaignHeroesList crossoverHeroes; // contains all heroes with the same state when the campaign scenario was finished
CampaignHeroesList placedHeroes; // contains all placed crossover heroes defined by hero placeholders when the scenario was started
template <typename Handler> void serialize(Handler &h, const int formatVersion)
{
h & crossoverHeroes;
h & placedHeroes;
}
};
/// Class that represents loaded campaign information
class DLL_LINKAGE Campaign : public CampaignHeader
{
@ -238,6 +223,9 @@ public:
class DLL_LINKAGE CampaignState : public Campaign
{
friend class CampaignHandler;
using ScenarioPoolType = std::vector<JsonNode>;
using CampaignPoolType = std::map<CampaignScenarioID, ScenarioPoolType>;
using GlobalPoolType = std::map<HeroTypeID, JsonNode>;
/// List of all maps completed by player, in order of their completion
std::vector<CampaignScenarioID> mapsConquered;
@ -246,9 +234,15 @@ class DLL_LINKAGE CampaignState : public Campaign
std::map<CampaignScenarioID, ui8> chosenCampaignBonuses;
std::optional<CampaignScenarioID> currentMap;
CampaignHeroes crossover;
/// Heroes from specific scenario, ordered by descending strength
CampaignPoolType scenarioHeroPool;
/// Pool of heroes currently reserved for usage in campaign
GlobalPoolType globalHeroPool;
public:
CampaignState() = default;
/// Returns last completed scenario, if any
std::optional<CampaignScenarioID> lastScenario() const;
@ -276,24 +270,29 @@ public:
void setCurrentMap(CampaignScenarioID which);
void setCurrentMapBonus(ui8 which);
void setCurrentMapAsConquered(const std::vector<CGHeroInstance*> & heroes);
void setCurrentMapAsConquered(std::vector<CGHeroInstance*> heroes);
/// Returns list of heroes that must be reserved for campaign and can only be used for hero placeholders
std::set<HeroTypeID> getReservedHeroes() const;
/// Returns strongest hero from specified scenario, or null if none found
const CGHeroInstance * strongestHero(CampaignScenarioID scenarioId, const PlayerColor & owner) const;
/// returns a list of crossover heroes which started the scenario, but didn't complete it
std::vector<CGHeroInstance *> getLostCrossoverHeroes(CampaignScenarioID scenarioId) const;
/// Returns heroes that can be instantiated as hero placeholders by power
const std::vector<JsonNode> & getHeroesByPower(CampaignScenarioID scenarioId) const;
std::vector<JsonNode> getCrossoverHeroes(CampaignScenarioID scenarioId) const;
/// Returns hero for instantiation as placeholder by type
/// May return empty JsonNode if such hero was not found
const JsonNode & getHeroByType(HeroTypeID heroID) const;
static JsonNode crossoverSerialize(CGHeroInstance * hero);
static CGHeroInstance * crossoverDeserialize(const JsonNode & node);
CampaignState() = default;
static CGHeroInstance * crossoverDeserialize(const JsonNode & node, CMap * map);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<Campaign&>(*this);
h & crossover;
h & scenarioHeroPool;
h & globalHeroPool;
h & mapPieces;
h & mapsConquered;
h & currentMap;

View File

@ -29,18 +29,6 @@
VCMI_LIB_NAMESPACE_BEGIN
void CrossoverHeroesList::addHeroToBothLists(CGHeroInstance * hero)
{
heroesFromPreviousScenario.push_back(hero);
heroesFromAnyPreviousScenarios.push_back(hero);
}
void CrossoverHeroesList::removeHeroFromBothLists(CGHeroInstance * hero)
{
heroesFromPreviousScenario -= hero;
heroesFromAnyPreviousScenarios -= hero;
}
CampaignHeroReplacement::CampaignHeroReplacement(CGHeroInstance * hero, const ObjectInstanceID & heroPlaceholderId):
hero(hero),
heroPlaceholderId(heroPlaceholderId)
@ -58,77 +46,17 @@ std::optional<CampaignBonus> CGameStateCampaign::currentBonus() const
{
auto campaignState = gameState->scenarioOps->campState;
return campaignState->getBonus(*campaignState->currentScenario());
}
CrossoverHeroesList CGameStateCampaign::getCrossoverHeroesFromPreviousScenarios() const
std::optional<CampaignScenarioID> CGameStateCampaign::getHeroesSourceScenario() const
{
CrossoverHeroesList crossoverHeroes;
auto campaignState = gameState->scenarioOps->campState;
auto bonus = currentBonus();
if(bonus && bonus->type == CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO)
{
auto scenarioID = static_cast<CampaignScenarioID>(bonus->info2);
return static_cast<CampaignScenarioID>(bonus->info2);;
std::vector<CGHeroInstance *> heroes;
for(auto & node : campaignState->getCrossoverHeroes(scenarioID))
{
auto * h = CampaignState::crossoverDeserialize(node);
heroes.push_back(h);
}
crossoverHeroes.heroesFromAnyPreviousScenarios = heroes;
crossoverHeroes.heroesFromPreviousScenario = heroes;
return crossoverHeroes;
}
if(!campaignState->lastScenario())
return crossoverHeroes;
for(auto mapNr : campaignState->conqueredScenarios())
{
// create a list of deleted heroes
auto lostCrossoverHeroes = campaignState->getLostCrossoverHeroes(mapNr);
// remove heroes which didn't reached the end of the scenario, but were available at the start
for(auto * hero : lostCrossoverHeroes)
{
// auto hero = CCampaignState::crossoverDeserialize(node);
vstd::erase_if(crossoverHeroes.heroesFromAnyPreviousScenarios, [hero](CGHeroInstance * h)
{
return hero->subID == h->subID;
});
}
// now add heroes which completed the scenario
for(auto node : campaignState->getCrossoverHeroes(mapNr))
{
auto * hero = CampaignState::crossoverDeserialize(node);
// add new heroes and replace old heroes with newer ones
auto it = range::find_if(crossoverHeroes.heroesFromAnyPreviousScenarios, [hero](CGHeroInstance * h)
{
return hero->subID == h->subID;
});
if(it != crossoverHeroes.heroesFromAnyPreviousScenarios.end())
{
// replace old hero with newer one
crossoverHeroes.heroesFromAnyPreviousScenarios[it - crossoverHeroes.heroesFromAnyPreviousScenarios.begin()] = hero;
}
else
{
// add new hero
crossoverHeroes.heroesFromAnyPreviousScenarios.push_back(hero);
}
if(mapNr == campaignState->lastScenario())
{
crossoverHeroes.heroesFromPreviousScenario.push_back(hero);
}
}
}
return crossoverHeroes;
return campaignState->lastScenario();
}
void CGameStateCampaign::trimCrossoverHeroesParameters(std::vector<CampaignHeroReplacement> & campaignHeroReplacements, const CampaignTravel & travelOptions)
@ -194,7 +122,8 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(std::vector<CampaignHeroR
for (size_t i = 0; i < totalArts; i++ )
{
auto artifactPosition = ArtifactPosition((si32)i);
if(artifactPosition == ArtifactPosition::SPELLBOOK) continue; // do not handle spellbook this way
if(artifactPosition == ArtifactPosition::SPELLBOOK)
continue; // do not handle spellbook this way
const ArtSlotInfo *info = hero->getSlot(artifactPosition);
if(!info)
@ -242,23 +171,6 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(std::vector<CampaignHeroR
void CGameStateCampaign::placeCampaignHeroes()
{
// WARNING: CURRENT CODE IS LIKELY INCORRECT AND LEADS TO MULTIPLE ISSUES WITH HERO TRANSFER
// Approximate behavior according to testing H3 game logic.
// 1) definitions:
// - 'reserved heroes' are heroes that have fixed placeholder in unfinished maps. See CMapHeader::reservedCampaignHeroes
// - 'campaign pool' are serialized heroes and is unique to a campaign
// - 'scenario pool' are serialized heroes and is unique to a scenario
//
// 2) scenario end logic:
// - at end of scenario, all 'reserved heroes' of a player go to 'campaign pool'
// - at end of scenario, rest of player's heroes go to 'scenario pool'
//
// 3) scenario start logic
// - at scenario start, all heroes that are placed on map but already exist in 'campaign pool' and are still 'reserved heroes' are replaced with other, randomly selected heroes (and probably marked as unavailable in map)
// - at scenario start, all fixed placeholders are replaced with heroes from 'campaign pool', if exist
// - at scenario start, all power placeholders owned by player are replaced by heroes from 'scenario pool' of last complete map, if exist
// - exception: if starting bonus is 'select player' then power placeholders are taken from 'scenario pool' of linked map
// place bonus hero
auto campaignState = gameState->scenarioOps->campState;
auto campaignBonus = campaignState->getBonus(*campaignState->currentScenario());
@ -280,67 +192,66 @@ void CGameStateCampaign::placeCampaignHeroes()
}
}
// replace heroes placeholders
auto crossoverHeroes = getCrossoverHeroesFromPreviousScenarios();
logGlobal->debug("\tGenerate list of hero placeholders");
auto campaignHeroReplacements = generateCampaignHeroesToReplace();
if(!crossoverHeroes.heroesFromAnyPreviousScenarios.empty())
logGlobal->debug("\tPrepare crossover heroes");
trimCrossoverHeroesParameters(campaignHeroReplacements, 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
// with the same hero type id
std::vector<CGHeroInstance *> removedHeroes;
std::set<HeroTypeID> heroesToRemove = campaignState->getReservedHeroes();
for(auto & campaignHeroReplacement : campaignHeroReplacements)
heroesToRemove.insert(HeroTypeID(campaignHeroReplacement.hero->subID));
for(auto & heroID : heroesToRemove)
{
logGlobal->debug("\tGenerate list of hero placeholders");
auto campaignHeroReplacements = generateCampaignHeroesToReplace(crossoverHeroes);
logGlobal->debug("\tPrepare crossover heroes");
trimCrossoverHeroesParameters(campaignHeroReplacements, 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
// with the same hero type id
std::vector<CGHeroInstance *> removedHeroes;
for(auto & campaignHeroReplacement : campaignHeroReplacements)
auto * hero = gameState->getUsedHero(heroID);
if(hero)
{
auto * hero = gameState->getUsedHero(HeroTypeID(campaignHeroReplacement.hero->subID));
if(hero)
{
removedHeroes.push_back(hero);
gameState->map->heroesOnMap -= hero;
gameState->map->objects[hero->id.getNum()] = nullptr;
gameState->map->removeBlockVisTiles(hero, true);
}
removedHeroes.push_back(hero);
gameState->map->heroesOnMap -= hero;
gameState->map->objects[hero->id.getNum()] = nullptr;
gameState->map->removeBlockVisTiles(hero, true);
}
}
logGlobal->debug("\tReplace placeholders with heroes");
replaceHeroesPlaceholders(campaignHeroReplacements);
logGlobal->debug("\tReplace placeholders with heroes");
replaceHeroesPlaceholders(campaignHeroReplacements);
// now add removed heroes again with unused type ID
for(auto * hero : removedHeroes)
// now add removed heroes again with unused type ID
for(auto * hero : removedHeroes)
{
si32 heroTypeId = 0;
if(hero->ID == Obj::HERO)
{
si32 heroTypeId = 0;
if(hero->ID == Obj::HERO)
heroTypeId = gameState->pickUnusedHeroTypeRandomly(hero->tempOwner);
}
else if(hero->ID == Obj::PRISON)
{
auto unusedHeroTypeIds = gameState->getUnusedAllowedHeroes();
if(!unusedHeroTypeIds.empty())
{
heroTypeId = gameState->pickUnusedHeroTypeRandomly(hero->tempOwner);
}
else if(hero->ID == Obj::PRISON)
{
auto unusedHeroTypeIds = gameState->getUnusedAllowedHeroes();
if(!unusedHeroTypeIds.empty())
{
heroTypeId = (*RandomGeneratorUtil::nextItem(unusedHeroTypeIds, gameState->getRandomGenerator())).getNum();
}
else
{
logGlobal->error("No free hero type ID found to replace prison.");
assert(0);
}
heroTypeId = (*RandomGeneratorUtil::nextItem(unusedHeroTypeIds, gameState->getRandomGenerator())).getNum();
}
else
{
assert(0); // should not happen
logGlobal->error("No free hero type ID found to replace prison.");
assert(0);
}
hero->subID = heroTypeId;
hero->portrait = hero->subID;
gameState->map->getEditManager()->insertObject(hero);
}
else
{
assert(0); // should not happen
}
hero->subID = heroTypeId;
hero->portrait = hero->subID;
gameState->map->getEditManager()->insertObject(hero);
}
}
@ -424,23 +335,7 @@ void CGameStateCampaign::replaceHeroesPlaceholders(const std::vector<CampaignHer
heroToPlace->tempOwner = heroPlaceholder->tempOwner;
heroToPlace->pos = heroPlaceholder->pos;
heroToPlace->type = VLC->heroh->objects[heroToPlace->subID];
heroToPlace->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO,
heroToPlace->type->heroClass->getIndex())->getTemplates().front();
for(auto &&i : heroToPlace->stacks)
i.second->type = VLC->creh->objects[i.second->getCreatureID()];
auto fixArtifact = [&](CArtifactInstance * art)
{
art->artType = VLC->arth->objects[art->artType->getId()];
gameState->map->artInstances.emplace_back(art);
art->id = ArtifactInstanceID((si32)gameState->map->artInstances.size() - 1);
};
for(auto &&i : heroToPlace->artifactsWorn)
fixArtifact(i.second.artifact);
for(auto &&i : heroToPlace->artifactsInBackpack)
fixArtifact(i.artifact);
heroToPlace->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, heroToPlace->type->heroClass->getIndex())->getTemplates().front();
gameState->map->removeBlockVisTiles(heroPlaceholder, true);
gameState->map->objects[heroPlaceholder->id.getNum()] = nullptr;
@ -455,67 +350,78 @@ void CGameStateCampaign::replaceHeroesPlaceholders(const std::vector<CampaignHer
}
}
std::vector<CampaignHeroReplacement> CGameStateCampaign::generateCampaignHeroesToReplace(CrossoverHeroesList & crossoverHeroes)
std::vector<CampaignHeroReplacement> CGameStateCampaign::generateCampaignHeroesToReplace()
{
auto campaignState = gameState->scenarioOps->campState;
std::vector<CampaignHeroReplacement> campaignHeroReplacements;
std::vector<CGHeroPlaceholder *> placeholdersByPower;
std::vector<CGHeroPlaceholder *> placeholdersByType;
// find all placeholders on map
for(auto obj : gameState->map->objects)
{
if(!obj)
continue;
if (obj->ID != Obj::HERO_PLACEHOLDER)
continue;
auto * heroPlaceholder = dynamic_cast<CGHeroPlaceholder *>(obj.get());
// only 1 field must be set
assert(heroPlaceholder->powerRank != heroPlaceholder->heroType);
if(heroPlaceholder->powerRank)
placeholdersByPower.push_back(heroPlaceholder);
if(heroPlaceholder->heroType)
placeholdersByType.push_back(heroPlaceholder);
}
//selecting heroes by type
for(auto obj : gameState->map->objects)
for (auto const * placeholder : placeholdersByType)
{
if(obj && obj->ID == Obj::HERO_PLACEHOLDER)
auto const & node = campaignState->getHeroByType(*placeholder->heroType);
if (node.isNull())
{
auto * heroPlaceholder = dynamic_cast<CGHeroPlaceholder *>(obj.get());
if(heroPlaceholder->subID != 0xFF) //select by type
{
auto it = range::find_if(crossoverHeroes.heroesFromAnyPreviousScenarios, [heroPlaceholder](CGHeroInstance * hero)
{
return hero->subID == heroPlaceholder->subID;
});
if(it != crossoverHeroes.heroesFromAnyPreviousScenarios.end())
{
auto * hero = *it;
crossoverHeroes.removeHeroFromBothLists(hero);
campaignHeroReplacements.emplace_back(CMemorySerializer::deepCopy(*hero).release(), heroPlaceholder->id);
}
}
logGlobal->info("Hero crossover: Unable to replace placeholder for %d (%s)!", placeholder->heroType->getNum(), VLC->heroTypes()->getById(*placeholder->heroType)->getNameTranslated());
continue;
}
CGHeroInstance * hero = CampaignState::crossoverDeserialize(node, gameState->map);
logGlobal->info("Hero crossover: Loading placeholder for %d (%s)", hero->subID, hero->getNameTranslated());
campaignHeroReplacements.emplace_back(hero, placeholder->id);
}
//selecting heroes by power
range::sort(crossoverHeroes.heroesFromPreviousScenario, [](const CGHeroInstance * a, const CGHeroInstance * b)
{
return a->getHeroStrength() > b->getHeroStrength();
}); //sort, descending strength
auto lastScenario = getHeroesSourceScenario();
// sort hero placeholders descending power
std::vector<CGHeroPlaceholder *> heroPlaceholders;
for(auto obj : gameState->map->objects)
if (!placeholdersByPower.empty() && lastScenario)
{
if(obj && obj->ID == Obj::HERO_PLACEHOLDER)
// sort hero placeholders descending power
boost::range::sort(placeholdersByPower, [](const CGHeroPlaceholder * a, const CGHeroPlaceholder * b)
{
auto * heroPlaceholder = dynamic_cast<CGHeroPlaceholder *>(obj.get());
if(heroPlaceholder->subID == 0xFF) //select by power
{
heroPlaceholders.push_back(heroPlaceholder);
}
return *a->powerRank > *b->powerRank;
});
auto const & nodeList = campaignState->getHeroesByPower(lastScenario.value());
auto nodeListIter = nodeList.begin();
for (auto const * placeholder : placeholdersByPower)
{
if (nodeListIter == nodeList.end())
break;
CGHeroInstance * hero = CampaignState::crossoverDeserialize(*nodeListIter, gameState->map);
nodeListIter++;
logGlobal->info("Hero crossover: Loading placeholder as %d (%s)", hero->subID, hero->getNameTranslated());
campaignHeroReplacements.emplace_back(hero, placeholder->id);
}
}
range::sort(heroPlaceholders, [](const CGHeroPlaceholder * a, const CGHeroPlaceholder * b)
{
return a->power > b->power;
});
for(int i = 0; i < heroPlaceholders.size(); ++i)
{
auto * heroPlaceholder = heroPlaceholders[i];
if(crossoverHeroes.heroesFromPreviousScenario.size() > i)
{
auto * hero = crossoverHeroes.heroesFromPreviousScenario[i];
campaignHeroReplacements.emplace_back(CMemorySerializer::deepCopy(*hero).release(), heroPlaceholder->id);
}
}
return campaignHeroReplacements;
}

View File

@ -10,6 +10,7 @@
#pragma once
#include "../GameConstants.h"
#include "../campaign/CampaignConstants.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -26,22 +27,15 @@ struct CampaignHeroReplacement
ObjectInstanceID heroPlaceholderId;
};
struct CrossoverHeroesList
{
std::vector<CGHeroInstance *> heroesFromPreviousScenario;
std::vector<CGHeroInstance *> heroesFromAnyPreviousScenarios;
void addHeroToBothLists(CGHeroInstance * hero);
void removeHeroFromBothLists(CGHeroInstance * hero);
};
class CGameStateCampaign
{
CGameState * gameState;
CrossoverHeroesList getCrossoverHeroesFromPreviousScenarios() const;
/// 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(CrossoverHeroesList & crossoverHeroes);
std::vector<CampaignHeroReplacement> generateCampaignHeroesToReplace();
std::optional<CampaignBonus> currentBonus() const;

View File

@ -28,13 +28,17 @@ enum class EHeroGender : uint8_t;
class CGHeroPlaceholder : public CGObjectInstance
{
public:
//subID stores id of hero type. If it's 0xff then following field is used
ui8 power;
/// if this is placeholder by power, then power rank of desired hero
std::optional<ui8> powerRank;
/// if this is placeholder by type, then hero type of desired hero
std::optional<HeroTypeID> heroType;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CGObjectInstance&>(*this);
h & power;
h & powerRank;
h & heroType;
}
};

View File

@ -1289,16 +1289,15 @@ CGObjectInstance * CMapLoaderH3M::readHeroPlaceholder(const int3 & mapPosition)
setOwnerAndValidate(mapPosition, object, reader->readPlayer());
HeroTypeID htid = reader->readHero(); //hero type id
object->subID = htid.getNum();
if(htid.getNum() == -1)
{
object->power = reader->readUInt8();
object->powerRank = reader->readUInt8();
logGlobal->debug("Map '%s': Hero placeholder: by power at %s, owned by %s", mapName, mapPosition.toString(), object->getOwner().getStr());
}
else
{
object->power = 0;
object->heroType = htid;
logGlobal->debug("Map '%s': Hero placeholder: %s at %s, owned by %s", mapName, VLC->heroh->getById(htid)->getJsonKey(), mapPosition.toString(), object->getOwner().getStr());
}

View File

@ -408,51 +408,51 @@ void CVCMIServer::threadHandleClient(std::shared_ptr<CConnection> c)
setThreadName("CVCMIServer::handleConnection");
c->enterLobbyConnectionMode();
#ifndef _MSC_VER
try
{
#endif
//#ifndef _MSC_VER
// try
// {
//#endif
while(c->connected)
{
CPack * pack;
try
{
//try
//{
pack = c->retrievePack();
}
catch(boost::system::system_error & e)
{
logNetwork->error("Network error receiving a pack. Connection %s dies. What happened: %s", c->toString(), e.what());
hangingConnections.insert(c);
connections.erase(c);
if(connections.empty() || hostClient == c)
state = EServerState::SHUTDOWN;
if(gh && state == EServerState::GAMEPLAY)
{
gh->handleClientDisconnection(c);
}
break;
}
//}
//catch(boost::system::system_error & e)
//{
// logNetwork->error("Network error receiving a pack. Connection %s dies. What happened: %s", c->toString(), e.what());
// hangingConnections.insert(c);
// connections.erase(c);
// if(connections.empty() || hostClient == c)
// state = EServerState::SHUTDOWN;
//
// if(gh && state == EServerState::GAMEPLAY)
// {
// gh->handleClientDisconnection(c);
// }
// break;
//}
CVCMIServerPackVisitor visitor(*this, this->gh);
pack->visit(visitor);
}
#ifndef _MSC_VER
}
catch(const std::exception & e)
{
(void)e;
boost::unique_lock<boost::recursive_mutex> queueLock(mx);
logNetwork->error("%s dies... \nWhat happened: %s", c->toString(), e.what());
}
catch(...)
{
state = EServerState::SHUTDOWN;
handleException();
throw;
}
#endif
//#ifndef _MSC_VER
// }
// catch(const std::exception & e)
// {
// (void)e;
// boost::unique_lock<boost::recursive_mutex> queueLock(mx);
// logNetwork->error("%s dies... \nWhat happened: %s", c->toString(), e.what());
// }
// catch(...)
// {
// state = EServerState::SHUTDOWN;
// handleException();
// throw;
// }
//#endif
boost::unique_lock<boost::recursive_mutex> queueLock(mx);
// if(state != ENDING_AND_STARTING_GAME)