1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-06-19 00:17:56 +02:00

Implemented tavern slot selection using rules similar to H3

This commit is contained in:
Ivan Savenko
2023-07-11 23:08:30 +03:00
parent ec7e046617
commit 9a38d8ea97
8 changed files with 144 additions and 29 deletions

View File

@ -396,6 +396,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/gameState/InfoAboutArmy.h ${MAIN_LIB_DIR}/gameState/InfoAboutArmy.h
${MAIN_LIB_DIR}/gameState/SThievesGuildInfo.h ${MAIN_LIB_DIR}/gameState/SThievesGuildInfo.h
${MAIN_LIB_DIR}/gameState/TavernHeroesPool.h ${MAIN_LIB_DIR}/gameState/TavernHeroesPool.h
${MAIN_LIB_DIR}/gameState/TavernSlot.h
${MAIN_LIB_DIR}/gameState/QuestInfo.h ${MAIN_LIB_DIR}/gameState/QuestInfo.h
${MAIN_LIB_DIR}/logging/CBasicLogConfigurator.h ${MAIN_LIB_DIR}/logging/CBasicLogConfigurator.h

View File

@ -19,6 +19,7 @@
#include "battle/BattleAction.h" #include "battle/BattleAction.h"
#include "battle/CObstacleInstance.h" #include "battle/CObstacleInstance.h"
#include "gameState/EVictoryLossCheckResult.h" #include "gameState/EVictoryLossCheckResult.h"
#include "gameState/TavernSlot.h"
#include "gameState/QuestInfo.h" #include "gameState/QuestInfo.h"
#include "mapObjects/CGHeroInstance.h" #include "mapObjects/CGHeroInstance.h"
#include "mapping/CMapDefines.h" #include "mapping/CMapDefines.h"
@ -338,7 +339,8 @@ struct DLL_LINKAGE SetAvailableHero : public CPackForClient
} }
void applyGs(CGameState * gs); void applyGs(CGameState * gs);
uint8_t slotID; TavernHeroSlot slotID;
TavernSlotRole roleID;
PlayerColor player; PlayerColor player;
HeroTypeID hid; //-1 if no hero HeroTypeID hid; //-1 if no hero
CSimpleArmy army; CSimpleArmy army;
@ -348,6 +350,7 @@ struct DLL_LINKAGE SetAvailableHero : public CPackForClient
template <typename Handler> void serialize(Handler & h, const int version) template <typename Handler> void serialize(Handler & h, const int version)
{ {
h & slotID; h & slotID;
h & roleID;
h & player; h & player;
h & hid; h & hid;
h & army; h & army;

View File

@ -942,7 +942,7 @@ void FoWChange::applyGs(CGameState *gs)
void SetAvailableHero::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) void GiveBonus::applyGs(CGameState *gs)

View File

@ -23,17 +23,27 @@ TavernHeroesPool::~TavernHeroesPool()
std::map<HeroTypeID, CGHeroInstance*> TavernHeroesPool::unusedHeroesFromPool() const std::map<HeroTypeID, CGHeroInstance*> TavernHeroesPool::unusedHeroesFromPool() const
{ {
std::map<HeroTypeID, CGHeroInstance*> pool = heroesPool; std::map<HeroTypeID, CGHeroInstance*> pool = heroesPool;
for(const auto & player : currentTavern) for(const auto & slot : currentTavern)
for(auto availableHero : player.second) pool.erase(HeroTypeID(slot.hero->subID));
if(availableHero.second)
pool.erase(HeroTypeID(availableHero.second->subID));
return pool; 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) if (hero == HeroTypeID::NONE)
return; return;
@ -43,7 +53,21 @@ void TavernHeroesPool::setHeroForPlayer(PlayerColor player, TavernHeroSlot slot,
if (h && army) if (h && army)
h->setToArmy(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 bool TavernHeroesPool::isHeroAvailableFor(HeroTypeID hero, PlayerColor color) const
@ -58,11 +82,11 @@ std::vector<const CGHeroInstance *> TavernHeroesPool::getHeroesFor(PlayerColor c
{ {
std::vector<const CGHeroInstance *> result; std::vector<const CGHeroInstance *> result;
if(!currentTavern.count(color)) for(const auto & slot : currentTavern)
return result; {
if (slot.player == color)
for(const auto & hero : currentTavern.at(color)) result.push_back(slot.hero);
result.push_back(hero.second); }
return result; return result;
} }

View File

@ -9,8 +9,8 @@
*/ */
#pragma once #pragma once
#include "../ConstTransitivePtr.h"
#include "../GameConstants.h" #include "../GameConstants.h"
#include "TavernSlot.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
@ -21,14 +21,24 @@ class CHeroClass;
class CGameState; class CGameState;
class CSimpleArmy; 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 class DLL_LINKAGE TavernHeroesPool
{ {
struct TavernSlot
{
CGHeroInstance * hero;
TavernHeroSlot slot;
TavernSlotRole role;
PlayerColor player;
template <typename Handler> 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 /// list of all heroes in pool, including those currently present in taverns
std::map<HeroTypeID, CGHeroInstance* > heroesPool; std::map<HeroTypeID, CGHeroInstance* > heroesPool;
@ -36,8 +46,8 @@ class DLL_LINKAGE TavernHeroesPool
/// if hero is not present in list, he is available for everyone /// if hero is not present in list, he is available for everyone
std::map<HeroTypeID, PlayerColor::Mask> pavailable; std::map<HeroTypeID, PlayerColor::Mask> pavailable;
/// list of heroes currently available in a tavern of a specific player /// list of heroes currently available in taverns
std::map<PlayerColor, std::map<TavernHeroSlot, CGHeroInstance*> > currentTavern; std::vector<TavernSlot> currentTavern;
public: public:
~TavernHeroesPool(); ~TavernHeroesPool();
@ -51,6 +61,8 @@ public:
/// Returns true if hero is available to a specific player /// Returns true if hero is available to a specific player
bool isHeroAvailableFor(HeroTypeID hero, PlayerColor color) const; bool isHeroAvailableFor(HeroTypeID hero, PlayerColor color) const;
TavernSlotRole getSlotRole(HeroTypeID hero) const;
CGHeroInstance * takeHero(HeroTypeID hero); CGHeroInstance * takeHero(HeroTypeID hero);
/// reset mana and movement points for all heroes in pool /// reset mana and movement points for all heroes in pool
@ -58,7 +70,7 @@ public:
void addHeroToPool(CGHeroInstance * hero); void addHeroToPool(CGHeroInstance * hero);
void setAvailability(HeroTypeID hero, PlayerColor::Mask mask); 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 <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {

View File

@ -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

View File

@ -20,6 +20,7 @@
#include "../lib/mapObjects/CGTownInstance.h" #include "../lib/mapObjects/CGTownInstance.h"
#include "../lib/gameState/CGameState.h" #include "../lib/gameState/CGameState.h"
#include "../lib/gameState/TavernHeroesPool.h" #include "../lib/gameState/TavernHeroesPool.h"
#include "../lib/gameState/TavernSlot.h"
HeroPoolProcessor::HeroPoolProcessor() HeroPoolProcessor::HeroPoolProcessor()
: gameHandler(nullptr) : 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) void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHeroInstance * hero)
{ {
SetAvailableHero sah; SetAvailableHero sah;
sah.slotID = 0; sah.slotID = selectSlotForRole(color, TavernSlotRole::SURRENDERED);
sah.roleID = TavernSlotRole::SURRENDERED;
sah.player = color; sah.player = color;
sah.hid = hero->subID; sah.hid = hero->subID;
sah.army.clear(); sah.army.clear();
@ -45,7 +79,8 @@ void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHer
void HeroPoolProcessor::onHeroEscaped(const PlayerColor & color, const CGHeroInstance * hero) void HeroPoolProcessor::onHeroEscaped(const PlayerColor & color, const CGHeroInstance * hero)
{ {
SetAvailableHero sah; SetAvailableHero sah;
sah.slotID = 0; sah.slotID = selectSlotForRole(color, TavernSlotRole::RETREATED);
sah.roleID = TavernSlotRole::RETREATED;
sah.player = color; sah.player = color;
sah.hid = hero->subID; sah.hid = hero->subID;
@ -56,7 +91,8 @@ void HeroPoolProcessor::clearHeroFromSlot(const PlayerColor & color, TavernHeroS
{ {
SetAvailableHero sah; SetAvailableHero sah;
sah.player = color; sah.player = color;
sah.slotID = static_cast<int>(slot); sah.roleID = TavernSlotRole::NONE;
sah.slotID = slot;
sah.hid = HeroTypeID::NONE; sah.hid = HeroTypeID::NONE;
gameHandler->sendAndApply(&sah); gameHandler->sendAndApply(&sah);
} }
@ -65,7 +101,7 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe
{ {
SetAvailableHero sah; SetAvailableHero sah;
sah.player = color; sah.player = color;
sah.slotID = static_cast<int>(slot); sah.slotID = slot;
//first hero - native if possible, second hero -> any other class //first hero - native if possible, second hero -> any other class
CGHeroInstance *h = pickHeroFor(needNativeHero, color); CGHeroInstance *h = pickHeroFor(needNativeHero, color);
@ -76,10 +112,12 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe
if (giveArmy) if (giveArmy)
{ {
sah.roleID = TavernSlotRole::FULL_ARMY;
h->initArmy(getRandomGenerator(color), &sah.army); h->initArmy(getRandomGenerator(color), &sah.army);
} }
else else
{ {
sah.roleID = TavernSlotRole::SINGLE_UNIT;
sah.army.clear(); sah.army.clear();
sah.army.setCreature(SlotID(0), h->type->initialArmy[0].creature, 1); sah.army.setCreature(SlotID(0), h->type->initialArmy[0].creature, 1);
} }

View File

@ -11,7 +11,8 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
enum class TavernHeroSlot; enum class TavernHeroSlot : int8_t;
enum class TavernSlotRole : int8_t;
class PlayerColor; class PlayerColor;
class CGHeroInstance; class CGHeroInstance;
class HeroTypeID; class HeroTypeID;
@ -41,6 +42,7 @@ class HeroPoolProcessor : boost::noncopyable
CRandomGenerator & getRandomGenerator(const PlayerColor & player); CRandomGenerator & getRandomGenerator(const PlayerColor & player);
TavernHeroSlot selectSlotForRole(const PlayerColor & player, TavernSlotRole roleID);
public: public:
CGameHandler * gameHandler; CGameHandler * gameHandler;