فهرست منبع

Merge pull request #3530 from Laserlicht/tavern

Invite hero
Ivan Savenko 1 سال پیش
والد
کامیت
4d0c0f10a9

+ 2 - 2
CCallback.cpp

@@ -261,12 +261,12 @@ void CCallback::setFormation(const CGHeroInstance * hero, EArmyFormation mode)
 	sendRequest(&pack);
 }
 
-void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero)
+void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero)
 {
 	assert(townOrTavern);
 	assert(hero);
 
-	HireHero pack(hero->getHeroType(), townOrTavern->id);
+	HireHero pack(hero->getHeroType(), townOrTavern->id, nextHero);
 	pack.player = *player;
 	sendRequest(&pack);
 }

+ 2 - 2
CCallback.h

@@ -73,7 +73,7 @@ public:
 	virtual void castSpell(const CGHeroInstance *hero, SpellID spellID, const int3 &pos = int3(-1, -1, -1))=0; //cast adventure map spell
 
 	//town
-	virtual void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero)=0;
+	virtual void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero=HeroTypeID::NONE)=0;
 	virtual bool buildBuilding(const CGTownInstance *town, BuildingID buildingID)=0;
 	virtual void recruitCreatures(const CGDwelling *obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1)=0;
 	virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made
@@ -185,7 +185,7 @@ public:
 	void trade(const IMarket * market, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override;
 	void trade(const IMarket * market, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr) override;
 	void setFormation(const CGHeroInstance * hero, EArmyFormation mode) override;
-	void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero) override;
+	void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero=HeroTypeID::NONE) override;
 	void save(const std::string &fname) override;
 	void sendMessage(const std::string &mess, const CGObjectInstance * currentObject = nullptr) override;
 	void gamePause(bool pause) override;

+ 2 - 0
Mods/vcmi/config/vcmi/english.json

@@ -233,6 +233,8 @@
 	"vcmi.heroWindow.openBackpack.hover" : "Open artifact backpack window",
 	"vcmi.heroWindow.openBackpack.help"  : "Opens window that allows easier artifact backpack management.",
 
+	"vcmi.tavernWindow.inviteHero"  : "Invite hero",
+
 	"vcmi.commanderWindow.artifactMessage" : "Do you want to return this artifact to the hero?",
 
 	"vcmi.creatureWindow.showBonuses.hover"    : "Switch to bonuses view",

+ 2 - 0
Mods/vcmi/config/vcmi/german.json

@@ -232,6 +232,8 @@
 	"vcmi.heroWindow.openBackpack.hover" : "Artefakt-Rucksack-Fenster öffnen",
 	"vcmi.heroWindow.openBackpack.help"  : "Öffnet ein Fenster, das die Verwaltung des Artefakt-Rucksacks erleichtert",
 
+	"vcmi.tavernWindow.inviteHero"  : "Helden einladen",
+
 	"vcmi.commanderWindow.artifactMessage" : "Möchtet Ihr diesen Artefakt dem Helden zurückgeben?",
 
 	"vcmi.creatureWindow.showBonuses.hover"    : "Wechsle zur Bonus-Ansicht",

+ 6 - 0
client/lobby/OptionsTabBase.cpp

@@ -407,8 +407,14 @@ void OptionsTabBase::recreate()
 	}
 
 	if(auto buttonCheatAllowed = widget<CToggleButton>("buttonCheatAllowed"))
+	{
 		buttonCheatAllowed->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.cheatsAllowed);
+		buttonCheatAllowed->block(SEL->screenType == ESelectionScreen::loadGame);
+	}
 
 	if(auto buttonUnlimitedReplay = widget<CToggleButton>("buttonUnlimitedReplay"))
+	{
 		buttonUnlimitedReplay->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.unlimitedReplay);
+		buttonUnlimitedReplay->block(SEL->screenType == ESelectionScreen::loadGame);
+	}
 }

+ 74 - 7
client/windows/GUIClasses.cpp

@@ -17,6 +17,8 @@
 #include "InfoWindows.h"
 
 #include "../CGameInfo.h"
+#include "../CServerHandler.h"
+#include "../Client.h"
 #include "../CMusicHandler.h"
 #include "../CPlayerInterface.h"
 #include "../CVideoHandler.h"
@@ -48,6 +50,7 @@
 #include "../lib/mapObjects/ObjectTemplate.h"
 #include "../lib/gameState/CGameState.h"
 #include "../lib/gameState/SThievesGuildInfo.h"
+#include "../lib/gameState/TavernHeroesPool.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/GameSettings.h"
@@ -443,7 +446,8 @@ CLevelWindow::~CLevelWindow()
 CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::function<void()> & onWindowClosed)
 	: CStatusbarWindow(PLAYER_COLORED, ImagePath::builtin("TPTAVERN")),
 	onWindowClosed(onWindowClosed),
-	tavernObj(TavernObj)
+	tavernObj(TavernObj),
+	heroToInvite(nullptr)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
@@ -459,8 +463,8 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func
 
 	oldSelected = -1;
 
-	h1 = std::make_shared<HeroPortrait>(selected, 0, 72, 299, h[0]);
-	h2 = std::make_shared<HeroPortrait>(selected, 1, 162, 299, h[1]);
+	h1 = std::make_shared<HeroPortrait>(selected, 0, 72, 299, h[0], [this]() { if(!recruit->isBlocked()) recruitb(); });
+	h2 = std::make_shared<HeroPortrait>(selected, 1, 162, 299, h[1], [this]() { if(!recruit->isBlocked()) recruitb(); });
 
 	title = std::make_shared<CLabel>(197, 32, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[37]);
 	cost = std::make_shared<CLabel>(320, 328, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, std::to_string(GameConstants::HERO_GOLD_COST));
@@ -507,6 +511,34 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func
 		CCS->videoh->open(townObj->town->clientInfo.tavernVideo);
 	else
 		CCS->videoh->open(VideoPath::builtin("TAVERN.BIK"));
+
+	addInvite();
+}
+
+void CTavernWindow::addInvite()
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+
+	if(!VLC->settings()->getBoolean(EGameSettings::HEROES_TAVERN_INVITE))
+		return;
+
+	const auto & heroesPool = CSH->client->gameState()->heroesPool;
+	for(auto & elem : heroesPool->unusedHeroesFromPool())
+	{
+		bool heroAvailable = heroesPool->isHeroAvailableFor(elem.first, tavernObj->getOwner());
+		if(heroAvailable)
+			inviteableHeroes[elem.first] = elem.second;
+	}
+
+	if(!inviteableHeroes.empty())
+	{
+		if(!heroToInvite)
+			heroToInvite = (*RandomGeneratorUtil::nextItem(inviteableHeroes, CRandomGenerator::getDefault())).second;
+
+		inviteHero = std::make_shared<CLabel>(170, 444, EFonts::FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("vcmi.tavernWindow.inviteHero"));
+		inviteHeroImage = std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[heroToInvite->getHeroType()]->imageIndex, 0, 245, 428);
+		inviteHeroImageArea = std::make_shared<LRClickableArea>(Rect(245, 428, 48, 32), [this](){ GH.windows().createAndPushWindow<HeroSelector>(inviteableHeroes, [this](CGHeroInstance* h){ heroToInvite = h; addInvite(); }); }, [this](){ GH.windows().createAndPushWindow<CRClickPopupInt>(std::make_shared<CHeroWindow>(heroToInvite)); });
+	}
 }
 
 void CTavernWindow::recruitb()
@@ -514,7 +546,7 @@ void CTavernWindow::recruitb()
 	const CGHeroInstance *toBuy = (selected ? h2 : h1)->h;
 	const CGObjectInstance *obj = tavernObj;
 
-	LOCPLINT->cb->recruitHero(obj, toBuy);
+	LOCPLINT->cb->recruitHero(obj, toBuy, heroToInvite ? heroToInvite->getHeroType() : HeroTypeID::NONE);
 	close();
 }
 
@@ -569,15 +601,23 @@ void CTavernWindow::HeroPortrait::clickPressed(const Point & cursorPosition)
 		*_sel = _id;
 }
 
+void CTavernWindow::HeroPortrait::clickDouble(const Point & cursorPosition)
+{
+	clickPressed(cursorPosition);
+	
+	if(onChoose)
+		onChoose();
+}
+
 void CTavernWindow::HeroPortrait::showPopupWindow(const Point & cursorPosition)
 {
 	if(h)
 		GH.windows().createAndPushWindow<CRClickPopupInt>(std::make_shared<CHeroWindow>(h));
 }
 
-CTavernWindow::HeroPortrait::HeroPortrait(int & sel, int id, int x, int y, const CGHeroInstance * H)
-	: CIntObject(LCLICK | SHOW_POPUP | HOVER),
-	h(H), _sel(&sel), _id(id)
+CTavernWindow::HeroPortrait::HeroPortrait(int & sel, int id, int x, int y, const CGHeroInstance * H, std::function<void()> OnChoose)
+	: CIntObject(LCLICK | DOUBLECLICK | SHOW_POPUP | HOVER),
+	h(H), _sel(&sel), _id(id), onChoose(OnChoose)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	h = H;
@@ -615,6 +655,33 @@ void CTavernWindow::HeroPortrait::hover(bool on)
 		GH.statusbar()->clear();
 }
 
+CTavernWindow::HeroSelector::HeroSelector(std::map<HeroTypeID, CGHeroInstance*> InviteableHeroes, std::function<void(CGHeroInstance*)> OnChoose)
+	: CWindowObject(BORDERED), inviteableHeroes(InviteableHeroes), onChoose(OnChoose)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	pos = Rect(0, 0, 16 * 48, (inviteableHeroes.size() / 16 + (inviteableHeroes.size() % 16 != 0)) * 32);
+	background = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, pos.w, pos.h));
+
+	int x = 0;
+	int y = 0;
+	for(auto & h : inviteableHeroes)
+	{
+		portraits.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[h.first]->imageIndex, 0, x * 48, y * 32));
+		portraitAreas.push_back(std::make_shared<LRClickableArea>(Rect(x * 48, y * 32, 48, 32), [this, h](){ close(); onChoose(inviteableHeroes[h.first]); }, [this, h](){ GH.windows().createAndPushWindow<CRClickPopupInt>(std::make_shared<CHeroWindow>(inviteableHeroes[h.first])); }));
+
+		if(x > 0 && x % 15 == 0)
+		{
+			x = 0;
+			y++;
+		}
+		else
+			x++;
+	}
+
+	center();
+}
+
 static const std::string QUICK_EXCHANGE_MOD_PREFIX = "quick-exchange";
 static const std::string QUICK_EXCHANGE_BG = QUICK_EXCHANGE_MOD_PREFIX + "/TRADEQE";
 

+ 28 - 1
client/windows/GUIClasses.h

@@ -36,6 +36,8 @@ class CTextBox;
 class CGarrisonInt;
 class CGarrisonSlot;
 class CHeroArea;
+class CAnimImage;
+class CFilledTexture;
 
 enum class EUserEvent;
 
@@ -206,10 +208,13 @@ public:
 		std::string description; // "XXX is a level Y ZZZ with N artifacts"
 		const CGHeroInstance * h;
 
+		std::function<void()> onChoose;
+
 		void clickPressed(const Point & cursorPosition) override;
+		void clickDouble(const Point & cursorPosition) override;
 		void showPopupWindow(const Point & cursorPosition) override;
 		void hover (bool on) override;
-		HeroPortrait(int & sel, int id, int x, int y, const CGHeroInstance * H);
+		HeroPortrait(int & sel, int id, int x, int y, const CGHeroInstance * H, std::function<void()> OnChoose = nullptr);
 
 	private:
 		int *_sel;
@@ -218,6 +223,21 @@ public:
 		std::shared_ptr<CAnimImage> portrait;
 	};
 
+	class HeroSelector : public CWindowObject
+	{
+	public:
+		std::shared_ptr<CFilledTexture> background;
+
+		HeroSelector(std::map<HeroTypeID, CGHeroInstance*> InviteableHeroes, std::function<void(CGHeroInstance*)> OnChoose);
+
+	private:
+		std::map<HeroTypeID, CGHeroInstance*> inviteableHeroes;
+		std::function<void(CGHeroInstance*)> onChoose;
+
+		std::vector<std::shared_ptr<CAnimImage>> portraits;
+		std::vector<std::shared_ptr<LRClickableArea>> portraitAreas;
+	};
+
 	//recruitable heroes
 	std::shared_ptr<HeroPortrait> h1;
 	std::shared_ptr<HeroPortrait> h2; //recruitable heroes
@@ -237,6 +257,13 @@ public:
 	std::shared_ptr<CTextBox> heroDescription;
 
 	std::shared_ptr<CTextBox> rumor;
+	
+	std::shared_ptr<CLabel> inviteHero;
+	std::shared_ptr<CAnimImage> inviteHeroImage;
+	std::shared_ptr<LRClickableArea> inviteHeroImageArea;
+	std::map<HeroTypeID, CGHeroInstance*> inviteableHeroes;
+	CGHeroInstance* heroToInvite;
+	void addInvite();
 
 	CTavernWindow(const CGObjectInstance * TavernObj, const std::function<void()> & onWindowClosed);
 	~CTavernWindow();

+ 3 - 1
config/gameConfig.json

@@ -289,7 +289,9 @@
 			// Chances for a hero with default army to receive corresponding stack out of his predefined starting troops
 			"startingStackChances": [ 100, 88, 25],
 			// number of artifacts that can fit in a backpack. -1 is unlimited.
-			"backpackSize"		: -1
+			"backpackSize"		: -1,
+			// if heroes are invitable in tavern
+			"tavernInvite"            : false
 		},
 
 		"towns":

+ 2 - 0
config/widgets/extraOptionsTab.json

@@ -64,12 +64,14 @@
 					"name": "buttonCheatAllowed",
 					"image": "lobby/checkbox",
 					"callback" : "setCheatAllowed",
+					"help" : "vcmi.optionsTab.cheatAllowed",
 					"selected" : true
 				},
 				{
 					"name": "buttonUnlimitedReplay",
 					"image": "lobby/checkbox",
 					"callback" : "setUnlimitedReplay",
+					"help" : "vcmi.optionsTab.unlimitedReplay",
 					"selected" : true
 				}
 			]

+ 1 - 0
lib/GameSettings.cpp

@@ -74,6 +74,7 @@ void GameSettings::load(const JsonNode & input)
 		{EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS,   "heroes",    "retreatOnWinWithoutTroops"  },
 		{EGameSettings::HEROES_STARTING_STACKS_CHANCES,         "heroes",    "startingStackChances"       },
 		{EGameSettings::HEROES_BACKPACK_CAP,                    "heroes",    "backpackSize"               },
+		{EGameSettings::HEROES_TAVERN_INVITE,                   "heroes",    "tavernInvite"               },
 		{EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA,      "mapFormat", "restorationOfErathia"       },
 		{EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE,           "mapFormat", "armageddonsBlade"           },
 		{EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH,             "mapFormat", "shadowOfDeath"              },

+ 1 - 0
lib/GameSettings.h

@@ -38,6 +38,7 @@ enum class EGameSettings
 	HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS,
 	HEROES_STARTING_STACKS_CHANCES,
 	HEROES_BACKPACK_CAP,
+	HEROES_TAVERN_INVITE,
 	MARKETS_BLACK_MARKET_RESTOCK_PERIOD,
 	BANKS_SHOW_GUARDS_COMPOSITION,
 	MODULE_COMMANDERS,

+ 4 - 1
lib/networkPacks/PacksForServer.h

@@ -516,12 +516,14 @@ struct DLL_LINKAGE SetFormation : public CPackForServer
 struct DLL_LINKAGE HireHero : public CPackForServer
 {
 	HireHero() = default;
-	HireHero(HeroTypeID HID, const ObjectInstanceID & TID)
+	HireHero(HeroTypeID HID, const ObjectInstanceID & TID, const HeroTypeID & NHID)
 		: hid(HID)
 		, tid(TID)
+		, nhid(NHID)
 	{
 	}
 	HeroTypeID hid; //available hero serial
+	HeroTypeID nhid; //next hero
 	ObjectInstanceID tid; //town (tavern) id
 	PlayerColor player;
 
@@ -531,6 +533,7 @@ struct DLL_LINKAGE HireHero : public CPackForServer
 	{
 		h & static_cast<CPackForServer &>(*this);
 		h & hid;
+		h & nhid;
 		h & tid;
 		h & player;
 	}

+ 1 - 1
server/NetPacksServer.cpp

@@ -282,7 +282,7 @@ void ApplyGhNetPackVisitor::visitHireHero(HireHero & pack)
 {
 	gh.throwIfWrongPlayer(&pack);
 
-	result = gh.heroPool->hireHero(pack.tid, pack.hid, pack.player);
+	result = gh.heroPool->hireHero(pack.tid, pack.hid, pack.player, pack.nhid);
 }
 
 void ApplyGhNetPackVisitor::visitBuildBoat(BuildBoat & pack)

+ 20 - 6
server/processors/HeroPoolProcessor.cpp

@@ -23,6 +23,7 @@
 #include "../../lib/gameState/CGameState.h"
 #include "../../lib/gameState/TavernHeroesPool.h"
 #include "../../lib/gameState/TavernSlot.h"
+#include "../../lib/GameSettings.h"
 
 HeroPoolProcessor::HeroPoolProcessor()
 	: gameHandler(nullptr)
@@ -104,14 +105,14 @@ void HeroPoolProcessor::clearHeroFromSlot(const PlayerColor & color, TavernHeroS
 	gameHandler->sendAndApply(&sah);
 }
 
-void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot, bool needNativeHero, bool giveArmy)
+void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot, bool needNativeHero, bool giveArmy, const HeroTypeID & nextHero)
 {
 	SetAvailableHero sah;
 	sah.player = color;
 	sah.slotID = slot;
 	sah.replenishPoints = true;
 
-	CGHeroInstance *newHero = pickHeroFor(needNativeHero, color);
+	CGHeroInstance *newHero = (nextHero == HeroTypeID::NONE) ? pickHeroFor(needNativeHero, color) : gameHandler->gameState()->heroesPool->unusedHeroesFromPool()[nextHero];
 
 	if (newHero)
 	{
@@ -145,11 +146,12 @@ void HeroPoolProcessor::onNewWeek(const PlayerColor & color)
 	selectNewHeroForSlot(color, TavernHeroSlot::RANDOM, false, true);
 }
 
-bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTypeID & heroToRecruit, const PlayerColor & player)
+bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTypeID & heroToRecruit, const PlayerColor & player, const HeroTypeID & nextHero)
 {
 	const PlayerState * playerState = gameHandler->getPlayerState(player);
 	const CGObjectInstance * mapObject = gameHandler->getObj(objectID);
 	const CGTownInstance * town = gameHandler->getTown(objectID);
+	const auto & heroesPool = gameHandler->gameState()->heroesPool;
 
 	if (!mapObject && gameHandler->complain("Invalid map object!"))
 		return false;
@@ -166,6 +168,18 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy
 	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 (nextHero != HeroTypeID::NONE) // player attempts to invite next hero
+	{
+		if(!VLC->settings()->getBoolean(EGameSettings::HEROES_TAVERN_INVITE) && gameHandler->complain("Inviting heroes not allowed!"))
+			return false;
+
+		if(!heroesPool->unusedHeroesFromPool().count(nextHero) && gameHandler->complain("Cannot invite specified hero!"))
+			return false;
+		
+		if(!heroesPool->isHeroAvailableFor(nextHero, player) && gameHandler->complain("Cannot invite specified hero!"))
+			return false;
+	}
+
 	if(town) //tavern in town
 	{
 		if(gameHandler->getPlayerRelations(mapObject->tempOwner, player) == PlayerRelations::ENEMIES && gameHandler->complain("Can't buy hero in enemy town!"))
@@ -192,7 +206,7 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy
 			return false;
 	}
 
-	auto recruitableHeroes = gameHandler->gameState()->heroesPool->getHeroesFor(player);
+	auto recruitableHeroes = heroesPool->getHeroesFor(player);
 
 	const CGHeroInstance * recruitedHero = nullptr;
 
@@ -226,9 +240,9 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy
 	gameHandler->sendAndApply(&hr);
 
 	if(recruitableHeroes[0] == recruitedHero)
-		selectNewHeroForSlot(player, TavernHeroSlot::NATIVE, false, false);
+		selectNewHeroForSlot(player, TavernHeroSlot::NATIVE, false, false, nextHero);
 	else
-		selectNewHeroForSlot(player, TavernHeroSlot::RANDOM, false, false);
+		selectNewHeroForSlot(player, TavernHeroSlot::RANDOM, false, false, nextHero);
 
 	gameHandler->giveResource(player, EGameResID::GOLD, -GameConstants::HERO_GOLD_COST);
 

+ 4 - 2
server/processors/HeroPoolProcessor.h

@@ -9,6 +9,8 @@
  */
 #pragma once
 
+#include "../../lib/constants/EntityIdentifiers.h"
+
 VCMI_LIB_NAMESPACE_BEGIN
 
 enum class TavernHeroSlot : int8_t;
@@ -33,7 +35,7 @@ class HeroPoolProcessor : boost::noncopyable
 	std::map<HeroTypeID, std::unique_ptr<CRandomGenerator>> heroSeed;
 
 	void clearHeroFromSlot(const PlayerColor & color, TavernHeroSlot slot);
-	void selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot, bool needNativeHero, bool giveStartingArmy);
+	void selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot, bool needNativeHero, bool giveStartingArmy, const HeroTypeID & nextHero = HeroTypeID::NONE);
 
 	std::vector<const CHeroClass *> findAvailableClassesFor(const PlayerColor & player) const;
 	std::vector<CGHeroInstance *> findAvailableHeroesFor(const PlayerColor & player, const CHeroClass * heroClass) const;
@@ -60,7 +62,7 @@ public:
 	CRandomGenerator & getHeroSkillsRandomGenerator(const HeroTypeID & hero);
 
 	/// Incoming net pack handling
-	bool hireHero(const ObjectInstanceID & objectID, const HeroTypeID & hid, const PlayerColor & player);
+	bool hireHero(const ObjectInstanceID & objectID, const HeroTypeID & hid, const PlayerColor & player, const HeroTypeID & nextHero);
 
 	template <typename Handler> void serialize(Handler &h)
 	{