Selaa lähdekoodia

Merge pull request #145 from vcmi/feature/tavernRumors

Tavern rumors and Thieves Guild fixes
ArseniyShestakov 10 vuotta sitten
vanhempi
sitoutus
360ebcce7e

+ 1 - 0
ChangeLog

@@ -7,6 +7,7 @@ GENERAL:
 * New artifacts supported
 - Angel Wings
 - Boots of Levitation
+* Implemented rumors in tavern window
 
 ADVETURE AI:
 * Fixed AI trying to go through underground rock

+ 4 - 2
client/windows/GUIClasses.cpp

@@ -705,7 +705,9 @@ CTavernWindow::CTavernWindow(const CGObjectInstance *TavernObj):
 
 	new CLabel(200, 35, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[37]);
 	new CLabel(320, 328, FONT_SMALL, CENTER, Colors::WHITE, "2500");
-	new CTextBox(LOCPLINT->cb->getTavernGossip(tavernObj), Rect(32, 190, 330, 68), 0, FONT_SMALL, CENTER, Colors::WHITE);
+
+	auto rumorText = boost::str(boost::format(CGI->generaltexth->allTexts[216]) % LOCPLINT->cb->getTavernRumor(tavernObj));
+	new CTextBox(rumorText, Rect(32, 190, 330, 68), 0, FONT_SMALL, CENTER, Colors::WHITE);
 
 	new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
 	cancel = new CButton(Point(310, 428), "ICANCEL.DEF", CButton::tooltip(CGI->generaltexth->tavernInfo[7]), std::bind(&CTavernWindow::close, this), SDLK_ESCAPE);
@@ -1628,9 +1630,9 @@ CThievesGuildWindow::CThievesGuildWindow(const CGObjectInstance * _owner):
 	int counter = 0;
 	for(auto & iter : tgi.colorToBestHero)
 	{
+		new CPicture(colorToBox[iter.first.getNum()], 253 + 66 * counter, 334);
 		if(iter.second.portrait >= 0)
 		{
-			new CPicture(colorToBox[iter.first.getNum()], 253 + 66 * counter, 334);
 			new CAnimImage("PortraitsSmall", iter.second.portrait, 0, 260 + 66 * counter, 360);
 			//TODO: r-click info:
 			// - r-click on hero

+ 36 - 3
lib/CGameInfoCallback.cpp

@@ -12,6 +12,7 @@
 #include "CGameInfoCallback.h"
 
 #include "CGameState.h" // PlayerState
+#include "CGeneralTextHandler.h"
 #include "mapObjects/CObjectHandler.h" // for CGObjectInstance
 #include "StartInfo.h" // for StartInfo
 #include "BattleState.h" // for BattleInfo
@@ -198,7 +199,14 @@ void CGameInfoCallback::getThievesGuildInfo(SThievesGuildInfo & thi, const CGObj
 
 	if(obj->ID == Obj::TOWN  ||  obj->ID == Obj::TAVERN)
 	{
-		gs->obtainPlayersStats(thi, gs->players[obj->tempOwner].towns.size());
+		int taverns = 0;
+		for(auto town : gs->players[*player].towns)
+		{
+			if(town->hasBuilt(BuildingID::TAVERN))
+				taverns++;
+		}
+
+		gs->obtainPlayersStats(thi, taverns);
 	}
 	else if(obj->ID == Obj::DEN_OF_THIEVES)
 	{
@@ -566,9 +574,34 @@ EPlayerStatus::EStatus CGameInfoCallback::getPlayerStatus(PlayerColor player, bo
 	return ps->status;
 }
 
-std::string CGameInfoCallback::getTavernGossip(const CGObjectInstance * townOrTavern) const
+std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTavern) const
 {
-	return "GOSSIP TEST";
+	std::string text = "", extraText = "";
+	if(gs->rumor.type == RumorState::TYPE_NONE) // (version < 755 backward compatability
+		return text;
+
+	auto rumor = gs->rumor.last[gs->rumor.type];
+	switch(gs->rumor.type)
+	{
+	case RumorState::TYPE_SPECIAL:
+		if(rumor.first == RumorState::RUMOR_GRAIL)
+			extraText = VLC->generaltexth->arraytxt[158 + rumor.second];
+		else
+			extraText = VLC->generaltexth->capColors[rumor.second];
+
+		text = boost::str(boost::format(VLC->generaltexth->allTexts[rumor.first]) % extraText);
+
+		break;
+	case RumorState::TYPE_MAP:
+		text = gs->map->rumors[rumor.first].text;
+		break;
+
+	case RumorState::TYPE_RAND:
+		text = VLC->generaltexth->tavernRumors[rumor.first];
+		break;
+	}
+
+	return text;
 }
 
 PlayerRelations::PlayerRelations CGameInfoCallback::getPlayerRelations( PlayerColor color1, PlayerColor color2 ) const

+ 1 - 1
lib/CGameInfoCallback.h

@@ -99,7 +99,7 @@ public:
 	int howManyTowns(PlayerColor Player) const;
 	const CGTownInstance * getTownInfo(int val, bool mode)const; //mode = 0 -> val = player town serial; mode = 1 -> val = object id (serial)
 	std::vector<const CGHeroInstance *> getAvailableHeroes(const CGObjectInstance * townOrTavern) const; //heroes that can be recruited
-	std::string getTavernGossip(const CGObjectInstance * townOrTavern) const; 
+	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;

+ 154 - 11
lib/CGameState.cpp

@@ -2134,6 +2134,76 @@ int3 CGameState::guardingCreaturePosition (int3 pos) const
 	return gs->map->guardingCreaturePositions[pos.x][pos.y][pos.z];
 }
 
+void CGameState::updateRumor()
+{
+	static std::vector<RumorState::ERumorType> rumorTypes = {RumorState::TYPE_MAP, RumorState::TYPE_SPECIAL, RumorState::TYPE_RAND, RumorState::TYPE_RAND};
+	std::vector<RumorState::ERumorTypeSpecial> sRumorTypes = {
+		RumorState::RUMOR_OBELISKS, RumorState::RUMOR_ARTIFACTS, RumorState::RUMOR_ARMY, RumorState::RUMOR_INCOME};
+	if(map->grailPos.valid()) // Grail should always be on map, but I had related crash I didn't manage to reproduce
+		sRumorTypes.push_back(RumorState::RUMOR_GRAIL);
+
+	int rumorId = -1, rumorExtra = -1;
+	auto & rand = getRandomGenerator();
+	rumor.type = *RandomGeneratorUtil::nextItem(rumorTypes, rand);
+	if(!map->rumors.size() && rumor.type == RumorState::TYPE_MAP)
+		rumor.type = RumorState::TYPE_RAND;
+
+	do
+	{
+		switch(rumor.type)
+		{
+		case RumorState::TYPE_SPECIAL:
+		{
+			SThievesGuildInfo tgi;
+			obtainPlayersStats(tgi, 20);
+			rumorId = *RandomGeneratorUtil::nextItem(sRumorTypes, rand);
+			if(rumorId == RumorState::RUMOR_GRAIL)
+			{
+				rumorExtra = getTile(map->grailPos)->terType;
+				break;
+			}
+
+			std::vector<PlayerColor> players = {};
+			switch(rumorId)
+			{
+			case RumorState::RUMOR_OBELISKS:
+				players = tgi.obelisks[0];
+				break;
+
+			case RumorState::RUMOR_ARTIFACTS:
+				players = tgi.artifacts[0];
+				break;
+
+			case RumorState::RUMOR_ARMY:
+				players = tgi.army[0];
+				break;
+
+			case RumorState::RUMOR_INCOME:
+				players = tgi.income[0];
+				break;
+			}
+			rumorExtra = RandomGeneratorUtil::nextItem(players, rand)->getNum();
+
+			break;
+		}
+		case RumorState::TYPE_MAP:
+			rumorId = rand.nextInt(map->rumors.size() - 1);
+
+			break;
+
+		case RumorState::TYPE_RAND:
+			do
+			{
+				rumorId = rand.nextInt(VLC->generaltexth->tavernRumors.size() - 1);
+			}
+			while(!VLC->generaltexth->tavernRumors[rumorId].length());
+
+			break;
+		}
+	}
+	while(!rumor.update(rumorId, rumorExtra));
+}
+
 bool CGameState::isVisible(int3 pos, PlayerColor player)
 {
 	if(player == PlayerColor::NEUTRAL)
@@ -2455,6 +2525,58 @@ struct statsHLP
 		}
 		return str;
 	}
+
+	// get total gold income
+	static int getIncome(const PlayerState * ps)
+	{
+		int totalIncome = 0;
+		const CGObjectInstance * heroOrTown = nullptr;
+
+		//Heroes can produce gold as well - skill, specialty or arts
+		for(auto & h : ps->heroes)
+		{
+			totalIncome += h->valOfBonuses(Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::ESTATES));
+			totalIncome += h->valOfBonuses(Selector::typeSubtype(Bonus::GENERATE_RESOURCE, Res::GOLD));
+
+			if(!heroOrTown)
+				heroOrTown = h;
+		}
+
+		//Add town income of all towns
+		for(auto & t : ps->towns)
+		{
+			totalIncome += t->dailyIncome()[Res::GOLD];
+
+			if(!heroOrTown)
+				heroOrTown = t;
+		}
+
+		/// FIXME: Dirty dirty hack
+		/// Stats helper need some access to gamestate.
+		std::vector<const CGObjectInstance *> ownedObjects;
+		for(const CGObjectInstance * obj : heroOrTown->cb->gameState()->map->objects)
+		{
+			if(obj && obj->tempOwner == ps->color)
+				ownedObjects.push_back(obj);
+		}
+		/// This is code from CPlayerSpecificInfoCallback::getMyObjects
+		/// I'm really need to find out about callback interface design...
+
+		for(auto object : ownedObjects)
+		{
+			//Mines
+			if ( object->ID == Obj::MINE )
+			{
+				const CGMine *mine = dynamic_cast<const CGMine*>(object);
+				assert(mine);
+
+				if (mine->producedResource == Res::GOLD)
+					totalIncome += mine->producedQuantity;
+			}
+		}
+
+		return totalIncome;
+	}
 };
 
 void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
@@ -2485,20 +2607,22 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
 			tgi.playerColors.push_back(elem.second.color);
 	}
 
-	if(level >= 1) //num of towns & num of heroes
+	if(level >= 0) //num of towns & num of heroes
 	{
 		//num of towns
 		FILL_FIELD(numOfTowns, g->second.towns.size())
 		//num of heroes
 		FILL_FIELD(numOfHeroes, g->second.heroes.size())
-		//best hero's portrait
+	}
+	if(level >= 1) //best hero's portrait
+	{
 		for(auto g = players.cbegin(); g != players.cend(); ++g)
 		{
 			if(playerInactive(g->second.color))
 				continue;
 			const CGHeroInstance * best = statsHLP::findBestHero(this, g->second.color);
 			InfoAboutHero iah;
-			iah.initFromHero(best, level >= 8);
+			iah.initFromHero(best, level >= 2);
 			iah.army.clear();
 			tgi.colorToBestHero[g->second.color] = iah;
 		}
@@ -2515,27 +2639,27 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
 	{
 		FILL_FIELD(mercSulfCrystGems, g->second.resources[Res::MERCURY] + g->second.resources[Res::SULFUR] + g->second.resources[Res::CRYSTAL] + g->second.resources[Res::GEMS])
 	}
-	if(level >= 4) //obelisks found
+	if(level >= 3) //obelisks found
 	{
 		FILL_FIELD(obelisks, CGObelisk::visited[gs->getPlayerTeam(g->second.color)->id])
 	}
-	if(level >= 5) //artifacts
+	if(level >= 4) //artifacts
 	{
 		FILL_FIELD(artifacts, statsHLP::getNumberOfArts(&g->second))
 	}
-	if(level >= 6) //army strength
+	if(level >= 4) //army strength
 	{
 		FILL_FIELD(army, statsHLP::getArmyStrength(&g->second))
 	}
-	if(level >= 7) //income
+	if(level >= 5) //income
 	{
-		//TODO:obtainPlayersStats - income
+		FILL_FIELD(income, statsHLP::getIncome(&g->second))
 	}
-	if(level >= 8) //best hero's stats
+	if(level >= 2) //best hero's stats
 	{
 		//already set in  lvl 1 handling
 	}
-	if(level >= 9) //personality
+	if(level >= 3) //personality
 	{
 		for(auto g = players.cbegin(); g != players.cend(); ++g)
 		{
@@ -2552,7 +2676,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
 
 		}
 	}
-	if(level >= 10) //best creature
+	if(level >= 4) //best creature
 	{
 		//best creatures belonging to player (highest AI value)
 		for(auto g = players.cbegin(); g != players.cend(); ++g)
@@ -2803,6 +2927,25 @@ std::string PlayerState::nodeName() const
 	return "Player " + (color.getNum() < VLC->generaltexth->capColors.size() ? VLC->generaltexth->capColors[color.getNum()] : boost::lexical_cast<std::string>(color));
 }
 
+
+bool RumorState::update(int id, int extra)
+{
+	if(vstd::contains(last, type))
+	{
+		if(last[type].first != id)
+		{
+			last[type].first = id;
+			last[type].second = extra;
+		}
+		else
+			return false;
+	}
+	else
+		last[type] = std::make_pair(id, extra);
+
+	return true;
+}
+
 InfoAboutArmy::InfoAboutArmy():
     owner(PlayerColor::NEUTRAL)
 {}

+ 37 - 0
lib/CGameState.h

@@ -81,6 +81,34 @@ struct DLL_LINKAGE SThievesGuildInfo
 
 };
 
+struct DLL_LINKAGE RumorState
+{
+	enum ERumorType : ui8
+	{
+		TYPE_NONE = 0, TYPE_RAND, TYPE_SPECIAL, TYPE_MAP
+	};
+
+	enum ERumorTypeSpecial : ui8
+	{
+		RUMOR_OBELISKS = 208,
+		RUMOR_ARTIFACTS = 209,
+		RUMOR_ARMY = 210,
+		RUMOR_INCOME = 211,
+		RUMOR_GRAIL = 212
+	};
+
+	ERumorType type;
+	std::map<ERumorType, std::pair<int, int>> last;
+
+	RumorState(){type = TYPE_NONE; last = {};};
+	bool update(int id, int extra);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & type & last;
+	}
+};
+
 struct UpgradeInfo
 {
 	CreatureID oldID; //creature to be upgraded
@@ -183,6 +211,7 @@ public:
 	std::map<PlayerColor, PlayerState> players;
 	std::map<TeamID, TeamState> teams;
 	CBonusSystemNode globalEffects;
+	RumorState rumor;
 
 	boost::shared_mutex *mx;
 
@@ -196,6 +225,7 @@ public:
 	void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out); //calculates possible paths for hero, by default uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
 	int3 guardingCreaturePosition (int3 pos) const;
 	std::vector<CGObjectInstance*> guardingCreatures (int3 pos) const;
+	void updateRumor();
 
 	// ----- victory, loss condition checks -----
 
@@ -219,6 +249,13 @@ public:
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & scenarioOps & initialOpts & currentPlayer & day & map & players & teams & hpool & globalEffects & rand;
+		if(version >= 755)
+		{
+			h & rumor;
+		}
+		else if(!h.saving)
+			rumor = RumorState();
+
 		BONUS_TREE_DESERIALIZATION_FIX
 	}
 

+ 1 - 0
lib/CGeneralTextHandler.cpp

@@ -327,6 +327,7 @@ CGeneralTextHandler::CGeneralTextHandler()
 	readToVector("DATA/PRISKILL.TXT", primarySkillNames);
 	readToVector("DATA/JKTEXT.TXT",   jktexts);
 	readToVector("DATA/TVRNINFO.TXT", tavernInfo);
+	readToVector("DATA/RANDTVRN.TXT", tavernRumors);
 	readToVector("DATA/TURNDUR.TXT",  turnDurations);
 	readToVector("DATA/HEROSCRN.TXT", heroscrn);
 	readToVector("DATA/TENTCOLR.TXT", tentColors);

+ 1 - 0
lib/CGeneralTextHandler.h

@@ -109,6 +109,7 @@ public:
 	//towns
 	std::vector<std::string> tcommands, hcommands, fcommands; //texts for town screen, town hall screen and fort screen
 	std::vector<std::string> tavernInfo;
+	std::vector<std::string> tavernRumors;
 
 	std::vector<std::pair<std::string,std::string> > zelp;
 	std::vector<std::string> lossCondtions;

+ 3 - 0
lib/NetPacksLib.cpp

@@ -1033,6 +1033,9 @@ DLL_LINKAGE void NewTurn::applyGs( CGameState *gs )
 
 	for(CGTownInstance* t : gs->map->towns)
 		t->builded = 0;
+
+	if(gs->getDate(Date::DAY_OF_WEEK) == 1)
+		gs->updateRumor();
 }
 
 DLL_LINKAGE void SetObjectProperty::applyGs( CGameState *gs )