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:
parent
e2bd98e21e
commit
48ac84110b
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user