From 19ace6a849366498e8651892ea9c7506272a4acb Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 11 Jul 2023 15:16:02 +0300 Subject: [PATCH 1/8] Moved hero pool logic to the separate files --- CCallback.cpp | 15 +-- cmake_modules/VCMI_lib.cmake | 2 + lib/CGameInfoCallback.cpp | 17 +-- lib/CGameInfoCallback.h | 2 - lib/CPlayerState.cpp | 1 - lib/CPlayerState.h | 2 - lib/GameConstants.h | 5 +- lib/IGameCallback.cpp | 1 + lib/NetPackVisitor.h | 4 +- lib/NetPacks.h | 19 +-- lib/NetPacksLib.cpp | 41 ++---- lib/StartInfo.h | 6 +- lib/gameState/CGameState.cpp | 101 +-------------- lib/gameState/CGameState.h | 23 +--- lib/gameState/TavernHeroesPool.cpp | 176 +++++++++++++++++++++++++ lib/gameState/TavernHeroesPool.h | 75 +++++++++++ lib/mapping/CMap.h | 4 +- lib/registerTypes/RegisterTypes.h | 2 +- lib/registerTypes/TypesLobbyPacks.cpp | 1 + server/CGameHandler.cpp | 179 ++------------------------ server/CGameHandler.h | 7 +- server/CMakeLists.txt | 2 + server/CVCMIServer.cpp | 4 +- server/HeroPoolProcessor.cpp | 162 +++++++++++++++++++++++ server/HeroPoolProcessor.h | 45 +++++++ server/NetPacksServer.cpp | 4 +- 26 files changed, 535 insertions(+), 365 deletions(-) create mode 100644 lib/gameState/TavernHeroesPool.cpp create mode 100644 lib/gameState/TavernHeroesPool.h create mode 100644 server/HeroPoolProcessor.cpp create mode 100644 server/HeroPoolProcessor.h diff --git a/CCallback.cpp b/CCallback.cpp index 6dd134f07..0266f6b31 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -270,17 +270,10 @@ void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroIn { assert(townOrTavern); assert(hero); - ui8 i=0; - for(; iplayers[*player].availableHeroes.size(); i++) - { - if(gs->players[*player].availableHeroes[i] == hero) - { - HireHero pack(i, townOrTavern->id); - pack.player = *player; - sendRequest(&pack); - return; - } - } + + HireHero pack(HeroTypeID(hero->subID), townOrTavern->id); + pack.player = *player; + sendRequest(&pack); } void CCallback::save( const std::string &fname ) diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index f325bfa4f..3ddf40f68 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -68,6 +68,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/gameState/CGameState.cpp ${MAIN_LIB_DIR}/gameState/CGameStateCampaign.cpp ${MAIN_LIB_DIR}/gameState/InfoAboutArmy.cpp + ${MAIN_LIB_DIR}/gameState/TavernHeroesPool.cpp ${MAIN_LIB_DIR}/logging/CBasicLogConfigurator.cpp ${MAIN_LIB_DIR}/logging/CLogger.cpp @@ -394,6 +395,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/gameState/EVictoryLossCheckResult.h ${MAIN_LIB_DIR}/gameState/InfoAboutArmy.h ${MAIN_LIB_DIR}/gameState/SThievesGuildInfo.h + ${MAIN_LIB_DIR}/gameState/TavernHeroesPool.h ${MAIN_LIB_DIR}/gameState/QuestInfo.h ${MAIN_LIB_DIR}/logging/CBasicLogConfigurator.h diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 97be80869..940c33489 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -13,6 +13,7 @@ #include "gameState/CGameState.h" #include "gameState/InfoAboutArmy.h" #include "gameState/SThievesGuildInfo.h" +#include "gameState/TavernHeroesPool.h" #include "CGeneralTextHandler.h" #include "StartInfo.h" // for StartInfo #include "battle/BattleInfo.h" // for BattleInfo @@ -99,13 +100,6 @@ const PlayerState * CGameInfoCallback::getPlayerState(PlayerColor color, bool ve } } -const CTown * CGameInfoCallback::getNativeTown(PlayerColor color) const -{ - const PlayerSettings *ps = getPlayerSettings(color); - ERROR_RET_VAL_IF(!ps, "There is no such player!", nullptr); - return (*VLC->townh)[ps->castle]->town; -} - const CGObjectInstance * CGameInfoCallback::getObjByQuestIdentifier(int identifier) const { if(gs->map->questIdentifierToId.empty()) @@ -486,13 +480,10 @@ std::vector CGameInfoCallback::getAvailableHeroes(const //ERROR_RET_VAL_IF(!isOwnedOrVisited(townOrTavern), "Town or tavern must be owned or visited!", ret); //TODO: town needs to be owned, advmap tavern needs to be visited; to be reimplemented when visit tracking is done const CGTownInstance * town = getTown(townOrTavern->id); + if(townOrTavern->ID == Obj::TAVERN || (town && town->hasBuilt(BuildingID::TAVERN))) - { - range::copy(gs->players[*player].availableHeroes, std::back_inserter(ret)); - vstd::erase_if(ret, [](const CGHeroInstance * h) { - return h == nullptr; - }); - } + return gs->hpool->getHeroesFor(*player); + return ret; } diff --git a/lib/CGameInfoCallback.h b/lib/CGameInfoCallback.h index 6e57fa34a..8bca8a99d 100644 --- a/lib/CGameInfoCallback.h +++ b/lib/CGameInfoCallback.h @@ -108,7 +108,6 @@ public: // std::string getTavernRumor(const CGObjectInstance * townOrTavern) const; // EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID);//// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements // virtual bool getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject = nullptr) const; -// const CTown *getNativeTown(PlayerColor color) const; //from gs // const TeamState *getTeam(TeamID teamID) const; @@ -206,7 +205,6 @@ public: virtual std::string getTavernRumor(const CGObjectInstance * townOrTavern) const; virtual EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID);//// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements virtual bool getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject = nullptr) const; - virtual const CTown *getNativeTown(PlayerColor color) const; //from gs virtual const TeamState *getTeam(TeamID teamID) const; diff --git a/lib/CPlayerState.cpp b/lib/CPlayerState.cpp index 4abfe8f3d..41d2676d9 100644 --- a/lib/CPlayerState.cpp +++ b/lib/CPlayerState.cpp @@ -35,7 +35,6 @@ PlayerState::PlayerState(PlayerState && other) noexcept: std::swap(visitedObjects, other.visitedObjects); std::swap(heroes, other.heroes); std::swap(towns, other.towns); - std::swap(availableHeroes, other.availableHeroes); std::swap(dwellings, other.dwellings); std::swap(quests, other.quests); } diff --git a/lib/CPlayerState.h b/lib/CPlayerState.h index 26f7075b5..cfb99617c 100644 --- a/lib/CPlayerState.h +++ b/lib/CPlayerState.h @@ -33,7 +33,6 @@ public: std::set visitedObjects; // as a std::set, since most accesses here will be from visited status checks std::vector > heroes; std::vector > towns; - std::vector > availableHeroes; //heroes available in taverns std::vector > dwellings; //used for town growth std::vector quests; //store info about all received quests @@ -74,7 +73,6 @@ public: h & status; h & heroes; h & towns; - h & availableHeroes; h & dwellings; h & quests; h & visitedObjects; diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 4a58b77a5..bb0acfd91 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -357,9 +357,12 @@ class PlayerColor : public BaseForID enum EPlayerColor { - PLAYER_LIMIT_I = 8 + PLAYER_LIMIT_I = 8, + ALL_PLAYERS_MASK = 0xff }; + using Mask = uint8_t; + DLL_LINKAGE static const PlayerColor SPECTATOR; //252 DLL_LINKAGE static const PlayerColor CANNOT_DETERMINE; //253 DLL_LINKAGE static const PlayerColor UNFLAGGABLE; //254 - neutral objects (pandora, banks) diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index a898f5367..3e0a9c543 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -36,6 +36,7 @@ #include "StartInfo.h" #include "gameState/CGameState.h" #include "gameState/CGameStateCampaign.h" +#include "gameState/TavernHeroesPool.h" #include "mapping/CMap.h" #include "CPlayerState.h" #include "GameSettings.h" diff --git a/lib/NetPackVisitor.h b/lib/NetPackVisitor.h index 226b4a18a..f312339d2 100644 --- a/lib/NetPackVisitor.h +++ b/lib/NetPackVisitor.h @@ -36,7 +36,7 @@ public: virtual void visitSetMana(SetMana & pack) {} virtual void visitSetMovePoints(SetMovePoints & pack) {} virtual void visitFoWChange(FoWChange & pack) {} - virtual void visitSetAvailableHeroes(SetAvailableHeroes & pack) {} + virtual void visitSetAvailableHeroes(SetAvailableHero & pack) {} virtual void visitGiveBonus(GiveBonus & pack) {} virtual void visitChangeObjPos(ChangeObjPos & pack) {} virtual void visitPlayerEndsGame(PlayerEndsGame & pack) {} @@ -162,4 +162,4 @@ public: virtual void visitLobbyShowMessage(LobbyShowMessage & pack) {} }; -VCMI_LIB_NAMESPACE_END \ No newline at end of file +VCMI_LIB_NAMESPACE_END diff --git a/lib/NetPacks.h b/lib/NetPacks.h index e3a6f25c5..2071079c7 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -330,23 +330,24 @@ struct DLL_LINKAGE FoWChange : public CPackForClient } }; -struct DLL_LINKAGE SetAvailableHeroes : public CPackForClient +struct DLL_LINKAGE SetAvailableHero : public CPackForClient { - SetAvailableHeroes() + SetAvailableHero() { - for(auto & i : army) - i.clear(); + army.clear(); } void applyGs(CGameState * gs); + uint8_t slotID; PlayerColor player; - si32 hid[GameConstants::AVAILABLE_HEROES_PER_PLAYER]; //-1 if no hero - CSimpleArmy army[GameConstants::AVAILABLE_HEROES_PER_PLAYER]; + HeroTypeID hid; //-1 if no hero + CSimpleArmy army; virtual void visitTyped(ICPackVisitor & visitor) override; template void serialize(Handler & h, const int version) { + h & slotID; h & player; h & hid; h & army; @@ -692,7 +693,7 @@ struct DLL_LINKAGE HeroRecruited : public CPackForClient { void applyGs(CGameState * gs) const; - si32 hid = -1; //subID of hero + HeroTypeID hid; //subID of hero ObjectInstanceID tid; ObjectInstanceID boatId; int3 tile; @@ -2437,12 +2438,12 @@ struct DLL_LINKAGE SetFormation : public CPackForServer struct DLL_LINKAGE HireHero : public CPackForServer { HireHero() = default; - HireHero(si32 HID, const ObjectInstanceID & TID) + HireHero(HeroTypeID HID, const ObjectInstanceID & TID) : hid(HID) , tid(TID) { } - si32 hid = 0; //available hero serial + HeroTypeID hid; //available hero serial ObjectInstanceID tid; //town (tavern) id PlayerColor player; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index b8368d741..e2eadfd64 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -20,6 +20,7 @@ #include "spells/CSpellHandler.h" #include "CCreatureHandler.h" #include "gameState/CGameState.h" +#include "gameState/TavernHeroesPool.h" #include "CStack.h" #include "battle/BattleInfo.h" #include "CTownHandler.h" @@ -151,7 +152,7 @@ void FoWChange::visitTyped(ICPackVisitor & visitor) visitor.visitFoWChange(*this); } -void SetAvailableHeroes::visitTyped(ICPackVisitor & visitor) +void SetAvailableHero::visitTyped(ICPackVisitor & visitor) { visitor.visitSetAvailableHeroes(*this); } @@ -939,18 +940,9 @@ void FoWChange::applyGs(CGameState *gs) } } -void SetAvailableHeroes::applyGs(CGameState *gs) +void SetAvailableHero::applyGs(CGameState *gs) { - PlayerState *p = gs->getPlayerState(player); - p->availableHeroes.clear(); - - for (int i = 0; i < GameConstants::AVAILABLE_HEROES_PER_PLAYER; i++) - { - CGHeroInstance *h = (hid[i]>=0 ? gs->hpool.heroesPool[hid[i]].get() : nullptr); - if(h && army[i]) - h->setToArmy(army[i]); - p->availableHeroes.emplace_back(h); - } + gs->hpool->setHeroForPlayer(player, TavernHeroSlot(slotID), hid, army); } void GiveBonus::applyGs(CGameState *gs) @@ -1150,11 +1142,8 @@ void RemoveObject::applyGs(CGameState *gs) beatenHero->inTownGarrison = false; } //return hero to the pool, so he may reappear in tavern - gs->hpool.heroesPool[beatenHero->subID] = beatenHero; - - if(!vstd::contains(gs->hpool.pavailable, beatenHero->subID)) - gs->hpool.pavailable[beatenHero->subID] = 0xff; + gs->hpool->addHeroToPool(beatenHero); gs->map->objects[id.getNum()] = nullptr; //If hero on Boat is removed, the Boat disappears @@ -1379,8 +1368,7 @@ void SetHeroesInTown::applyGs(CGameState * gs) const void HeroRecruited::applyGs(CGameState * gs) const { - assert(vstd::contains(gs->hpool.heroesPool, hid)); - CGHeroInstance *h = gs->hpool.heroesPool[hid]; + CGHeroInstance *h = gs->hpool->takeHero(hid); CGTownInstance *t = gs->getTown(tid); PlayerState *p = gs->getPlayerState(player); @@ -1411,7 +1399,6 @@ void HeroRecruited::applyGs(CGameState * gs) const } } - gs->hpool.heroesPool.erase(hid); if(h->id == ObjectInstanceID()) { h->id = ObjectInstanceID(static_cast(gs->map->objects.size())); @@ -2021,26 +2008,14 @@ void NewTurn::applyGs(CGameState *gs) { CGHeroInstance *hero = gs->getHero(h.id); if(!hero) - { - // retreated or surrendered hero who has not been reset yet - for(auto& hp : gs->hpool.heroesPool) - { - if(hp.second->id == h.id) - { - hero = hp.second; - break; - } - } - } - if(!hero) { logGlobal->error("Hero %d not found in NewTurn::applyGs", h.id.getNum()); continue; } - hero->setMovementPoints(h.move); - hero->mana = h.mana; } + gs->hpool->onNewDay(); + for(const auto & re : res) { assert(re.first < PlayerColor::PLAYER_LIMIT); diff --git a/lib/StartInfo.h b/lib/StartInfo.h index 495892d82..31b4dfe15 100644 --- a/lib/StartInfo.h +++ b/lib/StartInfo.h @@ -35,9 +35,9 @@ struct DLL_LINKAGE PlayerSettings }; Ebonus bonus; - si16 castle; - si32 hero, - heroPortrait; //-1 if default, else ID + FactionID castle; + HeroTypeID hero; + HeroTypeID heroPortrait; //-1 if default, else ID std::string heroName; PlayerColor color; //from 0 - diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 52c0ebc53..545dd06e3 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -12,6 +12,7 @@ #include "EVictoryLossCheckResult.h" #include "InfoAboutArmy.h" +#include "TavernHeroesPool.h" #include "CGameStateCampaign.h" #include "SThievesGuildInfo.h" @@ -102,81 +103,6 @@ static CGObjectInstance * createObject(const Obj & id, int subid, const int3 & p return nobj; } -CGHeroInstance * CGameState::HeroesPool::pickHeroFor(bool native, - const PlayerColor & player, - const CTown * town, - std::map> & available, - CRandomGenerator & rand, - const CHeroClass * bannedClass) const -{ - CGHeroInstance *ret = nullptr; - - if(player>=PlayerColor::PLAYER_LIMIT) - { - logGlobal->error("Cannot pick hero for faction %s. Wrong owner!", town->faction->getJsonKey()); - return nullptr; - } - - std::vector pool; - - if(native) - { - for(auto & elem : available) - { - if(pavailable.find(elem.first)->second & 1<type->heroClass->faction == town->faction->getIndex()) - { - pool.push_back(elem.second); //get all available heroes - } - } - if(pool.empty()) - { - logGlobal->error("Cannot pick native hero for %s. Picking any...", player.getStr()); - return pickHeroFor(false, player, town, available, rand); - } - else - { - ret = *RandomGeneratorUtil::nextItem(pool, rand); - } - } - else - { - int sum = 0; - int r; - - for(auto & elem : available) - { - if (pavailable.find(elem.first)->second & (1<type->heroClass != bannedClass) ) // and his class is not same as other hero - { - pool.push_back(elem.second); - sum += elem.second->type->heroClass->selectionProbability[town->faction->getId()]; //total weight - } - } - if(pool.empty() || sum == 0) - { - logGlobal->error("There are no heroes available for player %s!", player.getStr()); - return nullptr; - } - - r = rand.nextInt(sum - 1); - for (auto & elem : pool) - { - r -= elem->type->heroClass->selectionProbability[town->faction->getId()]; - if(r < 0) - { - ret = elem; - break; - } - } - if(!ret) - ret = pool.back(); - } - - available.erase(ret->subID); - return ret; -} - HeroTypeID CGameState::pickNextHeroType(const PlayerColor & owner) { const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner); @@ -459,6 +385,7 @@ int CGameState::getDate(Date::EDateType mode) const CGameState::CGameState() { gs = this; + hpool = std::make_unique(this); applier = std::make_shared>(); registerTypesClientPacks1(*applier); registerTypesClientPacks2(*applier); @@ -469,9 +396,6 @@ CGameState::~CGameState() { map.dellNull(); curB.dellNull(); - - for(auto ptr : hpool.heroesPool) // clean hero pool - ptr.second.dellNull(); } void CGameState::preInit(Services * services) @@ -951,8 +875,7 @@ void CGameState::initHeroes() if(!vstd::contains(heroesToCreate, HeroTypeID(ph->subID))) continue; ph->initHero(getRandomGenerator()); - hpool.heroesPool[ph->subID] = ph; - hpool.pavailable[ph->subID] = 0xff; + hpool->addHeroToPool(ph); heroesToCreate.erase(ph->type->getId()); map->allHeroes[ph->subID] = ph; @@ -965,14 +888,11 @@ void CGameState::initHeroes() int typeID = htype.getNum(); map->allHeroes[typeID] = vhi; - hpool.heroesPool[typeID] = vhi; - hpool.pavailable[typeID] = 0xff; + hpool->addHeroToPool(vhi); } for(auto & elem : map->disposedHeroes) - { - hpool.pavailable[elem.heroId] = elem.players; - } + hpool->setAvailability(elem.heroId, elem.players); if (campaign) campaign->initHeroes(); @@ -2067,17 +1987,6 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) #undef FILL_FIELD } -std::map > CGameState::unusedHeroesFromPool() -{ - std::map > pool = hpool.heroesPool; - for(const auto & player : players) - for(auto availableHero : player.second.availableHeroes) - if(availableHero) - pool.erase((*availableHero).subID); - - return pool; -} - void CGameState::buildBonusSystemTree() { buildGlobalTeamPlayerTree(); diff --git a/lib/gameState/CGameState.h b/lib/gameState/CGameState.h index 99548f5b7..e11bd8a4f 100644 --- a/lib/gameState/CGameState.h +++ b/lib/gameState/CGameState.h @@ -29,6 +29,7 @@ struct EventCondition; struct CampaignTravel; class CStackInstance; class CGameStateCampaign; +class TavernHeroesPool; struct SThievesGuildInfo; template class CApplier; @@ -78,25 +79,10 @@ DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EVictoryLossCheck class DLL_LINKAGE CGameState : public CNonConstInfoCallback { friend class CGameStateCampaign; + public: - struct DLL_LINKAGE HeroesPool - { - std::map > heroesPool; //[subID] - heroes available to buy; nullptr if not available - std::map pavailable; // [subid] -> which players can recruit hero (binary flags) - - CGHeroInstance * pickHeroFor(bool native, - const PlayerColor & player, - const CTown * town, - std::map> & available, - CRandomGenerator & rand, - const CHeroClass * bannedClass = nullptr) const; - - template void serialize(Handler &h, const int version) - { - h & heroesPool; - h & pavailable; - } - } hpool; //we have here all heroes available on this map that are not hired + //we have here all heroes available on this map that are not hired + std::unique_ptr hpool; CGameState(); virtual ~CGameState(); @@ -142,7 +128,6 @@ public: bool checkForStandardLoss(const PlayerColor & player) const; //checks if given player lost the game void obtainPlayersStats(SThievesGuildInfo & tgi, int level); //fills tgi with info about other players that is available at given level of thieves' guild - std::map > unusedHeroesFromPool(); //heroes pool without heroes that are available in taverns bool isVisible(int3 pos, const std::optional & player) const override; bool isVisible(const CGObjectInstance * obj, const std::optional & player) const override; diff --git a/lib/gameState/TavernHeroesPool.cpp b/lib/gameState/TavernHeroesPool.cpp new file mode 100644 index 000000000..0634bdde8 --- /dev/null +++ b/lib/gameState/TavernHeroesPool.cpp @@ -0,0 +1,176 @@ +/* + * TavernHeroesPool.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "TavernHeroesPool.h" + +#include "CGameState.h" +#include "CPlayerState.h" + +#include "../mapObjects/CGHeroInstance.h" +#include "../CHeroHandler.h" + +TavernHeroesPool::TavernHeroesPool() = default; + +TavernHeroesPool::TavernHeroesPool(CGameState * gameState) + : gameState(gameState) +{ +} + +TavernHeroesPool::~TavernHeroesPool() +{ + for(auto ptr : heroesPool) // clean hero pool + delete ptr.second; +} + +std::map TavernHeroesPool::unusedHeroesFromPool() +{ + std::map pool = heroesPool; + for(const auto & player : currentTavern) + for(auto availableHero : player.second) + if(availableHero.second) + pool.erase(HeroTypeID(availableHero.second->subID)); + + return pool; +} + +void TavernHeroesPool::setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, HeroTypeID hero, CSimpleArmy & army) +{ + currentTavern[player].erase(slot); + + if (hero == HeroTypeID::NONE) + return; + + CGHeroInstance * h = heroesPool[hero]; + + if (h && army) + h->setToArmy(army); + + currentTavern[player][slot] = h; +} + +bool TavernHeroesPool::isHeroAvailableFor(HeroTypeID hero, PlayerColor color) const +{ + if (pavailable.count(hero)) + return pavailable.at(hero) & (1 << color.getNum()); + + return true; +} + +CGHeroInstance * TavernHeroesPool::pickHeroFor(TavernHeroSlot slot, + const PlayerColor & player, + const FactionID & factionID, + CRandomGenerator & rand, + const CHeroClass * bannedClass) const +{ + if(player>=PlayerColor::PLAYER_LIMIT) + { + logGlobal->error("Cannot pick hero for player %d. Wrong owner!", player.getStr()); + return nullptr; + } + + if(slot == TavernHeroSlot::NATIVE) + { + std::vector pool; + + for(auto & elem : heroesPool) + { + //get all available heroes + bool heroAvailable = isHeroAvailableFor(elem.first, player); + bool heroClassNative = elem.second->type->heroClass->faction == factionID; + + if(heroAvailable && heroClassNative) + pool.push_back(elem.second); + } + + if(!pool.empty()) + return *RandomGeneratorUtil::nextItem(pool, rand); + + logGlobal->error("Cannot pick native hero for %s. Picking any...", player.getStr()); + } + + std::vector pool; + int totalWeight = 0; + + for(auto & elem : heroesPool) + { + bool heroAvailable = isHeroAvailableFor(elem.first, player); + bool heroClassBanned = bannedClass && elem.second->type->heroClass == bannedClass; + + if ( heroAvailable && !heroClassBanned) + { + pool.push_back(elem.second); + totalWeight += elem.second->type->heroClass->selectionProbability[factionID]; //total weight + } + } + if(pool.empty() || totalWeight == 0) + { + logGlobal->error("There are no heroes available for player %s!", player.getStr()); + return nullptr; + } + + int roll = rand.nextInt(totalWeight - 1); + for (auto & elem : pool) + { + roll -= elem->type->heroClass->selectionProbability[factionID]; + if(roll < 0) + { + return elem; + } + } + + return pool.back(); +} + +std::vector TavernHeroesPool::getHeroesFor(PlayerColor color) const +{ + std::vector result; + + if(!currentTavern.count(color)) + return result; + + for(const auto & hero : currentTavern.at(color)) + result.push_back(hero.second); + + return result; +} + +CGHeroInstance * TavernHeroesPool::takeHero(HeroTypeID hero) +{ + assert(heroesPool.count(hero)); + + CGHeroInstance * result = heroesPool[hero]; + heroesPool.erase(hero); + + assert(result); + return result; +} + +void TavernHeroesPool::onNewDay() +{ + for(auto & hero : heroesPool) + { + assert(hero.second); + if(!hero.second) + continue; + + hero.second->setMovementPoints(hero.second->movementPointsLimit(true)); + hero.second->mana = hero.second->manaLimit(); + } +} + +void TavernHeroesPool::addHeroToPool(CGHeroInstance * hero) +{ + heroesPool[HeroTypeID(hero->subID)] = hero; +} + +void TavernHeroesPool::setAvailability(HeroTypeID hero, PlayerColor::Mask mask) +{ + pavailable[hero] = mask; +} diff --git a/lib/gameState/TavernHeroesPool.h b/lib/gameState/TavernHeroesPool.h new file mode 100644 index 000000000..d9e636949 --- /dev/null +++ b/lib/gameState/TavernHeroesPool.h @@ -0,0 +1,75 @@ +/* + * TavernHeroesPool.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../ConstTransitivePtr.h" +#include "../GameConstants.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class CGHeroInstance; +class CTown; +class CRandomGenerator; +class CHeroClass; +class CGameState; +class CSimpleArmy; + +enum class TavernHeroSlot +{ + NATIVE, + RANDOM +}; + +class DLL_LINKAGE TavernHeroesPool +{ + CGameState * gameState; + + //[subID] - heroes available to buy; nullptr if not available + std::map heroesPool; + + // [subid] -> which players can recruit hero (binary flags) + std::map pavailable; + + std::map unusedHeroesFromPool(); //heroes pool without heroes that are available in taverns + + std::map > currentTavern; + + bool isHeroAvailableFor(HeroTypeID hero, PlayerColor color) const; +public: + TavernHeroesPool(); + TavernHeroesPool(CGameState * gameState); + ~TavernHeroesPool(); + + CGHeroInstance * pickHeroFor(TavernHeroSlot slot, + const PlayerColor & player, + const FactionID & faction, + CRandomGenerator & rand, + const CHeroClass * bannedClass = nullptr) const; + + std::vector getHeroesFor(PlayerColor color) const; + + CGHeroInstance * takeHero(HeroTypeID hero); + + /// reset mana and movement points for all heroes in pool + void onNewDay(); + + void addHeroToPool(CGHeroInstance * hero); + void setAvailability(HeroTypeID hero, PlayerColor::Mask mask); + void setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, HeroTypeID hero, CSimpleArmy & army); + + template void serialize(Handler &h, const int version) + { + h & gameState; + h & heroesPool; + h & pavailable; + } +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index 58a3568d2..dc8dc60af 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -56,10 +56,10 @@ struct DLL_LINKAGE DisposedHero { DisposedHero(); - ui32 heroId; + HeroTypeID heroId; ui32 portrait; /// The portrait id of the hero, -1 is default. std::string name; - ui8 players; /// Who can hire this hero (bitfield). + PlayerColor::Mask players; /// Who can hire this hero (bitfield). template void serialize(Handler & h, const int version) diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index c23a60074..6d3d78bb3 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -239,7 +239,7 @@ void registerTypesClientPacks1(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); - s.template registerType(); + s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); diff --git a/lib/registerTypes/TypesLobbyPacks.cpp b/lib/registerTypes/TypesLobbyPacks.cpp index a347447f4..8ac2734a7 100644 --- a/lib/registerTypes/TypesLobbyPacks.cpp +++ b/lib/registerTypes/TypesLobbyPacks.cpp @@ -14,6 +14,7 @@ #include "../StartInfo.h" #include "../gameState/CGameState.h" #include "../gameState/CGameStateCampaign.h" +#include "../gameState/TavernHeroesPool.h" #include "../mapping/CMap.h" #include "../CModHandler.h" #include "../mapObjects/CObjectHandler.h" diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 19dbeb8e2..a2e988392 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -8,6 +8,12 @@ * */ #include "StdInc.h" +#include "CGameHandler.h" + +#include "HeroPoolProcessor.h" +#include "ServerNetPackVisitors.h" +#include "ServerSpellCastEnvironment.h" +#include "CVCMIServer.h" #include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/FileInfo.h" @@ -35,7 +41,6 @@ #include "../lib/GameSettings.h" #include "../lib/battle/BattleInfo.h" #include "../lib/CondSh.h" -#include "ServerNetPackVisitors.h" #include "../lib/VCMI_Lib.h" #include "../lib/mapping/CMap.h" #include "../lib/mapping/CMapService.h" @@ -44,9 +49,6 @@ #include "../lib/ScopeGuard.h" #include "../lib/CSoundBase.h" #include "../lib/TerrainHandler.h" -#include "CGameHandler.h" -#include "ServerSpellCastEnvironment.h" -#include "CVCMIServer.h" #include "../lib/CCreatureSet.h" #include "../lib/CThreadHelper.h" #include "../lib/GameConstants.h" @@ -868,24 +870,12 @@ void CGameHandler::battleAfterLevelUp(const BattleResult &result) std::set playerColors = {finishingBattle->loser, finishingBattle->victor}; checkVictoryLossConditions(playerColors); - if (result.result == BattleResult::SURRENDER || result.result == BattleResult::ESCAPE) //loser has escaped or surrendered - { - SetAvailableHeroes sah; - sah.player = finishingBattle->loser; - sah.hid[0] = finishingBattle->loserHero->subID; - if (result.result == BattleResult::ESCAPE) //retreat - { - sah.army[0].clear(); - sah.army[0].setCreature(SlotID(0), finishingBattle->loserHero->type->initialArmy.at(0).creature, 1); - } + if (result.result == BattleResult::SURRENDER) + heroPool->onHeroSurrendered(finishingBattle->loser, finishingBattle->loserHero); - if (const CGHeroInstance *another = getPlayerState(finishingBattle->loser)->availableHeroes.at(0)) - sah.hid[1] = another->subID; - else - sah.hid[1] = -1; + if (result.result == BattleResult::ESCAPE) + heroPool->onHeroEscaped(finishingBattle->loser, finishingBattle->loserHero); - sendAndApply(&sah); - } if (result.winner != 2 && finishingBattle->winnerHero && finishingBattle->winnerHero->stacks.empty() && (!finishingBattle->winnerHero->commander || !finishingBattle->winnerHero->commander->alive)) { @@ -893,20 +883,7 @@ void CGameHandler::battleAfterLevelUp(const BattleResult &result) sendAndApply(&ro); if (VLC->settings()->getBoolean(EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS)) - { - SetAvailableHeroes sah; - sah.player = finishingBattle->victor; - sah.hid[0] = finishingBattle->winnerHero->subID; - sah.army[0].clear(); - sah.army[0].setCreature(SlotID(0), finishingBattle->winnerHero->type->initialArmy.at(0).creature, 1); - - if (const CGHeroInstance *another = getPlayerState(finishingBattle->victor)->availableHeroes.at(0)) - sah.hid[1] = another->subID; - else - sah.hid[1] = -1; - - sendAndApply(&sah); - } + heroPool->onHeroEscaped(finishingBattle->victor, finishingBattle->winnerHero); } finishingBattle.reset(); @@ -1576,6 +1553,7 @@ int CGameHandler::moveStack(int stack, BattleHex dest) CGameHandler::CGameHandler(CVCMIServer * lobby) : lobby(lobby) + , heroPool(std::make_unique(this)) , complainNoCreatures("No creatures to split") , complainNotEnoughCreatures("Cannot split that stack, not enough creatures!") , complainInvalidSlot("Invalid slot accessed!") @@ -1765,27 +1743,6 @@ void CGameHandler::newTurn() } } - std::map > pool = gs->hpool.heroesPool; - - for (auto& hp : pool) - { - auto hero = hp.second; - if (hero->isInitialized() && hero->stacks.size()) - { - // reset retreated or surrendered heroes - auto maxmove = hero->movementPointsLimit(true); - // if movement is greater than maxmove, we should decrease it - if (hero->movementPointsRemaining() != maxmove || hero->mana < hero->manaLimit()) - { - NewTurn::Hero hth; - hth.id = hero->id; - hth.move = maxmove; - hth.mana = hero->getManaNewTurn(); - n.heroes.insert(hth); - } - } - } - for (auto & elem : gs->players) { if (elem.first == PlayerColor::NEUTRAL) @@ -1797,29 +1754,7 @@ void CGameHandler::newTurn() hadGold.insert(playerGold); if (newWeek) //new heroes in tavern - { - SetAvailableHeroes sah; - sah.player = elem.first; - - //pick heroes and their armies - CHeroClass *banned = nullptr; - for (int j = 0; j < GameConstants::AVAILABLE_HEROES_PER_PLAYER; j++) - { - //first hero - native if possible, second hero -> any other class - if (CGHeroInstance *h = gs->hpool.pickHeroFor(j == 0, elem.first, getNativeTown(elem.first), pool, getRandomGenerator(), banned)) - { - sah.hid[j] = h->subID; - h->initArmy(getRandomGenerator(), &sah.army[j]); - banned = h->type->heroClass; - } - else - { - sah.hid[j] = -1; - } - } - - sendAndApply(&sah); - } + heroPool->onNewWeek(elem.first); n.res[elem.first] = elem.second.resources; @@ -4383,94 +4318,6 @@ bool CGameHandler::setFormation(ObjectInstanceID hid, ui8 formation) return true; } -bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, PlayerColor player) -{ - const PlayerState * p = getPlayerState(player); - const CGTownInstance * t = getTown(obj->id); - - //common preconditions -// if ((p->resources.at(EGameResID::GOLD)= GameConstants::MAX_HEROES_PER_PLAYER && complain("Cannot hire hero, only 8 wandering heroes are allowed!"))) - if ((p->resources[EGameResID::GOLD] < GameConstants::HERO_GOLD_COST && complain("Not enough gold for buying hero!")) - || ((getHeroCount(player, false) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP) && complain("Cannot hire hero, too many wandering heroes already!"))) - || ((getHeroCount(player, true) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP) && complain("Cannot hire hero, too many heroes garrizoned and wandering already!")))) - { - return false; - } - - if (t) //tavern in town - { - if ((!t->hasBuilt(BuildingID::TAVERN) && complain("No tavern!")) - || (t->visitingHero && complain("There is visiting hero - no place!"))) - { - return false; - } - } - else if (obj->ID == Obj::TAVERN) - { - if (getTile(obj->visitablePos())->visitableObjects.back() != obj && complain("Tavern entry must be unoccupied!")) - { - return false; - } - } - - const CGHeroInstance *nh = p->availableHeroes.at(hid); - if (!nh) - { - complain ("Hero is not available for hiring!"); - return false; - } - - HeroRecruited hr; - hr.tid = obj->id; - hr.hid = nh->subID; - hr.player = player; - hr.tile = nh->convertFromVisitablePos(obj->visitablePos()); - if (getTile(hr.tile)->isWater()) - { - //Create a new boat for hero - createObject(obj->visitablePos(), Obj::BOAT, nh->getBoatType().getNum()); - - hr.boatId = getTopObj(hr.tile)->id; - } - sendAndApply(&hr); - - std::map > pool = gs->unusedHeroesFromPool(); - - const CGHeroInstance *theOtherHero = p->availableHeroes.at(!hid); - const CGHeroInstance *newHero = nullptr; - if (theOtherHero) //on XXL maps all heroes can be imprisoned :( - { - newHero = gs->hpool.pickHeroFor(false, player, getNativeTown(player), pool, getRandomGenerator(), theOtherHero->type->heroClass); - } - - SetAvailableHeroes sah; - sah.player = player; - - if (newHero) - { - sah.hid[hid] = newHero->subID; - sah.army[hid].clear(); - sah.army[hid].setCreature(SlotID(0), newHero->type->initialArmy[0].creature, 1); - } - else - { - sah.hid[hid] = -1; - } - - sah.hid[!hid] = theOtherHero ? theOtherHero->subID : -1; - sendAndApply(&sah); - - giveResource(player, EGameResID::GOLD, -GameConstants::HERO_GOLD_COST); - - if(t) - { - visitCastleObjects(t, nh); - giveSpells (t,nh); - } - return true; -} - bool CGameHandler::queryReply(QueryID qid, const JsonNode & answer, PlayerColor player) { boost::unique_lock lock(gsm); diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 24e97067d..7da768eaa 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -46,6 +46,7 @@ template class CApplier; VCMI_LIB_NAMESPACE_END +class HeroPoolProcessor; class CGameHandler; class CVCMIServer; class CBaseForGHApply; @@ -97,7 +98,10 @@ class CGameHandler : public IGameCallback, public CBattleInfoCallback, public En CVCMIServer * lobby; std::shared_ptr> applier; std::unique_ptr battleThread; + public: + std::unique_ptr heroPool; + using FireShieldInfo = std::vector>; //use enums as parameters, because doMove(sth, true, false, true) is not readable enum EGuardLook {CHECK_FOR_GUARDS, IGNORE_GUARDS}; @@ -145,6 +149,7 @@ public: void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town); void setBattleResult(BattleResult::EResult resultType, int victoriusSide); + CGameHandler() = default; CGameHandler(CVCMIServer * lobby); ~CGameHandler(); @@ -240,7 +245,6 @@ public: void removeObstacle(const CObstacleInstance &obstacle); bool queryReply( QueryID qid, const JsonNode & answer, PlayerColor player ); - bool hireHero( const CGObjectInstance *obj, ui8 hid, PlayerColor player ); bool buildBoat( ObjectInstanceID objid, PlayerColor player ); bool setFormation( ObjectInstanceID hid, ui8 formation ); bool tradeResources(const IMarket *market, ui32 val, PlayerColor player, ui32 id1, ui32 id2); @@ -283,6 +287,7 @@ public: h & QID; h & states; h & finishingBattle; + h & heroPool; h & getRandomGenerator(); #if SCRIPTING_ENABLED diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 4c09889c0..76030afd0 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -2,6 +2,7 @@ set(server_SRCS StdInc.cpp CGameHandler.cpp + HeroPoolProcessor.cpp ServerSpellCastEnvironment.cpp CQuery.cpp CVCMIServer.cpp @@ -13,6 +14,7 @@ set(server_HEADERS StdInc.h CGameHandler.h + HeroPoolProcessor.h ServerSpellCastEnvironment.h CQuery.h CVCMIServer.h diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index b49cef5a2..892869efd 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -822,7 +822,7 @@ void CVCMIServer::setPlayer(PlayerColor clickedColor) void CVCMIServer::optionNextCastle(PlayerColor player, int dir) { PlayerSettings & s = si->playerInfos[player]; - si16 & cur = s.castle; + FactionID & cur = s.castle; auto & allowed = getPlayerInfo(player.getNum()).allowedFactions; const bool allowRandomTown = getPlayerInfo(player.getNum()).isFactionRandom; @@ -856,7 +856,7 @@ void CVCMIServer::optionNextCastle(PlayerColor player, int dir) else { assert(dir >= -1 && dir <= 1); //othervice std::advance may go out of range - auto iter = allowed.find(FactionID(cur)); + auto iter = allowed.find(cur); std::advance(iter, dir); cur = *iter; } diff --git a/server/HeroPoolProcessor.cpp b/server/HeroPoolProcessor.cpp new file mode 100644 index 000000000..f4278508c --- /dev/null +++ b/server/HeroPoolProcessor.cpp @@ -0,0 +1,162 @@ +/* + * HeroPoolProcessor.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "HeroPoolProcessor.h" + +#include "CGameHandler.h" + +#include "../lib/CHeroHandler.h" +#include "../lib/CPlayerState.h" +#include "../lib/GameSettings.h" +#include "../lib/NetPacks.h" +#include "../lib/StartInfo.h" +#include "../lib/mapObjects/CGTownInstance.h" +#include "../lib/gameState/CGameState.h" +#include "../lib/gameState/TavernHeroesPool.h" + +HeroPoolProcessor::HeroPoolProcessor() = default; + +HeroPoolProcessor::HeroPoolProcessor(CGameHandler * gameHandler): + gameHandler(gameHandler) +{ +} + +void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHeroInstance * hero) +{ + SetAvailableHero sah; + sah.slotID = 0; + sah.player = color; + sah.hid = hero->subID; + sah.army.clear(); + sah.army.setCreature(SlotID(0), hero->type->initialArmy.at(0).creature, 1); + gameHandler->sendAndApply(&sah); +} + +void HeroPoolProcessor::onHeroEscaped(const PlayerColor & color, const CGHeroInstance * hero) +{ + SetAvailableHero sah; + sah.slotID = 0; + sah.player = color; + sah.hid = hero->subID; + + gameHandler->sendAndApply(&sah); +} + +void HeroPoolProcessor::clearHeroFromSlot(const PlayerColor & color, TavernHeroSlot slot) +{ + SetAvailableHero sah; + sah.player = color; + sah.slotID = static_cast(slot); + sah.hid = HeroTypeID::NONE; + gameHandler->sendAndApply(&sah); +} + +void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot) +{ + SetAvailableHero sah; + sah.player = color; + sah.slotID = static_cast(slot); + + //first hero - native if possible, second hero -> any other class + CGHeroInstance *h = gameHandler->gameState()->hpool->pickHeroFor(slot, color, gameHandler->getPlayerSettings(color)->castle, gameHandler->getRandomGenerator()); + + if (h) + { + sah.hid = h->subID; + h->initArmy(gameHandler->getRandomGenerator(), &sah.army); + } + else + { + sah.hid = -1; + } + gameHandler->sendAndApply(&sah); +} + +void HeroPoolProcessor::onNewWeek(const PlayerColor & color) +{ + clearHeroFromSlot(color, TavernHeroSlot::NATIVE); + clearHeroFromSlot(color, TavernHeroSlot::RANDOM); + selectNewHeroForSlot(color, TavernHeroSlot::NATIVE); + selectNewHeroForSlot(color, TavernHeroSlot::RANDOM); +} + +bool HeroPoolProcessor::hireHero(const CGObjectInstance *obj, const HeroTypeID & heroToRecruit, const PlayerColor & player) +{ + const PlayerState * playerState = gameHandler->getPlayerState(player); + const CGTownInstance * town = gameHandler->getTown(obj->id); + + if (playerState->resources[EGameResID::GOLD] < GameConstants::HERO_GOLD_COST && gameHandler->complain("Not enough gold for buying hero!")) + return false; + + if (gameHandler->getHeroCount(player, false) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP) && gameHandler->complain("Cannot hire hero, too many wandering heroes already!")) + return false; + + if (gameHandler->getHeroCount(player, true) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP) && gameHandler->complain("Cannot hire hero, too many heroes garrizoned and wandering already!")) + return false; + + if (town) //tavern in town + { + if (!town->hasBuilt(BuildingID::TAVERN) && gameHandler->complain("No tavern!")) + return false; + + if (town->visitingHero && gameHandler->complain("There is visiting hero - no place!")) + return false; + } + + if (obj->ID == Obj::TAVERN) + { + if (gameHandler->getTile(obj->visitablePos())->visitableObjects.back() != obj && gameHandler->complain("Tavern entry must be unoccupied!")) + return false; + } + + auto recruitableHeroes = gameHandler->gameState()->hpool->getHeroesFor(player); + + const CGHeroInstance *recruitedHero = nullptr;; + + for(const auto & hero : recruitableHeroes) + { + if (hero->subID == heroToRecruit) + recruitedHero = hero; + } + + if (!recruitedHero) + { + gameHandler->complain ("Hero is not available for hiring!"); + return false; + } + + HeroRecruited hr; + hr.tid = obj->id; + hr.hid = recruitedHero->subID; + hr.player = player; + hr.tile = recruitedHero->convertFromVisitablePos(obj->visitablePos()); + if (gameHandler->getTile(hr.tile)->isWater()) + { + //Create a new boat for hero + gameHandler->createObject(obj->visitablePos(), Obj::BOAT, recruitedHero->getBoatType().getNum()); + + hr.boatId = gameHandler->getTopObj(hr.tile)->id; + } + gameHandler->sendAndApply(&hr); + + if (recruitableHeroes[0] == recruitedHero) + selectNewHeroForSlot(player, TavernHeroSlot::NATIVE); + else + selectNewHeroForSlot(player, TavernHeroSlot::RANDOM); + + gameHandler->giveResource(player, EGameResID::GOLD, -GameConstants::HERO_GOLD_COST); + + if(town) + { + gameHandler->visitCastleObjects(town, recruitedHero); + gameHandler->giveSpells(town, recruitedHero); + } + return true; +} diff --git a/server/HeroPoolProcessor.h b/server/HeroPoolProcessor.h new file mode 100644 index 000000000..c37d6cb42 --- /dev/null +++ b/server/HeroPoolProcessor.h @@ -0,0 +1,45 @@ +/* + * HeroPoolProcessor.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +VCMI_LIB_NAMESPACE_BEGIN + +enum class TavernHeroSlot; +class PlayerColor; +class CGHeroInstance; +class HeroTypeID; +class CGObjectInstance; + +VCMI_LIB_NAMESPACE_END + +class CGameHandler; + +class HeroPoolProcessor : boost::noncopyable +{ + CGameHandler * gameHandler; + + void clearHeroFromSlot(const PlayerColor & color, TavernHeroSlot slot); + void selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot); +public: + HeroPoolProcessor(); + HeroPoolProcessor(CGameHandler * gameHandler); + + void onHeroSurrendered(const PlayerColor & color, const CGHeroInstance * hero); + void onHeroEscaped(const PlayerColor & color, const CGHeroInstance * hero); + + void onNewWeek(const PlayerColor & color); + + bool hireHero(const CGObjectInstance *obj, const HeroTypeID & hid, const PlayerColor & player); + + template void serialize(Handler &h, const int version) + { + h & gameHandler; + } +}; diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 892be093d..d4ceffb47 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -11,6 +11,8 @@ #include "ServerNetPackVisitors.h" #include "CGameHandler.h" +#include "HeroPoolProcessor.h" + #include "../lib/IGameCallback.h" #include "../lib/mapObjects/CGTownInstance.h" #include "../lib/gameState/CGameState.h" @@ -251,7 +253,7 @@ void ApplyGhNetPackVisitor::visitHireHero(HireHero & pack) if(town && PlayerRelations::ENEMIES == gh.getPlayerRelations(obj->tempOwner, gh.getPlayerAt(pack.c))) gh.throwAndComplain(&pack, "Can't buy hero in enemy town!"); - result = gh.hireHero(obj, pack.hid, pack.player); + result = gh.heroPool->hireHero(obj, pack.hid, pack.player); } void ApplyGhNetPackVisitor::visitBuildBoat(BuildBoat & pack) From f8187ce1d8a803bc33ac11dfeaf5c251880c41c8 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 11 Jul 2023 17:51:14 +0300 Subject: [PATCH 2/8] Fix regressions --- lib/NetPacksLib.cpp | 3 + lib/gameState/CGameState.cpp | 2 +- lib/gameState/TavernHeroesPool.cpp | 80 +------------------- lib/gameState/TavernHeroesPool.h | 31 ++++---- server/HeroPoolProcessor.cpp | 114 ++++++++++++++++++++++++----- server/HeroPoolProcessor.h | 7 +- 6 files changed, 123 insertions(+), 114 deletions(-) diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index e2eadfd64..78e454ed2 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -2012,6 +2012,9 @@ void NewTurn::applyGs(CGameState *gs) logGlobal->error("Hero %d not found in NewTurn::applyGs", h.id.getNum()); continue; } + + hero->setMovementPoints(h.move); + hero->mana = h.mana; } gs->hpool->onNewDay(); diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 545dd06e3..abf9ebf44 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -385,7 +385,7 @@ int CGameState::getDate(Date::EDateType mode) const CGameState::CGameState() { gs = this; - hpool = std::make_unique(this); + hpool = std::make_unique(); applier = std::make_shared>(); registerTypesClientPacks1(*applier); registerTypesClientPacks2(*applier); diff --git a/lib/gameState/TavernHeroesPool.cpp b/lib/gameState/TavernHeroesPool.cpp index 0634bdde8..0b6693a9a 100644 --- a/lib/gameState/TavernHeroesPool.cpp +++ b/lib/gameState/TavernHeroesPool.cpp @@ -10,18 +10,9 @@ #include "StdInc.h" #include "TavernHeroesPool.h" -#include "CGameState.h" -#include "CPlayerState.h" - #include "../mapObjects/CGHeroInstance.h" -#include "../CHeroHandler.h" -TavernHeroesPool::TavernHeroesPool() = default; - -TavernHeroesPool::TavernHeroesPool(CGameState * gameState) - : gameState(gameState) -{ -} +VCMI_LIB_NAMESPACE_BEGIN TavernHeroesPool::~TavernHeroesPool() { @@ -29,7 +20,7 @@ TavernHeroesPool::~TavernHeroesPool() delete ptr.second; } -std::map TavernHeroesPool::unusedHeroesFromPool() +std::map TavernHeroesPool::unusedHeroesFromPool() const { std::map pool = heroesPool; for(const auto & player : currentTavern) @@ -63,71 +54,6 @@ bool TavernHeroesPool::isHeroAvailableFor(HeroTypeID hero, PlayerColor color) co return true; } -CGHeroInstance * TavernHeroesPool::pickHeroFor(TavernHeroSlot slot, - const PlayerColor & player, - const FactionID & factionID, - CRandomGenerator & rand, - const CHeroClass * bannedClass) const -{ - if(player>=PlayerColor::PLAYER_LIMIT) - { - logGlobal->error("Cannot pick hero for player %d. Wrong owner!", player.getStr()); - return nullptr; - } - - if(slot == TavernHeroSlot::NATIVE) - { - std::vector pool; - - for(auto & elem : heroesPool) - { - //get all available heroes - bool heroAvailable = isHeroAvailableFor(elem.first, player); - bool heroClassNative = elem.second->type->heroClass->faction == factionID; - - if(heroAvailable && heroClassNative) - pool.push_back(elem.second); - } - - if(!pool.empty()) - return *RandomGeneratorUtil::nextItem(pool, rand); - - logGlobal->error("Cannot pick native hero for %s. Picking any...", player.getStr()); - } - - std::vector pool; - int totalWeight = 0; - - for(auto & elem : heroesPool) - { - bool heroAvailable = isHeroAvailableFor(elem.first, player); - bool heroClassBanned = bannedClass && elem.second->type->heroClass == bannedClass; - - if ( heroAvailable && !heroClassBanned) - { - pool.push_back(elem.second); - totalWeight += elem.second->type->heroClass->selectionProbability[factionID]; //total weight - } - } - if(pool.empty() || totalWeight == 0) - { - logGlobal->error("There are no heroes available for player %s!", player.getStr()); - return nullptr; - } - - int roll = rand.nextInt(totalWeight - 1); - for (auto & elem : pool) - { - roll -= elem->type->heroClass->selectionProbability[factionID]; - if(roll < 0) - { - return elem; - } - } - - return pool.back(); -} - std::vector TavernHeroesPool::getHeroesFor(PlayerColor color) const { std::vector result; @@ -174,3 +100,5 @@ void TavernHeroesPool::setAvailability(HeroTypeID hero, PlayerColor::Mask mask) { pavailable[hero] = mask; } + +VCMI_LIB_NAMESPACE_END diff --git a/lib/gameState/TavernHeroesPool.h b/lib/gameState/TavernHeroesPool.h index d9e636949..79bcccc8b 100644 --- a/lib/gameState/TavernHeroesPool.h +++ b/lib/gameState/TavernHeroesPool.h @@ -23,38 +23,34 @@ class CSimpleArmy; enum class TavernHeroSlot { - NATIVE, - RANDOM + NATIVE, // 1st / left slot in tavern, contains hero native to player's faction on new week + RANDOM // 2nd / right slot in tavern, contains hero of random class }; class DLL_LINKAGE TavernHeroesPool { - CGameState * gameState; - - //[subID] - heroes available to buy; nullptr if not available + /// list of all heroes in pool, including those currently present in taverns std::map heroesPool; - // [subid] -> which players can recruit hero (binary flags) + /// list of which players are able to purchase specific hero + /// if hero is not present in list, he is available for everyone std::map pavailable; - std::map unusedHeroesFromPool(); //heroes pool without heroes that are available in taverns - + /// list of heroes currently available in a tavern of a specific player std::map > currentTavern; - bool isHeroAvailableFor(HeroTypeID hero, PlayerColor color) const; public: - TavernHeroesPool(); - TavernHeroesPool(CGameState * gameState); ~TavernHeroesPool(); - CGHeroInstance * pickHeroFor(TavernHeroSlot slot, - const PlayerColor & player, - const FactionID & faction, - CRandomGenerator & rand, - const CHeroClass * bannedClass = nullptr) const; - + /// Returns heroes currently availabe in tavern of a specific player std::vector getHeroesFor(PlayerColor color) const; + /// returns heroes in pool without heroes that are available in taverns + std::map unusedHeroesFromPool() const; + + /// Returns true if hero is available to a specific player + bool isHeroAvailableFor(HeroTypeID hero, PlayerColor color) const; + CGHeroInstance * takeHero(HeroTypeID hero); /// reset mana and movement points for all heroes in pool @@ -66,7 +62,6 @@ public: template void serialize(Handler &h, const int version) { - h & gameState; h & heroesPool; h & pavailable; } diff --git a/server/HeroPoolProcessor.cpp b/server/HeroPoolProcessor.cpp index f4278508c..a05664946 100644 --- a/server/HeroPoolProcessor.cpp +++ b/server/HeroPoolProcessor.cpp @@ -58,19 +58,28 @@ void HeroPoolProcessor::clearHeroFromSlot(const PlayerColor & color, TavernHeroS gameHandler->sendAndApply(&sah); } -void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot) +void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot, bool needNativeHero, bool giveArmy) { SetAvailableHero sah; sah.player = color; sah.slotID = static_cast(slot); //first hero - native if possible, second hero -> any other class - CGHeroInstance *h = gameHandler->gameState()->hpool->pickHeroFor(slot, color, gameHandler->getPlayerSettings(color)->castle, gameHandler->getRandomGenerator()); + CGHeroInstance *h = pickHeroFor(needNativeHero, color, gameHandler->getPlayerSettings(color)->castle, gameHandler->getRandomGenerator(), nullptr); if (h) { sah.hid = h->subID; - h->initArmy(gameHandler->getRandomGenerator(), &sah.army); + + if (giveArmy) + { + h->initArmy(gameHandler->getRandomGenerator(), &sah.army); + } + else + { + sah.army.clear(); + sah.army.setCreature(SlotID(0), h->type->initialArmy[0].creature, 1); + } } else { @@ -83,8 +92,8 @@ void HeroPoolProcessor::onNewWeek(const PlayerColor & color) { clearHeroFromSlot(color, TavernHeroSlot::NATIVE); clearHeroFromSlot(color, TavernHeroSlot::RANDOM); - selectNewHeroForSlot(color, TavernHeroSlot::NATIVE); - selectNewHeroForSlot(color, TavernHeroSlot::RANDOM); + selectNewHeroForSlot(color, TavernHeroSlot::NATIVE, true, true); + selectNewHeroForSlot(color, TavernHeroSlot::RANDOM, false, true); } bool HeroPoolProcessor::hireHero(const CGObjectInstance *obj, const HeroTypeID & heroToRecruit, const PlayerColor & player) @@ -101,34 +110,34 @@ bool HeroPoolProcessor::hireHero(const CGObjectInstance *obj, const HeroTypeID & if (gameHandler->getHeroCount(player, true) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP) && gameHandler->complain("Cannot hire hero, too many heroes garrizoned and wandering already!")) return false; - if (town) //tavern in town + if(town) //tavern in town { - if (!town->hasBuilt(BuildingID::TAVERN) && gameHandler->complain("No tavern!")) + if(!town->hasBuilt(BuildingID::TAVERN) && gameHandler->complain("No tavern!")) return false; - if (town->visitingHero && gameHandler->complain("There is visiting hero - no place!")) + if(town->visitingHero && gameHandler->complain("There is visiting hero - no place!")) return false; } - if (obj->ID == Obj::TAVERN) + if(obj->ID == Obj::TAVERN) { - if (gameHandler->getTile(obj->visitablePos())->visitableObjects.back() != obj && gameHandler->complain("Tavern entry must be unoccupied!")) + if(gameHandler->getTile(obj->visitablePos())->visitableObjects.back() != obj && gameHandler->complain("Tavern entry must be unoccupied!")) return false; } auto recruitableHeroes = gameHandler->gameState()->hpool->getHeroesFor(player); - const CGHeroInstance *recruitedHero = nullptr;; + const CGHeroInstance * recruitedHero = nullptr; for(const auto & hero : recruitableHeroes) { - if (hero->subID == heroToRecruit) + if(hero->subID == heroToRecruit) recruitedHero = hero; } - if (!recruitedHero) + if(!recruitedHero) { - gameHandler->complain ("Hero is not available for hiring!"); + gameHandler->complain("Hero is not available for hiring!"); return false; } @@ -137,19 +146,21 @@ bool HeroPoolProcessor::hireHero(const CGObjectInstance *obj, const HeroTypeID & hr.hid = recruitedHero->subID; hr.player = player; hr.tile = recruitedHero->convertFromVisitablePos(obj->visitablePos()); - if (gameHandler->getTile(hr.tile)->isWater()) + if(gameHandler->getTile(hr.tile)->isWater()) { //Create a new boat for hero gameHandler->createObject(obj->visitablePos(), Obj::BOAT, recruitedHero->getBoatType().getNum()); hr.boatId = gameHandler->getTopObj(hr.tile)->id; } + + // apply netpack -> this will remove hired hero from tavern slot gameHandler->sendAndApply(&hr); - if (recruitableHeroes[0] == recruitedHero) - selectNewHeroForSlot(player, TavernHeroSlot::NATIVE); + if(recruitableHeroes[0] == recruitedHero) + selectNewHeroForSlot(player, TavernHeroSlot::NATIVE, false, false); else - selectNewHeroForSlot(player, TavernHeroSlot::RANDOM); + selectNewHeroForSlot(player, TavernHeroSlot::RANDOM, false, false); gameHandler->giveResource(player, EGameResID::GOLD, -GameConstants::HERO_GOLD_COST); @@ -160,3 +171,70 @@ bool HeroPoolProcessor::hireHero(const CGObjectInstance *obj, const HeroTypeID & } return true; } + +CGHeroInstance * HeroPoolProcessor::pickHeroFor(bool isNative, + const PlayerColor & player, + const FactionID & factionID, + CRandomGenerator & rand, + const CHeroClass * bannedClass) const +{ + if(player >= PlayerColor::PLAYER_LIMIT) + { + logGlobal->error("Cannot pick hero for player %d. Wrong owner!", player.getStr()); + return nullptr; + } + + const auto & hpool = gameHandler->gameState()->hpool; + + if(isNative) + { + std::vector pool; + + for(auto & elem : hpool->unusedHeroesFromPool()) + { + //get all available heroes + bool heroAvailable = hpool->isHeroAvailableFor(elem.first, player); + bool heroClassNative = elem.second->type->heroClass->faction == factionID; + + if(heroAvailable && heroClassNative) + pool.push_back(elem.second); + } + + if(!pool.empty()) + return *RandomGeneratorUtil::nextItem(pool, rand); + + logGlobal->error("Cannot pick native hero for %s. Picking any...", player.getStr()); + } + + std::vector pool; + int totalWeight = 0; + + for(auto & elem : hpool->unusedHeroesFromPool()) + { + bool heroAvailable = hpool->isHeroAvailableFor(elem.first, player); + bool heroClassBanned = bannedClass && elem.second->type->heroClass == bannedClass; + + if(heroAvailable && !heroClassBanned) + { + pool.push_back(elem.second); + totalWeight += elem.second->type->heroClass->selectionProbability[factionID]; //total weight + } + } + if(pool.empty() || totalWeight == 0) + { + logGlobal->error("There are no heroes available for player %s!", player.getStr()); + return nullptr; + } + + int roll = rand.nextInt(totalWeight - 1); + for(auto & elem : pool) + { + roll -= elem->type->heroClass->selectionProbability[factionID]; + if(roll < 0) + { + return elem; + } + } + + return pool.back(); +} diff --git a/server/HeroPoolProcessor.h b/server/HeroPoolProcessor.h index c37d6cb42..eeacd1444 100644 --- a/server/HeroPoolProcessor.h +++ b/server/HeroPoolProcessor.h @@ -16,6 +16,9 @@ class PlayerColor; class CGHeroInstance; class HeroTypeID; class CGObjectInstance; +class FactionID; +class CRandomGenerator; +class CHeroClass; VCMI_LIB_NAMESPACE_END @@ -26,7 +29,9 @@ class HeroPoolProcessor : boost::noncopyable CGameHandler * gameHandler; void clearHeroFromSlot(const PlayerColor & color, TavernHeroSlot slot); - void selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot); + void selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot, bool needNativeHero, bool giveStartingArmy); + + CGHeroInstance * pickHeroFor(bool isNative, const PlayerColor & player, const FactionID & faction, CRandomGenerator & rand, const CHeroClass * bannedClass) const; public: HeroPoolProcessor(); HeroPoolProcessor(CGameHandler * gameHandler); From a2d2ecc96f13b31d1dc8f6f29b6d9006882b48d7 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 11 Jul 2023 18:49:19 +0300 Subject: [PATCH 3/8] Roll first for hero class, and then - for actual hero for tavern --- lib/gameState/TavernHeroesPool.cpp | 2 +- server/HeroPoolProcessor.cpp | 149 +++++++++++++++++++---------- server/HeroPoolProcessor.h | 5 + 3 files changed, 105 insertions(+), 51 deletions(-) diff --git a/lib/gameState/TavernHeroesPool.cpp b/lib/gameState/TavernHeroesPool.cpp index 0b6693a9a..ea5bbc996 100644 --- a/lib/gameState/TavernHeroesPool.cpp +++ b/lib/gameState/TavernHeroesPool.cpp @@ -16,7 +16,7 @@ VCMI_LIB_NAMESPACE_BEGIN TavernHeroesPool::~TavernHeroesPool() { - for(auto ptr : heroesPool) // clean hero pool + for(const auto & ptr : heroesPool) // clean hero pool delete ptr.second; } diff --git a/server/HeroPoolProcessor.cpp b/server/HeroPoolProcessor.cpp index a05664946..12da68b5c 100644 --- a/server/HeroPoolProcessor.cpp +++ b/server/HeroPoolProcessor.cpp @@ -21,10 +21,13 @@ #include "../lib/gameState/CGameState.h" #include "../lib/gameState/TavernHeroesPool.h" -HeroPoolProcessor::HeroPoolProcessor() = default; +HeroPoolProcessor::HeroPoolProcessor() + : gameHandler(nullptr) +{ +} -HeroPoolProcessor::HeroPoolProcessor(CGameHandler * gameHandler): - gameHandler(gameHandler) +HeroPoolProcessor::HeroPoolProcessor(CGameHandler * gameHandler) + : gameHandler(gameHandler) { } @@ -172,11 +175,44 @@ bool HeroPoolProcessor::hireHero(const CGObjectInstance *obj, const HeroTypeID & return true; } -CGHeroInstance * HeroPoolProcessor::pickHeroFor(bool isNative, - const PlayerColor & player, - const FactionID & factionID, - CRandomGenerator & rand, - const CHeroClass * bannedClass) const +std::set HeroPoolProcessor::findAvailableClassesFor(const PlayerColor & player) const +{ + std::set result; + + const auto & hpool = gameHandler->gameState()->hpool; + FactionID factionID = gameHandler->getPlayerSettings(player)->castle; + + for(auto & elem : hpool->unusedHeroesFromPool()) + { + bool heroAvailable = hpool->isHeroAvailableFor(elem.first, player); + bool heroClassBanned = elem.second->type->heroClass->selectionProbability[factionID] == 0; + + if(heroAvailable && !heroClassBanned) + result.insert(elem.second->type->heroClass); + } + + return result; +} + +std::set HeroPoolProcessor::findAvailableHeroesFor(const PlayerColor & player, const CHeroClass * heroClass) const +{ + std::set result; + + const auto & hpool = gameHandler->gameState()->hpool; + + for(auto & elem : hpool->unusedHeroesFromPool()) + { + bool heroAvailable = hpool->isHeroAvailableFor(elem.first, player); + bool heroClassMatches = elem.second->type->heroClass == heroClass; + + if(heroAvailable && heroClassMatches) + result.insert(elem.second); + } + + return result; +} + +const CHeroClass * HeroPoolProcessor::pickClassFor(bool isNative, const PlayerColor & player, const FactionID & factionID, CRandomGenerator & rand) const { if(player >= PlayerColor::PLAYER_LIMIT) { @@ -185,56 +221,69 @@ CGHeroInstance * HeroPoolProcessor::pickHeroFor(bool isNative, } const auto & hpool = gameHandler->gameState()->hpool; + const auto & currentTavern = hpool->getHeroesFor(player); - if(isNative) - { - std::vector pool; + std::set potentialClasses = findAvailableClassesFor(player); + std::set possibleClasses; - for(auto & elem : hpool->unusedHeroesFromPool()) - { - //get all available heroes - bool heroAvailable = hpool->isHeroAvailableFor(elem.first, player); - bool heroClassNative = elem.second->type->heroClass->faction == factionID; - - if(heroAvailable && heroClassNative) - pool.push_back(elem.second); - } - - if(!pool.empty()) - return *RandomGeneratorUtil::nextItem(pool, rand); - - logGlobal->error("Cannot pick native hero for %s. Picking any...", player.getStr()); - } - - std::vector pool; - int totalWeight = 0; - - for(auto & elem : hpool->unusedHeroesFromPool()) - { - bool heroAvailable = hpool->isHeroAvailableFor(elem.first, player); - bool heroClassBanned = bannedClass && elem.second->type->heroClass == bannedClass; - - if(heroAvailable && !heroClassBanned) - { - pool.push_back(elem.second); - totalWeight += elem.second->type->heroClass->selectionProbability[factionID]; //total weight - } - } - if(pool.empty() || totalWeight == 0) + if(potentialClasses.empty()) { logGlobal->error("There are no heroes available for player %s!", player.getStr()); return nullptr; } - int roll = rand.nextInt(totalWeight - 1); - for(auto & elem : pool) + for(const auto & heroClass : potentialClasses) { - roll -= elem->type->heroClass->selectionProbability[factionID]; - if(roll < 0) - { - return elem; - } + if (isNative && heroClass->faction != factionID) + continue; + + bool hasSameClass = vstd::contains_if(currentTavern, [&](const CGHeroInstance * hero){ + return hero->type->heroClass == heroClass; + }); + + if (hasSameClass) + continue; + + possibleClasses.insert(heroClass); } - return pool.back(); + if (possibleClasses.empty()) + { + logGlobal->error("Cannot pick native hero for %s. Picking any...", player.getStr()); + possibleClasses = potentialClasses; + } + + int totalWeight = 0; + for(const auto & heroClass : possibleClasses) + totalWeight += heroClass->selectionProbability.at(factionID); + + int roll = rand.nextInt(totalWeight - 1); + for(const auto & heroClass : possibleClasses) + { + roll -= heroClass->selectionProbability.at(factionID); + if(roll < 0) + return heroClass; + } + + return *possibleClasses.rbegin(); +} + +CGHeroInstance * HeroPoolProcessor::pickHeroFor(bool isNative, + const PlayerColor & player, + const FactionID & factionID, + CRandomGenerator & rand, + const CHeroClass * bannedClass) const +{ + const CHeroClass * heroClass = pickClassFor(isNative, player, factionID, rand); + + if(!heroClass) + return nullptr; + + std::set possibleHeroes = findAvailableHeroesFor(player, heroClass); + + assert(!possibleHeroes.empty()); + if(possibleHeroes.empty()) + return nullptr; + + return *RandomGeneratorUtil::nextItem(possibleHeroes, rand); } diff --git a/server/HeroPoolProcessor.h b/server/HeroPoolProcessor.h index eeacd1444..671a48102 100644 --- a/server/HeroPoolProcessor.h +++ b/server/HeroPoolProcessor.h @@ -31,6 +31,11 @@ class HeroPoolProcessor : boost::noncopyable void clearHeroFromSlot(const PlayerColor & color, TavernHeroSlot slot); void selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot, bool needNativeHero, bool giveStartingArmy); + std::set findAvailableClassesFor(const PlayerColor & player) const; + std::set findAvailableHeroesFor(const PlayerColor & player, const CHeroClass * heroClass) const; + + const CHeroClass * pickClassFor(bool isNative, const PlayerColor & player, const FactionID & faction, CRandomGenerator & rand) const; + CGHeroInstance * pickHeroFor(bool isNative, const PlayerColor & player, const FactionID & faction, CRandomGenerator & rand, const CHeroClass * bannedClass) const; public: HeroPoolProcessor(); From 539c508870192358182486b0e9951aef088d51dd Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 11 Jul 2023 19:23:14 +0300 Subject: [PATCH 4/8] Implemented persistent random seed for hero pool --- lib/CRandomGenerator.cpp | 5 +++++ lib/CRandomGenerator.h | 3 +++ server/HeroPoolProcessor.cpp | 27 ++++++++++++++++----------- server/HeroPoolProcessor.h | 10 ++++++++-- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/lib/CRandomGenerator.cpp b/lib/CRandomGenerator.cpp index 0052e3c25..4f896444b 100644 --- a/lib/CRandomGenerator.cpp +++ b/lib/CRandomGenerator.cpp @@ -20,6 +20,11 @@ CRandomGenerator::CRandomGenerator() resetSeed(); } +CRandomGenerator::CRandomGenerator(int seed) +{ + setSeed(seed); +} + void CRandomGenerator::setSeed(int seed) { rand.seed(seed); diff --git a/lib/CRandomGenerator.h b/lib/CRandomGenerator.h index 5b76adcb1..8e27b46fa 100644 --- a/lib/CRandomGenerator.h +++ b/lib/CRandomGenerator.h @@ -30,6 +30,9 @@ public: /// current thread ID. CRandomGenerator(); + /// Seeds the generator with provided initial seed + explicit CRandomGenerator(int seed); + void setSeed(int seed); /// Resets the seed to the product of the current time in milliseconds and the diff --git a/server/HeroPoolProcessor.cpp b/server/HeroPoolProcessor.cpp index 12da68b5c..2ad418822 100644 --- a/server/HeroPoolProcessor.cpp +++ b/server/HeroPoolProcessor.cpp @@ -68,7 +68,7 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe sah.slotID = static_cast(slot); //first hero - native if possible, second hero -> any other class - CGHeroInstance *h = pickHeroFor(needNativeHero, color, gameHandler->getPlayerSettings(color)->castle, gameHandler->getRandomGenerator(), nullptr); + CGHeroInstance *h = pickHeroFor(needNativeHero, color); if (h) { @@ -76,7 +76,7 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe if (giveArmy) { - h->initArmy(gameHandler->getRandomGenerator(), &sah.army); + h->initArmy(getRandomGenerator(color), &sah.army); } else { @@ -212,7 +212,7 @@ std::set HeroPoolProcessor::findAvailableHeroesFor(const Playe return result; } -const CHeroClass * HeroPoolProcessor::pickClassFor(bool isNative, const PlayerColor & player, const FactionID & factionID, CRandomGenerator & rand) const +const CHeroClass * HeroPoolProcessor::pickClassFor(bool isNative, const PlayerColor & player) { if(player >= PlayerColor::PLAYER_LIMIT) { @@ -220,6 +220,7 @@ const CHeroClass * HeroPoolProcessor::pickClassFor(bool isNative, const PlayerCo return nullptr; } + FactionID factionID = gameHandler->getPlayerSettings(player)->castle; const auto & hpool = gameHandler->gameState()->hpool; const auto & currentTavern = hpool->getHeroesFor(player); @@ -257,7 +258,7 @@ const CHeroClass * HeroPoolProcessor::pickClassFor(bool isNative, const PlayerCo for(const auto & heroClass : possibleClasses) totalWeight += heroClass->selectionProbability.at(factionID); - int roll = rand.nextInt(totalWeight - 1); + int roll = getRandomGenerator(player).nextInt(totalWeight - 1); for(const auto & heroClass : possibleClasses) { roll -= heroClass->selectionProbability.at(factionID); @@ -268,13 +269,9 @@ const CHeroClass * HeroPoolProcessor::pickClassFor(bool isNative, const PlayerCo return *possibleClasses.rbegin(); } -CGHeroInstance * HeroPoolProcessor::pickHeroFor(bool isNative, - const PlayerColor & player, - const FactionID & factionID, - CRandomGenerator & rand, - const CHeroClass * bannedClass) const +CGHeroInstance * HeroPoolProcessor::pickHeroFor(bool isNative, const PlayerColor & player) { - const CHeroClass * heroClass = pickClassFor(isNative, player, factionID, rand); + const CHeroClass * heroClass = pickClassFor(isNative, player); if(!heroClass) return nullptr; @@ -285,5 +282,13 @@ CGHeroInstance * HeroPoolProcessor::pickHeroFor(bool isNative, if(possibleHeroes.empty()) return nullptr; - return *RandomGeneratorUtil::nextItem(possibleHeroes, rand); + return *RandomGeneratorUtil::nextItem(possibleHeroes, getRandomGenerator(player)); +} + +CRandomGenerator & HeroPoolProcessor::getRandomGenerator(const PlayerColor & player) +{ + if (playerSeed.count(player) == 0) + playerSeed.emplace(player, CRandomGenerator(gameHandler->getRandomGenerator().nextInt())); + + return playerSeed.at(player); } diff --git a/server/HeroPoolProcessor.h b/server/HeroPoolProcessor.h index 671a48102..f92c401b4 100644 --- a/server/HeroPoolProcessor.h +++ b/server/HeroPoolProcessor.h @@ -28,15 +28,20 @@ class HeroPoolProcessor : boost::noncopyable { CGameHandler * gameHandler; + /// per-player random generators + std::map playerSeed; + void clearHeroFromSlot(const PlayerColor & color, TavernHeroSlot slot); void selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot, bool needNativeHero, bool giveStartingArmy); std::set findAvailableClassesFor(const PlayerColor & player) const; std::set findAvailableHeroesFor(const PlayerColor & player, const CHeroClass * heroClass) const; - const CHeroClass * pickClassFor(bool isNative, const PlayerColor & player, const FactionID & faction, CRandomGenerator & rand) const; + const CHeroClass * pickClassFor(bool isNative, const PlayerColor & player); - CGHeroInstance * pickHeroFor(bool isNative, const PlayerColor & player, const FactionID & faction, CRandomGenerator & rand, const CHeroClass * bannedClass) const; + CGHeroInstance * pickHeroFor(bool isNative, const PlayerColor & player); + + CRandomGenerator & getRandomGenerator(const PlayerColor & player); public: HeroPoolProcessor(); HeroPoolProcessor(CGameHandler * gameHandler); @@ -51,5 +56,6 @@ public: template void serialize(Handler &h, const int version) { h & gameHandler; + h & playerSeed; } }; From ec7e0466173b794534f140305680826357148eb0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 11 Jul 2023 20:29:44 +0300 Subject: [PATCH 5/8] Fix hero pool persistency between saves --- lib/gameState/TavernHeroesPool.h | 1 + server/CGameHandler.cpp | 7 +++++++ server/CGameHandler.h | 5 +++++ server/HeroPoolProcessor.cpp | 33 ++++++++++++++++++++------------ server/HeroPoolProcessor.h | 12 ++++++------ 5 files changed, 40 insertions(+), 18 deletions(-) diff --git a/lib/gameState/TavernHeroesPool.h b/lib/gameState/TavernHeroesPool.h index 79bcccc8b..e77cfe499 100644 --- a/lib/gameState/TavernHeroesPool.h +++ b/lib/gameState/TavernHeroesPool.h @@ -64,6 +64,7 @@ public: { h & heroesPool; h & pavailable; + h & currentTavern; } }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index a2e988392..c96f8d808 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -7242,3 +7242,10 @@ void CGameHandler::createObject(const int3 & visitablePosition, Obj type, int32_ no.targetPos = visitablePosition; sendAndApply(&no); } + +void CGameHandler::deserializationFix() +{ + //FIXME: pointer to GameHandler itself can't be deserialized at the moment since GameHandler is top-level entity in serialization + // restore any places that requires such pointer manually + heroPool->gameHandler = this; +} diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 7da768eaa..2d9f24c6e 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -99,6 +99,8 @@ class CGameHandler : public IGameCallback, public CBattleInfoCallback, public En std::shared_ptr> applier; std::unique_ptr battleThread; + void deserializationFix(); + public: std::unique_ptr heroPool; @@ -290,6 +292,9 @@ public: h & heroPool; h & getRandomGenerator(); + if (!h.saving) + deserializationFix(); + #if SCRIPTING_ENABLED JsonNode scriptsState; if(h.saving) diff --git a/server/HeroPoolProcessor.cpp b/server/HeroPoolProcessor.cpp index 2ad418822..08253531d 100644 --- a/server/HeroPoolProcessor.cpp +++ b/server/HeroPoolProcessor.cpp @@ -175,38 +175,43 @@ bool HeroPoolProcessor::hireHero(const CGObjectInstance *obj, const HeroTypeID & return true; } -std::set HeroPoolProcessor::findAvailableClassesFor(const PlayerColor & player) const +std::vector HeroPoolProcessor::findAvailableClassesFor(const PlayerColor & player) const { - std::set result; + std::vector result; const auto & hpool = gameHandler->gameState()->hpool; FactionID factionID = gameHandler->getPlayerSettings(player)->castle; for(auto & elem : hpool->unusedHeroesFromPool()) { + if (vstd::contains(result, elem.second->type->heroClass)) + continue; + bool heroAvailable = hpool->isHeroAvailableFor(elem.first, player); bool heroClassBanned = elem.second->type->heroClass->selectionProbability[factionID] == 0; if(heroAvailable && !heroClassBanned) - result.insert(elem.second->type->heroClass); + result.push_back(elem.second->type->heroClass); } return result; } -std::set HeroPoolProcessor::findAvailableHeroesFor(const PlayerColor & player, const CHeroClass * heroClass) const +std::vector HeroPoolProcessor::findAvailableHeroesFor(const PlayerColor & player, const CHeroClass * heroClass) const { - std::set result; + std::vector result; const auto & hpool = gameHandler->gameState()->hpool; for(auto & elem : hpool->unusedHeroesFromPool()) { + assert(!vstd::contains(result, elem.second)); + bool heroAvailable = hpool->isHeroAvailableFor(elem.first, player); bool heroClassMatches = elem.second->type->heroClass == heroClass; if(heroAvailable && heroClassMatches) - result.insert(elem.second); + result.push_back(elem.second); } return result; @@ -224,8 +229,8 @@ const CHeroClass * HeroPoolProcessor::pickClassFor(bool isNative, const PlayerCo const auto & hpool = gameHandler->gameState()->hpool; const auto & currentTavern = hpool->getHeroesFor(player); - std::set potentialClasses = findAvailableClassesFor(player); - std::set possibleClasses; + std::vector potentialClasses = findAvailableClassesFor(player); + std::vector possibleClasses; if(potentialClasses.empty()) { @@ -245,7 +250,7 @@ const CHeroClass * HeroPoolProcessor::pickClassFor(bool isNative, const PlayerCo if (hasSameClass) continue; - possibleClasses.insert(heroClass); + possibleClasses.push_back(heroClass); } if (possibleClasses.empty()) @@ -259,6 +264,7 @@ const CHeroClass * HeroPoolProcessor::pickClassFor(bool isNative, const PlayerCo totalWeight += heroClass->selectionProbability.at(factionID); int roll = getRandomGenerator(player).nextInt(totalWeight - 1); + for(const auto & heroClass : possibleClasses) { roll -= heroClass->selectionProbability.at(factionID); @@ -276,7 +282,7 @@ CGHeroInstance * HeroPoolProcessor::pickHeroFor(bool isNative, const PlayerColor if(!heroClass) return nullptr; - std::set possibleHeroes = findAvailableHeroesFor(player, heroClass); + std::vector possibleHeroes = findAvailableHeroesFor(player, heroClass); assert(!possibleHeroes.empty()); if(possibleHeroes.empty()) @@ -288,7 +294,10 @@ CGHeroInstance * HeroPoolProcessor::pickHeroFor(bool isNative, const PlayerColor CRandomGenerator & HeroPoolProcessor::getRandomGenerator(const PlayerColor & player) { if (playerSeed.count(player) == 0) - playerSeed.emplace(player, CRandomGenerator(gameHandler->getRandomGenerator().nextInt())); + { + int seed = gameHandler->getRandomGenerator().nextInt(); + playerSeed.emplace(player, std::make_unique(seed)); + } - return playerSeed.at(player); + return *playerSeed.at(player); } diff --git a/server/HeroPoolProcessor.h b/server/HeroPoolProcessor.h index f92c401b4..3934ba225 100644 --- a/server/HeroPoolProcessor.h +++ b/server/HeroPoolProcessor.h @@ -26,23 +26,24 @@ class CGameHandler; class HeroPoolProcessor : boost::noncopyable { - CGameHandler * gameHandler; - /// per-player random generators - std::map playerSeed; + std::map> playerSeed; void clearHeroFromSlot(const PlayerColor & color, TavernHeroSlot slot); void selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot, bool needNativeHero, bool giveStartingArmy); - std::set findAvailableClassesFor(const PlayerColor & player) const; - std::set findAvailableHeroesFor(const PlayerColor & player, const CHeroClass * heroClass) const; + std::vector findAvailableClassesFor(const PlayerColor & player) const; + std::vector findAvailableHeroesFor(const PlayerColor & player, const CHeroClass * heroClass) const; const CHeroClass * pickClassFor(bool isNative, const PlayerColor & player); CGHeroInstance * pickHeroFor(bool isNative, const PlayerColor & player); CRandomGenerator & getRandomGenerator(const PlayerColor & player); + public: + CGameHandler * gameHandler; + HeroPoolProcessor(); HeroPoolProcessor(CGameHandler * gameHandler); @@ -55,7 +56,6 @@ public: template void serialize(Handler &h, const int version) { - h & gameHandler; h & playerSeed; } }; From 9a38d8ea97fa191a1a16b5152efeb18034b44cb0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 11 Jul 2023 23:08:30 +0300 Subject: [PATCH 6/8] Implemented tavern slot selection using rules similar to H3 --- cmake_modules/VCMI_lib.cmake | 1 + lib/NetPacks.h | 5 +++- lib/NetPacksLib.cpp | 2 +- lib/gameState/TavernHeroesPool.cpp | 48 ++++++++++++++++++++++-------- lib/gameState/TavernHeroesPool.h | 32 +++++++++++++------- lib/gameState/TavernSlot.h | 35 ++++++++++++++++++++++ server/HeroPoolProcessor.cpp | 46 +++++++++++++++++++++++++--- server/HeroPoolProcessor.h | 4 ++- 8 files changed, 144 insertions(+), 29 deletions(-) create mode 100644 lib/gameState/TavernSlot.h diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index 3ddf40f68..70c41549a 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -396,6 +396,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/gameState/InfoAboutArmy.h ${MAIN_LIB_DIR}/gameState/SThievesGuildInfo.h ${MAIN_LIB_DIR}/gameState/TavernHeroesPool.h + ${MAIN_LIB_DIR}/gameState/TavernSlot.h ${MAIN_LIB_DIR}/gameState/QuestInfo.h ${MAIN_LIB_DIR}/logging/CBasicLogConfigurator.h diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 2071079c7..01fd68d1f 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -19,6 +19,7 @@ #include "battle/BattleAction.h" #include "battle/CObstacleInstance.h" #include "gameState/EVictoryLossCheckResult.h" +#include "gameState/TavernSlot.h" #include "gameState/QuestInfo.h" #include "mapObjects/CGHeroInstance.h" #include "mapping/CMapDefines.h" @@ -338,7 +339,8 @@ struct DLL_LINKAGE SetAvailableHero : public CPackForClient } void applyGs(CGameState * gs); - uint8_t slotID; + TavernHeroSlot slotID; + TavernSlotRole roleID; PlayerColor player; HeroTypeID hid; //-1 if no hero CSimpleArmy army; @@ -348,6 +350,7 @@ struct DLL_LINKAGE SetAvailableHero : public CPackForClient template void serialize(Handler & h, const int version) { h & slotID; + h & roleID; h & player; h & hid; h & army; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 78e454ed2..2bbe19aa2 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -942,7 +942,7 @@ void FoWChange::applyGs(CGameState *gs) void SetAvailableHero::applyGs(CGameState *gs) { - gs->hpool->setHeroForPlayer(player, TavernHeroSlot(slotID), hid, army); + gs->hpool->setHeroForPlayer(player, slotID, hid, army, roleID); } void GiveBonus::applyGs(CGameState *gs) diff --git a/lib/gameState/TavernHeroesPool.cpp b/lib/gameState/TavernHeroesPool.cpp index ea5bbc996..b8f0f2636 100644 --- a/lib/gameState/TavernHeroesPool.cpp +++ b/lib/gameState/TavernHeroesPool.cpp @@ -23,17 +23,27 @@ TavernHeroesPool::~TavernHeroesPool() std::map TavernHeroesPool::unusedHeroesFromPool() const { std::map pool = heroesPool; - for(const auto & player : currentTavern) - for(auto availableHero : player.second) - if(availableHero.second) - pool.erase(HeroTypeID(availableHero.second->subID)); + for(const auto & slot : currentTavern) + pool.erase(HeroTypeID(slot.hero->subID)); return pool; } -void TavernHeroesPool::setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, HeroTypeID hero, CSimpleArmy & army) +TavernSlotRole TavernHeroesPool::getSlotRole(HeroTypeID hero) const { - currentTavern[player].erase(slot); + for (auto const & slot : currentTavern) + { + if (HeroTypeID(slot.hero->subID) == hero) + return slot.role; + } + return TavernSlotRole::NONE; +} + +void TavernHeroesPool::setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, HeroTypeID hero, CSimpleArmy & army, TavernSlotRole role) +{ + vstd::erase_if(currentTavern, [&](const TavernSlot & entry){ + return entry.player == player && entry.slot == slot; + }); if (hero == HeroTypeID::NONE) return; @@ -43,7 +53,21 @@ void TavernHeroesPool::setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, if (h && army) h->setToArmy(army); - currentTavern[player][slot] = h; + TavernSlot newSlot; + newSlot.hero = h; + newSlot.player = player; + newSlot.role = TavernSlotRole::SINGLE_UNIT; // TODO + newSlot.slot = slot; + + currentTavern.push_back(newSlot); + + boost::range::sort(currentTavern, [](const TavernSlot & left, const TavernSlot & right) + { + if (left.slot == right.slot) + return left.player < right.player; + else + return left.slot < right.slot; + }); } bool TavernHeroesPool::isHeroAvailableFor(HeroTypeID hero, PlayerColor color) const @@ -58,11 +82,11 @@ std::vector TavernHeroesPool::getHeroesFor(PlayerColor c { std::vector result; - if(!currentTavern.count(color)) - return result; - - for(const auto & hero : currentTavern.at(color)) - result.push_back(hero.second); + for(const auto & slot : currentTavern) + { + if (slot.player == color) + result.push_back(slot.hero); + } return result; } diff --git a/lib/gameState/TavernHeroesPool.h b/lib/gameState/TavernHeroesPool.h index e77cfe499..8472d08e3 100644 --- a/lib/gameState/TavernHeroesPool.h +++ b/lib/gameState/TavernHeroesPool.h @@ -9,8 +9,8 @@ */ #pragma once -#include "../ConstTransitivePtr.h" #include "../GameConstants.h" +#include "TavernSlot.h" VCMI_LIB_NAMESPACE_BEGIN @@ -21,14 +21,24 @@ class CHeroClass; class CGameState; class CSimpleArmy; -enum class TavernHeroSlot -{ - NATIVE, // 1st / left slot in tavern, contains hero native to player's faction on new week - RANDOM // 2nd / right slot in tavern, contains hero of random class -}; - class DLL_LINKAGE TavernHeroesPool { + struct TavernSlot + { + CGHeroInstance * hero; + TavernHeroSlot slot; + TavernSlotRole role; + PlayerColor player; + + template void serialize(Handler &h, const int version) + { + h & hero; + h & slot; + h & role; + h & player; + } + }; + /// list of all heroes in pool, including those currently present in taverns std::map heroesPool; @@ -36,8 +46,8 @@ class DLL_LINKAGE TavernHeroesPool /// if hero is not present in list, he is available for everyone std::map pavailable; - /// list of heroes currently available in a tavern of a specific player - std::map > currentTavern; + /// list of heroes currently available in taverns + std::vector currentTavern; public: ~TavernHeroesPool(); @@ -51,6 +61,8 @@ public: /// Returns true if hero is available to a specific player bool isHeroAvailableFor(HeroTypeID hero, PlayerColor color) const; + TavernSlotRole getSlotRole(HeroTypeID hero) const; + CGHeroInstance * takeHero(HeroTypeID hero); /// reset mana and movement points for all heroes in pool @@ -58,7 +70,7 @@ public: void addHeroToPool(CGHeroInstance * hero); void setAvailability(HeroTypeID hero, PlayerColor::Mask mask); - void setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, HeroTypeID hero, CSimpleArmy & army); + void setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, HeroTypeID hero, CSimpleArmy & army, TavernSlotRole role); template void serialize(Handler &h, const int version) { diff --git a/lib/gameState/TavernSlot.h b/lib/gameState/TavernSlot.h new file mode 100644 index 000000000..0e5a7b35c --- /dev/null +++ b/lib/gameState/TavernSlot.h @@ -0,0 +1,35 @@ +/* + * TavernSlot.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +VCMI_LIB_NAMESPACE_BEGIN + +enum class TavernHeroSlot : int8_t +{ + NONE = -1, + NATIVE, // 1st / left slot in tavern, contains hero native to player's faction on new week + RANDOM // 2nd / right slot in tavern, contains hero of random class +}; + +enum class TavernSlotRole : int8_t +{ + NONE = -1, + + SINGLE_UNIT, // hero was added after buying hero from this slot, and only has 1 creature in army + FULL_ARMY, // hero was added to tavern on new week and still has full army + + RETREATED, // hero was owned by player before, but have retreated from battle and only has 1 creature in army + SURRENDERED, // hero was owned by player before, but have surrendered in battle and kept some troops + +// SURRENDERED_DAY7, // helper value for heroes that surrendered after 7th day during enemy turn +// RETREATED_DAY7, +}; + +VCMI_LIB_NAMESPACE_END diff --git a/server/HeroPoolProcessor.cpp b/server/HeroPoolProcessor.cpp index 08253531d..572cc4e3b 100644 --- a/server/HeroPoolProcessor.cpp +++ b/server/HeroPoolProcessor.cpp @@ -20,6 +20,7 @@ #include "../lib/mapObjects/CGTownInstance.h" #include "../lib/gameState/CGameState.h" #include "../lib/gameState/TavernHeroesPool.h" +#include "../lib/gameState/TavernSlot.h" HeroPoolProcessor::HeroPoolProcessor() : gameHandler(nullptr) @@ -31,10 +32,43 @@ HeroPoolProcessor::HeroPoolProcessor(CGameHandler * gameHandler) { } +TavernHeroSlot HeroPoolProcessor::selectSlotForRole(const PlayerColor & player, TavernSlotRole roleID) +{ + const auto & hpool = gameHandler->gameState()->hpool; + + const auto & heroes = hpool->getHeroesFor(player); + + // if tavern has empty slot - use it + if (heroes.size() == 0) + return TavernHeroSlot::NATIVE; + + if (heroes.size() == 1) + return TavernHeroSlot::RANDOM; + + // try to find "better" slot to overwrite + // we want to avoid overwriting retreated heroes when tavern still has slot with random hero + // as well as avoid overwriting surrendered heroes if we can overwrite retreated hero + auto roleLeft = hpool->getSlotRole(HeroTypeID(heroes[0]->subID)); + auto roleRight = hpool->getSlotRole(HeroTypeID(heroes[1]->subID)); + + if (roleLeft > roleRight) + return TavernHeroSlot::RANDOM; + + if (roleLeft < roleRight) + return TavernHeroSlot::NATIVE; + + // both slots are equal in "value", so select randomly + if (getRandomGenerator(player).nextInt(100) > 50) + return TavernHeroSlot::RANDOM; + else + return TavernHeroSlot::NATIVE; +} + void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHeroInstance * hero) { SetAvailableHero sah; - sah.slotID = 0; + sah.slotID = selectSlotForRole(color, TavernSlotRole::SURRENDERED); + sah.roleID = TavernSlotRole::SURRENDERED; sah.player = color; sah.hid = hero->subID; sah.army.clear(); @@ -45,7 +79,8 @@ void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHer void HeroPoolProcessor::onHeroEscaped(const PlayerColor & color, const CGHeroInstance * hero) { SetAvailableHero sah; - sah.slotID = 0; + sah.slotID = selectSlotForRole(color, TavernSlotRole::RETREATED); + sah.roleID = TavernSlotRole::RETREATED; sah.player = color; sah.hid = hero->subID; @@ -56,7 +91,8 @@ void HeroPoolProcessor::clearHeroFromSlot(const PlayerColor & color, TavernHeroS { SetAvailableHero sah; sah.player = color; - sah.slotID = static_cast(slot); + sah.roleID = TavernSlotRole::NONE; + sah.slotID = slot; sah.hid = HeroTypeID::NONE; gameHandler->sendAndApply(&sah); } @@ -65,7 +101,7 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe { SetAvailableHero sah; sah.player = color; - sah.slotID = static_cast(slot); + sah.slotID = slot; //first hero - native if possible, second hero -> any other class CGHeroInstance *h = pickHeroFor(needNativeHero, color); @@ -76,10 +112,12 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe if (giveArmy) { + sah.roleID = TavernSlotRole::FULL_ARMY; h->initArmy(getRandomGenerator(color), &sah.army); } else { + sah.roleID = TavernSlotRole::SINGLE_UNIT; sah.army.clear(); sah.army.setCreature(SlotID(0), h->type->initialArmy[0].creature, 1); } diff --git a/server/HeroPoolProcessor.h b/server/HeroPoolProcessor.h index 3934ba225..3b61f369f 100644 --- a/server/HeroPoolProcessor.h +++ b/server/HeroPoolProcessor.h @@ -11,7 +11,8 @@ VCMI_LIB_NAMESPACE_BEGIN -enum class TavernHeroSlot; +enum class TavernHeroSlot : int8_t; +enum class TavernSlotRole : int8_t; class PlayerColor; class CGHeroInstance; class HeroTypeID; @@ -41,6 +42,7 @@ class HeroPoolProcessor : boost::noncopyable CRandomGenerator & getRandomGenerator(const PlayerColor & player); + TavernHeroSlot selectSlotForRole(const PlayerColor & player, TavernSlotRole roleID); public: CGameHandler * gameHandler; From cb16636fcee532dec0b34df67c6e2e0369418cb7 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 11 Jul 2023 23:37:17 +0300 Subject: [PATCH 7/8] Implemented preserving hero on retreat on 7th day after end of turn --- lib/gameState/TavernHeroesPool.cpp | 9 +++++ lib/gameState/TavernSlot.h | 6 +-- server/CGameHandler.h | 7 ++-- server/HeroPoolProcessor.cpp | 63 ++++++++++++++++++++++++++---- server/HeroPoolProcessor.h | 3 +- 5 files changed, 73 insertions(+), 15 deletions(-) diff --git a/lib/gameState/TavernHeroesPool.cpp b/lib/gameState/TavernHeroesPool.cpp index b8f0f2636..d66690ffd 100644 --- a/lib/gameState/TavernHeroesPool.cpp +++ b/lib/gameState/TavernHeroesPool.cpp @@ -113,6 +113,15 @@ void TavernHeroesPool::onNewDay() hero.second->setMovementPoints(hero.second->movementPointsLimit(true)); hero.second->mana = hero.second->manaLimit(); } + + for (auto & slot : currentTavern) + { + if (slot.role == TavernSlotRole::RETREATED_TODAY) + slot.role = TavernSlotRole::RETREATED; + + if (slot.role == TavernSlotRole::SURRENDERED_TODAY) + slot.role = TavernSlotRole::SURRENDERED; + } } void TavernHeroesPool::addHeroToPool(CGHeroInstance * hero) diff --git a/lib/gameState/TavernSlot.h b/lib/gameState/TavernSlot.h index 0e5a7b35c..192fd047d 100644 --- a/lib/gameState/TavernSlot.h +++ b/lib/gameState/TavernSlot.h @@ -26,10 +26,10 @@ enum class TavernSlotRole : int8_t FULL_ARMY, // hero was added to tavern on new week and still has full army RETREATED, // hero was owned by player before, but have retreated from battle and only has 1 creature in army - SURRENDERED, // hero was owned by player before, but have surrendered in battle and kept some troops + RETREATED_TODAY, -// SURRENDERED_DAY7, // helper value for heroes that surrendered after 7th day during enemy turn -// RETREATED_DAY7, + SURRENDERED, // hero was owned by player before, but have surrendered in battle and kept some troops + SURRENDERED_TODAY, }; VCMI_LIB_NAMESPACE_END diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 2d9f24c6e..af01e43c1 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -99,8 +99,6 @@ class CGameHandler : public IGameCallback, public CBattleInfoCallback, public En std::shared_ptr> applier; std::unique_ptr battleThread; - void deserializationFix(); - public: std::unique_ptr heroPool; @@ -365,6 +363,8 @@ public: scripting::Pool * getContextPool() const override; #endif + std::list generatePlayerTurnOrder() const; + friend class CVCMIServer; private: std::unique_ptr serverEventBus; @@ -373,8 +373,9 @@ private: #endif void reinitScripting(); + void deserializationFix(); + - std::list generatePlayerTurnOrder() const; void makeStackDoNothing(const CStack * next); void getVictoryLossMessage(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult, InfoWindow & out) const; diff --git a/server/HeroPoolProcessor.cpp b/server/HeroPoolProcessor.cpp index 572cc4e3b..61cd683f6 100644 --- a/server/HeroPoolProcessor.cpp +++ b/server/HeroPoolProcessor.cpp @@ -32,6 +32,29 @@ HeroPoolProcessor::HeroPoolProcessor(CGameHandler * gameHandler) { } +bool HeroPoolProcessor::playerEndedTurn(const PlayerColor & player) +{ + // our player is acting right now and have not ended turn + if (player == gameHandler->gameState()->currentPlayer) + return false; + + auto turnOrder = gameHandler->generatePlayerTurnOrder(); + + for (auto const & entry : turnOrder) + { + // our player is yet to start turn + if (entry == gameHandler->gameState()->currentPlayer) + return false; + + // our player have finished turn + if (entry == player) + return true; + } + + assert(false); + return false; +} + TavernHeroSlot HeroPoolProcessor::selectSlotForRole(const PlayerColor & player, TavernSlotRole roleID) { const auto & hpool = gameHandler->gameState()->hpool; @@ -67,8 +90,12 @@ TavernHeroSlot HeroPoolProcessor::selectSlotForRole(const PlayerColor & player, void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHeroInstance * hero) { SetAvailableHero sah; - sah.slotID = selectSlotForRole(color, TavernSlotRole::SURRENDERED); - sah.roleID = TavernSlotRole::SURRENDERED; + if (playerEndedTurn(color)) + sah.roleID = TavernSlotRole::SURRENDERED_TODAY; + else + sah.roleID = TavernSlotRole::SURRENDERED; + + sah.slotID = selectSlotForRole(color, sah.roleID); sah.player = color; sah.hid = hero->subID; sah.army.clear(); @@ -79,8 +106,12 @@ void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHer void HeroPoolProcessor::onHeroEscaped(const PlayerColor & color, const CGHeroInstance * hero) { SetAvailableHero sah; - sah.slotID = selectSlotForRole(color, TavernSlotRole::RETREATED); - sah.roleID = TavernSlotRole::RETREATED; + if (playerEndedTurn(color)) + sah.roleID = TavernSlotRole::RETREATED_TODAY; + else + sah.roleID = TavernSlotRole::RETREATED; + + sah.slotID = selectSlotForRole(color, sah.roleID); sah.player = color; sah.hid = hero->subID; @@ -131,10 +162,26 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe void HeroPoolProcessor::onNewWeek(const PlayerColor & color) { - clearHeroFromSlot(color, TavernHeroSlot::NATIVE); - clearHeroFromSlot(color, TavernHeroSlot::RANDOM); - selectNewHeroForSlot(color, TavernHeroSlot::NATIVE, true, true); - selectNewHeroForSlot(color, TavernHeroSlot::RANDOM, false, true); + const auto & hpool = gameHandler->gameState()->hpool; + const auto & heroes = hpool->getHeroesFor(color); + + const auto nativeSlotRole = heroes.size() < 1 ? TavernSlotRole::NONE : hpool->getSlotRole(heroes[0]->type->getId()); + const auto randomSlotRole = heroes.size() < 2 ? TavernSlotRole::NONE : hpool->getSlotRole(heroes[1]->type->getId()); + + bool resetNativeSlot = nativeSlotRole != TavernSlotRole::RETREATED_TODAY && nativeSlotRole != TavernSlotRole::SURRENDERED_TODAY; + bool resetRandomSlot = randomSlotRole != TavernSlotRole::RETREATED_TODAY && randomSlotRole != TavernSlotRole::SURRENDERED_TODAY; + + if (resetNativeSlot) + clearHeroFromSlot(color, TavernHeroSlot::NATIVE); + + if (resetRandomSlot) + clearHeroFromSlot(color, TavernHeroSlot::RANDOM); + + if (resetNativeSlot) + selectNewHeroForSlot(color, TavernHeroSlot::NATIVE, true, true); + + if (resetRandomSlot) + selectNewHeroForSlot(color, TavernHeroSlot::RANDOM, false, true); } bool HeroPoolProcessor::hireHero(const CGObjectInstance *obj, const HeroTypeID & heroToRecruit, const PlayerColor & player) diff --git a/server/HeroPoolProcessor.h b/server/HeroPoolProcessor.h index 3b61f369f..38b24bb32 100644 --- a/server/HeroPoolProcessor.h +++ b/server/HeroPoolProcessor.h @@ -17,7 +17,6 @@ class PlayerColor; class CGHeroInstance; class HeroTypeID; class CGObjectInstance; -class FactionID; class CRandomGenerator; class CHeroClass; @@ -43,6 +42,8 @@ class HeroPoolProcessor : boost::noncopyable CRandomGenerator & getRandomGenerator(const PlayerColor & player); TavernHeroSlot selectSlotForRole(const PlayerColor & player, TavernSlotRole roleID); + + bool playerEndedTurn(const PlayerColor & player); public: CGameHandler * gameHandler; From 463efea7bb6d357b1236283d862e85c4f4e93517 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 12 Jul 2023 12:29:05 +0300 Subject: [PATCH 8/8] Fix hero retreating handling & code cleanup --- lib/CGameInfoCallback.cpp | 2 +- lib/GameConstants.h | 1 - lib/NetPacks.h | 2 +- lib/NetPacksLib.cpp | 8 ++-- lib/gameState/CGameState.cpp | 8 ++-- lib/gameState/CGameState.h | 4 +- lib/gameState/TavernHeroesPool.cpp | 15 ++++-- lib/gameState/TavernHeroesPool.h | 10 ++-- lib/mapping/CMap.h | 2 +- server/HeroPoolProcessor.cpp | 75 +++++++++++++++++------------- server/HeroPoolProcessor.h | 6 ++- server/NetPacksServer.cpp | 8 ++-- 12 files changed, 79 insertions(+), 62 deletions(-) diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 940c33489..16b10d9d8 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -482,7 +482,7 @@ std::vector CGameInfoCallback::getAvailableHeroes(const const CGTownInstance * town = getTown(townOrTavern->id); if(townOrTavern->ID == Obj::TAVERN || (town && town->hasBuilt(BuildingID::TAVERN))) - return gs->hpool->getHeroesFor(*player); + return gs->heroesPool->getHeroesFor(*player); return ret; } diff --git a/lib/GameConstants.h b/lib/GameConstants.h index bb0acfd91..9cc315f6b 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -358,7 +358,6 @@ class PlayerColor : public BaseForID enum EPlayerColor { PLAYER_LIMIT_I = 8, - ALL_PLAYERS_MASK = 0xff }; using Mask = uint8_t; diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 01fd68d1f..d56dac4f8 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -342,7 +342,7 @@ struct DLL_LINKAGE SetAvailableHero : public CPackForClient TavernHeroSlot slotID; TavernSlotRole roleID; PlayerColor player; - HeroTypeID hid; //-1 if no hero + HeroTypeID hid; //HeroTypeID::NONE if no hero CSimpleArmy army; virtual void visitTyped(ICPackVisitor & visitor) override; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 2bbe19aa2..67e64463b 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -942,7 +942,7 @@ void FoWChange::applyGs(CGameState *gs) void SetAvailableHero::applyGs(CGameState *gs) { - gs->hpool->setHeroForPlayer(player, slotID, hid, army, roleID); + gs->heroesPool->setHeroForPlayer(player, slotID, hid, army, roleID); } void GiveBonus::applyGs(CGameState *gs) @@ -1143,7 +1143,7 @@ void RemoveObject::applyGs(CGameState *gs) } //return hero to the pool, so he may reappear in tavern - gs->hpool->addHeroToPool(beatenHero); + gs->heroesPool->addHeroToPool(beatenHero); gs->map->objects[id.getNum()] = nullptr; //If hero on Boat is removed, the Boat disappears @@ -1368,7 +1368,7 @@ void SetHeroesInTown::applyGs(CGameState * gs) const void HeroRecruited::applyGs(CGameState * gs) const { - CGHeroInstance *h = gs->hpool->takeHero(hid); + CGHeroInstance *h = gs->heroesPool->takeHeroFromPool(hid); CGTownInstance *t = gs->getTown(tid); PlayerState *p = gs->getPlayerState(player); @@ -2017,7 +2017,7 @@ void NewTurn::applyGs(CGameState *gs) hero->mana = h.mana; } - gs->hpool->onNewDay(); + gs->heroesPool->onNewDay(); for(const auto & re : res) { diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index abf9ebf44..7108232d9 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -385,7 +385,7 @@ int CGameState::getDate(Date::EDateType mode) const CGameState::CGameState() { gs = this; - hpool = std::make_unique(); + heroesPool = std::make_unique(); applier = std::make_shared>(); registerTypesClientPacks1(*applier); registerTypesClientPacks2(*applier); @@ -875,7 +875,7 @@ void CGameState::initHeroes() if(!vstd::contains(heroesToCreate, HeroTypeID(ph->subID))) continue; ph->initHero(getRandomGenerator()); - hpool->addHeroToPool(ph); + heroesPool->addHeroToPool(ph); heroesToCreate.erase(ph->type->getId()); map->allHeroes[ph->subID] = ph; @@ -888,11 +888,11 @@ void CGameState::initHeroes() int typeID = htype.getNum(); map->allHeroes[typeID] = vhi; - hpool->addHeroToPool(vhi); + heroesPool->addHeroToPool(vhi); } for(auto & elem : map->disposedHeroes) - hpool->setAvailability(elem.heroId, elem.players); + heroesPool->setAvailability(elem.heroId, elem.players); if (campaign) campaign->initHeroes(); diff --git a/lib/gameState/CGameState.h b/lib/gameState/CGameState.h index e11bd8a4f..0281aa18e 100644 --- a/lib/gameState/CGameState.h +++ b/lib/gameState/CGameState.h @@ -82,7 +82,7 @@ class DLL_LINKAGE CGameState : public CNonConstInfoCallback public: //we have here all heroes available on this map that are not hired - std::unique_ptr hpool; + std::unique_ptr heroesPool; CGameState(); virtual ~CGameState(); @@ -154,7 +154,7 @@ public: h & map; h & players; h & teams; - h & hpool; + h & heroesPool; h & globalEffects; h & rand; h & rumor; diff --git a/lib/gameState/TavernHeroesPool.cpp b/lib/gameState/TavernHeroesPool.cpp index d66690ffd..895c052e2 100644 --- a/lib/gameState/TavernHeroesPool.cpp +++ b/lib/gameState/TavernHeroesPool.cpp @@ -11,6 +11,7 @@ #include "TavernHeroesPool.h" #include "../mapObjects/CGHeroInstance.h" +#include "../CHeroHandler.h" VCMI_LIB_NAMESPACE_BEGIN @@ -56,7 +57,7 @@ void TavernHeroesPool::setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, TavernSlot newSlot; newSlot.hero = h; newSlot.player = player; - newSlot.role = TavernSlotRole::SINGLE_UNIT; // TODO + newSlot.role = role; newSlot.slot = slot; currentTavern.push_back(newSlot); @@ -72,8 +73,8 @@ void TavernHeroesPool::setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, bool TavernHeroesPool::isHeroAvailableFor(HeroTypeID hero, PlayerColor color) const { - if (pavailable.count(hero)) - return pavailable.at(hero) & (1 << color.getNum()); + if (perPlayerAvailability.count(hero)) + return perPlayerAvailability.at(hero) & (1 << color.getNum()); return true; } @@ -91,13 +92,17 @@ std::vector TavernHeroesPool::getHeroesFor(PlayerColor c return result; } -CGHeroInstance * TavernHeroesPool::takeHero(HeroTypeID hero) +CGHeroInstance * TavernHeroesPool::takeHeroFromPool(HeroTypeID hero) { assert(heroesPool.count(hero)); CGHeroInstance * result = heroesPool[hero]; heroesPool.erase(hero); + vstd::erase_if(currentTavern, [&](const TavernSlot & entry){ + return entry.hero->type->getId() == hero; + }); + assert(result); return result; } @@ -131,7 +136,7 @@ void TavernHeroesPool::addHeroToPool(CGHeroInstance * hero) void TavernHeroesPool::setAvailability(HeroTypeID hero, PlayerColor::Mask mask) { - pavailable[hero] = mask; + perPlayerAvailability[hero] = mask; } VCMI_LIB_NAMESPACE_END diff --git a/lib/gameState/TavernHeroesPool.h b/lib/gameState/TavernHeroesPool.h index 8472d08e3..97c54879c 100644 --- a/lib/gameState/TavernHeroesPool.h +++ b/lib/gameState/TavernHeroesPool.h @@ -44,7 +44,7 @@ class DLL_LINKAGE TavernHeroesPool /// list of which players are able to purchase specific hero /// if hero is not present in list, he is available for everyone - std::map pavailable; + std::map perPlayerAvailability; /// list of heroes currently available in taverns std::vector currentTavern; @@ -63,19 +63,23 @@ public: TavernSlotRole getSlotRole(HeroTypeID hero) const; - CGHeroInstance * takeHero(HeroTypeID hero); + CGHeroInstance * takeHeroFromPool(HeroTypeID hero); /// reset mana and movement points for all heroes in pool void onNewDay(); void addHeroToPool(CGHeroInstance * hero); + + /// Marks hero as available to only specific set of players void setAvailability(HeroTypeID hero, PlayerColor::Mask mask); + + /// Makes hero available in tavern of specified player void setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, HeroTypeID hero, CSimpleArmy & army, TavernSlotRole role); template void serialize(Handler &h, const int version) { h & heroesPool; - h & pavailable; + h & perPlayerAvailability; h & currentTavern; } }; diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index dc8dc60af..a6ef1e973 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -57,7 +57,7 @@ struct DLL_LINKAGE DisposedHero DisposedHero(); HeroTypeID heroId; - ui32 portrait; /// The portrait id of the hero, -1 is default. + HeroTypeID portrait; /// The portrait id of the hero, -1 is default. std::string name; PlayerColor::Mask players; /// Who can hire this hero (bitfield). diff --git a/server/HeroPoolProcessor.cpp b/server/HeroPoolProcessor.cpp index 61cd683f6..9923123dd 100644 --- a/server/HeroPoolProcessor.cpp +++ b/server/HeroPoolProcessor.cpp @@ -57,9 +57,9 @@ bool HeroPoolProcessor::playerEndedTurn(const PlayerColor & player) TavernHeroSlot HeroPoolProcessor::selectSlotForRole(const PlayerColor & player, TavernSlotRole roleID) { - const auto & hpool = gameHandler->gameState()->hpool; + const auto & heroesPool = gameHandler->gameState()->heroesPool; - const auto & heroes = hpool->getHeroesFor(player); + const auto & heroes = heroesPool->getHeroesFor(player); // if tavern has empty slot - use it if (heroes.size() == 0) @@ -71,8 +71,8 @@ TavernHeroSlot HeroPoolProcessor::selectSlotForRole(const PlayerColor & player, // try to find "better" slot to overwrite // we want to avoid overwriting retreated heroes when tavern still has slot with random hero // as well as avoid overwriting surrendered heroes if we can overwrite retreated hero - auto roleLeft = hpool->getSlotRole(HeroTypeID(heroes[0]->subID)); - auto roleRight = hpool->getSlotRole(HeroTypeID(heroes[1]->subID)); + auto roleLeft = heroesPool->getSlotRole(HeroTypeID(heroes[0]->subID)); + auto roleRight = heroesPool->getSlotRole(HeroTypeID(heroes[1]->subID)); if (roleLeft > roleRight) return TavernHeroSlot::RANDOM; @@ -98,8 +98,6 @@ void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHer sah.slotID = selectSlotForRole(color, sah.roleID); sah.player = color; sah.hid = hero->subID; - sah.army.clear(); - sah.army.setCreature(SlotID(0), hero->type->initialArmy.at(0).creature, 1); gameHandler->sendAndApply(&sah); } @@ -114,6 +112,8 @@ void HeroPoolProcessor::onHeroEscaped(const PlayerColor & color, const CGHeroIns sah.slotID = selectSlotForRole(color, sah.roleID); sah.player = color; sah.hid = hero->subID; + sah.army.clear(); + sah.army.setCreature(SlotID(0), hero->type->initialArmy.at(0).creature, 1); gameHandler->sendAndApply(&sah); } @@ -134,23 +134,22 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe sah.player = color; sah.slotID = slot; - //first hero - native if possible, second hero -> any other class - CGHeroInstance *h = pickHeroFor(needNativeHero, color); + CGHeroInstance *newHero = pickHeroFor(needNativeHero, color); - if (h) + if (newHero) { - sah.hid = h->subID; + sah.hid = newHero->subID; if (giveArmy) { sah.roleID = TavernSlotRole::FULL_ARMY; - h->initArmy(getRandomGenerator(color), &sah.army); + newHero->initArmy(getRandomGenerator(color), &sah.army); } else { sah.roleID = TavernSlotRole::SINGLE_UNIT; sah.army.clear(); - sah.army.setCreature(SlotID(0), h->type->initialArmy[0].creature, 1); + sah.army.setCreature(SlotID(0), newHero->type->initialArmy[0].creature, 1); } } else @@ -162,11 +161,11 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe void HeroPoolProcessor::onNewWeek(const PlayerColor & color) { - const auto & hpool = gameHandler->gameState()->hpool; - const auto & heroes = hpool->getHeroesFor(color); + const auto & heroesPool = gameHandler->gameState()->heroesPool; + const auto & heroes = heroesPool->getHeroesFor(color); - const auto nativeSlotRole = heroes.size() < 1 ? TavernSlotRole::NONE : hpool->getSlotRole(heroes[0]->type->getId()); - const auto randomSlotRole = heroes.size() < 2 ? TavernSlotRole::NONE : hpool->getSlotRole(heroes[1]->type->getId()); + const auto nativeSlotRole = heroes.size() < 1 ? TavernSlotRole::NONE : heroesPool->getSlotRole(heroes[0]->type->getId()); + const auto randomSlotRole = heroes.size() < 2 ? TavernSlotRole::NONE : heroesPool->getSlotRole(heroes[1]->type->getId()); bool resetNativeSlot = nativeSlotRole != TavernSlotRole::RETREATED_TODAY && nativeSlotRole != TavernSlotRole::SURRENDERED_TODAY; bool resetRandomSlot = randomSlotRole != TavernSlotRole::RETREATED_TODAY && randomSlotRole != TavernSlotRole::SURRENDERED_TODAY; @@ -184,10 +183,17 @@ void HeroPoolProcessor::onNewWeek(const PlayerColor & color) selectNewHeroForSlot(color, TavernHeroSlot::RANDOM, false, true); } -bool HeroPoolProcessor::hireHero(const CGObjectInstance *obj, const HeroTypeID & heroToRecruit, const PlayerColor & player) +bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTypeID & heroToRecruit, const PlayerColor & player) { const PlayerState * playerState = gameHandler->getPlayerState(player); - const CGTownInstance * town = gameHandler->getTown(obj->id); + const CGObjectInstance * mapObject = gameHandler->getObj(objectID); + const CGTownInstance * town = gameHandler->getTown(objectID); + + if (!mapObject && gameHandler->complain("Invalid map object!")) + return false; + + if (!playerState && gameHandler->complain("Invalid player!")) + return false; if (playerState->resources[EGameResID::GOLD] < GameConstants::HERO_GOLD_COST && gameHandler->complain("Not enough gold for buying hero!")) return false; @@ -200,6 +206,9 @@ bool HeroPoolProcessor::hireHero(const CGObjectInstance *obj, const HeroTypeID & if(town) //tavern in town { + if(gameHandler->getPlayerRelations(mapObject->tempOwner, player) == PlayerRelations::ENEMIES && gameHandler->complain("Can't buy hero in enemy town!")) + return false; + if(!town->hasBuilt(BuildingID::TAVERN) && gameHandler->complain("No tavern!")) return false; @@ -207,13 +216,13 @@ bool HeroPoolProcessor::hireHero(const CGObjectInstance *obj, const HeroTypeID & return false; } - if(obj->ID == Obj::TAVERN) + if(mapObject->ID == Obj::TAVERN) { - if(gameHandler->getTile(obj->visitablePos())->visitableObjects.back() != obj && gameHandler->complain("Tavern entry must be unoccupied!")) + if(gameHandler->getTile(mapObject->visitablePos())->visitableObjects.back() != mapObject && gameHandler->complain("Tavern entry must be unoccupied!")) return false; } - auto recruitableHeroes = gameHandler->gameState()->hpool->getHeroesFor(player); + auto recruitableHeroes = gameHandler->gameState()->heroesPool->getHeroesFor(player); const CGHeroInstance * recruitedHero = nullptr; @@ -230,19 +239,19 @@ bool HeroPoolProcessor::hireHero(const CGObjectInstance *obj, const HeroTypeID & } HeroRecruited hr; - hr.tid = obj->id; + hr.tid = mapObject->id; hr.hid = recruitedHero->subID; hr.player = player; - hr.tile = recruitedHero->convertFromVisitablePos(obj->visitablePos()); + hr.tile = recruitedHero->convertFromVisitablePos(mapObject->visitablePos()); if(gameHandler->getTile(hr.tile)->isWater()) { //Create a new boat for hero - gameHandler->createObject(obj->visitablePos(), Obj::BOAT, recruitedHero->getBoatType().getNum()); + gameHandler->createObject(mapObject->visitablePos(), Obj::BOAT, recruitedHero->getBoatType().getNum()); hr.boatId = gameHandler->getTopObj(hr.tile)->id; } - // apply netpack -> this will remove hired hero from tavern slot + // apply netpack -> this will remove hired hero from pool gameHandler->sendAndApply(&hr); if(recruitableHeroes[0] == recruitedHero) @@ -264,15 +273,15 @@ std::vector HeroPoolProcessor::findAvailableClassesFor(const { std::vector result; - const auto & hpool = gameHandler->gameState()->hpool; + const auto & heroesPool = gameHandler->gameState()->heroesPool; FactionID factionID = gameHandler->getPlayerSettings(player)->castle; - for(auto & elem : hpool->unusedHeroesFromPool()) + for(auto & elem : heroesPool->unusedHeroesFromPool()) { if (vstd::contains(result, elem.second->type->heroClass)) continue; - bool heroAvailable = hpool->isHeroAvailableFor(elem.first, player); + bool heroAvailable = heroesPool->isHeroAvailableFor(elem.first, player); bool heroClassBanned = elem.second->type->heroClass->selectionProbability[factionID] == 0; if(heroAvailable && !heroClassBanned) @@ -286,13 +295,13 @@ std::vector HeroPoolProcessor::findAvailableHeroesFor(const Pl { std::vector result; - const auto & hpool = gameHandler->gameState()->hpool; + const auto & heroesPool = gameHandler->gameState()->heroesPool; - for(auto & elem : hpool->unusedHeroesFromPool()) + for(auto & elem : heroesPool->unusedHeroesFromPool()) { assert(!vstd::contains(result, elem.second)); - bool heroAvailable = hpool->isHeroAvailableFor(elem.first, player); + bool heroAvailable = heroesPool->isHeroAvailableFor(elem.first, player); bool heroClassMatches = elem.second->type->heroClass == heroClass; if(heroAvailable && heroClassMatches) @@ -311,8 +320,8 @@ const CHeroClass * HeroPoolProcessor::pickClassFor(bool isNative, const PlayerCo } FactionID factionID = gameHandler->getPlayerSettings(player)->castle; - const auto & hpool = gameHandler->gameState()->hpool; - const auto & currentTavern = hpool->getHeroesFor(player); + const auto & heroesPool = gameHandler->gameState()->heroesPool; + const auto & currentTavern = heroesPool->getHeroesFor(player); std::vector potentialClasses = findAvailableClassesFor(player); std::vector possibleClasses; diff --git a/server/HeroPoolProcessor.h b/server/HeroPoolProcessor.h index 38b24bb32..dc69a7cf3 100644 --- a/server/HeroPoolProcessor.h +++ b/server/HeroPoolProcessor.h @@ -16,7 +16,7 @@ enum class TavernSlotRole : int8_t; class PlayerColor; class CGHeroInstance; class HeroTypeID; -class CGObjectInstance; +class ObjectInstanceID; class CRandomGenerator; class CHeroClass; @@ -55,10 +55,12 @@ public: void onNewWeek(const PlayerColor & color); - bool hireHero(const CGObjectInstance *obj, const HeroTypeID & hid, const PlayerColor & player); + /// Incoming net pack handling + bool hireHero(const ObjectInstanceID & objectID, const HeroTypeID & hid, const PlayerColor & player); template void serialize(Handler &h, const int version) { + // h & gameHandler; // FIXME: make this work instead of using deserializationFix in gameHandler h & playerSeed; } }; diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index d4ceffb47..dc3fd7ef1 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -248,12 +248,10 @@ void ApplyGhNetPackVisitor::visitSetFormation(SetFormation & pack) void ApplyGhNetPackVisitor::visitHireHero(HireHero & pack) { - const CGObjectInstance * obj = gh.getObj(pack.tid); - const CGTownInstance * town = dynamic_ptr_cast(obj); - if(town && PlayerRelations::ENEMIES == gh.getPlayerRelations(obj->tempOwner, gh.getPlayerAt(pack.c))) - gh.throwAndComplain(&pack, "Can't buy hero in enemy town!"); + if (!gh.hasPlayerAt(pack.player, pack.c)) + gh.throwAndComplain(&pack, "No such pack.player!"); - result = gh.heroPool->hireHero(obj, pack.hid, pack.player); + result = gh.heroPool->hireHero(pack.tid, pack.hid, pack.player); } void ApplyGhNetPackVisitor::visitBuildBoat(BuildBoat & pack)