瀏覽代碼

Implemented tavern slot selection using rules similar to H3

Ivan Savenko 2 年之前
父節點
當前提交
9a38d8ea97

+ 1 - 0
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

+ 4 - 1
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 <typename Handler> void serialize(Handler & h, const int version)
 	{
 		h & slotID;
+		h & roleID;
 		h & player;
 		h & hid;
 		h & army;

+ 1 - 1
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)

+ 36 - 12
lib/gameState/TavernHeroesPool.cpp

@@ -23,17 +23,27 @@ TavernHeroesPool::~TavernHeroesPool()
 std::map<HeroTypeID, CGHeroInstance*> TavernHeroesPool::unusedHeroesFromPool() const
 {
 	std::map<HeroTypeID, CGHeroInstance*> 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<const CGHeroInstance *> TavernHeroesPool::getHeroesFor(PlayerColor c
 {
 	std::vector<const CGHeroInstance *> 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;
 }

+ 22 - 10
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 <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
 	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
 	std::map<HeroTypeID, PlayerColor::Mask> pavailable;
 
-	/// list of heroes currently available in a tavern of a specific player
-	std::map<PlayerColor, std::map<TavernHeroSlot, CGHeroInstance*> > currentTavern;
+	/// list of heroes currently available in taverns
+	std::vector<TavernSlot> 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 <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 35 - 0
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

+ 42 - 4
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<int>(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<int>(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);
 		}

+ 3 - 1
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;