Explorar el Código

Merge pull request #4821 from IvanSavenko/flaggable

Basic support for configurable flaggable objects
Ivan Savenko hace 1 año
padre
commit
c2e49bd10a

+ 3 - 3
client/widgets/MiscWidgets.cpp

@@ -581,13 +581,13 @@ void MoraleLuckBox::set(const AFactionMember * node)
 	else if(morale && node && node->getBonusBearer()->hasBonusOfType(BonusType::NO_MORALE))
 	{
 		auto noMorale = node->getBonusBearer()->getBonus(Selector::type()(BonusType::NO_MORALE));
-		text += "\n" + noMorale->Description();
+		text += "\n" + noMorale->Description(LOCPLINT->cb.get());
 		component.value = 0;
 	}
 	else if (!morale && node && node->getBonusBearer()->hasBonusOfType(BonusType::NO_LUCK))
 	{
 		auto noLuck = node->getBonusBearer()->getBonus(Selector::type()(BonusType::NO_LUCK));
-		text += "\n" + noLuck->Description();
+		text += "\n" + noLuck->Description(LOCPLINT->cb.get());
 		component.value = 0;
 	}
 	else
@@ -596,7 +596,7 @@ void MoraleLuckBox::set(const AFactionMember * node)
 		for(auto & bonus : * modifierList)
 		{
 			if(bonus->val) {
-				const std::string& description = bonus->Description();
+				const std::string& description = bonus->Description(LOCPLINT->cb.get());
 				//arraytxt already contains \n
 				if (description.size() && description[0] != '\n')
 					addInfo += '\n';

+ 4 - 16
client/windows/CKingdomInterface.cpp

@@ -583,28 +583,16 @@ void CKingdomInterface::generateMinesList(const std::vector<const CGObjectInstan
 		if(object->ID == Obj::MINE || object->ID == Obj::ABANDONED_MINE)
 		{
 			const CGMine * mine = dynamic_cast<const CGMine *>(object);
-			assert(mine);
 			minesCount[mine->producedResource]++;
-			totalIncome += mine->dailyIncome()[EGameResID::GOLD];
 		}
 	}
 
-	//Heroes can produce gold as well - skill, specialty or arts
-	std::vector<const CGHeroInstance*> heroes = LOCPLINT->cb->getHeroesInfo(true);
-	auto * playerSettings = LOCPLINT->cb->getPlayerSettings(LOCPLINT->playerID);
-	for(auto & hero : heroes)
-	{
-		totalIncome += hero->dailyIncome()[EGameResID::GOLD];
-	}
-
-	//Add town income of all towns
-	std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
-	for(auto & town : towns)
-	{
-		totalIncome += town->dailyIncome()[EGameResID::GOLD];
-	}
+	for(auto & mapObject : ownedObjects)
+		totalIncome += mapObject->asOwnable()->dailyIncome()[EGameResID::GOLD];
 
 	//if player has some modded boosts we want to show that as well
+	const auto * playerSettings = LOCPLINT->cb->getPlayerSettings(LOCPLINT->playerID);
+	const auto & towns = LOCPLINT->cb->getTownsInfo(true);
 	totalIncome += LOCPLINT->cb->getPlayerState(LOCPLINT->playerID)->valOfBonuses(BonusType::RESOURCES_CONSTANT_BOOST, BonusSubtypeID(GameResID(EGameResID::GOLD))) * playerSettings->handicap.percentIncome / 100;
 	totalIncome += LOCPLINT->cb->getPlayerState(LOCPLINT->playerID)->valOfBonuses(BonusType::RESOURCES_TOWN_MULTIPLYING_BOOST, BonusSubtypeID(GameResID(EGameResID::GOLD))) * towns.size() * playerSettings->handicap.percentIncome / 100;
 

+ 1 - 0
config/gameConfig.json

@@ -53,6 +53,7 @@
 		"config/objects/creatureBanks.json",
 		"config/objects/dwellings.json",
 		"config/objects/generic.json",
+		"config/objects/lighthouse.json",
 		"config/objects/magicSpring.json",
 		"config/objects/magicWell.json",
 		"config/objects/moddables.json",

+ 0 - 17
config/objects/generic.json

@@ -270,23 +270,6 @@
 			}
 		}
 	},
-	"lighthouse" : {
-		"index" :42,
-		"handler" : "lighthouse",
-		"base" : {
-			"sounds" : {
-				"visit" : ["LIGHTHOUSE"]
-			}
-		},
-		"types" : {
-			"object" : {
-				"index" : 0,
-				"aiValue" : 500,
-				"rmg" : {
-				}
-			}
-		}
-	},
 	"obelisk" : {
 		"index" :57,
 		"handler" : "obelisk",

+ 29 - 0
config/objects/lighthouse.json

@@ -0,0 +1,29 @@
+{
+	"lighthouse" : {
+		"index" :42,
+		"handler" : "flaggable",
+		"base" : {
+			"sounds" : {
+				"visit" : ["LIGHTHOUSE"]
+			}
+		},
+		"types" : {
+			"lighthouse" : {
+				"compatibilityIdentifiers" : [ "object" ],
+				"index" : 0,
+				"aiValue" : 500,
+				"rmg" : {
+				},
+				
+				"message" : "@core.advevent.69",
+				"bonuses" : {
+					"seaMovement" : {
+						"type" : "MOVEMENT",
+						"subtype" : "heroMovementSea",
+						"val" : 500
+					}
+				}
+			}
+		}
+	}
+}

+ 1 - 1
docs/modders/Map_Object_Format.md

@@ -49,6 +49,7 @@ These are object types that are available for modding and have configurable prop
 - `dwelling` - see [Dwelling](Map_Objects/Dwelling.md). Object that allows recruitments of units outside of towns
 - `market` - see [Market](Map_Objects/Market.md). Trading resources, artifacts, creatures and such
 - `boat` - see [Boat](Map_Objects/Boat.md). Object to move across different terrains, such as water
+- `flaggable` - see [Flaggable](Map_Objects/Flaggable.md). Object that can be flagged by a player to provide [Bonus](Bonus_Format.md) or resources
 - `hillFort` - TODO: documentation. See config files in vcmi installation for reference
 - `shipyard` - TODO: documentation. See config files in vcmi installation for reference
 - `terrain` - Defines terrain overlays such as magic grounds. TODO: documentation. See config files in vcmi installation for reference
@@ -60,7 +61,6 @@ These are types that don't have configurable properties, however it is possible
 - `generic` - Defines empty object type that provides no functionality. Note that unlike `static`, objects of this type are never used by RMG
 - `borderGate`
 - `borderGuard`
-- `lighthouse`
 - `magi`
 - `mine`
 - `obelisk`

+ 39 - 0
docs/modders/Map_Objects/Flaggable.md

@@ -0,0 +1,39 @@
+# Flaggable objects
+
+Flaggable object are those that can be captured by a visiting hero. H3 examples are mines, dwellings, or lighthouse.
+
+Currently, it is possible to make flaggable objects that provide player with:
+- Any [Bonus](Bonus_Format.md) supported by bonus system
+- Daily resources income (wood, ore, gold, etc)
+
+## Format description
+
+```jsonc
+{
+  "baseObjectName" : {
+    "name" : "Object name",
+    "handler" : "flaggable", 
+    "types" : {
+      "objectName" : {
+        
+        // Text for message that player will get on capturing this object with a hero
+        // Alternatively, it is possible to reuse existing string from H3 using form '@core.advevent.69'
+        "onVisit" : "{Object Name}\r\n\r\nText of messages that player will see on visit.",
+        
+        // List of bonuses that will be granted to player that owns this object
+        "bonuses" : {
+          "firstBonus" : { BONUS FORMAT },
+          "secondBonus" : { BONUS FORMAT },
+        },
+        
+        // Resources that will be given to owner on every day
+        "dailyIncome" : {
+          "wood" : 2,
+          "ore"  : 2,
+          "gold" : 1000
+        }
+      }
+    }
+  }
+}
+```

+ 4 - 0
lib/CMakeLists.txt

@@ -116,6 +116,7 @@ set(lib_MAIN_SRCS
 	mapObjectConstructors/CommonConstructors.cpp
 	mapObjectConstructors/CRewardableConstructor.cpp
 	mapObjectConstructors/DwellingInstanceConstructor.cpp
+	mapObjectConstructors/FlaggableInstanceConstructor.cpp
 	mapObjectConstructors/HillFortInstanceConstructor.cpp
 	mapObjectConstructors/ShipyardInstanceConstructor.cpp
 
@@ -132,6 +133,7 @@ set(lib_MAIN_SRCS
 	mapObjects/CObjectHandler.cpp
 	mapObjects/CQuest.cpp
 	mapObjects/CRewardableObject.cpp
+	mapObjects/FlaggableMapObject.cpp
 	mapObjects/IMarket.cpp
 	mapObjects/IObjectInterface.cpp
 	mapObjects/MiscObjects.cpp
@@ -497,6 +499,7 @@ set(lib_MAIN_HEADERS
 	mapObjectConstructors/CRewardableConstructor.h
 	mapObjectConstructors/DwellingInstanceConstructor.h
 	mapObjectConstructors/HillFortInstanceConstructor.h
+	mapObjectConstructors/FlaggableInstanceConstructor.h
 	mapObjectConstructors/IObjectInfo.h
 	mapObjectConstructors/RandomMapInfo.h
 	mapObjectConstructors/ShipyardInstanceConstructor.h
@@ -515,6 +518,7 @@ set(lib_MAIN_HEADERS
 	mapObjects/CObjectHandler.h
 	mapObjects/CQuest.h
 	mapObjects/CRewardableObject.h
+	mapObjects/FlaggableMapObject.h
 	mapObjects/IMarket.h
 	mapObjects/IObjectInterface.h
 	mapObjects/IOwnableObject.h

+ 1 - 1
lib/CSkillHandler.cpp

@@ -122,7 +122,7 @@ CSkill::LevelInfo & CSkill::at(int level)
 DLL_LINKAGE std::ostream & operator<<(std::ostream & out, const CSkill::LevelInfo & info)
 {
 	for(int i=0; i < info.effects.size(); i++)
-		out << (i ? "," : "") << info.effects[i]->Description();
+		out << (i ? "," : "") << info.effects[i]->Description(nullptr);
 	return out << "])";
 }
 

+ 2 - 2
lib/battle/BattleInfo.cpp

@@ -885,12 +885,12 @@ void BattleInfo::addOrUpdateUnitBonus(CStack * sta, const Bonus & value, bool fo
 	if(forceAdd || !sta->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, value.sid).And(Selector::typeSubtypeValueType(value.type, value.subtype, value.valType))))
 	{
 		//no such effect or cumulative - add new
-		logBonus->trace("%s receives a new bonus: %s", sta->nodeName(), value.Description());
+		logBonus->trace("%s receives a new bonus: %s", sta->nodeName(), value.Description(nullptr));
 		sta->addNewBonus(std::make_shared<Bonus>(value));
 	}
 	else
 	{
-		logBonus->trace("%s updated bonus: %s", sta->nodeName(), value.Description());
+		logBonus->trace("%s updated bonus: %s", sta->nodeName(), value.Description(nullptr));
 
 		for(const auto & stackBonus : sta->getExportedBonusList()) //TODO: optimize
 		{

+ 8 - 1
lib/bonuses/Bonus.cpp

@@ -18,8 +18,11 @@
 #include "../CCreatureHandler.h"
 #include "../CCreatureSet.h"
 #include "../CSkillHandler.h"
+#include "../IGameCallback.h"
 #include "../TerrainHandler.h"
 #include "../VCMI_Lib.h"
+#include "../mapObjects/CGObjectInstance.h"
+#include "../mapObjectConstructors/CObjectClassesHandler.h"
 #include "../battle/BattleInfo.h"
 #include "../constants/StringConstants.h"
 #include "../entities/hero/CHero.h"
@@ -87,7 +90,7 @@ JsonNode CAddInfo::toJsonNode() const
 		return node;
 	}
 }
-std::string Bonus::Description(std::optional<si32> customValue) const
+std::string Bonus::Description(const IGameInfoCallback * cb, std::optional<si32> customValue) const
 {
 	MetaString descriptionHelper = description;
 	auto valueToShow = customValue.value_or(val);
@@ -112,6 +115,10 @@ std::string Bonus::Description(std::optional<si32> customValue) const
 			case BonusSource::HERO_SPECIAL:
 				descriptionHelper.appendTextID(sid.as<HeroTypeID>().toEntity(VLC)->getNameTextID());
 				break;
+			case BonusSource::OBJECT_INSTANCE:
+				const auto * object = cb->getObj(sid.as<ObjectInstanceID>());
+				if (object)
+					descriptionHelper.appendTextID(VLC->objtypeh->getObjectName(object->ID, object->subID));
 		}
 	}
 

+ 2 - 1
lib/bonuses/Bonus.h

@@ -26,6 +26,7 @@ class IPropagator;
 class IUpdater;
 class BonusList;
 class CSelector;
+class IGameInfoCallback;
 
 using BonusSubtypeID = VariantIdentifier<BonusCustomSubtype, SpellID, CreatureID, PrimarySkill, TerrainId, GameResID, SpellSchool>;
 using BonusSourceID = VariantIdentifier<BonusCustomSource, SpellID, CreatureID, ArtifactID, CampaignScenarioID, SecondarySkill, HeroTypeID, Obj, ObjectInstanceID, BuildingTypeUniqueID, BattleField>;
@@ -177,7 +178,7 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>, public Se
 		val += Val;
 	}
 
-	std::string Description(std::optional<si32> customValue = {}) const;
+	std::string Description(const IGameInfoCallback * cb, std::optional<si32> customValue = {}) const;
 	JsonNode toJsonNode() const;
 
 	std::shared_ptr<Bonus> addLimiter(const TLimiterPtr & Limiter); //returns this for convenient chain-calls

+ 3 - 3
lib/bonuses/CBonusSystemNode.cpp

@@ -378,7 +378,7 @@ void CBonusSystemNode::propagateBonus(const std::shared_ptr<Bonus> & b, const CB
 			? source.getUpdatedBonus(b, b->propagationUpdater)
 			: b;
 		bonuses.push_back(propagated);
-		logBonus->trace("#$# %s #propagated to# %s",  propagated->Description(), nodeName());
+		logBonus->trace("#$# %s #propagated to# %s",  propagated->Description(nullptr), nodeName());
 	}
 
 	TNodes lchildren;
@@ -392,9 +392,9 @@ void CBonusSystemNode::unpropagateBonus(const std::shared_ptr<Bonus> & b)
 	if(b->propagator->shouldBeAttached(this))
 	{
 		if (bonuses -= b)
-			logBonus->trace("#$# %s #is no longer propagated to# %s",  b->Description(), nodeName());
+			logBonus->trace("#$# %s #is no longer propagated to# %s",  b->Description(nullptr), nodeName());
 		else
-			logBonus->warn("Attempt to remove #$# %s, which is not propagated to %s", b->Description(), nodeName());
+			logBonus->warn("Attempt to remove #$# %s, which is not propagated to %s", b->Description(nullptr), nodeName());
 
 		bonuses.remove_if([b](const auto & bonus)
 		{

+ 9 - 5
lib/mapObjectConstructors/CObjectClassesHandler.cpp

@@ -23,17 +23,21 @@
 #include "../mapObjectConstructors/CRewardableConstructor.h"
 #include "../mapObjectConstructors/CommonConstructors.h"
 #include "../mapObjectConstructors/DwellingInstanceConstructor.h"
+#include "../mapObjectConstructors/FlaggableInstanceConstructor.h"
 #include "../mapObjectConstructors/HillFortInstanceConstructor.h"
 #include "../mapObjectConstructors/ShipyardInstanceConstructor.h"
+
 #include "../mapObjects/CGCreature.h"
+#include "../mapObjects/CGHeroInstance.h"
+#include "../mapObjects/CGMarket.h"
 #include "../mapObjects/CGPandoraBox.h"
+#include "../mapObjects/CGTownInstance.h"
 #include "../mapObjects/CQuest.h"
-#include "../mapObjects/ObjectTemplate.h"
-#include "../mapObjects/CGMarket.h"
+#include "../mapObjects/FlaggableMapObject.h"
 #include "../mapObjects/MiscObjects.h"
-#include "../mapObjects/CGHeroInstance.h"
-#include "../mapObjects/CGTownInstance.h"
+#include "../mapObjects/ObjectTemplate.h"
 #include "../mapObjects/ObstacleSetHandler.h"
+
 #include "../modding/IdentifierStorage.h"
 #include "../modding/CModHandler.h"
 #include "../modding/ModScope.h"
@@ -57,6 +61,7 @@ CObjectClassesHandler::CObjectClassesHandler()
 	SET_HANDLER_CLASS("town", CTownInstanceConstructor);
 	SET_HANDLER_CLASS("bank", CBankInstanceConstructor);
 	SET_HANDLER_CLASS("boat", BoatInstanceConstructor);
+	SET_HANDLER_CLASS("flaggable", FlaggableInstanceConstructor);
 	SET_HANDLER_CLASS("market", MarketInstanceConstructor);
 	SET_HANDLER_CLASS("hillFort", HillFortInstanceConstructor);
 	SET_HANDLER_CLASS("shipyard", ShipyardInstanceConstructor);
@@ -82,7 +87,6 @@ CObjectClassesHandler::CObjectClassesHandler()
 	SET_HANDLER("garrison", CGGarrison);
 	SET_HANDLER("heroPlaceholder", CGHeroPlaceholder);
 	SET_HANDLER("keymaster", CGKeymasterTent);
-	SET_HANDLER("lighthouse", CGLighthouse);
 	SET_HANDLER("magi", CGMagi);
 	SET_HANDLER("mine", CGMine);
 	SET_HANDLER("obelisk", CGObelisk);

+ 60 - 0
lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp

@@ -0,0 +1,60 @@
+/*
+* FlaggableInstanceConstructor.cpp, 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
+*
+*/
+#include "StdInc.h"
+#include "FlaggableInstanceConstructor.h"
+
+#include "../json/JsonBonus.h"
+#include "../texts/CGeneralTextHandler.h"
+#include "../VCMI_Lib.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+void FlaggableInstanceConstructor::initTypeData(const JsonNode & config)
+{
+	for (const auto & bonusJson : config["bonuses"].Struct())
+		providedBonuses.push_back(JsonUtils::parseBonus(bonusJson.second));
+
+	if (!config["message"].isNull())
+	{
+		std::string message = config["message"].String();
+		if (!message.empty() && message.at(0) == '@')
+		{
+			visitMessageTextID = message.substr(1);
+		}
+		else
+		{
+			visitMessageTextID = TextIdentifier(getBaseTextID(), "onVisit").get();
+			VLC->generaltexth->registerString( config.getModScope(), visitMessageTextID, config["message"]);
+		}
+	}
+
+	dailyIncome = ResourceSet(config["dailyIncome"]);
+}
+
+void FlaggableInstanceConstructor::initializeObject(FlaggableMapObject * flaggable) const
+{
+}
+
+const std::string & FlaggableInstanceConstructor::getVisitMessageTextID() const
+{
+	return visitMessageTextID;
+}
+
+const std::vector<std::shared_ptr<Bonus>> & FlaggableInstanceConstructor::getProvidedBonuses() const
+{
+	return providedBonuses;
+}
+
+const ResourceSet & FlaggableInstanceConstructor::getDailyIncome() const
+{
+	return dailyIncome;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 41 - 0
lib/mapObjectConstructors/FlaggableInstanceConstructor.h

@@ -0,0 +1,41 @@
+/*
+* FlaggableInstanceConstructor.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
+
+#include "CDefaultObjectTypeHandler.h"
+
+#include "../ResourceSet.h"
+#include "../bonuses/Bonus.h"
+#include "../mapObjects/FlaggableMapObject.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class FlaggableInstanceConstructor final : public CDefaultObjectTypeHandler<FlaggableMapObject>
+{
+	/// List of bonuses that are provided by every map object of this type
+	std::vector<std::shared_ptr<Bonus>> providedBonuses;
+
+	/// ID of message to show on hero visit
+	std::string visitMessageTextID;
+
+	/// Amount of resources granted by this object to owner every day
+	ResourceSet dailyIncome;
+
+protected:
+	void initTypeData(const JsonNode & config) override;
+	void initializeObject(FlaggableMapObject * object) const override;
+
+public:
+	const std::string & getVisitMessageTextID() const;
+	const std::vector<std::shared_ptr<Bonus>> & getProvidedBonuses() const;
+	const ResourceSet & getDailyIncome() const;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 2 - 2
lib/mapObjects/CGTownInstance.cpp

@@ -166,7 +166,7 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const
 		const auto growth = b->val * (base + castleBonus) / 100;
 		if (growth)
 		{
-			ret.entries.emplace_back(growth, b->Description(growth));
+			ret.entries.emplace_back(growth, b->Description(cb, growth));
 		}
 	}
 
@@ -174,7 +174,7 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const
 	// Note: bonus uses 1-based levels (Pikeman is level 1), town list uses 0-based (Pikeman in 0-th creatures entry)
 	TConstBonusListPtr bonuses = getBonuses(Selector::typeSubtype(BonusType::CREATURE_GROWTH, BonusCustomSubtype::creatureLevel(level+1)));
 	for(const auto & b : *bonuses)
-		ret.entries.emplace_back(b->val, b->Description());
+		ret.entries.emplace_back(b->val, b->Description(cb));
 
 	int dwellingBonus = 0;
 	if(const PlayerState *p = cb->getPlayerState(tempOwner, false))

+ 105 - 0
lib/mapObjects/FlaggableMapObject.cpp

@@ -0,0 +1,105 @@
+/*
+ * FlaggableMapObject.cpp, 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
+ *
+ */
+
+#include "StdInc.h"
+#include "FlaggableMapObject.h"
+
+#include "../IGameCallback.h"
+#include "CGHeroInstance.h"
+#include "../networkPacks/PacksForClient.h"
+#include "../mapObjectConstructors/FlaggableInstanceConstructor.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+const IOwnableObject * FlaggableMapObject::asOwnable() const
+{
+	return this;
+}
+
+ResourceSet FlaggableMapObject::dailyIncome() const
+{
+	return getFlaggableHandler()->getDailyIncome();
+}
+
+std::vector<CreatureID> FlaggableMapObject::providedCreatures() const
+{
+	return {};
+}
+
+void FlaggableMapObject::onHeroVisit( const CGHeroInstance * h ) const
+{
+	if (cb->getPlayerRelations(h->getOwner(), getOwner()) != PlayerRelations::ENEMIES)
+		return; // H3 behavior - revisiting owned Lighthouse is a no-op
+
+	if (getOwner().isValidPlayer())
+		takeBonusFrom(getOwner());
+
+	cb->setOwner(this, h->getOwner()); //not ours? flag it!
+
+	InfoWindow iw;
+	iw.player = h->getOwner();
+	iw.text.appendTextID(getFlaggableHandler()->getVisitMessageTextID());
+	cb->showInfoDialog(&iw);
+
+	giveBonusTo(h->getOwner());
+}
+
+void FlaggableMapObject::initObj(vstd::RNG & rand)
+{
+	if(getOwner().isValidPlayer())
+	{
+		// FIXME: This is dirty hack
+		giveBonusTo(getOwner(), true);
+	}
+}
+
+std::shared_ptr<FlaggableInstanceConstructor> FlaggableMapObject::getFlaggableHandler() const
+{
+	return std::dynamic_pointer_cast<FlaggableInstanceConstructor>(getObjectHandler());
+}
+
+void FlaggableMapObject::giveBonusTo(const PlayerColor & player, bool onInit) const
+{
+	for (auto const & bonus : getFlaggableHandler()->getProvidedBonuses())
+	{
+		GiveBonus gb(GiveBonus::ETarget::PLAYER);
+		gb.id = player;
+		gb.bonus = *bonus;
+
+		// FIXME: better place for this code?
+		gb.bonus.duration = BonusDuration::PERMANENT;
+		gb.bonus.source = BonusSource::OBJECT_INSTANCE;
+		gb.bonus.sid = BonusSourceID(id);
+
+		// FIXME: This is really dirty hack
+		// Proper fix would be to make FlaggableMapObject into bonus system node
+		// Unfortunately this will cause saves breakage
+		if(onInit)
+			gb.applyGs(cb->gameState());
+		else
+			cb->sendAndApply(gb);
+	}
+}
+
+void FlaggableMapObject::takeBonusFrom(const PlayerColor & player) const
+{
+	RemoveBonus rb(GiveBonus::ETarget::PLAYER);
+	rb.whoID = player;
+	rb.source = BonusSource::OBJECT_INSTANCE;
+	rb.id = BonusSourceID(id);
+	cb->sendAndApply(rb);
+}
+
+void FlaggableMapObject::serializeJsonOptions(JsonSerializeFormat& handler)
+{
+	serializeJsonOwner(handler);
+}
+
+VCMI_LIB_NAMESPACE_END

+ 41 - 0
lib/mapObjects/FlaggableMapObject.h

@@ -0,0 +1,41 @@
+/*
+ * FlaggableMapObject.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
+
+#include "CGObjectInstance.h"
+#include "IOwnableObject.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct Bonus;
+class FlaggableInstanceConstructor;
+
+class DLL_LINKAGE FlaggableMapObject : public CGObjectInstance, public IOwnableObject
+{
+	std::shared_ptr<FlaggableInstanceConstructor> getFlaggableHandler() const;
+
+	void giveBonusTo(const PlayerColor & player, bool onInit = false) const;
+	void takeBonusFrom(const PlayerColor & player) const;
+
+public:
+	using CGObjectInstance::CGObjectInstance;
+
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void initObj(vstd::RNG & rand) override;
+
+	const IOwnableObject * asOwnable() const final;
+	ResourceSet dailyIncome() const override;
+	std::vector<CreatureID> providedCreatures() const override;
+
+protected:
+	void serializeJsonOptions(JsonSerializeFormat & handler) override;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 0 - 69
lib/mapObjects/MiscObjects.cpp

@@ -1311,75 +1311,6 @@ void CGObelisk::setPropertyDer(ObjProperty what, ObjPropertyID identifier)
 	}
 }
 
-const IOwnableObject * CGLighthouse::asOwnable() const
-{
-	return this;
-}
-
-ResourceSet CGLighthouse::dailyIncome() const
-{
-	return {};
-}
-
-std::vector<CreatureID> CGLighthouse::providedCreatures() const
-{
-	return {};
-}
-
-void CGLighthouse::onHeroVisit( const CGHeroInstance * h ) const
-{
-	if(h->tempOwner != tempOwner)
-	{
-		PlayerColor oldOwner = tempOwner;
-		cb->setOwner(this,h->tempOwner); //not ours? flag it!
-		h->showInfoDialog(69);
-		giveBonusTo(h->tempOwner);
-
-		if(oldOwner.isValidPlayer()) //remove bonus from old owner
-		{
-			RemoveBonus rb(GiveBonus::ETarget::PLAYER);
-			rb.whoID = oldOwner;
-			rb.source = BonusSource::OBJECT_INSTANCE;
-			rb.id = BonusSourceID(id);
-			cb->sendAndApply(rb);
-		}
-	}
-}
-
-void CGLighthouse::initObj(vstd::RNG & rand)
-{
-	if(tempOwner.isValidPlayer())
-	{
-		// FIXME: This is dirty hack
-		giveBonusTo(tempOwner, true);
-	}
-}
-
-void CGLighthouse::giveBonusTo(const PlayerColor & player, bool onInit) const
-{
-	GiveBonus gb(GiveBonus::ETarget::PLAYER);
-	gb.bonus.type = BonusType::MOVEMENT;
-	gb.bonus.val = 500;
-	gb.id = player;
-	gb.bonus.duration = BonusDuration::PERMANENT;
-	gb.bonus.source = BonusSource::OBJECT_INSTANCE;
-	gb.bonus.sid = BonusSourceID(id);
-	gb.bonus.subtype = BonusCustomSubtype::heroMovementSea;
-
-	// FIXME: This is really dirty hack
-	// Proper fix would be to make CGLighthouse into bonus system node
-	// Unfortunately this will cause saves breakage
-	if(onInit)
-		gb.applyGs(cb->gameState());
-	else
-		cb->sendAndApply(gb);
-}
-
-void CGLighthouse::serializeJsonOptions(JsonSerializeFormat& handler)
-{
-	serializeJsonOwner(handler);
-}
-
 void HillFort::onHeroVisit(const CGHeroInstance * h) const
 {
 	cb->showObjectWindow(this, EOpenWindowMode::HILL_FORT_WINDOW, h, false);

+ 0 - 22
lib/mapObjects/MiscObjects.h

@@ -413,28 +413,6 @@ protected:
 	void setPropertyDer(ObjProperty what, ObjPropertyID identifier) override;
 };
 
-class DLL_LINKAGE CGLighthouse : public CGObjectInstance, public IOwnableObject
-{
-public:
-	using CGObjectInstance::CGObjectInstance;
-
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void initObj(vstd::RNG & rand) override;
-
-	const IOwnableObject * asOwnable() const final;
-	ResourceSet dailyIncome() const override;
-	std::vector<CreatureID> providedCreatures() const override;
-
-	template <typename Handler> void serialize(Handler &h)
-	{
-		h & static_cast<CGObjectInstance&>(*this);
-	}
-	void giveBonusTo(const PlayerColor & player, bool onInit = false) const;
-
-protected:
-	void serializeJsonOptions(JsonSerializeFormat & handler) override;
-};
-
 class DLL_LINKAGE CGTerrainPatch : public CGObjectInstance
 {
 public:

+ 3 - 3
lib/mapping/MapFormatH3M.cpp

@@ -1478,9 +1478,9 @@ CGObjectInstance * CMapLoaderH3M::readShipyard(const int3 & mapPosition, std::sh
 	return object;
 }
 
-CGObjectInstance * CMapLoaderH3M::readLighthouse(const int3 & mapPosition)
+CGObjectInstance * CMapLoaderH3M::readLighthouse(const int3 & mapPosition, std::shared_ptr<const ObjectTemplate> objectTemplate)
 {
-	auto * object = new CGLighthouse(map->cb);
+	auto * object = readGeneric(mapPosition, objectTemplate);
 	setOwnerAndValidate(mapPosition, object, reader->readPlayer32());
 	return object;
 }
@@ -1618,7 +1618,7 @@ CGObjectInstance * CMapLoaderH3M::readObject(std::shared_ptr<const ObjectTemplat
 			return readPyramid(mapPosition, objectTemplate);
 
 		case Obj::LIGHTHOUSE:
-			return readLighthouse(mapPosition);
+			return readLighthouse(mapPosition, objectTemplate);
 
 		case Obj::CREATURE_BANK:
 		case Obj::DERELICT_SHIP:

+ 1 - 1
lib/mapping/MapFormatH3M.h

@@ -208,7 +208,7 @@ private:
 	CGObjectInstance * readPyramid(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);
 	CGObjectInstance * readQuestGuard(const int3 & position);
 	CGObjectInstance * readShipyard(const int3 & mapPosition, std::shared_ptr<const ObjectTemplate> objectTemplate);
-	CGObjectInstance * readLighthouse(const int3 & mapPosition);
+	CGObjectInstance * readLighthouse(const int3 & mapPosition, std::shared_ptr<const ObjectTemplate> objectTemplate);
 	CGObjectInstance * readGeneric(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate);
 	CGObjectInstance * readBank(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate);
 

+ 4 - 1
lib/serializer/RegisterTypes.h

@@ -20,14 +20,17 @@
 #include "../gameState/CGameState.h"
 #include "../gameState/CGameStateCampaign.h"
 #include "../gameState/TavernHeroesPool.h"
+
 #include "../mapObjects/CGCreature.h"
 #include "../mapObjects/CGDwelling.h"
 #include "../mapObjects/CGMarket.h"
 #include "../mapObjects/CGPandoraBox.h"
 #include "../mapObjects/CGTownInstance.h"
 #include "../mapObjects/CQuest.h"
+#include "../mapObjects/FlaggableMapObject.h"
 #include "../mapObjects/MiscObjects.h"
 #include "../mapObjects/TownBuildingInstance.h"
+
 #include "../mapping/CMap.h"
 #include "../networkPacks/PacksForClient.h"
 #include "../networkPacks/PacksForClientBattle.h"
@@ -73,7 +76,7 @@ void registerTypes(Serializer &s)
 	s.template registerType<CGSirens>(15);
 	s.template registerType<CGShipyard>(16);
 	s.template registerType<CGDenOfthieves>(17);
-	s.template registerType<CGLighthouse>(18);
+	s.template registerType<FlaggableMapObject>(18);
 	s.template registerType<CGTerrainPatch>(19);
 	s.template registerType<HillFort>(20);
 	s.template registerType<CGMarket>(21);

+ 6 - 6
mapeditor/inspector/inspector.cpp

@@ -60,7 +60,7 @@ Initializer::Initializer(CGObjectInstance * o, const PlayerColor & pl) : default
 	INIT_OBJ_TYPE(CGHeroPlaceholder);
 	INIT_OBJ_TYPE(CGHeroInstance);
 	INIT_OBJ_TYPE(CGSignBottle);
-	INIT_OBJ_TYPE(CGLighthouse);
+	INIT_OBJ_TYPE(FlaggableMapObject);
 	//INIT_OBJ_TYPE(CRewardableObject);
 	//INIT_OBJ_TYPE(CGPandoraBox);
 	//INIT_OBJ_TYPE(CGEvent);
@@ -108,7 +108,7 @@ void Initializer::initialize(CGShipyard * o)
 	o->tempOwner = defaultPlayer;
 }
 
-void Initializer::initialize(CGLighthouse * o)
+void Initializer::initialize(FlaggableMapObject * o)
 {
 	if(!o) return;
 	
@@ -246,7 +246,7 @@ void Inspector::updateProperties(CGDwelling * o)
 	}
 }
 
-void Inspector::updateProperties(CGLighthouse * o)
+void Inspector::updateProperties(FlaggableMapObject * o)
 {
 	if(!o) return;
 
@@ -494,7 +494,7 @@ void Inspector::updateProperties()
 	UPDATE_OBJ_PROPERTIES(CGHeroPlaceholder);
 	UPDATE_OBJ_PROPERTIES(CGHeroInstance);
 	UPDATE_OBJ_PROPERTIES(CGSignBottle);
-	UPDATE_OBJ_PROPERTIES(CGLighthouse);
+	UPDATE_OBJ_PROPERTIES(FlaggableMapObject);
 	UPDATE_OBJ_PROPERTIES(CRewardableObject);
 	UPDATE_OBJ_PROPERTIES(CGPandoraBox);
 	UPDATE_OBJ_PROPERTIES(CGEvent);
@@ -542,7 +542,7 @@ void Inspector::setProperty(const QString & key, const QVariant & value)
 	SET_PROPERTIES(CGHeroInstance);
 	SET_PROPERTIES(CGShipyard);
 	SET_PROPERTIES(CGSignBottle);
-	SET_PROPERTIES(CGLighthouse);
+	SET_PROPERTIES(FlaggableMapObject);
 	SET_PROPERTIES(CRewardableObject);
 	SET_PROPERTIES(CGPandoraBox);
 	SET_PROPERTIES(CGEvent);
@@ -555,7 +555,7 @@ void Inspector::setProperty(CArmedInstance * o, const QString & key, const QVari
 	if(!o) return;
 }
 
-void Inspector::setProperty(CGLighthouse * o, const QString & key, const QVariant & value)
+void Inspector::setProperty(FlaggableMapObject * o, const QString & key, const QVariant & value)
 {
 	if(!o) return;
 }

+ 3 - 2
mapeditor/inspector/inspector.h

@@ -17,6 +17,7 @@
 #include "../lib/GameConstants.h"
 #include "../lib/mapObjects/CGCreature.h"
 #include "../lib/mapObjects/MapObjects.h"
+#include "../lib/mapObjects/FlaggableMapObject.h"
 #include "../lib/mapObjects/CRewardableObject.h"
 #include "../lib/texts/CGeneralTextHandler.h"
 #include "../lib/ResourceSet.h"
@@ -48,7 +49,7 @@ public:
 	DECLARE_OBJ_TYPE(CGHeroInstance);
 	DECLARE_OBJ_TYPE(CGCreature);
 	DECLARE_OBJ_TYPE(CGSignBottle);
-	DECLARE_OBJ_TYPE(CGLighthouse);
+	DECLARE_OBJ_TYPE(FlaggableMapObject);
 	//DECLARE_OBJ_TYPE(CRewardableObject);
 	//DECLARE_OBJ_TYPE(CGEvent);
 	//DECLARE_OBJ_TYPE(CGPandoraBox);
@@ -78,7 +79,7 @@ protected:
 	DECLARE_OBJ_PROPERTY_METHODS(CGHeroInstance);
 	DECLARE_OBJ_PROPERTY_METHODS(CGCreature);
 	DECLARE_OBJ_PROPERTY_METHODS(CGSignBottle);
-	DECLARE_OBJ_PROPERTY_METHODS(CGLighthouse);
+	DECLARE_OBJ_PROPERTY_METHODS(FlaggableMapObject);
 	DECLARE_OBJ_PROPERTY_METHODS(CRewardableObject);
 	DECLARE_OBJ_PROPERTY_METHODS(CGPandoraBox);
 	DECLARE_OBJ_PROPERTY_METHODS(CGEvent);

+ 1 - 1
mapeditor/mapcontroller.cpp

@@ -121,7 +121,7 @@ void MapController::repairMap(CMap * map) const
 			   dynamic_cast<CGTownInstance*>(obj.get()) ||
 			   dynamic_cast<CGGarrison*>(obj.get()) ||
 			   dynamic_cast<CGShipyard*>(obj.get()) ||
-			   dynamic_cast<CGLighthouse*>(obj.get()) ||
+			   dynamic_cast<FlaggableMapObject*>(obj.get()) ||
 			   dynamic_cast<CGHeroInstance*>(obj.get()))
 				obj->tempOwner = PlayerColor::NEUTRAL;
 		}