Browse Source

Unify rewardable map object and town building code

Ivan Savenko 1 year ago
parent
commit
f2c20b54d0

+ 9 - 151
lib/mapObjects/CRewardableObject.cpp

@@ -12,87 +12,30 @@
 #include "CRewardableObject.h"
 
 #include "../CPlayerState.h"
-#include "../GameSettings.h"
 #include "../IGameCallback.h"
+#include "../IGameSettings.h"
 #include "../battle/BattleLayout.h"
 #include "../gameState/CGameState.h"
 #include "../mapObjectConstructors/AObjectTypeHandler.h"
-#include "../mapObjectConstructors/CObjectClassesHandler.h"
 #include "../mapObjectConstructors/CRewardableConstructor.h"
 #include "../mapObjects/CGHeroInstance.h"
 #include "../networkPacks/PacksForClient.h"
 #include "../networkPacks/PacksForClientBattle.h"
 #include "../serializer/JsonSerializeFormat.h"
-#include "../texts/CGeneralTextHandler.h"
 
 #include <vstd/RNG.h>
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-void CRewardableObject::grantRewardWithMessage(const CGHeroInstance * contextHero, int index, bool markAsVisit) const
+const IObjectInterface * CRewardableObject::getObject() const
 {
-	auto vi = configuration.info.at(index);
-	logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString());
-	// show message only if it is not empty or in infobox
-	if (configuration.infoWindowType != EInfoWindowMode::MODAL || !vi.message.toString().empty())
-	{
-		InfoWindow iw;
-		iw.player = contextHero->tempOwner;
-		iw.text = vi.message;
-		vi.reward.loadComponents(iw.components, contextHero);
-		iw.type = configuration.infoWindowType;
-		if(!iw.components.empty() || !iw.text.toString().empty())
-			cb->showInfoDialog(&iw);
-	}
-	// grant reward afterwards. Note that it may remove object
-	if(markAsVisit)
-		markAsVisited(contextHero);
-	grantReward(index, contextHero);
-}
-
-void CRewardableObject::selectRewardWithMessage(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices, const MetaString & dialog) const
-{
-	BlockingDialog sd(configuration.canRefuse, rewardIndices.size() > 1);
-	sd.player = contextHero->tempOwner;
-	sd.text = dialog;
-	sd.components = loadComponents(contextHero, rewardIndices);
-	cb->showBlockingDialog(this, &sd);
-
+	return this;
 }
 
-void CRewardableObject::grantAllRewardsWithMessage(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices, bool markAsVisit) const
+void CRewardableObject::markAsScouted(const CGHeroInstance * hero) const
 {
-	if (rewardIndices.empty())
-		return;
-		
-	for (auto index : rewardIndices)
-	{
-		// TODO: Merge all rewards of same type, with single message?
-		grantRewardWithMessage(contextHero, index, false);
-	}
-	// Mark visited only after all rewards were processed
-	if(markAsVisit)
-		markAsVisited(contextHero);
-}
-
-std::vector<Component> CRewardableObject::loadComponents(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices) const
-{
-	std::vector<Component> result;
-
-	if (rewardIndices.empty())
-		return result;
-
-	if (configuration.selectMode != Rewardable::SELECT_FIRST && rewardIndices.size() > 1)
-	{
-		for (auto index : rewardIndices)
-			result.push_back(configuration.info.at(index).reward.getDisplayedComponent(contextHero));
-	}
-	else
-	{
-		configuration.info.at(rewardIndices.front()).reward.loadComponents(result, contextHero);
-	}
-
-	return result;
+	ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, hero->id);
+	cb->sendAndApply(&cov);
 }
 
 bool CRewardableObject::isGuarded() const
@@ -127,94 +70,9 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *hero) const
 	}
 }
 
-void CRewardableObject::doHeroVisit(const CGHeroInstance *h) const
-{
-	if(!wasVisitedBefore(h))
-	{
-		auto rewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT);
-		bool objectRemovalPossible = false;
-		for(auto index : rewards)
-		{
-			if(configuration.info.at(index).reward.removeObject)
-				objectRemovalPossible = true;
-		}
-
-		logGlobal->debug("Visiting object with %d possible rewards", rewards.size());
-		switch (rewards.size())
-		{
-			case 0: // no available rewards, e.g. visiting School of War without gold
-			{
-				auto emptyRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_NOT_AVAILABLE);
-				if (!emptyRewards.empty())
-					grantRewardWithMessage(h, emptyRewards[0], false);
-				else
-					logMod->warn("No applicable message for visiting empty object!");
-				break;
-			}
-			case 1: // one reward. Just give it with message
-			{
-				if (configuration.canRefuse)
-					selectRewardWithMessage(h, rewards, configuration.info.at(rewards.front()).message);
-				else
-					grantRewardWithMessage(h, rewards.front(), true);
-				break;
-			}
-			default: // multiple rewards. Act according to select mode
-			{
-				switch (configuration.selectMode) {
-					case Rewardable::SELECT_PLAYER: // player must select
-						selectRewardWithMessage(h, rewards, configuration.onSelect);
-						break;
-					case Rewardable::SELECT_FIRST: // give first available
-						if (configuration.canRefuse)
-							selectRewardWithMessage(h, { rewards.front() }, configuration.info.at(rewards.front()).message);
-						else
-							grantRewardWithMessage(h, rewards.front(), true);
-						break;
-					case Rewardable::SELECT_RANDOM: // give random
-					{
-						ui32 rewardIndex = *RandomGeneratorUtil::nextItem(rewards, cb->gameState()->getRandomGenerator());
-						if (configuration.canRefuse)
-							selectRewardWithMessage(h, { rewardIndex }, configuration.info.at(rewardIndex).message);
-						else
-							grantRewardWithMessage(h, rewardIndex, true);
-						break;
-					}
-					case Rewardable::SELECT_ALL: // grant all possible
-						grantAllRewardsWithMessage(h, rewards, true);
-						break;
-				}
-				break;
-			}
-		}
-
-		if(!objectRemovalPossible && getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT).empty())
-		{
-			ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, h->id);
-			cb->sendAndApply(&cov);
-		}
-	}
-	else
-	{
-		logGlobal->debug("Revisiting already visited object");
-
-		if (!wasVisited(h->getOwner()))
-		{
-			ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, h->id);
-			cb->sendAndApply(&cov);
-		}
-
-		auto visitedRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_ALREADY_VISITED);
-		if (!visitedRewards.empty())
-			grantRewardWithMessage(h, visitedRewards[0], false);
-		else
-			logMod->warn("No applicable message for visiting already visited object!");
-	}
-}
-
 void CRewardableObject::heroLevelUpDone(const CGHeroInstance *hero) const
 {
-	grantRewardAfterLevelup(cb, configuration.info.at(selectedReward), this, hero);
+	grantRewardAfterLevelup(configuration.info.at(selectedReward), this, hero);
 }
 
 void CRewardableObject::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
@@ -264,12 +122,12 @@ void CRewardableObject::markAsVisited(const CGHeroInstance * hero) const
 void CRewardableObject::grantReward(ui32 rewardID, const CGHeroInstance * hero) const
 {
 	cb->setObjPropertyValue(id, ObjProperty::REWARD_SELECT, rewardID);
-	grantRewardBeforeLevelup(cb, configuration.info.at(rewardID), hero);
+	grantRewardBeforeLevelup(configuration.info.at(rewardID), hero);
 	
 	// hero is not blocked by levelup dialog - grant remainder immediately
 	if(!cb->isVisitCoveredByAnotherQuery(this, hero))
 	{
-		grantRewardAfterLevelup(cb, configuration.info.at(rewardID), this, hero);
+		grantRewardAfterLevelup(configuration.info.at(rewardID), this, hero);
 	}
 }
 

+ 6 - 12
lib/mapObjects/CRewardableObject.h

@@ -25,28 +25,22 @@ protected:
 	/// reward selected by player, no serialize
 	ui16 selectedReward = 0;
 	
-	void grantReward(ui32 rewardID, const CGHeroInstance * hero) const;
-	void markAsVisited(const CGHeroInstance * hero) const;
+	void grantReward(ui32 rewardID, const CGHeroInstance * hero) const override;
+	void markAsVisited(const CGHeroInstance * hero) const override;
+
+	const IObjectInterface * getObject() const override;
+	void markAsScouted(const CGHeroInstance * hero) const override;
 	
 	/// return true if this object was "cleared" before and no longer has rewards applicable to selected hero
 	/// unlike wasVisited, this method uses information not available to player owner, for example, if object was cleared by another player before
-	bool wasVisitedBefore(const CGHeroInstance * contextHero) const;
+	bool wasVisitedBefore(const CGHeroInstance * contextHero) const override;
 	
 	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 	
-	virtual void grantRewardWithMessage(const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const;
-	virtual void selectRewardWithMessage(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices, const MetaString & dialog) const;
-
-	virtual void grantAllRewardsWithMessage(const CGHeroInstance * contextHero, const std::vector<ui32>& rewardIndices, bool markAsVisit) const;
-
-	std::vector<Component> loadComponents(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices) const;
-
 	std::string getDisplayTextImpl(PlayerColor player, const CGHeroInstance * hero, bool includeDescription) const;
 	std::string getDescriptionMessage(PlayerColor player, const CGHeroInstance * hero) const;
 	std::vector<Component> getPopupComponentsImpl(PlayerColor player, const CGHeroInstance * hero) const;
 
-	void doHeroVisit(const CGHeroInstance *h) const;
-
 	/// Returns true if this object is currently guarded
 	bool isGuarded() const;
 public:

+ 33 - 90
lib/mapObjects/TownBuildingInstance.cpp

@@ -12,14 +12,10 @@
 #include "TownBuildingInstance.h"
 
 #include "CGTownInstance.h"
-#include "../texts/CGeneralTextHandler.h"
 #include "../IGameCallback.h"
-#include "../gameState/CGameState.h"
 #include "../mapObjects/CGHeroInstance.h"
-#include "../networkPacks/PacksForClient.h"
 #include "../entities/building/CBuilding.h"
 
-
 #include <vstd/RNG.h>
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -130,7 +126,7 @@ void TownRewardableBuildingInstance::setProperty(ObjProperty what, ObjPropertyID
 
 void TownRewardableBuildingInstance::heroLevelUpDone(const CGHeroInstance *hero) const
 {
-	grantRewardAfterLevelup(cb, configuration.info.at(selectedReward), town, hero);
+	grantRewardAfterLevelup(configuration.info.at(selectedReward), town, hero);
 }
 
 void TownRewardableBuildingInstance::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const
@@ -154,14 +150,12 @@ void TownRewardableBuildingInstance::blockingDialogAnswered(const CGHeroInstance
 
 void TownRewardableBuildingInstance::grantReward(ui32 rewardID, const CGHeroInstance * hero) const
 {
-	town->addHeroToStructureVisitors(hero, getBuildingType());
-	
-	grantRewardBeforeLevelup(cb, configuration.info.at(rewardID), hero);
+	grantRewardBeforeLevelup(configuration.info.at(rewardID), hero);
 	
 	// hero is not blocked by levelup dialog - grant remainder immediately
 	if(!cb->isVisitCoveredByAnotherQuery(town, hero))
 	{
-		grantRewardAfterLevelup(cb, configuration.info.at(rewardID), town, hero);
+		grantRewardAfterLevelup(configuration.info.at(rewardID), town, hero);
 	}
 }
 
@@ -196,93 +190,42 @@ bool TownRewardableBuildingInstance::wasVisitedBefore(const CGHeroInstance * con
 
 void TownRewardableBuildingInstance::onHeroVisit(const CGHeroInstance *h) const
 {
-	auto grantRewardWithMessage = [&](int index) -> void
-	{
-		auto vi = configuration.info.at(index);
-		logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString());
-		
-		town->addHeroToStructureVisitors(h, getBuildingType()); //adding to visitors
-
-		InfoWindow iw;
-		iw.player = h->tempOwner;
-		iw.text = vi.message;
-		vi.reward.loadComponents(iw.components, h);
-		iw.type = EInfoWindowMode::MODAL;
-		if(!iw.components.empty() || !iw.text.toString().empty())
-			cb->showInfoDialog(&iw);
-		
-		grantReward(index, h);
-	};
-	auto selectRewardsMessage = [&](const std::vector<ui32> & rewards, const MetaString & dialog) -> void
-	{
-		BlockingDialog sd(configuration.canRefuse, rewards.size() > 1);
-		sd.player = h->tempOwner;
-		sd.text = dialog;
+	assert(town->hasBuilt(getBuildingType()));
 
-		if (rewards.size() > 1)
-			for (auto index : rewards)
-				sd.components.push_back(configuration.info.at(index).reward.getDisplayedComponent(h));
-
-		if (rewards.size() == 1)
-			configuration.info.at(rewards.front()).reward.loadComponents(sd.components, h);
+	if(town->hasBuilt(getBuildingType()))
+		doHeroVisit(h);
+}
 
-		cb->showBlockingDialog(this, &sd);
-	};
-	
-	if(!town->hasBuilt(getBuildingType()))
-		return;
+const IObjectInterface * TownRewardableBuildingInstance::getObject() const
+{
+	return this;
+}
 
-	if(!wasVisitedBefore(h))
+bool TownRewardableBuildingInstance::wasVisited(PlayerColor player) const
+{
+	switch (configuration.visitMode)
 	{
-		auto rewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT);
-
-		logGlobal->debug("Visiting object with %d possible rewards", rewards.size());
-		switch (rewards.size())
-		{
-			case 0: // no available rewards, e.g. visiting School of War without gold
-			{
-				auto emptyRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_NOT_AVAILABLE);
-				if (!emptyRewards.empty())
-					grantRewardWithMessage(emptyRewards[0]);
-				else
-					logMod->warn("No applicable message for visiting empty object!");
-				break;
-			}
-			case 1: // one reward. Just give it with message
-			{
-				if (configuration.canRefuse)
-					selectRewardsMessage(rewards, configuration.info.at(rewards.front()).message);
-				else
-					grantRewardWithMessage(rewards.front());
-				break;
-			}
-			default: // multiple rewards. Act according to select mode
-			{
-				switch (configuration.selectMode) {
-					case Rewardable::SELECT_PLAYER: // player must select
-						selectRewardsMessage(rewards, configuration.onSelect);
-						break;
-					case Rewardable::SELECT_FIRST: // give first available
-						grantRewardWithMessage(rewards.front());
-						break;
-					case Rewardable::SELECT_RANDOM: // give random
-						grantRewardWithMessage(*RandomGeneratorUtil::nextItem(rewards, cb->gameState()->getRandomGenerator()));
-						break;
-				}
-				break;
-			}
-		}
+		case Rewardable::VISIT_UNLIMITED:
+		case Rewardable::VISIT_BONUS:
+		case Rewardable::VISIT_HERO:
+		case Rewardable::VISIT_LIMITER:
+			return false;
+		case Rewardable::VISIT_ONCE:
+		case Rewardable::VISIT_PLAYER:
+			return !visitors.empty();
+		default:
+			return false;
 	}
-	else
-	{
-		logGlobal->debug("Revisiting already visited object");
+}
 
-		auto visitedRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_ALREADY_VISITED);
-		if (!visitedRewards.empty())
-			grantRewardWithMessage(visitedRewards[0]);
-		else
-			logMod->debug("No applicable message for visiting already visited object!");
-	}
+void TownRewardableBuildingInstance::markAsVisited(const CGHeroInstance * hero) const
+{
+	town->addHeroToStructureVisitors(hero, getBuildingType());
+}
+
+void TownRewardableBuildingInstance::markAsScouted(const CGHeroInstance * hero) const
+{
+	// no-op - town building is always 'scouted' by owner
 }
 
 

+ 6 - 2
lib/mapObjects/TownBuildingInstance.h

@@ -63,10 +63,14 @@ class DLL_LINKAGE TownRewardableBuildingInstance : public TownBuildingInstance,
 	ui16 selectedReward = 0;
 	std::set<ObjectInstanceID> visitors;
 
-	bool wasVisitedBefore(const CGHeroInstance * contextHero) const;
-	void grantReward(ui32 rewardID, const CGHeroInstance * hero) const;
+	bool wasVisitedBefore(const CGHeroInstance * contextHero) const override;
+	void grantReward(ui32 rewardID, const CGHeroInstance * hero) const override;
 	Rewardable::Configuration generateConfiguration(vstd::RNG & rand) const;
 
+	const IObjectInterface * getObject() const override;
+	bool wasVisited(PlayerColor player) const override;
+	void markAsVisited(const CGHeroInstance * hero) const override;
+	void markAsScouted(const CGHeroInstance * hero) const override;
 public:
 	void setProperty(ObjProperty what, ObjPropertyID identifier) override;
 	void onHeroVisit(const CGHeroInstance * h) const override;

+ 152 - 2
lib/rewardable/Interface.cpp

@@ -25,6 +25,8 @@
 #include "../networkPacks/PacksForClient.h"
 #include "../IGameCallback.h"
 
+#include <vstd/RNG.h>
+
 VCMI_LIB_NAMESPACE_BEGIN
 
 std::vector<ui32> Rewardable::Interface::getAvailableRewards(const CGHeroInstance * hero, Rewardable::EEventType event) const
@@ -44,8 +46,10 @@ std::vector<ui32> Rewardable::Interface::getAvailableRewards(const CGHeroInstanc
 	return ret;
 }
 
-void Rewardable::Interface::grantRewardBeforeLevelup(IGameCallback * cb, const Rewardable::VisitInfo & info, const CGHeroInstance * hero) const
+void Rewardable::Interface::grantRewardBeforeLevelup(const Rewardable::VisitInfo & info, const CGHeroInstance * hero) const
 {
+	auto cb = getObject()->cb;
+
 	assert(hero);
 	assert(hero->tempOwner.isValidPlayer());
 	assert(info.reward.creatures.size() <= GameConstants::ARMY_SIZE);
@@ -129,8 +133,10 @@ void Rewardable::Interface::grantRewardBeforeLevelup(IGameCallback * cb, const R
 		cb->giveExperience(hero, expToGive);
 }
 
-void Rewardable::Interface::grantRewardAfterLevelup(IGameCallback * cb, const Rewardable::VisitInfo & info, const CArmedInstance * army, const CGHeroInstance * hero) const
+void Rewardable::Interface::grantRewardAfterLevelup(const Rewardable::VisitInfo & info, const CArmedInstance * army, const CGHeroInstance * hero) const
 {
+	auto cb = getObject()->cb;
+
 	if(info.reward.manaDiff || info.reward.manaPercentage >= 0)
 		cb->setManaPoints(hero->id, info.reward.calculateManaPoints(hero));
 
@@ -216,4 +222,148 @@ void Rewardable::Interface::serializeJson(JsonSerializeFormat & handler)
 	configuration.serializeJson(handler);
 }
 
+void Rewardable::Interface::grantRewardWithMessage(const CGHeroInstance * contextHero, int index, bool markAsVisit) const
+{
+	auto vi = configuration.info.at(index);
+	logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString());
+	// show message only if it is not empty or in infobox
+	if (configuration.infoWindowType != EInfoWindowMode::MODAL || !vi.message.toString().empty())
+	{
+		InfoWindow iw;
+		iw.player = contextHero->tempOwner;
+		iw.text = vi.message;
+		vi.reward.loadComponents(iw.components, contextHero);
+		iw.type = configuration.infoWindowType;
+		if(!iw.components.empty() || !iw.text.toString().empty())
+			getObject()->cb->showInfoDialog(&iw);
+	}
+	// grant reward afterwards. Note that it may remove object
+	if(markAsVisit)
+		markAsVisited(contextHero);
+	grantReward(index, contextHero);
+}
+
+void Rewardable::Interface::selectRewardWithMessage(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices, const MetaString & dialog) const
+{
+	BlockingDialog sd(configuration.canRefuse, rewardIndices.size() > 1);
+	sd.player = contextHero->tempOwner;
+	sd.text = dialog;
+	sd.components = loadComponents(contextHero, rewardIndices);
+	getObject()->cb->showBlockingDialog(getObject(), &sd);
+}
+
+std::vector<Component> Rewardable::Interface::loadComponents(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices) const
+{
+	std::vector<Component> result;
+
+	if (rewardIndices.empty())
+		return result;
+
+	if (configuration.selectMode != Rewardable::SELECT_FIRST && rewardIndices.size() > 1)
+	{
+		for (auto index : rewardIndices)
+			result.push_back(configuration.info.at(index).reward.getDisplayedComponent(contextHero));
+	}
+	else
+	{
+		configuration.info.at(rewardIndices.front()).reward.loadComponents(result, contextHero);
+	}
+
+	return result;
+}
+
+void Rewardable::Interface::grantAllRewardsWithMessage(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices, bool markAsVisit) const
+{
+	if (rewardIndices.empty())
+		return;
+
+	for (auto index : rewardIndices)
+	{
+		// TODO: Merge all rewards of same type, with single message?
+		grantRewardWithMessage(contextHero, index, false);
+	}
+	// Mark visited only after all rewards were processed
+	if(markAsVisit)
+		markAsVisited(contextHero);
+}
+
+void Rewardable::Interface::doHeroVisit(const CGHeroInstance *h) const
+{
+	if(!wasVisitedBefore(h))
+	{
+		auto rewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT);
+		bool objectRemovalPossible = false;
+		for(auto index : rewards)
+		{
+			if(configuration.info.at(index).reward.removeObject)
+				objectRemovalPossible = true;
+		}
+
+		logGlobal->debug("Visiting object with %d possible rewards", rewards.size());
+		switch (rewards.size())
+		{
+			case 0: // no available rewards, e.g. visiting School of War without gold
+			{
+				auto emptyRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_NOT_AVAILABLE);
+				if (!emptyRewards.empty())
+					grantRewardWithMessage(h, emptyRewards[0], false);
+				else
+					logMod->warn("No applicable message for visiting empty object!");
+				break;
+			}
+			case 1: // one reward. Just give it with message
+			{
+				if (configuration.canRefuse)
+					selectRewardWithMessage(h, rewards, configuration.info.at(rewards.front()).message);
+				else
+					grantRewardWithMessage(h, rewards.front(), true);
+				break;
+			}
+			default: // multiple rewards. Act according to select mode
+			{
+				switch (configuration.selectMode) {
+					case Rewardable::SELECT_PLAYER: // player must select
+						selectRewardWithMessage(h, rewards, configuration.onSelect);
+						break;
+					case Rewardable::SELECT_FIRST: // give first available
+						if (configuration.canRefuse)
+							selectRewardWithMessage(h, { rewards.front() }, configuration.info.at(rewards.front()).message);
+						else
+							grantRewardWithMessage(h, rewards.front(), true);
+						break;
+					case Rewardable::SELECT_RANDOM: // give random
+					{
+						ui32 rewardIndex = *RandomGeneratorUtil::nextItem(rewards, getObject()->cb->getRandomGenerator());
+						if (configuration.canRefuse)
+							selectRewardWithMessage(h, { rewardIndex }, configuration.info.at(rewardIndex).message);
+						else
+							grantRewardWithMessage(h, rewardIndex, true);
+						break;
+					}
+					case Rewardable::SELECT_ALL: // grant all possible
+						grantAllRewardsWithMessage(h, rewards, true);
+						break;
+				}
+				break;
+			}
+		}
+
+		if(!objectRemovalPossible && getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT).empty())
+			markAsScouted(h);
+	}
+	else
+	{
+		logGlobal->debug("Revisiting already visited object");
+
+		if (!wasVisited(h->getOwner()))
+			markAsScouted(h);
+
+		auto visitedRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_ALREADY_VISITED);
+		if (!visitedRewards.empty())
+			grantRewardWithMessage(h, visitedRewards[0], false);
+		else
+			logMod->warn("No applicable message for visiting already visited object!");
+	}
+}
+
 VCMI_LIB_NAMESPACE_END

+ 16 - 3
lib/rewardable/Interface.h

@@ -15,7 +15,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class IGameCallback;
+class IObjectInterface;
 
 namespace Rewardable
 {
@@ -30,11 +30,24 @@ private:
 protected:
 	
 	/// function that must be called if hero got level-up during grantReward call
-	virtual void grantRewardAfterLevelup(IGameCallback * cb, const Rewardable::VisitInfo & reward, const CArmedInstance * army, const CGHeroInstance * hero) const;
+	void grantRewardAfterLevelup(const Rewardable::VisitInfo & reward, const CArmedInstance * army, const CGHeroInstance * hero) const;
 
 	/// grants reward to hero
-	virtual void grantRewardBeforeLevelup(IGameCallback * cb, const Rewardable::VisitInfo & reward, const CGHeroInstance * hero) const;
+	void grantRewardBeforeLevelup(const Rewardable::VisitInfo & reward, const CGHeroInstance * hero) const;
 	
+	virtual void grantRewardWithMessage(const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const;
+	void selectRewardWithMessage(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices, const MetaString & dialog) const;
+	void grantAllRewardsWithMessage(const CGHeroInstance * contextHero, const std::vector<ui32>& rewardIndices, bool markAsVisit) const;
+	std::vector<Component> loadComponents(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices) const;
+
+	void doHeroVisit(const CGHeroInstance *h) const;
+
+	virtual const IObjectInterface * getObject() const = 0;
+	virtual bool wasVisitedBefore(const CGHeroInstance * hero) const = 0;
+	virtual bool wasVisited(PlayerColor player) const = 0;
+	virtual void markAsVisited(const CGHeroInstance * hero) const = 0;
+	virtual void markAsScouted(const CGHeroInstance * hero) const = 0;
+	virtual void grantReward(ui32 rewardID, const CGHeroInstance * hero) const = 0;
 public:
 
 	/// filters list of visit info and returns rewards that can be granted to current hero

+ 1 - 1
server/CGameHandler.cpp

@@ -1194,7 +1194,7 @@ void CGameHandler::visitCastleObjects(const CGTownInstance * t, std::vector<cons
 
 	for (auto & building : t->rewardableBuildings)
 	{
-		if (!t->town->buildings.at(building.first)->manualHeroVisit)
+		if (!t->town->buildings.at(building.first)->manualHeroVisit && t->hasBuilt(building.first))
 			buildingsToVisit.push_back(building.first);
 	}