Browse Source

Feature: Mods system improvement, Part III. Bunusing buildings customization.

Dmitry Orlov 4 years ago
parent
commit
854a2e6c39

+ 1 - 1
client/lobby/OptionsTab.cpp

@@ -88,7 +88,7 @@ size_t OptionsTab::CPlayerSettingsHelper::getImageIndex()
 	switch(type)
 	switch(type)
 	{
 	{
 	case TOWN:
 	case TOWN:
-		switch (settings.castle)
+		switch(settings.castle)
 		{
 		{
 		case PlayerSettings::NONE:
 		case PlayerSettings::NONE:
 			return TOWN_NONE;
 			return TOWN_NONE;

+ 1 - 1
config/factions/castle.json

@@ -176,7 +176,7 @@
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },
 				"special2":       { "type" : "stables", "requires" : [ "dwellingLvl4" ] },
 				"special2":       { "type" : "stables", "requires" : [ "dwellingLvl4" ] },
 				"special3":       { "type" : "brotherhoodOfSword", "upgrades" : "tavern" },
 				"special3":       { "type" : "brotherhoodOfSword", "upgrades" : "tavern" },
-				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
+				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }, "bonuses": [ { "type": "MORALE", "val": 2, "propagator": "PLAYER_PROPAGATOR" } ] },
 
 
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },
 				"dwellingLvl2":   { "id" : 31, "requires" : [ "dwellingLvl1" ] },
 				"dwellingLvl2":   { "id" : 31, "requires" : [ "dwellingLvl1" ] },

+ 2 - 1
config/factions/dungeon.json

@@ -175,7 +175,8 @@
 				"special2":       { "type" : "manaVortex", "requires" : [ "mageGuild1" ] },
 				"special2":       { "type" : "manaVortex", "requires" : [ "mageGuild1" ] },
 				"special3":       { "type" : "portalOfSummoning" },
 				"special3":       { "type" : "portalOfSummoning" },
 				"special4":       { "type" : "experienceVisitingBonus" },
 				"special4":       { "type" : "experienceVisitingBonus" },
-				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
+				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 },
+					"bonuses": [ { "type": "PRIMARY_SKILL", "subtype": "primSkill.spellpower", "val": 12 } ] },
 
 
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },
 				"dwellingLvl2":   { "id" : 31, "requires" : [ "dwellingLvl1" ] },
 				"dwellingLvl2":   { "id" : 31, "requires" : [ "dwellingLvl1" ] },

+ 7 - 2
config/factions/fortress.json

@@ -175,9 +175,14 @@
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },
 				"special2":       { "type" : "defenseGarrisonBonus", "requires" : [ "fort" ] },
 				"special2":       { "type" : "defenseGarrisonBonus", "requires" : [ "fort" ] },
 				"special3":       { "type" : "attackGarrisonBonus", "requires" : [ "special2" ] },
 				"special3":       { "type" : "attackGarrisonBonus", "requires" : [ "special2" ] },
-				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
-				"extraCapitol":   { "id" : 29, "requires" : [ "capitol" ], "mode" : "auto" },
+				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }, 
+					"bonuses": [
+						{ "type": "PRIMARY_SKILL", "subtype": "primSkill.attack", "val": 10 },
+						{ "type": "PRIMARY_SKILL", "subtype": "primSkill.defence", "val": 10 }
+					]
+				},
 
 
+				"extraCapitol":   { "id" : 29, "requires" : [ "capitol" ], "mode" : "auto" },
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },
 				"dwellingLvl2":   { "id" : 31, "requires" : [ "dwellingLvl1" ] },
 				"dwellingLvl2":   { "id" : 31, "requires" : [ "dwellingLvl1" ] },
 				"dwellingLvl3":   { "id" : 32, "requires" : [ "dwellingLvl1" ] },
 				"dwellingLvl3":   { "id" : 32, "requires" : [ "dwellingLvl1" ] },

+ 6 - 3
config/factions/necropolis.json

@@ -175,13 +175,16 @@
 				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ], "produce": { "ore": 1, "wood": 1 } },
 				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ], "produce": { "ore": 1, "wood": 1 } },
 				"blacksmith":     { "id" : 16 },
 				"blacksmith":     { "id" : 16 },
 
 
-				"special1":       { "requires" : [ "fort" ] },
+				"special1":       { "requires" : [ "fort" ], "bonuses": [ { "type": "DARKNESS", "val": 20  } ] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1", "requires" : [ "special3" ] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1", "requires" : [ "special3" ] },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },
-				"special2":       { "requires" : [ "mageGuild1" ] },
+				"special2":       { "requires" : [ "mageGuild1" ],
+					"bonuses": [ { "type": "SECONDARY_SKILL_PREMY", "subtype": "skill.necromancy", "val": 10, "propagator": "PLAYER_PROPAGATOR" } ] },
 				"special3":       { "type" : "creatureTransformer", "requires" : [ "dwellingLvl1" ] },
 				"special3":       { "type" : "creatureTransformer", "requires" : [ "dwellingLvl1" ] },
-				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
+				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 },
+					"bonuses": [ { "type": "SECONDARY_SKILL_PREMY", "subtype": "skill.necromancy", "val": 20, "propagator": "PLAYER_PROPAGATOR" } ] },
+
 				"extraTownHall":  { "id" : 27, "requires" : [ "townHall" ], "mode" : "auto" },
 				"extraTownHall":  { "id" : 27, "requires" : [ "townHall" ], "mode" : "auto" },
 				"extraCityHall":  { "id" : 28, "requires" : [ "cityHall" ], "mode" : "auto" },
 				"extraCityHall":  { "id" : 28, "requires" : [ "cityHall" ], "mode" : "auto" },
 				"extraCapitol":   { "id" : 29, "requires" : [ "capitol" ], "mode" : "auto" },
 				"extraCapitol":   { "id" : 29, "requires" : [ "capitol" ], "mode" : "auto" },

+ 2 - 1
config/factions/rampart.json

@@ -181,7 +181,8 @@
 				"special3":       { "type" : "treasury", "requires" : [ "horde1" ] },
 				"special3":       { "type" : "treasury", "requires" : [ "horde1" ] },
 				"horde2":         { "id" : 24, "upgrades" : "dwellingLvl5" },
 				"horde2":         { "id" : 24, "upgrades" : "dwellingLvl5" },
 				"horde2Upgr":     { "id" : 25, "upgrades" : "dwellingUpLvl5", "requires" : [ "horde2" ], "mode" : "auto" },
 				"horde2Upgr":     { "id" : 25, "upgrades" : "dwellingUpLvl5", "requires" : [ "horde2" ], "mode" : "auto" },
-				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
+				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }, "bonuses": [ { "type": "LUCK", "val": 2, "propagator": "PLAYER_PROPAGATOR" } ] },
+
 				"extraTownHall":  { "id" : 27, "requires" : [ "townHall" ], "mode" : "auto" },
 				"extraTownHall":  { "id" : 27, "requires" : [ "townHall" ], "mode" : "auto" },
 				"extraCityHall":  { "id" : 28, "requires" : [ "cityHall" ], "mode" : "auto" },
 				"extraCityHall":  { "id" : 28, "requires" : [ "cityHall" ], "mode" : "auto" },
 				"extraCapitol":   { "id" : 29, "requires" : [ "capitol" ], "mode" : "auto" },
 				"extraCapitol":   { "id" : 29, "requires" : [ "capitol" ], "mode" : "auto" },

+ 2 - 1
config/factions/stronghold.json

@@ -172,7 +172,8 @@
 				"special2":       { "type" : "freelancersGuild", "requires" : [ "marketplace" ] },
 				"special2":       { "type" : "freelancersGuild", "requires" : [ "marketplace" ] },
 				"special3":       { "type" : "ballistaYard", "requires" : [ "blacksmith" ] },
 				"special3":       { "type" : "ballistaYard", "requires" : [ "blacksmith" ] },
 				"special4":       { "type" : "attackVisitingBonus", "requires" : [ "fort" ] },
 				"special4":       { "type" : "attackVisitingBonus", "requires" : [ "fort" ] },
-				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
+				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 },
+					"bonuses": [ { "type": "PRIMARY_SKILL", "subtype": "primSkill.attack", "val": 20 } ] },
 
 
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },
 				"dwellingLvl2":   { "id" : 31, "requires" : [ "dwellingLvl1" ] },
 				"dwellingLvl2":   { "id" : 31, "requires" : [ "dwellingLvl1" ] },

+ 1 - 1
config/factions/tower.json

@@ -175,7 +175,7 @@
 				"special2":       { "type" : "lookoutTower", "height" : "high", "requires" : [ "fort" ] },
 				"special2":       { "type" : "lookoutTower", "height" : "high", "requires" : [ "fort" ] },
 				"special3":       { "type" : "library", "requires" : [ "mageGuild1" ] },
 				"special3":       { "type" : "library", "requires" : [ "mageGuild1" ] },
 				"special4":       { "type" : "knowledgeVisitingBonus", "requires" : [ "mageGuild1" ] },
 				"special4":       { "type" : "knowledgeVisitingBonus", "requires" : [ "mageGuild1" ] },
-				"grail":          { "height" : "skyship",  "produce" : { "gold": 5000 } },
+				"grail":          { "height" : "skyship",  "produce" : { "gold": 5000 }, "bonuses": [ { "type": "PRIMARY_SKILL", "subtype": "primSkill.knowledge", "val": 15 } ] },
 
 
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },
 				"dwellingLvl2":   { "id" : 31, "requires" : [ "dwellingLvl1" ] },
 				"dwellingLvl2":   { "id" : 31, "requires" : [ "dwellingLvl1" ] },

+ 19 - 1
config/schemas/townBuilding.json

@@ -85,7 +85,25 @@
 				"gems":    { "type":"number"},
 				"gems":    { "type":"number"},
 				"gold":    { "type":"number"}
 				"gold":    { "type":"number"}
 			}
 			}
+		},
+		"overrides": {
+			"type" : "array",
+			"items" : [
+				{
+					"description" : "The buildings which bonuses should be overridden with bonuses of the current building",
+					"type" : "string"
+				}
+			]
+		},
+		"bonuses": {
+			"type":"array",
+			"description": "Bonuses, provided by this special building on build using bonus system",
+			"items": { "$ref" : "bonus.json" }
+		},
+		"onVisitBonuses": {
+			"type":"array",
+			"description": "Bonuses, provided by this special building on hero visit and applied to the visiting hero",
+			"items": { "$ref" : "bonus.json" }
 		}
 		}
-	  
 	}
 	}
 }
 }

+ 3 - 1
config/translate.json

@@ -51,7 +51,9 @@
 		"greetingAttack" : "Some time spent at the %s allows you to learn more effective combat skills (+1 Attack Skill).",
 		"greetingAttack" : "Some time spent at the %s allows you to learn more effective combat skills (+1 Attack Skill).",
 		"greetingDefence" : "Spending time in the %s, the experienced warriors therein teach you additional defensive skills (+1 Defense).",
 		"greetingDefence" : "Spending time in the %s, the experienced warriors therein teach you additional defensive skills (+1 Defense).",
 		"hasNotProduced" : "The %s has not produced anything yet.",
 		"hasNotProduced" : "The %s has not produced anything yet.",
-		"hasProduced" : "The %s produced %d %s this week."
+		"hasProduced" : "The %s produced %d %s this week.",
+		"greetingCustomBonus" : "%s gives you +%d %s%s",
+		"greetingCustomUntil" : " until next battle."
 	},
 	},
 	"logicalExpressions" :
 	"logicalExpressions" :
 	{
 	{

+ 5 - 0
lib/CCreatureHandler.cpp

@@ -1210,6 +1210,11 @@ void CCreatureHandler::removeBonusesFromAllCreatures()
 	allCreatures.removeBonuses(Selector::all);
 	allCreatures.removeBonuses(Selector::all);
 }
 }
 
 
+void CCreatureHandler::restoreAllCreaturesNodeType794()
+{
+	allCreatures.setNodeType(CBonusSystemNode::ENodeTypes::ALL_CREATURES);
+}
+
 void CCreatureHandler::buildBonusTreeForTiers()
 void CCreatureHandler::buildBonusTreeForTiers()
 {
 {
 	for(CCreature *c : creatures)
 	for(CCreature *c : creatures)

+ 1 - 0
lib/CCreatureHandler.h

@@ -249,6 +249,7 @@ public:
 	void addBonusForTier(int tier, std::shared_ptr<Bonus> b); //tier must be <1-7>
 	void addBonusForTier(int tier, std::shared_ptr<Bonus> b); //tier must be <1-7>
 	void addBonusForAllCreatures(std::shared_ptr<Bonus> b);
 	void addBonusForAllCreatures(std::shared_ptr<Bonus> b);
 	void removeBonusesFromAllCreatures();
 	void removeBonusesFromAllCreatures();
+	void restoreAllCreaturesNodeType794(); //restore ALL_CREATURES node type for old saves
 
 
 	CCreatureHandler();
 	CCreatureHandler();
 	~CCreatureHandler();
 	~CCreatureHandler();

+ 233 - 31
lib/CTownHandler.cpp

@@ -22,6 +22,7 @@
 #include "filesystem/Filesystem.h"
 #include "filesystem/Filesystem.h"
 #include "mapObjects/CObjectClassesHandler.h"
 #include "mapObjects/CObjectClassesHandler.h"
 #include "mapObjects/CObjectHandler.h"
 #include "mapObjects/CObjectHandler.h"
+#include "HeroBonus.h"
 
 
 const int NAMES_PER_TOWN=16; // number of town names per faction in H3 files. Json can define any number
 const int NAMES_PER_TOWN=16; // number of town names per faction in H3 files. Json can define any number
 
 
@@ -93,50 +94,115 @@ void CBuilding::deserializeFix()
 	}
 	}
 }
 }
 
 
-void CBuilding::update792(const BuildingID & bid, BuildingSubID::EBuildingSubID & subId, ETowerHeight & height)
+void CBuilding::addNewBonus(std::shared_ptr<Bonus> b, BonusList & bonusList)
 {
 {
-	subId = BuildingSubID::NONE;
-	height = ETowerHeight::HEIGHT_NO_TOWER;
-
-	if(!bid.IsSpecialOrGrail() || town == nullptr || town->faction == nullptr || town->faction->identifier.empty())
-		return;
-
-	const auto buildingName = CTownHandler::getMappedValue<std::string, BuildingID>(bid, std::string(), MappedKeys::BUILDING_TYPES_TO_NAMES);
-
-	if(buildingName.empty())
-		return;
+	bonusList.push_back(b);
+}
 
 
+const JsonNode & CBuilding::getCurrentFactionForUpdateRoutine() const
+{
 	const auto & faction = town->faction->identifier;
 	const auto & faction = town->faction->identifier;
-	auto factionsContent = (*VLC->modh->content)["factions"];
-	auto & coreData = factionsContent.modData.at("core");
-	auto & coreFactions = coreData.modData;
-	auto & currentFaction = coreFactions[faction];
+	const auto & factionsContent = (*VLC->modh->content)["factions"];
+	const auto & coreData = factionsContent.modData.at("core");
+	const auto & coreFactions = coreData.modData;
+	const auto & currentFaction = coreFactions[faction];
 
 
-	if (currentFaction.isNull())
+	if(currentFaction.isNull())
 	{
 	{
 		const auto index = faction.find(':');
 		const auto index = faction.find(':');
 		const std::string factionDir = index == std::string::npos ? faction : faction.substr(0, index);
 		const std::string factionDir = index == std::string::npos ? faction : faction.substr(0, index);
 		const auto it = factionsContent.modData.find(factionDir);
 		const auto it = factionsContent.modData.find(factionDir);
 
 
-		if (it == factionsContent.modData.end())
+		if(it == factionsContent.modData.end())
 		{
 		{
 			logMod->warn("Warning: Update old save failed: Faction: '%s' is not found.", factionDir);
 			logMod->warn("Warning: Update old save failed: Faction: '%s' is not found.", factionDir);
-			return;
+			return currentFaction;
 		}
 		}
 		const std::string modFaction = index == std::string::npos ? faction : faction.substr(index + 1);
 		const std::string modFaction = index == std::string::npos ? faction : faction.substr(index + 1);
-		currentFaction = it->second.modData[modFaction];
+		return it->second.modData[modFaction];
 	}
 	}
+	return currentFaction;
+}
+
+void CBuilding::update792()
+{
+	subId = BuildingSubID::NONE;
+	height = ETowerHeight::HEIGHT_NO_TOWER;
+
+	if(!bid.IsSpecialOrGrail() || town == nullptr || town->faction == nullptr || town->faction->identifier.empty())
+		return;
+
+	const auto buildingName = CTownHandler::getMappedValue<std::string, BuildingID>(bid, std::string(), MappedKeys::BUILDING_TYPES_TO_NAMES);
+
+	if(buildingName.empty())
+		return;
+
+	auto & currentFaction = getCurrentFactionForUpdateRoutine();
 
 
-	if (!currentFaction.isNull() && currentFaction.getType() == JsonNode::JsonType::DATA_STRUCT)
+	if(!currentFaction.isNull() && currentFaction.getType() == JsonNode::JsonType::DATA_STRUCT)
 	{
 	{
 		const auto & buildings = currentFaction["town"]["buildings"];
 		const auto & buildings = currentFaction["town"]["buildings"];
 		const auto & currentBuilding = buildings[buildingName];
 		const auto & currentBuilding = buildings[buildingName];
 
 
 		subId = CTownHandler::getMappedValue<BuildingSubID::EBuildingSubID>(currentBuilding["type"], BuildingSubID::NONE, MappedKeys::SPECIAL_BUILDINGS);
 		subId = CTownHandler::getMappedValue<BuildingSubID::EBuildingSubID>(currentBuilding["type"], BuildingSubID::NONE, MappedKeys::SPECIAL_BUILDINGS);
-		height = CBuilding::HEIGHT_NO_TOWER;
+		height = subId == BuildingSubID::LOOKOUT_TOWER || bid == BuildingID::GRAIL
+			? CTownHandler::getMappedValue<CBuilding::ETowerHeight>(currentBuilding["height"], CBuilding::HEIGHT_NO_TOWER, CBuilding::TOWER_TYPES)
+			: height = CBuilding::HEIGHT_NO_TOWER;
+	}
+}
+
+void CBuilding::update794()
+{
+	if(bid == BuildingID::TAVERN || subId == BuildingSubID::BROTHERHOOD_OF_SWORD)
+	{
+		VLC->townh->addBonusesForVanilaBuilding(this);
+		return;
+	}
+	if(!bid.IsSpecialOrGrail())
+		return;
+
+	VLC->townh->addBonusesForVanilaBuilding(this);
+
+	if(!buildingBonuses.empty() //addBonusesForVanilaBuilding has done all work
+		|| town->faction == nullptr //or faction data is not valid
+		|| town->faction->identifier.empty())
+		return;
+
+	const auto buildingName = CTownHandler::getMappedValue<std::string, BuildingID>(bid, std::string(), MappedKeys::BUILDING_TYPES_TO_NAMES, false);
+
+	if(buildingName.empty())
+		return;
+
+	auto & currentFaction = getCurrentFactionForUpdateRoutine();
+
+	if(currentFaction.isNull() || currentFaction.getType() != JsonNode::JsonType::DATA_STRUCT)
+		return;
+
+	const auto & buildings = currentFaction["town"]["buildings"];
+	const auto & currentBuilding = buildings[buildingName];
+
+	CTownHandler::loadSpecialBuildingBonuses(currentBuilding["bonuses"], buildingBonuses, this);
+	CTownHandler::loadSpecialBuildingBonuses(currentBuilding["onVisitBonuses"], onVisitBonuses, this);
+
+	if(!onVisitBonuses.empty())
+	{
+		if(subId == BuildingSubID::NONE)
+			subId = BuildingSubID::CUSTOM_VISITING_BONUS;
+
+		for(auto & bonus : onVisitBonuses)
+			bonus->sid = Bonus::getSid32(town->faction->index, bid);
+	}
+	const auto & overriddenBids = currentBuilding["overrides"];
+
+	if(overriddenBids.isNull())
+		return;
 
 
-		if (subId == BuildingSubID::LOOKOUT_TOWER || bid == BuildingID::GRAIL)
-			height = CTownHandler::getMappedValue<CBuilding::ETowerHeight>(currentBuilding["height"], CBuilding::HEIGHT_NO_TOWER, CBuilding::TOWER_TYPES);
+	auto scope = town->getBuildingScope();
+
+	for(auto b : overriddenBids.Vector())
+	{
+		auto bid = BuildingID(VLC->modh->identifiers.getIdentifier(scope, b).get());
+		overrideBids.insert(bid);
 	}
 	}
 }
 }
 
 
@@ -258,6 +324,8 @@ JsonNode readBuilding(CLegacyConfigParser & parser)
 	return ret;
 	return ret;
 }
 }
 
 
+TPropagatorPtr CTownHandler::emptyPropagator = std::make_shared<CPropagatorNodeType>();
+
 std::vector<JsonNode> CTownHandler::loadLegacyData(size_t dataSize)
 std::vector<JsonNode> CTownHandler::loadLegacyData(size_t dataSize)
 {
 {
 	std::vector<JsonNode> dest(dataSize);
 	std::vector<JsonNode> dest(dataSize);
@@ -412,7 +480,7 @@ std::vector<JsonNode> CTownHandler::loadLegacyData(size_t dataSize)
 	return dest;
 	return dest;
 }
 }
 
 
-void CTownHandler::loadBuildingRequirements(CBuilding * building, const JsonNode & source)
+void CTownHandler::loadBuildingRequirements(CBuilding * building, const JsonNode & source, std::vector<BuildingRequirementsHelper> & bidsToLoad)
 {
 {
 	if (source.isNull())
 	if (source.isNull())
 		return;
 		return;
@@ -421,7 +489,7 @@ void CTownHandler::loadBuildingRequirements(CBuilding * building, const JsonNode
 	hlp.building = building;
 	hlp.building = building;
 	hlp.town = building->town;
 	hlp.town = building->town;
 	hlp.json = source;
 	hlp.json = source;
-	requirementsToLoad.push_back(hlp);
+	bidsToLoad.push_back(hlp);
 }
 }
 
 
 template<typename R, typename K>
 template<typename R, typename K>
@@ -445,6 +513,100 @@ R CTownHandler::getMappedValue(const JsonNode & node, const R defval, const std:
 	return defval;
 	return defval;
 }
 }
 
 
+void CTownHandler::addBonusesForVanilaBuilding(CBuilding * building)
+{
+	std::shared_ptr<Bonus> b;
+	static TPropagatorPtr playerPropagator = std::make_shared<CPropagatorNodeType>(CBonusSystemNode::ENodeTypes::PLAYER);
+
+	if(building->subId == BuildingSubID::NONE)
+	{
+		if(building->bid == BuildingID::TAVERN)
+			b = createBonus(building, Bonus::MORALE, +1);
+		else if(building->bid == BuildingID::GRAIL 
+				&& building->town->faction != nullptr
+				&& boost::algorithm::ends_with(building->town->faction->identifier, ":cove"))
+		{
+				static TPropagatorPtr allCreaturesPropagator(new CPropagatorNodeType(CBonusSystemNode::ENodeTypes::ALL_CREATURES));
+				static auto factionLimiter = std::make_shared<CreatureFactionLimiter>(building->town->faction->index);
+				b = createBonus(building, Bonus::NO_TERRAIN_PENALTY, 0, allCreaturesPropagator);
+				b->addLimiter(factionLimiter);
+		}
+	}
+	else
+	{
+		switch(building->subId)
+		{
+		case BuildingSubID::BROTHERHOOD_OF_SWORD:
+			b = createBonus(building, Bonus::MORALE, +2);
+			building->overrideBids.insert(BuildingID::TAVERN);
+			break;
+		case BuildingSubID::FOUNTAIN_OF_FORTUNE:
+			b = createBonus(building, Bonus::LUCK, +2);
+			break;
+		case BuildingSubID::SPELL_POWER_GARRISON_BONUS:
+			b = createBonus(building, Bonus::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER);
+			break;
+		case BuildingSubID::ATTACK_GARRISON_BONUS:
+			b = createBonus(building, Bonus::PRIMARY_SKILL, +2, PrimarySkill::ATTACK);
+			break;
+		case BuildingSubID::DEFENSE_GARRISON_BONUS:
+			b = createBonus(building, Bonus::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE);
+			break;
+		case BuildingSubID::LIGHTHOUSE:
+			b = createBonus(building, Bonus::SEA_MOVEMENT, +500, playerPropagator);
+			break;
+		}
+	}
+	if(b)
+		building->addNewBonus(b, building->buildingBonuses);
+}
+
+std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, Bonus::BonusType type, int val, int subtype)
+{
+	return createBonus(build, type, val, emptyPropagator, subtype);
+}
+
+std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype)
+{
+	std::ostringstream descr;
+	descr << build->name;
+	return createBonusImpl(build->bid, type, val, prop, descr.str(), subtype);
+}
+
+std::shared_ptr<Bonus> CTownHandler::createBonusImpl(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, const std::string & description, int subtype)
+{
+	auto b = std::make_shared<Bonus>(Bonus::PERMANENT, type, Bonus::TOWN_STRUCTURE, val, building, description, subtype);
+
+	if(prop)
+		b->addPropagator(prop);
+
+	return b;
+}
+
+void CTownHandler::loadSpecialBuildingBonuses(const JsonNode & source, BonusList & bonusList, CBuilding * building)
+{
+	for(auto b : source.Vector())
+	{
+		auto bonus = JsonUtils::parseBuildingBonus(b, building->bid, building->name);
+
+		if(bonus == nullptr)
+			continue;
+
+		if(bonus->limiter != nullptr)
+		{
+			auto limPtr = dynamic_cast<CreatureFactionLimiter*>(bonus->limiter.get());
+
+			if(limPtr != nullptr && limPtr->faction == (TFaction)-1)
+				limPtr->faction = building->town->faction->index;
+		}
+		//JsonUtils::parseBuildingBonus produces UNKNOWN type propagator instead of empty.
+		if(bonus->propagator != nullptr 
+			&& bonus->propagator->getPropagatorType() == CBonusSystemNode::ENodeTypes::UNKNOWN)
+				bonus->addPropagator(emptyPropagator);
+		building->addNewBonus(bonus, bonusList);
+	}
+}
+
 void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, const JsonNode & source)
 void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, const JsonNode & source)
 {
 {
 	auto ret = new CBuilding();
 	auto ret = new CBuilding();
@@ -474,6 +636,26 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
 	ret->resources = TResources(source["cost"]);
 	ret->resources = TResources(source["cost"]);
 	ret->produce =   TResources(source["produce"]);
 	ret->produce =   TResources(source["produce"]);
 
 
+	if(ret->bid == BuildingID::TAVERN)
+		addBonusesForVanilaBuilding(ret);
+	else if(ret->bid.IsSpecialOrGrail())
+	{
+		loadSpecialBuildingBonuses(source["bonuses"], ret->buildingBonuses, ret);
+
+		if(ret->buildingBonuses.empty())
+			addBonusesForVanilaBuilding(ret);
+
+		loadSpecialBuildingBonuses(source["onVisitBonuses"], ret->onVisitBonuses, ret);
+
+		if(!ret->onVisitBonuses.empty())
+		{
+			if(ret->subId == BuildingSubID::NONE)
+				ret->subId = BuildingSubID::CUSTOM_VISITING_BONUS;
+
+			for(auto & bonus : ret->onVisitBonuses)
+				bonus->sid = Bonus::getSid32(ret->town->faction->index, ret->bid);
+		}
+	}
 	//MODS COMPATIBILITY FOR 0.96
 	//MODS COMPATIBILITY FOR 0.96
 	if(!ret->produce.nonZero())
 	if(!ret->produce.nonZero())
 	{
 	{
@@ -501,8 +683,10 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
 			}
 			}
 		}
 		}
 	}
 	}
+	loadBuildingRequirements(ret, source["requires"], requirementsToLoad);
 
 
-	loadBuildingRequirements(ret, source["requires"]);
+	if(ret->bid.IsSpecialOrGrail())
+		loadBuildingRequirements(ret, source["overrides"], overriddenBidsToLoad);
 
 
 	if (!source["upgrades"].isNull())
 	if (!source["upgrades"].isNull())
 	{
 	{
@@ -828,9 +1012,10 @@ ETerrainType::EETerrainType CTownHandler::getDefaultTerrainForAlignment(EAlignme
 	return terrain;
 	return terrain;
 }
 }
 
 
-CFaction * CTownHandler::loadFromJson(const JsonNode &source, const std::string & identifier)
+CFaction * CTownHandler::loadFromJson(const JsonNode &source, const std::string & identifier, TFaction index)
 {
 {
 	auto  faction = new CFaction();
 	auto  faction = new CFaction();
+	faction->index = index;
 
 
 	faction->name = source["name"].String();
 	faction->name = source["name"].String();
 	faction->identifier = identifier;
 	faction->identifier = identifier;
@@ -871,9 +1056,9 @@ CFaction * CTownHandler::loadFromJson(const JsonNode &source, const std::string
 
 
 void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
 void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
 {
 {
-	auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
+	auto index = static_cast<TFaction>(factions.size());
+	auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name), index);
 
 
-	object->index = static_cast<TFaction>(factions.size());
 	factions.push_back(object);
 	factions.push_back(object);
 
 
 	if (object->town)
 	if (object->town)
@@ -911,8 +1096,8 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod
 
 
 void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
 void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
 {
 {
-	auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
-	object->index = static_cast<TFaction>(index);
+	auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name), static_cast<TFaction>(index));
+	
 	if (factions.size() > index)
 	if (factions.size() > index)
 		assert(factions[index] == nullptr); // ensure that this id was not loaded before
 		assert(factions[index] == nullptr); // ensure that this id was not loaded before
 	else
 	else
@@ -957,6 +1142,7 @@ void CTownHandler::loadCustom()
 void CTownHandler::afterLoadFinalization()
 void CTownHandler::afterLoadFinalization()
 {
 {
 	initializeRequirements();
 	initializeRequirements();
+	initializeOverridden();
 	initializeWarMachines();
 	initializeWarMachines();
 }
 }
 
 
@@ -979,6 +1165,22 @@ void CTownHandler::initializeRequirements()
 	requirementsToLoad.clear();
 	requirementsToLoad.clear();
 }
 }
 
 
+void CTownHandler::initializeOverridden()
+{
+	for(auto & bidHelper : overriddenBidsToLoad)
+	{
+		auto jsonNode = bidHelper.json;
+		auto scope = bidHelper.town->getBuildingScope();
+
+		for(auto b : jsonNode.Vector())
+		{
+			auto bid = BuildingID(VLC->modh->identifiers.getIdentifier(scope, b).get());
+			bidHelper.building->overrideBids.insert(bid);
+		}
+	}
+	overriddenBidsToLoad.clear();
+}
+
 void CTownHandler::initializeWarMachines()
 void CTownHandler::initializeWarMachines()
 {
 {
 	// must be done separately after all objects are loaded
 	// must be done separately after all objects are loaded

+ 32 - 8
lib/CTownHandler.h

@@ -16,6 +16,7 @@
 #include "IHandlerBase.h"
 #include "IHandlerBase.h"
 #include "LogicalExpression.h"
 #include "LogicalExpression.h"
 #include "battle/BattleHex.h"
 #include "battle/BattleHex.h"
+#include "HeroBonus.h"
 
 
 class CLegacyConfigParser;
 class CLegacyConfigParser;
 class JsonNode;
 class JsonNode;
@@ -47,6 +48,9 @@ public:
 	BuildingID bid; //structure ID
 	BuildingID bid; //structure ID
 	BuildingID upgrade; /// indicates that building "upgrade" can be improved by this, -1 = empty
 	BuildingID upgrade; /// indicates that building "upgrade" can be improved by this, -1 = empty
 	BuildingSubID::EBuildingSubID subId; /// subtype for special buildings, -1 = the building is not special
 	BuildingSubID::EBuildingSubID subId; /// subtype for special buildings, -1 = the building is not special
+	std::set<BuildingID> overrideBids; /// the building which bonuses should be overridden with bonuses of the current building
+	BonusList buildingBonuses;
+	BonusList onVisitBonuses;
 
 
 	enum EBuildMode
 	enum EBuildMode
 	{
 	{
@@ -95,14 +99,16 @@ public:
 	bool IsVisitingBonus() const
 	bool IsVisitingBonus() const
 	{
 	{
 		return subId == BuildingSubID::ATTACK_VISITING_BONUS ||
 		return subId == BuildingSubID::ATTACK_VISITING_BONUS ||
-			subId == BuildingSubID::DEFENSE_VISITING_BONUS || 
+			subId == BuildingSubID::DEFENSE_VISITING_BONUS ||
 			subId == BuildingSubID::SPELL_POWER_VISITING_BONUS ||
 			subId == BuildingSubID::SPELL_POWER_VISITING_BONUS ||
 			subId == BuildingSubID::KNOWLEDGE_VISITING_BONUS ||
 			subId == BuildingSubID::KNOWLEDGE_VISITING_BONUS ||
-			subId == BuildingSubID::EXPERIENCE_VISITING_BONUS;
+			subId == BuildingSubID::EXPERIENCE_VISITING_BONUS ||
+			subId == BuildingSubID::CUSTOM_VISITING_BONUS;
 	}
 	}
 
 
-	/// input: faction, bid; output: subId, height;
-	void update792(const BuildingID & bid, BuildingSubID::EBuildingSubID & subId, ETowerHeight & height);
+	void addNewBonus(std::shared_ptr<Bonus> b, BonusList & bonusList);
+	void update792();
+	void update794();
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
@@ -123,9 +129,17 @@ public:
 			h & height;
 			h & height;
 		}
 		}
 		if(!h.saving && version < 793)
 		if(!h.saving && version < 793)
+			update792(); //adjust height, subId
+
+		if(version >= 794)
 		{
 		{
-			update792(bid, subId, height);
+			h & overrideBids;
+			h & buildingBonuses;
+			h & onVisitBonuses;
 		}
 		}
+		else if(!h.saving)
+			update794(); //populate overrideBids, buildingBonuses, onVisitBonuses
+
 		if(!h.saving)
 		if(!h.saving)
 			deserializeFix();
 			deserializeFix();
 	}
 	}
@@ -133,8 +147,8 @@ public:
 	friend class CTownHandler;
 	friend class CTownHandler;
 
 
 private:
 private:
-
 	void deserializeFix();
 	void deserializeFix();
+	const JsonNode & getCurrentFactionForUpdateRoutine() const;
 };
 };
 
 
 /// This is structure used only by client
 /// This is structure used only by client
@@ -354,19 +368,27 @@ class DLL_LINKAGE CTownHandler : public IHandlerBase
 
 
 	std::map<CTown *, JsonNode> warMachinesToLoad;
 	std::map<CTown *, JsonNode> warMachinesToLoad;
 	std::vector<BuildingRequirementsHelper> requirementsToLoad;
 	std::vector<BuildingRequirementsHelper> requirementsToLoad;
+	std::vector<BuildingRequirementsHelper> overriddenBidsToLoad; //list of buildings, which bonuses should be overridden.
 
 
 	const static ETerrainType::EETerrainType defaultGoodTerrain = ETerrainType::EETerrainType::GRASS;
 	const static ETerrainType::EETerrainType defaultGoodTerrain = ETerrainType::EETerrainType::GRASS;
 	const static ETerrainType::EETerrainType defaultEvilTerrain = ETerrainType::EETerrainType::LAVA;
 	const static ETerrainType::EETerrainType defaultEvilTerrain = ETerrainType::EETerrainType::LAVA;
 	const static ETerrainType::EETerrainType defaultNeutralTerrain = ETerrainType::EETerrainType::ROUGH;
 	const static ETerrainType::EETerrainType defaultNeutralTerrain = ETerrainType::EETerrainType::ROUGH;
 
 
+	static TPropagatorPtr emptyPropagator;
+
 	void initializeRequirements();
 	void initializeRequirements();
+	void initializeOverridden();
 	void initializeWarMachines();
 	void initializeWarMachines();
 
 
 	/// loads CBuilding's into town
 	/// loads CBuilding's into town
-	void loadBuildingRequirements(CBuilding * building, const JsonNode & source);
+	void loadBuildingRequirements(CBuilding * building, const JsonNode & source, std::vector<BuildingRequirementsHelper> & bidsToLoad);
 	void loadBuilding(CTown * town, const std::string & stringID, const JsonNode & source);
 	void loadBuilding(CTown * town, const std::string & stringID, const JsonNode & source);
 	void loadBuildings(CTown * town, const JsonNode & source);
 	void loadBuildings(CTown * town, const JsonNode & source);
 
 
+	std::shared_ptr<Bonus> createBonus(CBuilding * build, Bonus::BonusType type, int val, int subtype = -1);
+	std::shared_ptr<Bonus> createBonus(CBuilding * build, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype = -1);
+	std::shared_ptr<Bonus> createBonusImpl(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, const std::string & description, int subtype = -1);
+
 	/// loads CStructure's into town
 	/// loads CStructure's into town
 	void loadStructure(CTown &town, const std::string & stringID, const JsonNode & source);
 	void loadStructure(CTown &town, const std::string & stringID, const JsonNode & source);
 	void loadStructures(CTown &town, const JsonNode & source);
 	void loadStructures(CTown &town, const JsonNode & source);
@@ -383,7 +405,7 @@ class DLL_LINKAGE CTownHandler : public IHandlerBase
 
 
 	ETerrainType::EETerrainType getDefaultTerrainForAlignment(EAlignment::EAlignment aligment) const;
 	ETerrainType::EETerrainType getDefaultTerrainForAlignment(EAlignment::EAlignment aligment) const;
 
 
-	CFaction * loadFromJson(const JsonNode & data, const std::string & identifier);
+	CFaction * loadFromJson(const JsonNode & data, const std::string & identifier, TFaction index);
 
 
 	void loadRandomFaction();
 	void loadRandomFaction();
 
 
@@ -404,6 +426,7 @@ public:
 
 
 	void loadObject(std::string scope, std::string name, const JsonNode & data) override;
 	void loadObject(std::string scope, std::string name, const JsonNode & data) override;
 	void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
 	void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
+	void addBonusesForVanilaBuilding(CBuilding * building);
 
 
 	void loadCustom() override;
 	void loadCustom() override;
 	void afterLoadFinalization() override;
 	void afterLoadFinalization() override;
@@ -416,6 +439,7 @@ public:
 
 
 	//json serialization helper
 	//json serialization helper
 	static std::string encodeFaction(const si32 index);
 	static std::string encodeFaction(const si32 index);
+	static void loadSpecialBuildingBonuses(const JsonNode & source, BonusList & bonusList, CBuilding * building);
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{

+ 3 - 2
lib/GameConstants.h

@@ -297,7 +297,7 @@ class TeleportChannelID : public BaseForID<TeleportChannelID, si32>
 // Enum declarations
 // Enum declarations
 namespace PrimarySkill
 namespace PrimarySkill
 {
 {
-	enum PrimarySkill { ATTACK, DEFENSE, SPELL_POWER, KNOWLEDGE,
+	enum PrimarySkill { NONE = -1, ATTACK, DEFENSE, SPELL_POWER, KNOWLEDGE,
 				EXPERIENCE = 4}; //for some reason changePrimSkill uses it
 				EXPERIENCE = 4}; //for some reason changePrimSkill uses it
 }
 }
 
 
@@ -452,7 +452,8 @@ namespace BuildingSubID
 		KNOWLEDGE_VISITING_BONUS,
 		KNOWLEDGE_VISITING_BONUS,
 		EXPERIENCE_VISITING_BONUS,
 		EXPERIENCE_VISITING_BONUS,
 		LIGHTHOUSE,
 		LIGHTHOUSE,
-		TREASURY
+		TREASURY,
+		CUSTOM_VISITING_BONUS
 	};
 	};
 }
 }
 
 

+ 20 - 8
lib/HeroBonus.cpp

@@ -71,7 +71,8 @@ const std::map<std::string, TLimiterPtr> bonusLimiterMap =
 	{"SHOOTER_ONLY", std::make_shared<HasAnotherBonusLimiter>(Bonus::SHOOTER)},
 	{"SHOOTER_ONLY", std::make_shared<HasAnotherBonusLimiter>(Bonus::SHOOTER)},
 	{"DRAGON_NATURE", std::make_shared<HasAnotherBonusLimiter>(Bonus::DRAGON_NATURE)},
 	{"DRAGON_NATURE", std::make_shared<HasAnotherBonusLimiter>(Bonus::DRAGON_NATURE)},
 	{"IS_UNDEAD", std::make_shared<HasAnotherBonusLimiter>(Bonus::UNDEAD)},
 	{"IS_UNDEAD", std::make_shared<HasAnotherBonusLimiter>(Bonus::UNDEAD)},
-	{"CREATURE_NATIVE_TERRAIN", std::make_shared<CreatureTerrainLimiter>()}
+	{"CREATURE_NATIVE_TERRAIN", std::make_shared<CreatureTerrainLimiter>()},
+	{"CREATURE_FACTION", std::make_shared<CreatureFactionLimiter>()}
 };
 };
 
 
 const std::map<std::string, TPropagatorPtr> bonusPropagatorMap =
 const std::map<std::string, TPropagatorPtr> bonusPropagatorMap =
@@ -81,7 +82,8 @@ const std::map<std::string, TPropagatorPtr> bonusPropagatorMap =
 	{"PLAYER_PROPAGATOR", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::PLAYER)},
 	{"PLAYER_PROPAGATOR", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::PLAYER)},
 	{"HERO", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::HERO)},
 	{"HERO", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::HERO)},
 	{"TEAM_PROPAGATOR", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::TEAM)}, //untested
 	{"TEAM_PROPAGATOR", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::TEAM)}, //untested
-	{"GLOBAL_EFFECT", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::GLOBAL_EFFECTS)}
+	{"GLOBAL_EFFECT", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::GLOBAL_EFFECTS)},
+	{"ALL_CREATURES", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::ALL_CREATURES)}
 }; //untested
 }; //untested
 
 
 const std::map<std::string, TUpdaterPtr> bonusUpdaterMap =
 const std::map<std::string, TUpdaterPtr> bonusUpdaterMap =
@@ -1570,8 +1572,8 @@ std::string Bonus::nameForBonus() const
 	}
 	}
 }
 }
 
 
-Bonus::Bonus(ui16 Dur, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype)
-	: duration(Dur), type(Type), subtype(Subtype), source(Src), val(Val), sid(ID), description(Desc)
+Bonus::Bonus(Bonus::BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype)
+	: duration((ui16)Duration), type(Type), subtype(Subtype), source(Src), val(Val), sid(ID), description(Desc)
 {
 {
 	turnsRemain = 0;
 	turnsRemain = 0;
 	valType = ADDITIVE_VALUE;
 	valType = ADDITIVE_VALUE;
@@ -1579,8 +1581,8 @@ Bonus::Bonus(ui16 Dur, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::
 	boost::algorithm::trim(description);
 	boost::algorithm::trim(description);
 }
 }
 
 
-Bonus::Bonus(ui16 Dur, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype, ValueType ValType)
-	: duration(Dur), type(Type), subtype(Subtype), source(Src), val(Val), sid(ID), valType(ValType)
+Bonus::Bonus(Bonus::BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype, ValueType ValType)
+	: duration((ui16)Duration), type(Type), subtype(Subtype), source(Src), val(Val), sid(ID), valType(ValType)
 {
 {
 	turnsRemain = 0;
 	turnsRemain = 0;
 	effectRange = NO_LIMIT;
 	effectRange = NO_LIMIT;
@@ -1922,17 +1924,27 @@ bool IPropagator::shouldBeAttached(CBonusSystemNode *dest)
 	return false;
 	return false;
 }
 }
 
 
+CBonusSystemNode::ENodeTypes IPropagator::getPropagatorType() const
+{
+	return CBonusSystemNode::ENodeTypes::NONE;
+}
+
 CPropagatorNodeType::CPropagatorNodeType()
 CPropagatorNodeType::CPropagatorNodeType()
-	:nodeType(0)
+	:nodeType(CBonusSystemNode::ENodeTypes::UNKNOWN)
 {
 {
 
 
 }
 }
 
 
-CPropagatorNodeType::CPropagatorNodeType(int NodeType)
+CPropagatorNodeType::CPropagatorNodeType(CBonusSystemNode::ENodeTypes NodeType)
 	: nodeType(NodeType)
 	: nodeType(NodeType)
 {
 {
 }
 }
 
 
+CBonusSystemNode::ENodeTypes CPropagatorNodeType::getPropagatorType() const
+{
+	return nodeType;
+}
+
 bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest)
 bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest)
 {
 {
 	return nodeType == dest->getNodeType();
 	return nodeType == dest->getNodeType();

+ 34 - 26
lib/HeroBonus.h

@@ -418,8 +418,8 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
 
 
 	std::string description;
 	std::string description;
 
 
-	Bonus(ui16 Dur, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype=-1);
-	Bonus(ui16 Dur, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype=-1, ValueType ValType = ADDITIVE_VALUE);
+	Bonus(BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype=-1);
+	Bonus(BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype=-1, ValueType ValType = ADDITIVE_VALUE);
 	Bonus();
 	Bonus();
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
@@ -508,6 +508,10 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
 	{
 	{
 		val += Val;
 		val += Val;
 	}
 	}
+	STRONG_INLINE static ui32 getSid32(ui32 high, ui32 low)
+	{
+		return (high << 16) + low;
+	}
 
 
 	std::string Description() const;
 	std::string Description() const;
 	JsonNode toJsonNode() const;
 	JsonNode toJsonNode() const;
@@ -639,30 +643,6 @@ inline BonusList::const_iterator range_end(BonusList const &x)
 
 
 DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const BonusList &bonusList);
 DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const BonusList &bonusList);
 
 
-class DLL_LINKAGE IPropagator
-{
-public:
-	virtual ~IPropagator();
-	virtual bool shouldBeAttached(CBonusSystemNode *dest);
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{}
-};
-
-class DLL_LINKAGE CPropagatorNodeType : public IPropagator
-{
-	int nodeType; //CBonusSystemNode::ENodeTypes
-public:
-	CPropagatorNodeType();
-	CPropagatorNodeType(int NodeType);
-	bool shouldBeAttached(CBonusSystemNode *dest) override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & nodeType;
-	}
-};
-
 struct BonusLimitationContext
 struct BonusLimitationContext
 {
 {
 	std::shared_ptr<const Bonus> b;
 	std::shared_ptr<const Bonus> b;
@@ -756,6 +736,7 @@ class DLL_LINKAGE CBonusSystemNode : public virtual IBonusBearer, public boost::
 public:
 public:
 	enum ENodeTypes
 	enum ENodeTypes
 	{
 	{
+		NONE = -1, 
 		UNKNOWN, STACK_INSTANCE, STACK_BATTLE, SPECIALTY, ARTIFACT, CREATURE, ARTIFACT_INSTANCE, HERO, PLAYER, TEAM,
 		UNKNOWN, STACK_INSTANCE, STACK_BATTLE, SPECIALTY, ARTIFACT, CREATURE, ARTIFACT_INSTANCE, HERO, PLAYER, TEAM,
 		TOWN_AND_VISITOR, BATTLE, COMMANDER, GLOBAL_EFFECTS, ALL_CREATURES
 		TOWN_AND_VISITOR, BATTLE, COMMANDER, GLOBAL_EFFECTS, ALL_CREATURES
 	};
 	};
@@ -857,6 +838,33 @@ public:
 	friend class CBonusProxy;
 	friend class CBonusProxy;
 };
 };
 
 
+class DLL_LINKAGE IPropagator
+{
+public:
+	virtual ~IPropagator();
+	virtual bool shouldBeAttached(CBonusSystemNode *dest);
+	virtual CBonusSystemNode::ENodeTypes getPropagatorType() const;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{}
+};
+
+class DLL_LINKAGE CPropagatorNodeType : public IPropagator
+{
+	CBonusSystemNode::ENodeTypes nodeType;
+
+public:
+	CPropagatorNodeType();
+	CPropagatorNodeType(CBonusSystemNode::ENodeTypes NodeType);
+	bool shouldBeAttached(CBonusSystemNode *dest) override;
+	CBonusSystemNode::ENodeTypes getPropagatorType() const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & nodeType;
+	}
+};
+
 namespace NBonus
 namespace NBonus
 {
 {
 	//set of methods that may be safely called with nullptr objs
 	//set of methods that may be safely called with nullptr objs

+ 17 - 3
lib/JsonNode.cpp

@@ -697,6 +697,19 @@ std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonNode &ability)
 	return b;
 	return b;
 }
 }
 
 
+std::shared_ptr<Bonus> JsonUtils::parseBuildingBonus(const JsonNode &ability, BuildingID building, std::string description)
+{
+	/*	duration = Bonus::PERMANENT
+		source = Bonus::TOWN_STRUCTURE
+		bonusType, val, subtype - get from json
+	*/
+	auto b = std::make_shared<Bonus>(Bonus::PERMANENT, Bonus::NONE, Bonus::TOWN_STRUCTURE, 0, building, description, -1);
+
+	if(!parseBonus(ability, b.get()))
+		return nullptr;
+	return b;
+}
+
 bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 {
 {
 	const JsonNode *value;
 	const JsonNode *value;
@@ -726,7 +739,8 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 
 
 	b->sid = static_cast<si32>(ability["sourceID"].Float());
 	b->sid = static_cast<si32>(ability["sourceID"].Float());
 
 
-	b->description = ability["description"].String();
+	if(!ability["description"].isNull())
+		b->description = ability["description"].String();
 
 
 	value = &ability["effectRange"];
 	value = &ability["effectRange"];
 	if (!value->isNull())
 	if (!value->isNull())
@@ -738,7 +752,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 		switch (value->getType())
 		switch (value->getType())
 		{
 		{
 		case JsonNode::JsonType::DATA_STRING:
 		case JsonNode::JsonType::DATA_STRING:
-			b->duration = parseByMap(bonusDurationMap, value, "duration type ");
+			b->duration = (Bonus::BonusDuration)parseByMap(bonusDurationMap, value, "duration type ");
 			break;
 			break;
 		case JsonNode::JsonType::DATA_VECTOR:
 		case JsonNode::JsonType::DATA_VECTOR:
 			{
 			{
@@ -747,7 +761,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 				{
 				{
 					dur |= parseByMap(bonusDurationMap, &d, "duration type ");
 					dur |= parseByMap(bonusDurationMap, &d, "duration type ");
 				}
 				}
-				b->duration = dur;
+				b->duration = (Bonus::BonusDuration)dur;
 			}
 			}
 			break;
 			break;
 		default:
 		default:

+ 2 - 0
lib/JsonNode.h

@@ -8,6 +8,7 @@
  *
  *
  */
  */
 #pragma once
 #pragma once
+#include "GameConstants.h"
 
 
 class JsonNode;
 class JsonNode;
 typedef std::map <std::string, JsonNode> JsonMap;
 typedef std::map <std::string, JsonNode> JsonMap;
@@ -168,6 +169,7 @@ namespace JsonUtils
 	///
 	///
 	DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonVector &ability_vec);
 	DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonVector &ability_vec);
 	DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonNode &ability);
 	DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonNode &ability);
+	DLL_LINKAGE std::shared_ptr<Bonus> parseBuildingBonus(const JsonNode &ability, BuildingID building, std::string description);
 	DLL_LINKAGE bool parseBonus(const JsonNode &ability, Bonus *placement);
 	DLL_LINKAGE bool parseBonus(const JsonNode &ability, Bonus *placement);
 	DLL_LINKAGE std::shared_ptr<ILimiter> parseLimiter(const JsonNode & limiter);
 	DLL_LINKAGE std::shared_ptr<ILimiter> parseLimiter(const JsonNode & limiter);
 	DLL_LINKAGE void resolveIdentifier(si32 &var, const JsonNode &node, std::string name);
 	DLL_LINKAGE void resolveIdentifier(si32 &var, const JsonNode &node, std::string name);

+ 25 - 3
lib/NetPacksLib.cpp

@@ -245,10 +245,21 @@ DLL_LINKAGE void GiveBonus::applyGs(CGameState *gs)
 	std::string &descr = b->description;
 	std::string &descr = b->description;
 
 
 	if(!bdescr.message.size()
 	if(!bdescr.message.size()
-		&& bonus.source == Bonus::OBJECT
 		&& (bonus.type == Bonus::LUCK || bonus.type == Bonus::MORALE))
 		&& (bonus.type == Bonus::LUCK || bonus.type == Bonus::MORALE))
 	{
 	{
-		descr = VLC->generaltexth->arraytxt[bonus.val > 0 ? 110 : 109]; //+/-%d Temporary until next battle"
+		if (bonus.source == Bonus::OBJECT)
+		{
+			descr = VLC->generaltexth->arraytxt[bonus.val > 0 ? 110 : 109]; //+/-%d Temporary until next battle"
+		}
+		else if(bonus.source == Bonus::TOWN_STRUCTURE)
+		{
+			descr = bonus.description;
+			return;
+		}
+		else
+		{
+			bdescr.toString(descr);
+		}
 	}
 	}
 	else
 	else
 	{
 	{
@@ -556,16 +567,27 @@ void TryMoveHero::applyGs(CGameState *gs)
 DLL_LINKAGE void NewStructures::applyGs(CGameState *gs)
 DLL_LINKAGE void NewStructures::applyGs(CGameState *gs)
 {
 {
 	CGTownInstance *t = gs->getTown(tid);
 	CGTownInstance *t = gs->getTown(tid);
+
 	for(const auto & id : bid)
 	for(const auto & id : bid)
 	{
 	{
 		assert(t->town->buildings.at(id) != nullptr);
 		assert(t->town->buildings.at(id) != nullptr);
 		t->builtBuildings.insert(id);
 		t->builtBuildings.insert(id);
-
 		t->updateAppearance();
 		t->updateAppearance();
+		auto currentBuilding = t->town->buildings.at(id);
+
+		if(currentBuilding->overrideBids.empty())
+			continue;
+
+		for(auto overrideBid : currentBuilding->overrideBids)
+		{
+			t->overriddenBuildings.insert(overrideBid);
+			t->deleteTownBonus(overrideBid);
+		}
 	}
 	}
 	t->builded = builded;
 	t->builded = builded;
 	t->recreateBuildingsBonuses();
 	t->recreateBuildingsBonuses();
 }
 }
+
 DLL_LINKAGE void RazeStructures::applyGs(CGameState *gs)
 DLL_LINKAGE void RazeStructures::applyGs(CGameState *gs)
 {
 {
 	CGTownInstance *t = gs->getTown(tid);
 	CGTownInstance *t = gs->getTown(tid);

+ 5 - 0
lib/VCMI_Lib.cpp

@@ -196,3 +196,8 @@ void LibClasses::setContent(std::shared_ptr<CContentHandler> content)
 {
 {
 	modh->content = content;
 	modh->content = content;
 }
 }
+
+void LibClasses::restoreAllCreaturesNodeType794()
+{
+	creh->restoreAllCreaturesNodeType794();
+}

+ 4 - 0
lib/VCMI_Lib.h

@@ -36,6 +36,7 @@ class DLL_LINKAGE LibClasses
 	void makeNull(); //sets all handler pointers to null
 	void makeNull(); //sets all handler pointers to null
 	std::shared_ptr<CContentHandler> getContent() const;
 	std::shared_ptr<CContentHandler> getContent() const;
 	void setContent(std::shared_ptr<CContentHandler> content);
 	void setContent(std::shared_ptr<CContentHandler> content);
+	void restoreAllCreaturesNodeType794();
 
 
 public:
 public:
 	bool IS_AI_ENABLED; //unused?
 	bool IS_AI_ENABLED; //unused?
@@ -69,6 +70,9 @@ public:
 		h & heroh;
 		h & heroh;
 		h & arth;
 		h & arth;
 		h & creh;
 		h & creh;
+		if(!h.saving && version < 794)
+			restoreAllCreaturesNodeType794();
+
 		h & townh;
 		h & townh;
 		h & objh;
 		h & objh;
 		h & objtypeh;
 		h & objtypeh;

+ 142 - 106
lib/mapObjects/CGTownInstance.cpp

@@ -21,6 +21,7 @@
 #include "../mapping/CMap.h"
 #include "../mapping/CMap.h"
 #include "../CPlayerState.h"
 #include "../CPlayerState.h"
 #include "../serializer/JsonSerializeFormat.h"
 #include "../serializer/JsonSerializeFormat.h"
+#include "../HeroBonus.h"
 
 
 std::vector<const CArtifact *> CGTownInstance::merchantArtifacts;
 std::vector<const CArtifact *> CGTownInstance::merchantArtifacts;
 std::vector<int> CGTownInstance::universitySkills;
 std::vector<int> CGTownInstance::universitySkills;
@@ -435,8 +436,6 @@ void CGDwelling::serializeJsonOptions(JsonSerializeFormat & handler)
 	}
 	}
 }
 }
 
 
-TPropagatorPtr CGTownInstance::emptyPropagator = TPropagatorPtr();
-
 int CGTownInstance::getSightRadius() const //returns sight distance
 int CGTownInstance::getSightRadius() const //returns sight distance
 {
 {
 	auto ret = CBuilding::HEIGHT_NO_TOWER;
 	auto ret = CBuilding::HEIGHT_NO_TOWER;
@@ -753,6 +752,17 @@ void CGTownInstance::tryAddOnePerWeekBonus(BuildingSubID::EBuildingSubID subID)
 		bonusingBuildings.push_back(new COPWBonus(bid, subID, this));
 		bonusingBuildings.push_back(new COPWBonus(bid, subID, this));
 }
 }
 
 
+void CGTownInstance::initOverriddenBids()
+{
+	for(const auto & bid : builtBuildings)
+	{
+		auto & overrideThem = town->buildings.at(bid)->overrideBids;
+
+		for(auto & overrideIt : overrideThem)
+			overriddenBuildings.insert(overrideIt);
+	}
+}
+
 void CGTownInstance::tryAddVisitingBonus(BuildingSubID::EBuildingSubID subID)
 void CGTownInstance::tryAddVisitingBonus(BuildingSubID::EBuildingSubID subID)
 {
 {
 	auto bid = town->getBuildingType(subID);
 	auto bid = town->getBuildingType(subID);
@@ -765,6 +775,9 @@ void CGTownInstance::addTownBonuses()
 {
 {
 	for(const auto & kvp : town->buildings)
 	for(const auto & kvp : town->buildings)
 	{
 	{
+		if(vstd::contains(overriddenBuildings, kvp.first))
+			continue;
+
 		if(kvp.second->IsVisitingBonus())
 		if(kvp.second->IsVisitingBonus())
 			bonusingBuildings.push_back(new CTownBonus(kvp.second->bid, kvp.second->subId, this));
 			bonusingBuildings.push_back(new CTownBonus(kvp.second->bid, kvp.second->subId, this));
 
 
@@ -773,6 +786,36 @@ void CGTownInstance::addTownBonuses()
 	}
 	}
 }
 }
 
 
+void CGTownInstance::deleteTownBonus(BuildingID::EBuildingID bid)
+{
+	size_t i = 0;
+	CGTownBuilding * freeIt = nullptr;
+
+	for(i = 0; i != bonusingBuildings.size(); i++)
+	{
+		if(bonusingBuildings[i]->getBuildingType() == bid)
+		{
+			freeIt = bonusingBuildings[i];
+			break;
+		}
+	}
+	if(freeIt == nullptr)
+		return;
+
+	auto building = town->buildings.at(bid);
+	auto isVisitingBonus = building->IsVisitingBonus();
+	auto isWeekBonus = building->IsWeekBonus();
+
+	if(!isVisitingBonus && !isWeekBonus)
+		return;
+
+	bonusingBuildings.erase(bonusingBuildings.begin() + i);
+
+	if(isVisitingBonus)
+		delete (CTownBonus *)freeIt;
+	else if(isWeekBonus)
+		delete (COPWBonus *)freeIt;
+}
 
 
 void CGTownInstance::initObj(CRandomGenerator & rand) ///initialize town structures
 void CGTownInstance::initObj(CRandomGenerator & rand) ///initialize town structures
 {
 {
@@ -794,17 +837,18 @@ void CGTownInstance::initObj(CRandomGenerator & rand) ///initialize town structu
 				creatures[level].second.push_back(town->creatures[level][upgradeNum]);
 				creatures[level].second.push_back(town->creatures[level][upgradeNum]);
 		}
 		}
 	}
 	}
+	initOverriddenBids();
 	addTownBonuses(); //add special bonuses from buildings to the bonusingBuildings vector.
 	addTownBonuses(); //add special bonuses from buildings to the bonusingBuildings vector.
 	recreateBuildingsBonuses();
 	recreateBuildingsBonuses();
 	updateAppearance();
 	updateAppearance();
 }
 }
 
 
-void CGTownInstance::updateBonusingBuildings()
+void CGTownInstance::updateBonusingBuildings() //update to version 792
 {
 {
-	if (this->town->faction != nullptr)
+	if(this->town->faction != nullptr)
 	{
 	{
 		//firstly, update subtype for the Bonusing objects, which are already stored in the bonusing list
 		//firstly, update subtype for the Bonusing objects, which are already stored in the bonusing list
-		for (auto building : bonusingBuildings) //no garrison bonuses here, only week and visiting bonuses
+		for(auto building : bonusingBuildings) //no garrison bonuses here, only week and visiting bonuses
 		{
 		{
 			switch (this->town->faction->index)
 			switch (this->town->faction->index)
 			{
 			{
@@ -838,7 +882,7 @@ void CGTownInstance::updateBonusingBuildings()
 		}
 		}
 	}
 	}
 	//secondly, supplement bonusing buildings list and active bonuses; subtypes for these objects are already set in update792
 	//secondly, supplement bonusing buildings list and active bonuses; subtypes for these objects are already set in update792
-	for (auto & kvp : town->buildings)
+	for(auto & kvp : town->buildings)
 	{
 	{
 		auto & building = kvp.second;
 		auto & building = kvp.second;
 
 
@@ -864,6 +908,25 @@ void CGTownInstance::updateBonusingBuildings()
 	recreateBuildingsBonuses(); ///Clear all bonuses and recreate
 	recreateBuildingsBonuses(); ///Clear all bonuses and recreate
 }
 }
 
 
+void CGTownInstance::updateTown794()
+{
+	for(auto builtBuilding : builtBuildings)
+	{
+		auto building = town->buildings.at(builtBuilding);
+
+		for(auto overriddenBid : building->overrideBids)
+			overriddenBuildings.insert(overriddenBid);
+	}
+	for(auto & kvp : town->buildings)
+	{
+		auto & building = kvp.second;
+		//The building acts as a visiting bonus and it has not been overridden.
+		if(building->IsVisitingBonus() && overriddenBuildings.find(kvp.first) == overriddenBuildings.end())
+			tryAddVisitingBonus(building->subId);
+	}
+	recreateBuildingsBonuses();
+}
+
 bool CGTownInstance::hasBuiltInOldWay(ETownType::ETownType type, BuildingID bid) const
 bool CGTownInstance::hasBuiltInOldWay(ETownType::ETownType type, BuildingID bid) const
 {
 {
 	return (this->town->faction != nullptr && this->town->faction->index == type && hasBuilt(bid));
 	return (this->town->faction != nullptr && this->town->faction->index == type && hasBuilt(bid));
@@ -1184,112 +1247,30 @@ void CGTownInstance::updateMoraleBonusFromArmy()
 
 
 void CGTownInstance::recreateBuildingsBonuses()
 void CGTownInstance::recreateBuildingsBonuses()
 {
 {
-	static TPropagatorPtr playerProp(new CPropagatorNodeType(PLAYER));
-
 	BonusList bl;
 	BonusList bl;
 	getExportedBonusList().getBonuses(bl, Selector::sourceType()(Bonus::TOWN_STRUCTURE));
 	getExportedBonusList().getBonuses(bl, Selector::sourceType()(Bonus::TOWN_STRUCTURE));
+
 	for(auto b : bl)
 	for(auto b : bl)
 		removeBonus(b);
 		removeBonus(b);
 
 
-	//tricky! -> checks tavern only if no bratherhood of sword or not a castle
-	if(!addBonusIfBuilt(BuildingSubID::BROTHERHOOD_OF_SWORD, Bonus::MORALE, +2))
-		addBonusIfBuilt(BuildingID::TAVERN, Bonus::MORALE, +1);
-
-	addBonusIfBuilt(BuildingSubID::FOUNTAIN_OF_FORTUNE, Bonus::LUCK, +2); //fountain of fortune
-	addBonusIfBuilt(BuildingSubID::SPELL_POWER_GARRISON_BONUS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER);//works as Brimstone Clouds
-	addBonusIfBuilt(BuildingSubID::ATTACK_GARRISON_BONUS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::ATTACK);//works as Blood Obelisk
-	addBonusIfBuilt(BuildingSubID::DEFENSE_GARRISON_BONUS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE);//works as Glyphs of Fear
-	addBonusIfBuilt(BuildingSubID::LIGHTHOUSE, Bonus::SEA_MOVEMENT, +500, playerProp);
-
-	if(subID == ETownType::CASTLE) //castle
-	{
-		addBonusIfBuilt(BuildingID::GRAIL, Bonus::MORALE, +2, playerProp); //colossus
-	}
-	else if(subID == ETownType::RAMPART) //rampart
-	{
-		addBonusIfBuilt(BuildingID::GRAIL, Bonus::LUCK, +2, playerProp); //guardian spirit
-	}
-	else if(subID == ETownType::TOWER) //tower
-	{
-		addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +15, PrimarySkill::KNOWLEDGE); //grail
-	}
-	else if(subID == ETownType::NECROPOLIS) //necropolis
-	{
-		addBonusIfBuilt(BuildingID::COVER_OF_DARKNESS,    Bonus::DARKNESS, +20);
-		addBonusIfBuilt(BuildingID::NECROMANCY_AMPLIFIER, Bonus::SECONDARY_SKILL_PREMY, +10, playerProp, SecondarySkill::NECROMANCY); //necromancy amplifier
-		addBonusIfBuilt(BuildingID::GRAIL, Bonus::SECONDARY_SKILL_PREMY, +20, playerProp, SecondarySkill::NECROMANCY); //Soul prison
-	}
-	else if(subID == ETownType::DUNGEON) //Dungeon
-	{
-		addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +12, PrimarySkill::SPELL_POWER); //grail
-	}
-	else if(subID == ETownType::STRONGHOLD) //Stronghold
-	{
-		addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +20, PrimarySkill::ATTACK); //grail
-	}
-	else if(subID == ETownType::FORTRESS) //Fortress
+	for(auto bid : builtBuildings)
 	{
 	{
-		addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::ATTACK); //grail
-		addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::DEFENSE); //grail
-	}
-	else if(boost::algorithm::ends_with(this->town->faction->identifier, ":cove") && hasBuilt(BuildingID::GRAIL))
-	{
-		static TPropagatorPtr lsProp(new CPropagatorNodeType(ALL_CREATURES));
-		static auto factionLimiter = std::make_shared<CreatureFactionLimiter>(this->town->faction->index);
-		auto b = std::make_shared<Bonus>(Bonus::PERMANENT, Bonus::NO_TERRAIN_PENALTY, Bonus::TOWN_STRUCTURE, 0, BuildingID::GRAIL, "", -1);
+		if(vstd::contains(overriddenBuildings, bid)) //tricky! -> checks tavern only if no bratherhood of sword
+			continue;
 
 
-		b->addLimiter(factionLimiter);
-		b->addPropagator(lsProp);
-		VLC->creh->addBonusForAllCreatures(b);
-	}
-}
+		auto building = town->buildings.at(bid);
 
 
-bool CGTownInstance::addBonusIfBuilt(BuildingSubID::EBuildingSubID subId, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype)
-{
-	BuildingID currentBid = BuildingID::NONE;
-	std::ostringstream descr;
+		if(building->buildingBonuses.empty())
+			continue;
 
 
-	for(const auto & bid : builtBuildings)
-	{
-		if(town->buildings.at(bid)->subId == subId)
+		for(auto bonus : building->buildingBonuses)
 		{
 		{
-			descr << town->buildings.at(bid)->Name();
-			currentBid = bid;
-			break;
+			if(bonus->propagator != nullptr && bonus->propagator->getPropagatorType() == ALL_CREATURES)
+				VLC->creh->addBonusForAllCreatures(bonus);
+			else
+				addNewBonus(bonus);
 		}
 		}
 	}
 	}
-	return currentBid == BuildingID::NONE ? false
-		: addBonusImpl(currentBid, type, val, prop, descr.str(), subtype);
-}
-
-bool CGTownInstance::addBonusIfBuilt(BuildingSubID::EBuildingSubID subId, Bonus::BonusType type, int val, int subtype)
-{
-	return addBonusIfBuilt(subId, type, val, emptyPropagator, subtype);
-}
-
-bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype)
-{
-	if(!hasBuilt(building))
-		return false;
-	
-	std::ostringstream descr;
-	descr << town->buildings.at(building)->Name();
-	return addBonusImpl(building, type, val, prop, descr.str(), subtype);
-}
-
-bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype)
-{
-	return addBonusIfBuilt(building, type, val, emptyPropagator, subtype);
-}
-
-bool CGTownInstance::addBonusImpl(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, const std::string & description, int subtype)
-{
-	auto b = std::make_shared<Bonus>(Bonus::PERMANENT, type, Bonus::TOWN_STRUCTURE, val, building, description, subtype);
-
-	if(prop)
-		b->addPropagator(prop);
-	addNewBonus(b);
-	return true;
 }
 }
 
 
 void CGTownInstance::setVisitingHero(CGHeroInstance *h)
 void CGTownInstance::setVisitingHero(CGHeroInstance *h)
@@ -1738,11 +1719,11 @@ void CTownBonus::setProperty (ui8 what, ui32 val)
 void CTownBonus::onHeroVisit(const CGHeroInstance * h) const
 void CTownBonus::onHeroVisit(const CGHeroInstance * h) const
 {
 {
 	ObjectInstanceID heroID = h->id;
 	ObjectInstanceID heroID = h->id;
-	if (town->hasBuilt(bID) && visitors.find(heroID) == visitors.end())
+	if(town->hasBuilt(bID) && visitors.find(heroID) == visitors.end())
 	{
 	{
 		si64 val = 0;
 		si64 val = 0;
 		InfoWindow iw;
 		InfoWindow iw;
-		PrimarySkill::PrimarySkill what = PrimarySkill::ATTACK;
+		PrimarySkill::PrimarySkill what = PrimarySkill::NONE;
 
 
 		switch (bType)
 		switch (bType)
 		{
 		{
@@ -1775,13 +1756,49 @@ void CTownBonus::onHeroVisit(const CGHeroInstance * h) const
 			val = 1;
 			val = 1;
 			iw.components.push_back(Component(Component::PRIM_SKILL, 1, 1, 0));
 			iw.components.push_back(Component(Component::PRIM_SKILL, 1, 1, 0));
 			break;
 			break;
+
+		case BuildingSubID::CUSTOM_VISITING_BONUS:
+			const auto building = town->town->buildings.at(bID);
+			if(!h->hasBonusFrom(Bonus::TOWN_STRUCTURE, Bonus::getSid32(building->town->faction->index, building->bid)))
+			{
+				const auto & bonuses = building->onVisitBonuses;
+				applyBonuses(const_cast<CGHeroInstance *>(h), bonuses);
+			}
+			break;
+		}
+		if(what != PrimarySkill::NONE)
+		{
+			iw.player = cb->getOwner(heroID);
+			iw.text << getVisitingBonusGreeting();
+			cb->showInfoDialog(&iw);
+			cb->changePrimSkill(cb->getHero(heroID), what, val);
+			town->addHeroToStructureVisitors(h, indexOnTV);
 		}
 		}
-		iw.player = cb->getOwner(heroID);
-		iw.text << getVisitingBonusGreeting();
+	}
+}
+
+void CTownBonus::applyBonuses(CGHeroInstance * h, const BonusList & bonuses) const
+{
+	auto addToVisitors = false;
+
+	for(auto bonus : bonuses)
+	{
+		GiveBonus gb;
+		InfoWindow iw;
+
+		gb.bonus = * bonus;
+		gb.id = h->id.getNum();
+		cb->giveHeroBonus(&gb);
+
+		if(bonus->duration == Bonus::PERMANENT)
+			addToVisitors = true;
+
+		iw.player = cb->getOwner(h->id);
+		iw.text << getCustomBonusGreeting(gb.bonus);
 		cb->showInfoDialog(&iw);
 		cb->showInfoDialog(&iw);
-		cb->changePrimSkill(cb->getHero(heroID), what, val);
-		town->addHeroToStructureVisitors(h, indexOnTV);
 	}
 	}
+	if(addToVisitors)
+		town->addHeroToStructureVisitors(h, indexOnTV);
 }
 }
 
 
 GrowthInfo::Entry::Entry(const std::string &format, int _count)
 GrowthInfo::Entry::Entry(const std::string &format, int _count)
@@ -1851,3 +1868,22 @@ const std::string CGTownBuilding::getVisitingBonusGreeting() const
 	town->town->setGreeting(bType, bonusGreeting);
 	town->town->setGreeting(bType, bonusGreeting);
 	return bonusGreeting;
 	return bonusGreeting;
 }
 }
+
+const std::string CGTownBuilding::getCustomBonusGreeting(const Bonus & bonus)
+{
+	auto bonusGreeting = std::string(VLC->generaltexth->localizedTexts["townHall"]["greetingCustomBonus"].String()); //"%s gives you +%d %s%s"
+	std::string param = "";
+	std::string until = "";
+
+	if(bonus.type == Bonus::MORALE)
+		param = VLC->generaltexth->allTexts[384];
+	else if(bonus.type == Bonus::LUCK)
+		param = VLC->generaltexth->allTexts[385];
+
+	until = bonus.duration == (ui16)Bonus::ONE_BATTLE
+		? VLC->generaltexth->localizedTexts["townHall"]["greetingCustomUntil"].String() : ".";
+
+	boost::format fmt = boost::format(bonusGreeting) % bonus.description % bonus.val % param % until;
+	std::string greeting = fmt.str();
+	return greeting;
+}

+ 17 - 10
lib/mapObjects/CGTownInstance.h

@@ -133,6 +133,7 @@ protected:
 	BuildingSubID::EBuildingSubID bType;
 	BuildingSubID::EBuildingSubID bType;
 
 
 	const std::string getVisitingBonusGreeting() const;
 	const std::string getVisitingBonusGreeting() const;
+	static const std::string getCustomBonusGreeting(const Bonus & bonus);
 };
 };
 
 
 class DLL_LINKAGE COPWBonus : public CGTownBuilding
 class DLL_LINKAGE COPWBonus : public CGTownBuilding
@@ -169,6 +170,9 @@ public:
 		h & static_cast<CGTownBuilding&>(*this);
 		h & static_cast<CGTownBuilding&>(*this);
 		h & visitors;
 		h & visitors;
 	}
 	}
+
+private:
+	void applyBonuses(CGHeroInstance * h, const BonusList & bonuses) const;
 };
 };
 
 
 class DLL_LINKAGE CTownAndVisitingHero : public CBonusSystemNode
 class DLL_LINKAGE CTownAndVisitingHero : public CBonusSystemNode
@@ -205,7 +209,9 @@ public:
 	ConstTransitivePtr<CGHeroInstance> garrisonHero, visitingHero;
 	ConstTransitivePtr<CGHeroInstance> garrisonHero, visitingHero;
 	ui32 identifier; //special identifier from h3m (only > RoE maps)
 	ui32 identifier; //special identifier from h3m (only > RoE maps)
 	si32 alignment;
 	si32 alignment;
-	std::set<BuildingID> forbiddenBuildings, builtBuildings;
+	std::set<BuildingID> forbiddenBuildings;
+	std::set<BuildingID> builtBuildings;
+	std::set<BuildingID> overriddenBuildings; ///buildings which bonuses are overridden and should not be applied
 	std::vector<CGTownBuilding*> bonusingBuildings;
 	std::vector<CGTownBuilding*> bonusingBuildings;
 	std::vector<SpellID> possibleSpells, obligatorySpells;
 	std::vector<SpellID> possibleSpells, obligatorySpells;
 	std::vector<std::vector<SpellID> > spells; //spells[level] -> vector of spells, first will be available in guild
 	std::vector<std::vector<SpellID> > spells; //spells[level] -> vector of spells, first will be available in guild
@@ -237,8 +243,8 @@ public:
 		h & events;
 		h & events;
 		h & bonusingBuildings;
 		h & bonusingBuildings;
 
 
-		for (std::vector<CGTownBuilding*>::iterator i = bonusingBuildings.begin(); i!=bonusingBuildings.end(); i++)
-			(*i)->town = this;
+		for(auto * bonusingBuilding : bonusingBuildings)
+			bonusingBuilding->town = this;
 
 
 		h & town;
 		h & town;
 		h & townAndVis;
 		h & townAndVis;
@@ -256,6 +262,11 @@ public:
 
 
 		if(!h.saving && version < 793)
 		if(!h.saving && version < 793)
 			updateBonusingBuildings();
 			updateBonusingBuildings();
+
+		if(version >= 794)
+			h & overriddenBuildings;
+		else if(!h.saving)
+			updateTown794();
 	}
 	}
 	//////////////////////////////////////////////////////////////////////////
 	//////////////////////////////////////////////////////////////////////////
 
 
@@ -264,11 +275,6 @@ public:
 	void updateMoraleBonusFromArmy() override;
 	void updateMoraleBonusFromArmy() override;
 	void deserializationFix();
 	void deserializationFix();
 	void recreateBuildingsBonuses();
 	void recreateBuildingsBonuses();
-	///bid: param to bind a building with a bonus, subId: param to check if already built
-	bool addBonusIfBuilt(BuildingSubID::EBuildingSubID subId, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype = -1);
-	bool addBonusIfBuilt(BuildingSubID::EBuildingSubID subId, Bonus::BonusType type, int val, int subtype = -1);
-	bool addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr &prop, int subtype = -1); //returns true if building is built and bonus has been added
-	bool addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype = -1); //convienence version of above
 	void setVisitingHero(CGHeroInstance *h);
 	void setVisitingHero(CGHeroInstance *h);
 	void setGarrisonedHero(CGHeroInstance *h);
 	void setGarrisonedHero(CGHeroInstance *h);
 	const CArmedInstance *getUpperArmy() const; //garrisoned hero if present or the town itself
 	const CArmedInstance *getUpperArmy() const; //garrisoned hero if present or the town itself
@@ -318,6 +324,7 @@ public:
 	void removeCapitols (PlayerColor owner) const;
 	void removeCapitols (PlayerColor owner) const;
 	void clearArmy() const;
 	void clearArmy() const;
 	void addHeroToStructureVisitors(const CGHeroInstance *h, si64 structureInstanceID) const; //hero must be visiting or garrisoned in town
 	void addHeroToStructureVisitors(const CGHeroInstance *h, si64 structureInstanceID) const; //hero must be visiting or garrisoned in town
+	void deleteTownBonus(BuildingID::EBuildingID bid);
 
 
 	const CTown * getTown() const ;
 	const CTown * getTown() const ;
 
 
@@ -336,7 +343,6 @@ public:
 	static void reset();
 	static void reset();
 
 
 protected:
 protected:
-	static TPropagatorPtr emptyPropagator;
 	void setPropertyDer(ui8 what, ui32 val) override;
 	void setPropertyDer(ui8 what, ui32 val) override;
 	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 
 
@@ -344,9 +350,10 @@ private:
 	int getDwellingBonus(const std::vector<CreatureID>& creatureIds, const std::vector<ConstTransitivePtr<CGDwelling> >& dwellings) const;
 	int getDwellingBonus(const std::vector<CreatureID>& creatureIds, const std::vector<ConstTransitivePtr<CGDwelling> >& dwellings) const;
 	void updateBonusingBuildings();
 	void updateBonusingBuildings();
 	bool hasBuiltInOldWay(ETownType::ETownType type, BuildingID bid) const;
 	bool hasBuiltInOldWay(ETownType::ETownType type, BuildingID bid) const;
-	bool addBonusImpl(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, const std::string & description, int subtype = -1);
 	bool townEnvisagesBuilding(BuildingSubID::EBuildingSubID bid) const;
 	bool townEnvisagesBuilding(BuildingSubID::EBuildingSubID bid) const;
 	void tryAddOnePerWeekBonus(BuildingSubID::EBuildingSubID subID);
 	void tryAddOnePerWeekBonus(BuildingSubID::EBuildingSubID subID);
 	void tryAddVisitingBonus(BuildingSubID::EBuildingSubID subID);
 	void tryAddVisitingBonus(BuildingSubID::EBuildingSubID subID);
+	void initOverriddenBids();
 	void addTownBonuses();
 	void addTownBonuses();
+	void updateTown794(); //populate overriddenBuildings and vanila bonuses for old saves 
 };
 };

+ 1 - 1
lib/mapObjects/CObjectHandler.cpp

@@ -260,7 +260,7 @@ void CGObjectInstance::giveDummyBonus(ObjectInstanceID heroID, ui8 duration) con
 	GiveBonus gbonus;
 	GiveBonus gbonus;
 	gbonus.bonus.type = Bonus::NONE;
 	gbonus.bonus.type = Bonus::NONE;
 	gbonus.id = heroID.getNum();
 	gbonus.id = heroID.getNum();
-	gbonus.bonus.duration = duration;
+	gbonus.bonus.duration = (Bonus::BonusDuration)duration;
 	gbonus.bonus.source = Bonus::OBJECT;
 	gbonus.bonus.source = Bonus::OBJECT;
 	gbonus.bonus.sid = ID;
 	gbonus.bonus.sid = ID;
 	cb->giveHeroBonus(&gbonus);
 	cb->giveHeroBonus(&gbonus);

+ 1 - 1
lib/serializer/CSerializer.h

@@ -12,7 +12,7 @@
 #include "../ConstTransitivePtr.h"
 #include "../ConstTransitivePtr.h"
 #include "../GameConstants.h"
 #include "../GameConstants.h"
 
 
-const ui32 SERIALIZATION_VERSION = 793;
+const ui32 SERIALIZATION_VERSION = 794;
 const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
 const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
 const std::string SAVEGAME_MAGIC = "VCMISVG";
 const std::string SAVEGAME_MAGIC = "VCMISVG";
 
 

+ 6 - 7
server/CGameHandler.cpp

@@ -3147,16 +3147,14 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
 		processBeforeBuiltStructure(builtID);
 		processBeforeBuiltStructure(builtID);
 
 
 	//Take cost
 	//Take cost
-	if (!force)
-	{
+	if(!force)
 		giveResources(t->tempOwner, -requestedBuilding->resources);
 		giveResources(t->tempOwner, -requestedBuilding->resources);
-	}
 
 
-	//We know what has been built, appluy changes. Do this as final step to properly update town window
+	//We know what has been built, apply changes. Do this as final step to properly update town window
 	sendAndApply(&ns);
 	sendAndApply(&ns);
 
 
 	//Other post-built events. To some logic like giving spells to work gamestate changes for new building must be already in place!
 	//Other post-built events. To some logic like giving spells to work gamestate changes for new building must be already in place!
-	for (auto builtID : ns.bid)
+	for(auto builtID : ns.bid)
 		processAfterBuiltStructure(builtID);
 		processAfterBuiltStructure(builtID);
 
 
 	// now when everything is built - reveal tiles for lookout tower
 	// now when everything is built - reveal tiles for lookout tower
@@ -3166,14 +3164,15 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
 	getTilesInRange(fw.tiles, t->getSightCenter(), t->getSightRadius(), t->tempOwner, 1);
 	getTilesInRange(fw.tiles, t->getSightCenter(), t->getSightRadius(), t->tempOwner, 1);
 	sendAndApply(&fw);
 	sendAndApply(&fw);
 
 
-	if (t->visitingHero)
+	if(t->visitingHero)
 		visitCastleObjects(t, t->visitingHero);
 		visitCastleObjects(t, t->visitingHero);
-	if (t->garrisonHero)
+	if(t->garrisonHero)
 		visitCastleObjects(t, t->garrisonHero);
 		visitCastleObjects(t, t->garrisonHero);
 
 
 	checkVictoryLossConditionsForPlayer(t->tempOwner);
 	checkVictoryLossConditionsForPlayer(t->tempOwner);
 	return true;
 	return true;
 }
 }
+
 bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid)
 bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid)
 {
 {
 ///incomplete, simply erases target building
 ///incomplete, simply erases target building