From 9a38d8ea97fa191a1a16b5152efeb18034b44cb0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 11 Jul 2023 23:08:30 +0300 Subject: [PATCH] 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;