Browse Source

Merge pull request #4609 from IvanSavenko/building_fixes

Building fixes
Ivan Savenko 1 year ago
parent
commit
d0ac6458b9

+ 16 - 1
client/windows/CCastleInterface.cpp

@@ -54,6 +54,7 @@
 #include "../../lib/entities/building/CBuilding.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
+#include "../../lib/mapObjects/TownBuildingInstance.h"
 
 
 static bool useCompactCreatureBox()
@@ -845,7 +846,21 @@ bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, Bu
 
 void CCastleBuildings::enterRewardable(BuildingID building)
 {
-	LOCPLINT->cb->visitTownBuilding(town, building);
+	if (town->visitingHero == nullptr)
+	{
+		MetaString message;
+		message.appendTextID("core.genrltxt.273"); // only visiting heroes may visit %s
+		message.replaceTextID(town->town->buildings.at(building)->getNameTextID());
+
+		LOCPLINT->showInfoDialog(message.toString());
+	}
+	else
+	{
+		if (town->rewardableBuildings.at(building)->wasVisited(town->visitingHero))
+			enterBuilding(building);
+		else
+			LOCPLINT->cb->visitTownBuilding(town, building);
+	}
 }
 
 void CCastleBuildings::enterBlacksmith(BuildingID building, ArtifactID artifactID)

+ 1 - 0
docs/modders/Entities_Format/Town_Building_Format.md

@@ -197,6 +197,7 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config
 	"bonuses" : [ BONUS_FORMAT ]
 	
 	// If set to true, this building will not automatically activate on new day or on entering town and needs to be activated manually on click
+	// Note that such building can only be activated by visiting hero, and not by garrisoned hero.
 	"manualHeroVisit" : false,
 	
 	// Bonuses provided by this special building if this building or any of its upgrades are constructed in town

+ 3 - 0
lib/mapObjects/CRewardableObject.cpp

@@ -246,6 +246,9 @@ void CRewardableObject::blockingDialogAnswered(const CGHeroInstance * hero, int3
 	}
 	else
 	{
+		if (answer == 0)
+			return; //Player refused
+
 		if(answer > 0 && answer - 1 < configuration.info.size())
 		{
 			auto list = getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT);

+ 5 - 0
lib/mapObjects/TownBuildingInstance.cpp

@@ -165,6 +165,11 @@ void TownRewardableBuildingInstance::grantReward(ui32 rewardID, const CGHeroInst
 	}
 }
 
+bool TownRewardableBuildingInstance::wasVisited(const CGHeroInstance * contextHero) const
+{
+	return wasVisitedBefore(contextHero);
+}
+
 bool TownRewardableBuildingInstance::wasVisitedBefore(const CGHeroInstance * contextHero) const
 {
 	switch (configuration.visitMode)

+ 1 - 0
lib/mapObjects/TownBuildingInstance.h

@@ -70,6 +70,7 @@ class DLL_LINKAGE TownRewardableBuildingInstance : public TownBuildingInstance,
 public:
 	void setProperty(ObjProperty what, ObjPropertyID identifier) override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
+	bool wasVisited(const CGHeroInstance * contextHero) const override;
 	
 	void newTurn(vstd::RNG & rand) const override;
 	

+ 35 - 18
server/CGameHandler.cpp

@@ -1171,7 +1171,6 @@ void CGameHandler::heroVisitCastle(const CGTownInstance * obj, const CGHeroInsta
 		sendAndApply(&vc);
 	}
 	visitCastleObjects(obj, hero);
-	giveSpells (obj, hero);
 
 	if (obj->visitingHero && obj->garrisonHero)
 		useScholarSkill(obj->visitingHero->id, obj->garrisonHero->id);
@@ -1180,10 +1179,27 @@ void CGameHandler::heroVisitCastle(const CGTownInstance * obj, const CGHeroInsta
 
 void CGameHandler::visitCastleObjects(const CGTownInstance * t, const CGHeroInstance * h)
 {
+	std::vector<const CGHeroInstance * > visitors;
+	visitors.push_back(h);
+	visitCastleObjects(t, visitors);
+}
+
+void CGameHandler::visitCastleObjects(const CGTownInstance * t, std::vector<const CGHeroInstance * > visitors)
+{
+	std::vector<BuildingID> buildingsToVisit;
+	for (auto const & hero : visitors)
+		giveSpells (t, hero);
+
 	for (auto & building : t->rewardableBuildings)
 	{
 		if (!t->town->buildings.at(building.first)->manualHeroVisit)
-			building.second->onHeroVisit(h);
+			buildingsToVisit.push_back(building.first);
+	}
+
+	if (!buildingsToVisit.empty())
+	{
+		auto visitQuery = std::make_shared<TownBuildingVisitQuery>(this, t, visitors, buildingsToVisit);
+		queries->addQuery(visitQuery);
 	}
 }
 
@@ -2144,10 +2160,15 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
 
 	if (!force)
 	{
-		if(t->garrisonHero) //garrison hero first - consistent with original H3 Mana Vortex and Battle Scholar Academy levelup windows order
-			objectVisited(t, t->garrisonHero);
-		if(t->visitingHero)
-			objectVisited(t, t->visitingHero);
+		//garrison hero first - consistent with original H3 Mana Vortex and Battle Scholar Academy levelup windows order
+		std::vector<const CGHeroInstance *> visitors;
+		if (t->garrisonHero)
+			visitors.push_back(t->garrisonHero);
+		if (t->visitingHero)
+			visitors.push_back(t->visitingHero);
+
+		if (!visitors.empty())
+			visitCastleObjects(t, visitors);
 	}
 
 	checkVictoryLossConditionsForPlayer(t->tempOwner);
@@ -2173,19 +2194,15 @@ bool CGameHandler::visitTownBuilding(ObjectInstanceID tid, BuildingID bid)
 		return true;
 	}
 
-	if (t->rewardableBuildings.count(bid))
+	if (t->rewardableBuildings.count(bid) && t->visitingHero && t->town->buildings.at(bid)->manualHeroVisit)
 	{
-		auto & hero = t->garrisonHero ? t->garrisonHero : t->visitingHero;
-		auto * building = t->rewardableBuildings.at(bid);
-
-		if (hero && t->town->buildings.at(bid)->manualHeroVisit)
-		{
-			auto visitQuery = std::make_shared<TownBuildingVisitQuery>(this, t, hero, bid);
-			queries->addQuery(visitQuery);
-			building->onHeroVisit(hero);
-			queries->popIfTop(visitQuery);
-			return true;
-		}
+		std::vector<BuildingID> buildingsToVisit;
+		std::vector<const CGHeroInstance*> visitors;
+		buildingsToVisit.push_back(bid);
+		visitors.push_back(t->visitingHero);
+		auto visitQuery = std::make_shared<TownBuildingVisitQuery>(this, t, visitors, buildingsToVisit);
+		queries->addQuery(visitQuery);
+		return true;
 	}
 
 	return true;

+ 1 - 0
server/CGameHandler.h

@@ -182,6 +182,7 @@ public:
 	void visitObjectOnTile(const TerrainTile &t, const CGHeroInstance * h);
 	bool teleportHero(ObjectInstanceID hid, ObjectInstanceID dstid, ui8 source, PlayerColor asker = PlayerColor::NEUTRAL);
 	void visitCastleObjects(const CGTownInstance * obj, const CGHeroInstance * hero) override;
+	void visitCastleObjects(const CGTownInstance * obj, std::vector<const CGHeroInstance * > visitors);
 	void levelUpHero(const CGHeroInstance * hero, SecondarySkill skill);//handle client respond and send one more request if needed
 	void levelUpHero(const CGHeroInstance * hero);//initial call - check if hero have remaining levelups & handle them
 	void levelUpCommander (const CCommanderInstance * c, int skill); //secondary skill 1 to 6, special skill : skill - 100

+ 25 - 5
server/queries/VisitQueries.cpp

@@ -11,6 +11,8 @@
 #include "VisitQueries.h"
 
 #include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapObjects/CGTownInstance.h"
+#include "../../lib/mapObjects/TownBuildingInstance.h"
 #include "../CGameHandler.h"
 #include "QueriesProcessor.h"
 
@@ -29,7 +31,7 @@ bool VisitQuery::blocksPack(const CPack * pack) const
 	return true;
 }
 
-void VisitQuery::onExposure(QueryPtr topQuery)
+void MapObjectVisitQuery::onExposure(QueryPtr topQuery)
 {
 	//Object may have been removed and deleted.
 	if(gh->isValidObject(visitedObject))
@@ -54,13 +56,31 @@ void MapObjectVisitQuery::onRemoval(PlayerColor color)
 		gh->removeObject(visitedObject, color);
 }
 
-TownBuildingVisitQuery::TownBuildingVisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero, BuildingID buildingToVisit)
-	: VisitQuery(owner, Obj, Hero)
-	, visitedBuilding(buildingToVisit)
+TownBuildingVisitQuery::TownBuildingVisitQuery(CGameHandler * owner, const CGTownInstance * Obj, std::vector<const CGHeroInstance *> heroes, std::vector<BuildingID> buildingToVisit)
+	: VisitQuery(owner, Obj, heroes.front())
+	, visitedTown(Obj)
+{
+	// generate in reverse order - first building-hero pair to handle must be in the end of vector
+	for (auto const * hero : boost::adaptors::reverse(heroes))
+		for (auto const & building : boost::adaptors::reverse(buildingToVisit))
+			visitedBuilding.push_back({ hero, building});
+}
+
+void TownBuildingVisitQuery::onExposure(QueryPtr topQuery)
 {
+	onAdded(players.front());
 }
 
-void TownBuildingVisitQuery::onRemoval(PlayerColor color)
+void TownBuildingVisitQuery::onAdded(PlayerColor color)
 {
+	while (!visitedBuilding.empty() && owner->topQuery(color).get() == this)
+	{
+		visitingHero = visitedBuilding.back().hero;
+		auto * building = visitedTown->rewardableBuildings.at(visitedBuilding.back().building);
+		building->onHeroVisit(visitingHero);
+		visitedBuilding.pop_back();
+	}
 
+	if (visitedBuilding.empty() && owner->topQuery(color).get() == this)
+		owner->popIfTop(*this);
 }

+ 22 - 10
server/queries/VisitQueries.h

@@ -11,19 +11,22 @@
 
 #include "CQuery.h"
 
+VCMI_LIB_NAMESPACE_BEGIN
+class CGTownInstance;
+VCMI_LIB_NAMESPACE_END
+
 //Created when hero visits object.
 //Removed when query above is resolved (or immediately after visit if no queries were created)
 class VisitQuery : public CQuery
 {
 protected:
-	VisitQuery(CGameHandler * owner, const CGObjectInstance *Obj, const CGHeroInstance *Hero);
+	VisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero);
 
 public:
-	const CGObjectInstance *visitedObject;
-	const CGHeroInstance *visitingHero;
+	const CGObjectInstance * visitedObject;
+	const CGHeroInstance * visitingHero;
 
-	bool blocksPack(const CPack *pack) const final;
-	void onExposure(QueryPtr topQuery) final;
+	bool blocksPack(const CPack * pack) const final;
 };
 
 class MapObjectVisitQuery final : public VisitQuery
@@ -31,17 +34,26 @@ class MapObjectVisitQuery final : public VisitQuery
 public:
 	bool removeObjectAfterVisit;
 
-	MapObjectVisitQuery(CGameHandler * owner, const CGObjectInstance *Obj, const CGHeroInstance *Hero);
+	MapObjectVisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero);
 
 	void onRemoval(PlayerColor color) final;
+	void onExposure(QueryPtr topQuery) final;
 };
 
 class TownBuildingVisitQuery final : public VisitQuery
 {
-public:
-	BuildingID visitedBuilding;
+	struct BuildingVisit
+	{
+		const CGHeroInstance * hero;
+		BuildingID building;
+	};
 
-	TownBuildingVisitQuery(CGameHandler * owner, const CGObjectInstance *Obj, const CGHeroInstance *Hero, BuildingID buildingToVisit);
+	const CGTownInstance * visitedTown;
+	std::vector<BuildingVisit> visitedBuilding;
 
-	void onRemoval(PlayerColor color) final;
+public:
+	TownBuildingVisitQuery(CGameHandler * owner, const CGTownInstance * Obj, std::vector<const CGHeroInstance *> heroes, std::vector<BuildingID> buildingToVisit);
+
+	void onAdded(PlayerColor color) final;
+	void onExposure(QueryPtr topQuery) final;
 };