Browse Source

Mod system improvement Part I : Special buildings should work in the modders towns

Dmitry Orlov 5 years ago
parent
commit
f4816b0824

+ 29 - 39
client/windows/CCastleInterface.cpp

@@ -112,8 +112,11 @@ void CBuildingRect::hover(bool on)
 void CBuildingRect::clickLeft(tribool down, bool previousState)
 {
 	if( previousState && getBuilding() && area && !down && (parent->selectedBuilding==this))
-		if (!CSDL_Ext::isTransparent(area, GH.current->motion.x-pos.x, GH.current->motion.y-pos.y) ) //inside building image
-			parent->buildingClicked(getBuilding()->bid);
+		if (!CSDL_Ext::isTransparent(area, GH.current->motion.x - pos.x, GH.current->motion.y - pos.y)) //inside building image
+		{
+			auto building = getBuilding();
+			parent->buildingClicked(building->bid, building->subId);
+		}
 }
 
 void CBuildingRect::clickRight(tribool down, bool previousState)
@@ -650,7 +653,7 @@ const CGHeroInstance * CCastleBuildings::getHero()
 		return town->garrisonHero;
 }
 
-void CCastleBuildings::buildingClicked(BuildingID building)
+void CCastleBuildings::buildingClicked(BuildingID building, BuildingSubID::EBuildingSubID subID)
 {
 	logGlobal->trace("You've clicked on %d", (int)building.toEnum());
 	const CBuilding *b = town->town->buildings.find(building)->second;
@@ -703,82 +706,67 @@ void CCastleBuildings::buildingClicked(BuildingID building)
 				enterBlacksmith(town->town->warMachine);
 				break;
 
+		case BuildingID::SHIP:
+			LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[51]); //Cannot build another boat
+			break;
+
 		case BuildingID::SPECIAL_1:
-				switch(town->subID)
+		case BuildingID::SPECIAL_2:
+		case BuildingID::SPECIAL_3:
+				switch(subID)
 				{
-				case ETownType::RAMPART://Mystic Pond
+				case BuildingSubID::NONE:
+						break;
+
+				case BuildingSubID::MYSTIC_POND:
 						enterFountain(building);
 						break;
 
-				case ETownType::TOWER:
-				case ETownType::DUNGEON://Artifact Merchant
-				case ETownType::CONFLUX:
+				case BuildingSubID::ARTIFACT_MERCHANT:
 						if(town->visitingHero)
 							GH.pushIntT<CMarketplaceWindow>(town, town->visitingHero, EMarketMode::RESOURCE_ARTIFACT);
 						else
 							LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % b->Name())); //Only visiting heroes may use the %s.
 						break;
 
-				default:
-					enterBuilding(building);
-					break;
-				}
-				break;
-
-		case BuildingID::SHIP:
-				LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[51]); //Cannot build another boat
-				break;
-
-		case BuildingID::SPECIAL_2:
-				switch(town->subID)
-				{
-				case ETownType::RAMPART: //Fountain of Fortune
+				case BuildingSubID::FOUNTAIN_OF_FORTUNE:
 						enterFountain(building);
 						break;
 
-				case ETownType::STRONGHOLD: //Freelancer's Guild
+				case BuildingSubID::FREELANCERS_GUILD:
 						if(getHero())
 							GH.pushIntT<CMarketplaceWindow>(town, getHero(), EMarketMode::CREATURE_RESOURCE);
 						else
 							LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % b->Name())); //Only visiting heroes may use the %s.
 						break;
 
-				case ETownType::CONFLUX: //Magic University
+				case BuildingSubID::MAGIC_UNIVERSITY:
 						if (getHero())
 							GH.pushIntT<CUniversityWindow>(getHero(), town);
 						else
 							enterBuilding(building);
 						break;
 
-				default:
-						enterBuilding(building);
-						break;
-				}
-				break;
-
-		case BuildingID::SPECIAL_3:
-				switch(town->subID)
-				{
-				case ETownType::CASTLE: //Brotherhood of sword
+				case BuildingSubID::BROTHERHOOD_OF_SWORD:
 						LOCPLINT->showTavernWindow(town);
 						break;
 
-				case ETownType::INFERNO: //Castle Gate
+				case BuildingSubID::CASTLE_GATE:
 						enterCastleGate();
 						break;
 
-				case ETownType::NECROPOLIS: //Skeleton Transformer
+				case BuildingSubID::CREATURE_TRANSFORMER: //Skeleton Transformer
 						GH.pushIntT<CTransformerWindow>(getHero(), town);
 						break;
 
-				case ETownType::DUNGEON: //Portal of Summoning
+				case BuildingSubID::PORTAL_OF_SUMMONING:
 						if (town->creatures[GameConstants::CREATURES_PER_TOWN].second.empty())//No creatures
 							LOCPLINT->showInfoDialog(CGI->generaltexth->tcommands[30]);
 						else
 							enterDwelling(GameConstants::CREATURES_PER_TOWN);
 						break;
 
-				case ETownType::STRONGHOLD: //Ballista Yard
+				case BuildingSubID::BALLISTA_YARD:
 						enterBlacksmith(ArtifactID::BALLISTA);
 						break;
 
@@ -828,7 +816,8 @@ void CCastleBuildings::enterCastleGate()
 	{
 		const CGTownInstance *t = Town;
 		if (t->id != this->town->id && t->visitingHero == nullptr && //another town, empty and this is
-			t->hasBuilt(BuildingID::CASTLE_GATE, ETownType::INFERNO))
+			t->town->faction->index == town->town->faction->index && //the town of the same faction
+			t->hasBuilt(BuildingSubID::CASTLE_GATE)) //and the town has a castle gate
 		{
 			availableTowns.push_back(t->id.getNum());//add to the list
 		}
@@ -1173,6 +1162,7 @@ void CCastleInterface::close()
 void CCastleInterface::castleTeleport(int where)
 {
 	const CGTownInstance * dest = LOCPLINT->cb->getTown(ObjectInstanceID(where));
+	adventureInt->select(town->visitingHero);//according to assert(ho == adventureInt->selection) in the eraseCurrentPathOf
 	LOCPLINT->cb->teleportHero(town->visitingHero, dest);
 	LOCPLINT->eraseCurrentPathOf(town->visitingHero, false);
 }

+ 1 - 1
client/windows/CCastleInterface.h

@@ -153,7 +153,7 @@ public:
 	void enterDwelling(int level);
 	void enterToTheQuickRecruitmentWindow();
 
-	void buildingClicked(BuildingID building);
+	void buildingClicked(BuildingID building, BuildingSubID::EBuildingSubID subID = BuildingSubID::NONE);
 	void addBuilding(BuildingID building);
 	void removeBuilding(BuildingID building);//FIXME: not tested!!!
 };

+ 3 - 3
client/windows/GUIClasses.cpp

@@ -1781,9 +1781,6 @@ void CObjectListWindow::init(std::shared_ptr<CIntObject> titleWidget_, std::stri
 
 	title = std::make_shared<CLabel>(152, 27, FONT_BIG, CENTER, Colors::YELLOW, _title);
 	descr = std::make_shared<CLabel>(145, 133, FONT_SMALL, CENTER, Colors::WHITE, _descr);
-
-	ok = std::make_shared<CButton>(Point(15, 402), "IOKAY.DEF", CButton::tooltip(), std::bind(&CObjectListWindow::elementSelected, this), SDLK_RETURN);
-	ok->block(true);
 	exit = std::make_shared<CButton>( Point(228, 402), "ICANCEL.DEF", CButton::tooltip(), std::bind(&CObjectListWindow::exitPressed, this), SDLK_ESCAPE);
 
 	if(titleWidget)
@@ -1796,6 +1793,9 @@ void CObjectListWindow::init(std::shared_ptr<CIntObject> titleWidget_, std::stri
 	list = std::make_shared<CListBox>(std::bind(&CObjectListWindow::genItem, this, _1),
 		Point(14, 151), Point(0, 25), 9, items.size(), 0, 1, Rect(262, -32, 256, 256) );
 	list->type |= REDRAW_PARENT;
+
+	ok = std::make_shared<CButton>(Point(15, 402), "IOKAY.DEF", CButton::tooltip(), std::bind(&CObjectListWindow::elementSelected, this), SDLK_RETURN);
+	ok->block(!list->size());
 }
 
 std::shared_ptr<CIntObject> CObjectListWindow::genItem(size_t index)

+ 3 - 3
config/factions/castle.json

@@ -170,12 +170,12 @@
 				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ], "produce": { "ore": 1, "wood": 1 } },
 				"blacksmith":     { "id" : 16 },
 
-				"special1":       { "id" : 17, "requires" : [ "shipyard" ] },
+				"special1":       { "requires" : [ "shipyard" ] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl3" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl3", "requires" : [ "horde1" ], "mode" : "auto" },
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },
-				"special2":       { "id" : 21, "requires" : [ "dwellingLvl4" ] },
-				"special3":       { "id" : 22, "upgrades" : "tavern" },
+				"special2":       { "type" : "stables", "requires" : [ "dwellingLvl4" ] },
+				"special3":       { "type" : "brotherhoodOfSword", "upgrades" : "tavern" },
 				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
 
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },

+ 2 - 2
config/factions/conflux.json

@@ -175,11 +175,11 @@
 				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ], "produce": { "mercury": 1 } },
 				"blacksmith":     { "id" : 16 },
 
-				"special1":       { "id" : 17, "requires" : [ "marketplace" ] },
+				"special1":       { "type" : "artifactMerchant", "requires" : [ "marketplace" ] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },
-				"special2":       { "id" : 21, "requires" : [ "mageGuild1" ] },
+				"special2":       { "type" : "magicUniversity", "requires" : [ "mageGuild1" ] },
 				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
 				"extraTownHall":  { "id" : 27, "requires" : [ "townHall" ], "mode" : "auto" },
 				"extraCityHall":  { "id" : 28, "requires" : [ "cityHall" ], "mode" : "auto" },

+ 4 - 4
config/factions/dungeon.json

@@ -169,12 +169,12 @@
 				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ] },
 				"blacksmith":     { "id" : 16 },
 
-				"special1":       { "id" : 17, "requires" : [ "marketplace" ] },
+				"special1":       { "type" : "artifactMerchant", "requires" : [ "marketplace" ] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
-				"special2":       { "id" : 21, "requires" : [ "mageGuild1" ] },
-				"special3":       { "id" : 22 },
-				"special4":       { "id" : 23 },
+				"special2":       { "type" : "manaVortex", "requires" : [ "mageGuild1" ] },
+				"special3":       { "type" : "portalOfSummoning" },
+				"special4":       { },
 				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
 
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },

+ 3 - 3
config/factions/fortress.json

@@ -169,12 +169,12 @@
 				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ], "produce": { "wood": 1, "ore": 1 } },
 				"blacksmith":     { "id" : 16 },
 
-				"special1":       { "id" : 17, "requires" : [ "allOf", [ "townHall" ], [ "special2" ] ] },
+				"special1":       { "requires" : [ "allOf", [ "townHall" ], [ "special2" ] ] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },
-				"special2":       { "id" : 21, "requires" : [ "fort" ] },
-				"special3":       { "id" : 22, "requires" : [ "special2" ] },
+				"special2":       { "type" : "defenseGarrisonBonus", "requires" : [ "fort" ] },
+				"special3":       { "type" : "attackGarrisonBonus", "requires" : [ "special2" ] },
 				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
 				"extraCapitol":   { "id" : 29, "requires" : [ "capitol" ], "mode" : "auto" },
 

+ 3 - 3
config/factions/inferno.json

@@ -172,9 +172,9 @@
 
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
-				"special2":       { "id" : 21, "requires" : [ "fort" ] },
-				"special3":       { "id" : 22, "requires" : [ "citadel" ] },
-				"special4":       { "id" : 23, "requires" : [ "mageGuild1" ] },
+				"special2":       { "type" : "spellPowerGarrisonBonus", "requires" : [ "fort" ] },
+				"special3":       { "type" : "castleGate", "requires" : [ "citadel" ] },
+				"special4":       { "requires" : [ "mageGuild1" ] },
 				"horde2":         { "id" : 24, "upgrades" : "dwellingLvl3" },
 				"horde2Upgr":     { "id" : 25, "upgrades" : "dwellingUpLvl3", "requires" : [ "horde2" ], "mode" : "auto" },
 				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},

+ 3 - 3
config/factions/necropolis.json

@@ -175,12 +175,12 @@
 				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ], "produce": { "ore": 1, "wood": 1 } },
 				"blacksmith":     { "id" : 16 },
 
-				"special1":       { "id" : 17, "requires" : [ "fort" ] },
+				"special1":       { "requires" : [ "fort" ] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1", "requires" : [ "special3" ] },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },
-				"special2":       { "id" : 21, "requires" : [ "mageGuild1" ] },
-				"special3":       { "id" : 22, "requires" : [ "dwellingLvl1" ] },
+				"special2":       { "requires" : [ "mageGuild1" ] },
+				"special3":       { "type" : "creatureTransformer", "requires" : [ "dwellingLvl1" ] },
 				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
 				"extraTownHall":  { "id" : 27, "requires" : [ "townHall" ], "mode" : "auto" },
 				"extraCityHall":  { "id" : 28, "requires" : [ "cityHall" ], "mode" : "auto" },

+ 3 - 3
config/factions/rampart.json

@@ -174,11 +174,11 @@
 				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ], "produce": { "crystal": 1 } },
 				"blacksmith":     { "id" : 16 },
 
-				"special1":       { "id" : 17 },
+				"special1":       { "type" : "mysticPond" },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl2" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl2", "requires" : [ "horde1" ], "mode" : "auto" },
-				"special2":       { "id" : 21, "requires" : [ "special1" ] },
-				"special3":       { "id" : 22, "requires" : [ "horde1" ] },
+				"special2":       { "type" : "fountainOfFortune", "requires" : [ "special1" ] },
+				"special3":       { "requires" : [ "horde1" ] },
 				"horde2":         { "id" : 24, "upgrades" : "dwellingLvl5" },
 				"horde2Upgr":     { "id" : 25, "upgrades" : "dwellingUpLvl5", "requires" : [ "horde2" ], "mode" : "auto" },
 				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},

+ 4 - 4
config/factions/stronghold.json

@@ -166,12 +166,12 @@
 				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ], "produce": { "ore": 1, "wood": 1 } },
 				"blacksmith":     { "id" : 16 },
 
-				"special1":       { "id" : 17, "requires" : [ "fort" ] },
+				"special1":       { "type" : "escapeTunnel", "requires" : [ "fort" ] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
-				"special2":       { "id" : 21, "requires" : [ "marketplace" ] },
-				"special3":       { "id" : 22, "requires" : [ "blacksmith" ] },
-				"special4":       { "id" : 23, "requires" : [ "fort" ] },
+				"special2":       { "type" : "freelancersGuild", "requires" : [ "marketplace" ] },
+				"special3":       { "type" : "ballistaYard", "requires" : [ "blacksmith" ] },
+				"special4":       { "requires" : [ "fort" ] },
 				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
 
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },

+ 5 - 5
config/factions/tower.json

@@ -169,13 +169,13 @@
 				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ], "produce" : { "gems": 1 } },
 				"blacksmith":     { "id" : 16 },
 
-				"special1":       { "id" : 17, "requires" : [ "marketplace" ] },
+				"special1":       { "type" : "artifactMerchant", "requires" : [ "marketplace" ] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl2" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl2", "requires" : [ "horde1" ], "mode" : "auto" },
-				"special2":       { "id" : 21, "requires" : [ "fort" ] },
-				"special3":       { "id" : 22, "requires" : [ "mageGuild1" ] },
-				"special4":       { "id" : 23, "requires" : [ "mageGuild1" ] },
-				"grail":          { "id" : 26, "mode" : "grail", "produce" : { "gold": 5000 } },
+				"special2":       { "type" : "lookoutTower", "height" : "high", "requires" : [ "fort" ] },
+				"special3":       { "type" : "library", "requires" : [ "mageGuild1" ] },
+				"special4":       { "requires" : [ "mageGuild1" ] },
+				"grail":          { "height" : "skyship",  "produce" : { "gold": 5000 } },
 
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },
 				"dwellingLvl2":   { "id" : 31, "requires" : [ "dwellingLvl1" ] },

+ 9 - 1
config/schemas/townBuilding.json

@@ -4,7 +4,6 @@
 	"$schema": "http://json-schema.org/draft-04/schema",
 	"title" : "VCMI town building format",
 	"description" : "Format used to define town buildings in VCMI",
-	"required": [ "id" ],
 
 	"definitions" :
 	{
@@ -29,6 +28,15 @@
 			"type":"number",
 			"description" : "Numeric identifier of this building"
 		},
+		"type": {
+			"type":"string",
+			"description" : "Subtype for some special buildings"
+		},
+		"height": {
+			"type":"string",
+			"enum" : [ "skyship", "high", "average", "low"],
+			"description" : "Height for lookout towers and some grails"
+		},
 		"mode": {
 			"type":"string",
 			"enum" : [ "normal", "auto", "special", "grail" ],

+ 82 - 12
lib/CTownHandler.cpp

@@ -343,28 +343,98 @@ void CTownHandler::loadBuildingRequirements(CBuilding * building, const JsonNode
 	requirementsToLoad.push_back(hlp);
 }
 
+template<typename R>
+R CTownHandler::getMappedValue(const std::string key, const R defval, const std::map<std::string, R> & map, bool required) const
+{
+	auto it = map.find(key);
+
+	if(it != map.end())
+		return it->second;
+
+	if(required)
+		logMod->warn("Warning: Property: '%s' is unknown. Correct the typo or update VCMI.", key);
+	return defval;
+}
+
+template<typename R>
+R CTownHandler::getMappedValue(const JsonNode & node, const R defval, const std::map<std::string, R> & map, bool required) const
+{
+	if(!node.isNull() && node.getType() == JsonNode::JsonType::DATA_STRING)
+		return getMappedValue<R>(node.String(), defval, map, required);
+	return defval;
+}
+
 void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, const JsonNode & source)
 {
-	auto ret = new CBuilding();
+	static const std::map<std::string, CBuilding::EBuildMode> MODES =
+	{
+		{ "normal", CBuilding::BUILD_NORMAL },
+		{ "auto", CBuilding::BUILD_AUTO },
+		{ "special", CBuilding::BUILD_SPECIAL },
+		{ "grail", CBuilding::BUILD_GRAIL }
+	};
 
-	static const std::vector<std::string> MODES =
+	static const std::map<std::string, BuildingID> BUILDING_TYPES =
 	{
-		"normal", "auto", "special", "grail"
+		{ "special1", BuildingID::SPECIAL_1 },
+		{ "special2", BuildingID::SPECIAL_2 },
+		{ "special3", BuildingID::SPECIAL_3 },
+		{ "special4", BuildingID::SPECIAL_4 },
+		{ "grail", BuildingID::GRAIL }
 	};
 
-	ret->mode = CBuilding::BUILD_NORMAL;
+	static const std::map<std::string, CBuilding::ETowerHeight> LOOKOUT_TYPES =
 	{
-		if(source["mode"].getType() == JsonNode::JsonType::DATA_STRING)
-		{
-			auto rawMode = vstd::find_pos(MODES, source["mode"].String());
-			if(rawMode > 0)
-				ret->mode = static_cast<CBuilding::EBuildMode>(rawMode);
-		}
-	}
+		{ "low", CBuilding::HEIGHT_LOW },
+		{ "average", CBuilding::HEIGHT_AVERAGE },
+		{ "high", CBuilding::HEIGHT_HIGH },
+		{ "skyship", CBuilding::HEIGHT_SKYSHIP }
+	};
+
+	static const std::map<std::string, BuildingSubID::EBuildingSubID> SPECIAL_BUILDINGS =
+	{
+		{ "mysticPond", BuildingSubID::MYSTIC_POND },
+		{ "artifactMerchant", BuildingSubID::ARTIFACT_MERCHANT },
+		{ "freelancersGuild", BuildingSubID::FREELANCERS_GUILD },
+		{ "magicUniversity", BuildingSubID::MAGIC_UNIVERSITY },
+		{ "castleGate", BuildingSubID::CASTLE_GATE },
+		{ "creatureTransformer", BuildingSubID::CREATURE_TRANSFORMER },//only skeleton transformer yet
+		{ "portalOfSummoning", BuildingSubID::PORTAL_OF_SUMMONING },
+		{ "ballistaYard", BuildingSubID::BALLISTA_YARD },
+		{ "stables", BuildingSubID::STABLES },
+		{ "manaVortex", BuildingSubID::MANA_VORTEX },
+		{ "lookoutTower", BuildingSubID::LOOKOUT_TOWER },
+		{ "library", BuildingSubID::LIBRARY },
+		{ "brotherhoodOfSword", BuildingSubID::BROTHERHOOD_OF_SWORD },//morale garrison bonus
+		{ "fountainOfFortune", BuildingSubID::FOUNTAIN_OF_FORTUNE },//luck garrison bonus
+		{ "spellPowerGarrisonBonus", BuildingSubID::SPELL_POWER_GARRISON_BONUS },//such as 'stormclouds', but this name is not ok for good towns
+		{ "attackGarrisonBonus", BuildingSubID::ATTACK_GARRISON_BONUS },
+		{ "defenseGarrisonBonus", BuildingSubID::DEFENSE_GARRISON_BONUS },
+		{ "escapeTunnel", BuildingSubID::ESCAPE_TUNNEL }
+	};
+
+	auto ret = new CBuilding();
+	ret->bid = getMappedValue<BuildingID>(stringID, BuildingID::NONE, BUILDING_TYPES, false);
+
+	if(ret->bid == BuildingID::NONE)
+		ret->bid = source["id"].isNull() ? BuildingID(BuildingID::NONE) : BuildingID(source["id"].Float());
+
+	if (ret->bid == BuildingID::NONE)
+		logMod->error("Error: Building '%s' has not internal ID and won't work properly. Correct the typo or update VCMI.", stringID);
+
+	ret->mode = ret->bid == BuildingID::GRAIL
+		? CBuilding::BUILD_GRAIL
+		: getMappedValue<CBuilding::EBuildMode>(source["mode"], CBuilding::BUILD_NORMAL, MODES);
+
+	ret->subId = getMappedValue<BuildingSubID::EBuildingSubID>(source["type"], BuildingSubID::NONE, SPECIAL_BUILDINGS);
+	ret->height = CBuilding::HEIGHT_NO_TOWER;
+
+	if(ret->subId == BuildingSubID::LOOKOUT_TOWER 
+		|| ret->bid == BuildingID::GRAIL) 
+		ret->height = getMappedValue<CBuilding::ETowerHeight>(source["height"], CBuilding::HEIGHT_NO_TOWER, LOOKOUT_TYPES);
 
 	ret->identifier = stringID;
 	ret->town = town;
-	ret->bid = BuildingID((si32)source["id"].Float());
 	ret->name = source["name"].String();
 	ret->description = source["description"].String();
 	ret->resources = TResources(source["cost"]);

+ 27 - 0
lib/CTownHandler.h

@@ -46,6 +46,7 @@ public:
 
 	BuildingID bid; //structure ID
 	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
 
 	enum EBuildMode
 	{
@@ -55,6 +56,15 @@ public:
 		BUILD_GRAIL    // 3 - grail - building reqires grail to be built
 	} mode;
 
+	enum ETowerHeight // for lookup towers and some grails
+	{
+		HEIGHT_NO_TOWER = 5, // building has not 'lookout tower' ability
+		HEIGHT_LOW = 10,     // low lookout tower, but castle without lookout tower gives radius 5
+		HEIGHT_AVERAGE = 15,
+		HEIGHT_HIGH = 20,    // such tower is in the Tower town
+		HEIGHT_SKYSHIP = std::numeric_limits<int>::max()  // grail, open entire map
+	} height;
+
 	CBuilding();
 
 	const std::string &Name() const;
@@ -78,6 +88,17 @@ public:
 		h & requirements;
 		h & upgrade;
 		h & mode;
+
+		if(version >= 792)
+		{
+			h & subId;
+			h & height;
+		}
+		else if (!h.saving)
+		{
+			subId = BuildingSubID::NONE;
+			height = CBuilding::HEIGHT_NO_TOWER;
+		}
 		if(!h.saving)
 			deserializeFix();
 	}
@@ -330,6 +351,12 @@ class DLL_LINKAGE CTownHandler : public IHandlerBase
 	CFaction * loadFromJson(const JsonNode & data, const std::string & identifier);
 
 	void loadRandomFaction();
+
+	template<typename R>
+	R getMappedValue(const std::string key, const R defval, const std::map<std::string, R> & map, bool required = true) const;
+	template<typename R>
+	R getMappedValue(const JsonNode& node, const R defval, const std::map<std::string, R> & map, bool required = true) const;
+
 public:
 	std::vector<ConstTransitivePtr<CFaction> > factions;
 

+ 29 - 0
lib/GameConstants.h

@@ -414,6 +414,35 @@ public:
 	EBuildingID num;
 };
 
+
+namespace BuildingSubID
+{
+	enum EBuildingSubID
+	{
+		DEFAULT = -50,
+		NONE = -1,
+		STABLES,
+		BROTHERHOOD_OF_SWORD,
+		CASTLE_GATE,
+		CREATURE_TRANSFORMER,
+		MYSTIC_POND,
+		FOUNTAIN_OF_FORTUNE,
+		ARTIFACT_MERCHANT,
+		LOOKOUT_TOWER,
+		LIBRARY,
+		MANA_VORTEX,
+		PORTAL_OF_SUMMONING,
+		ESCAPE_TUNNEL,
+		FREELANCERS_GUILD,
+		BALLISTA_YARD,
+		HALL_OF_VALHALLA,
+		MAGIC_UNIVERSITY,
+		SPELL_POWER_GARRISON_BONUS,
+		ATTACK_GARRISON_BONUS,
+		DEFENSE_GARRISON_BONUS
+	};
+}
+
 ID_LIKE_OPERATORS(BuildingID, BuildingID::EBuildingID)
 
 namespace EAiTactic

+ 1 - 1
lib/IGameCallback.cpp

@@ -62,7 +62,7 @@ void CPrivilegedInfoCallback::getTilesInRange(std::unordered_set<int3, ShashInt3
 		logGlobal->error("Illegal call to getTilesInRange!");
 		return;
 	}
-	if (radious == -1) //reveal entire map
+	if(radious == CBuilding::HEIGHT_SKYSHIP) //reveal entire map
 		getAllTiles (tiles, player, -1, 0);
 	else
 	{

+ 2 - 2
lib/NetPacksBase.h

@@ -171,7 +171,7 @@ struct Component
 {
 	enum EComponentType {PRIM_SKILL, SEC_SKILL, RESOURCE, CREATURE, ARTIFACT, EXPERIENCE, SPELL, MORALE, LUCK, BUILDING, HERO_PORTRAIT, FLAG};
 	ui16 id, subtype; //id uses ^^^ enums, when id==EXPPERIENCE subtype==0 means exp points and subtype==1 levels)
-	si32 val; // + give; - take
+	si64 val; // + give; - take
 	si16 when; // 0 - now; +x - within x days; -x - per x days
 
 	template <typename Handler> void serialize(Handler &h, const int version)
@@ -186,7 +186,7 @@ struct Component
 	{
 	}
 	DLL_LINKAGE explicit Component(const CStackBasicDescriptor &stack);
-	Component(Component::EComponentType Type, ui16 Subtype, si32 Val, si16 When)
+	Component(Component::EComponentType Type, ui16 Subtype, si64 Val, si16 When)
 		:id(Type),subtype(Subtype),val(Val),when(When)
 	{
 	}

+ 1 - 1
lib/battle/CBattleInfoEssentials.cpp

@@ -279,7 +279,7 @@ bool CBattleInfoEssentials::battleCanFlee(PlayerColor player) const
 	if(side.get() == BattleSide::DEFENDER && battleGetSiegeLevel())
 	{
 		auto town = battleGetDefendedTown();
-		if(!town->hasBuilt(BuildingID::ESCAPE_TUNNEL, ETownType::STRONGHOLD))
+		if(!town->hasBuilt(BuildingSubID::ESCAPE_TUNNEL))
 			return false;
 	}
 

+ 132 - 59
lib/mapObjects/CGTownInstance.cpp

@@ -437,14 +437,22 @@ void CGDwelling::serializeJsonOptions(JsonSerializeFormat & handler)
 
 int CGTownInstance::getSightRadius() const //returns sight distance
 {
-	if (subID == ETownType::TOWER)
+	auto ret = CBuilding::HEIGHT_NO_TOWER;
+
+	for(const auto & bid : builtBuildings)
 	{
-		if (hasBuilt(BuildingID::GRAIL)) //skyship
-			return -1; //entire map
-		if (hasBuilt(BuildingID::LOOKOUT_TOWER)) //lookout tower
-			return 20;
+		if(bid == BuildingID::SPECIAL_1
+			|| bid == BuildingID::SPECIAL_2
+			|| bid == BuildingID::SPECIAL_3
+			|| bid == BuildingID::SPECIAL_4
+			|| bid == BuildingID::GRAIL)
+		{
+			auto height = town->buildings.at(bid)->height;
+			if(ret < height)
+				ret = height;
+		}
 	}
-	return 5;
+	return ret;
 }
 
 void CGTownInstance::setPropertyDer(ui8 what, ui32 val)
@@ -635,7 +643,7 @@ int CGTownInstance::spellsAtLevel(int level, bool checkGuild) const
 		return 0;
 	int ret = 6 - level; //how many spells are available at this level
 
-	if (hasBuilt(BuildingID::LIBRARY, ETownType::TOWER))
+	if (hasBuilt(BuildingSubID::LIBRARY))
 		ret++;
 
 	return ret;
@@ -733,15 +741,26 @@ std::string CGTownInstance::getObjectName() const
 	return name + ", " + town->faction->name;
 }
 
+bool CGTownInstance::townEnvisagesSpecialBuilding(BuildingSubID::EBuildingSubID bid) const
+{
+	for(const auto & it : town->buildings)
+	{
+		if(it.second->subId == bid)
+			return true;
+	}
+	return false;
+}
+
 void CGTownInstance::initObj(CRandomGenerator & rand)
 ///initialize town structures
 {
 	blockVisit = true;
 
-	if (subID == ETownType::DUNGEON)
-		creatures.resize(GameConstants::CREATURES_PER_TOWN+1);//extra dwelling for Dungeon
+	if(townEnvisagesSpecialBuilding(BuildingSubID::PORTAL_OF_SUMMONING)) //Dungeon for example
+		creatures.resize(GameConstants::CREATURES_PER_TOWN+1);
 	else
 		creatures.resize(GameConstants::CREATURES_PER_TOWN);
+
 	for (int level = 0; level < GameConstants::CREATURES_PER_TOWN; level++)
 	{
 		BuildingID buildID = BuildingID(BuildingID::DWELL_FIRST).advance(level);
@@ -753,16 +772,19 @@ void CGTownInstance::initObj(CRandomGenerator & rand)
 				creatures[level].second.push_back(town->creatures[level][upgradeNum]);
 		}
 	}
+	if(townEnvisagesSpecialBuilding(BuildingSubID::STABLES))
+		bonusingBuildings.push_back(new COPWBonus(BuildingID::STABLES, BuildingSubID::STABLES, this));
+
+	if(townEnvisagesSpecialBuilding(BuildingSubID::MANA_VORTEX))
+		bonusingBuildings.push_back(new COPWBonus(BuildingID::MANA_VORTEX, BuildingSubID::MANA_VORTEX, this));
 
 	switch (subID)
-	{ //add new visitable objects
-		case ETownType::CASTLE:
-			bonusingBuildings.push_back (new COPWBonus(BuildingID::STABLES, this));
-			break;
+	{
+		//add new visitable objects
 		case ETownType::DUNGEON:
-			bonusingBuildings.push_back (new COPWBonus(BuildingID::MANA_VORTEX, this));
-			FALLTHROUGH
-		case ETownType::TOWER: case ETownType::INFERNO: case ETownType::STRONGHOLD:
+		case ETownType::TOWER: 
+		case ETownType::INFERNO: 
+		case ETownType::STRONGHOLD:
 			bonusingBuildings.push_back (new CTownBonus(BuildingID::SPECIAL_4, this));
 			break;
 		case ETownType::FORTRESS:
@@ -779,9 +801,11 @@ void CGTownInstance::newTurn(CRandomGenerator & rand) const
 {
 	if (cb->getDate(Date::DAY_OF_WEEK) == 1) //reset on new week
 	{
-		//give resources for Rampart, Mystic Pond
-		if (hasBuilt(BuildingID::MYSTIC_POND, ETownType::RAMPART)
-			&& cb->getDate(Date::DAY) != 1 && (tempOwner < PlayerColor::PLAYER_LIMIT))
+		//give resources if there's a Mystic Pond
+		if (hasBuilt(BuildingSubID::MYSTIC_POND)
+			&& cb->getDate(Date::DAY) != 1 
+			&& (tempOwner < PlayerColor::PLAYER_LIMIT)
+			)
 		{
 			int resID = rand.nextInt(2, 5); //bonus to random rare resource
 			resID = (resID==2)?1:resID;
@@ -791,12 +815,10 @@ void CGTownInstance::newTurn(CRandomGenerator & rand) const
 			cb->setObjProperty (id, ObjProperty::BONUS_VALUE_SECOND, resVal);
 		}
 
-		if ( subID == ETownType::DUNGEON )
-			for (auto & elem : bonusingBuildings)
-		{
-			if ((elem)->ID == BuildingID::MANA_VORTEX)
-				cb->setObjProperty (id, ObjProperty::STRUCTURE_CLEAR_VISITORS, (elem)->id); //reset visitors for Mana Vortex
-		}
+		auto manaVortex = getBonusingBuilding(BuildingSubID::MANA_VORTEX);
+
+		if (manaVortex != nullptr)
+			cb->setObjProperty(id, ObjProperty::STRUCTURE_CLEAR_VISITORS, manaVortex->indexOnTV); //reset visitors for Mana Vortex
 
 		//get Mana Vortex or Stables bonuses
 		//same code is in the CGameHandler::buildStructure method
@@ -882,7 +904,7 @@ void CGTownInstance::getOutOffsets( std::vector<int3> &offsets ) const
 
 void CGTownInstance::mergeGarrisonOnSiege() const
 {
-	auto getWeakestStackSlot = [&](int powerLimit)
+	auto getWeakestStackSlot = [&](ui64 powerLimit)
 	{
 		std::vector<SlotID> weakSlots;
 		auto stacksList = visitingHero->stacks;
@@ -909,7 +931,8 @@ void CGTownInstance::mergeGarrisonOnSiege() const
 		return SlotID();
 	};
 
-	int count = static_cast<int>(stacks.size());
+	auto count = static_cast<int>(stacks.size());
+
 	for(int i = 0; i < count; i++)
 	{
 		auto pair = *vstd::maxElementByFun(stacks, [&](std::pair<SlotID, CStackInstance *> elem)
@@ -1099,9 +1122,14 @@ void CGTownInstance::recreateBuildingsBonuses()
 		removeBonus(b);
 
 	//tricky! -> checks tavern only if no bratherhood of sword or not a castle
-	if(subID != ETownType::CASTLE || !addBonusIfBuilt(BuildingID::BROTHERHOOD, Bonus::MORALE, +2))
+	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
+
 	if(subID == ETownType::CASTLE) //castle
 	{
 		addBonusIfBuilt(BuildingID::LIGHTHOUSE, Bonus::SEA_MOVEMENT, +500, playerProp);
@@ -1109,17 +1137,12 @@ void CGTownInstance::recreateBuildingsBonuses()
 	}
 	else if(subID == ETownType::RAMPART) //rampart
 	{
-		addBonusIfBuilt(BuildingID::FOUNTAIN_OF_FORTUNE, Bonus::LUCK, +2); //fountain of fortune
 		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::INFERNO) //Inferno
-	{
-		addBonusIfBuilt(BuildingID::STORMCLOUDS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER); //Brimstone Clouds
-	}
 	else if(subID == ETownType::NECROPOLIS) //necropolis
 	{
 		addBonusIfBuilt(BuildingID::COVER_OF_DARKNESS,    Bonus::DARKNESS, +20);
@@ -1136,15 +1159,32 @@ void CGTownInstance::recreateBuildingsBonuses()
 	}
 	else if(subID == ETownType::FORTRESS) //Fortress
 	{
-		addBonusIfBuilt(BuildingID::GLYPHS_OF_FEAR, Bonus::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE); //Glyphs of Fear
-		addBonusIfBuilt(BuildingID::BLOOD_OBELISK,  Bonus::PRIMARY_SKILL, +2, PrimarySkill::ATTACK); //Blood Obelisk
 		addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::ATTACK); //grail
 		addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::DEFENSE); //grail
 	}
-	else if(subID == ETownType::CONFLUX)
+}
+
+bool CGTownInstance::addBonusIfBuilt(BuildingSubID::EBuildingSubID building, Bonus::BonusType type, int val, int subtype)
+{
+	bool ret = false;
+
+	if (hasBuilt(building))
 	{
+		std::ostringstream descr;
 
+		for (const auto & it : town->buildings)
+		{
+			if (it.second->subId == building)
+			{
+				descr << it.second->Name();
+				break;
+			}
+		}
+		auto b = std::make_shared<Bonus>(Bonus::PERMANENT, type, Bonus::TOWN_STRUCTURE, val, building, descr.str(), subtype);
+		addNewBonus(b); //looks like a propagator is not necessary in this case
+		ret = true;
 	}
+	return ret;
 }
 
 bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype)
@@ -1248,10 +1288,14 @@ const CTown * CGTownInstance::getTown() const
 int CGTownInstance::getTownLevel() const
 {
 	// count all buildings that are not upgrades
-	return (int)boost::range::count_if(builtBuildings, [&](const BuildingID & build)
+	int level = 0;
+
+	for (const auto & bid : builtBuildings)
 	{
-		return town->buildings.at(build) && town->buildings.at(build)->upgrade == -1;
-	});
+		if(town->buildings.at(bid)->upgrade == BuildingID::NONE)
+			level++;
+	}
+	return level;
 }
 
 CBonusSystemNode * CGTownInstance::whatShouldBeAttached()
@@ -1266,6 +1310,31 @@ const CArmedInstance * CGTownInstance::getUpperArmy() const
 	return this;
 }
 
+const CGTownBuilding * CGTownInstance::getBonusingBuilding(BuildingSubID::EBuildingSubID subId) const
+{
+	for(const auto building : bonusingBuildings)
+	{
+		if(building->getBuildingSubtype() == subId)
+			return building;
+	}
+	return nullptr;
+}
+
+bool CGTownInstance::hasBuilt(BuildingSubID::EBuildingSubID buildingID) const
+{
+	for(const auto & bid : builtBuildings)
+	{
+		if(town->buildings.at(bid)->subId == buildingID)
+			return true;
+	}
+	return false;
+}
+
+bool CGTownInstance::hasBuilt(BuildingID buildingID) const
+{
+	return vstd::contains(builtBuildings, buildingID);
+}
+
 bool CGTownInstance::hasBuilt(BuildingID buildingID, int townID) const
 {
 	if (townID == town->faction->index || townID == ETownType::ANY)
@@ -1285,11 +1354,6 @@ TResources CGTownInstance::getBuildingCost(BuildingID buildingID) const
 
 }
 
-bool CGTownInstance::hasBuilt(BuildingID buildingID) const
-{
-	return vstd::contains(builtBuildings, buildingID);
-}
-
 CBuilding::TRequired CGTownInstance::genBuildingRequirements(BuildingID buildID, bool deep) const
 {
 	const CBuilding * building = town->buildings.at(buildID);
@@ -1338,7 +1402,7 @@ CBuilding::TRequired CGTownInstance::genBuildingRequirements(BuildingID buildID,
 	return ret;
 }
 
-void CGTownInstance::addHeroToStructureVisitors( const CGHeroInstance *h, si32 structureInstanceID ) const
+void CGTownInstance::addHeroToStructureVisitors( const CGHeroInstance *h, si64 structureInstanceID ) const
 {
 	if(visitingHero == h)
 		cb->setObjProperty(id, ObjProperty::STRUCTURE_ADD_VISITING_HERO, structureInstanceID); //add to visitors
@@ -1498,12 +1562,14 @@ void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler)
 	}
 }
 
-COPWBonus::COPWBonus (BuildingID index, CGTownInstance *TOWN)
+COPWBonus::COPWBonus (BuildingID bid, BuildingSubID::EBuildingSubID subId, CGTownInstance *TOWN)
 {
-	ID = index;
+	bID = bid;
+	bType = subId;
 	town = TOWN;
-	id = static_cast<si32>(town->bonusingBuildings.size());
+	indexOnTV = static_cast<si32>(town->bonusingBuildings.size());
 }
+
 void COPWBonus::setProperty(ui8 what, ui32 val)
 {
 	switch (what)
@@ -1516,16 +1582,18 @@ void COPWBonus::setProperty(ui8 what, ui32 val)
 			break;
 	}
 }
+
 void COPWBonus::onHeroVisit (const CGHeroInstance * h) const
 {
 	ObjectInstanceID heroID = h->id;
-	if (town->hasBuilt(ID))
+	if (town->hasBuilt(bID))
 	{
 		InfoWindow iw;
 		iw.player = h->tempOwner;
-		switch (town->subID)
+
+		switch (this->bType)
 		{
-			case ETownType::CASTLE: //Stables
+		case BuildingSubID::STABLES:
 				if (!h->hasBonusFrom(Bonus::OBJECT, Obj::STABLES)) //does not stack with advMap Stables
 				{
 					GiveBonus gb;
@@ -1543,7 +1611,8 @@ void COPWBonus::onHeroVisit (const CGHeroInstance * h) const
 					cb->showInfoDialog(&iw);
 				}
 				break;
-			case ETownType::DUNGEON: //Mana Vortex
+
+		case BuildingSubID::MANA_VORTEX:
 				if (visitors.empty())
 				{
 					if (h->mana < h->manaLimit() * 2)
@@ -1553,7 +1622,7 @@ void COPWBonus::onHeroVisit (const CGHeroInstance * h) const
 					iw.text << VLC->generaltexth->allTexts[579];
 					cb->showInfoDialog(&iw);
 					//extra visit penalty if hero alredy had double mana points (or even more?!)
-					town->addHeroToStructureVisitors(h, id);
+					town->addHeroToStructureVisitors(h, indexOnTV);
 				}
 				break;
 		}
@@ -1561,24 +1630,28 @@ void COPWBonus::onHeroVisit (const CGHeroInstance * h) const
 }
 CTownBonus::CTownBonus (BuildingID index, CGTownInstance *TOWN)
 {
-	ID = index;
+	bID = index;
 	town = TOWN;
-	id = static_cast<si32>(town->bonusingBuildings.size());
+	indexOnTV = static_cast<si32>(town->bonusingBuildings.size());
 }
+
 void CTownBonus::setProperty (ui8 what, ui32 val)
 {
 	if(what == ObjProperty::VISITORS)
 		visitors.insert(ObjectInstanceID(val));
 }
+
 void CTownBonus::onHeroVisit (const CGHeroInstance * h) const
 {
 	ObjectInstanceID heroID = h->id;
-	if (town->hasBuilt(ID) && visitors.find(heroID) == visitors.end())
+	if (town->hasBuilt(bID) && visitors.find(heroID) == visitors.end())
 	{
+		si32 mid=0;
+		si64 val = 0;
 		InfoWindow iw;
 		PrimarySkill::PrimarySkill what = PrimarySkill::ATTACK;
-		int val=0, mid=0;
-		switch (ID)
+
+		switch (bID)
 		{
 			case BuildingID::SPECIAL_4:
 				switch(town->subID)
@@ -1626,7 +1699,7 @@ void CTownBonus::onHeroVisit (const CGHeroInstance * h) const
 		iw.text << VLC->generaltexth->allTexts[mid];
 		cb->showInfoDialog(&iw);
 		cb->changePrimSkill (cb->getHero(heroID), what, val);
-		town->addHeroToStructureVisitors(h, id);
+		town->addHeroToStructureVisitors(h, indexOnTV);
 	}
 }
 

+ 31 - 8
lib/mapObjects/CGTownInstance.h

@@ -97,16 +97,31 @@ class DLL_LINKAGE CGTownBuilding : public IObjectInterface
 {
 ///basic class for town structures handled as map objects
 public:
-	BuildingID ID; //from buildig list
-	si32 id; //identifies its index on towns vector
+	si32 indexOnTV; //identifies its index on towns vector
 	CGTownInstance *town;
+	CGTownBuilding() : bType(BuildingSubID::NONE), indexOnTV(0), town(nullptr) {};
+
+	STRONG_INLINE
+	BuildingSubID::EBuildingSubID getBuildingSubtype() const
+	{
+		return bType;
+	}
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & ID;
-		h & id;
+		h & bID;
+		h & indexOnTV;
+
+		if(version >= 792)
+			h & bType;
+		else if(!h.saving)
+			bType = BuildingSubID::NONE;
 	}
+protected:
+	BuildingID bID; //from buildig list
+	BuildingSubID::EBuildingSubID bType;
 };
+
 class DLL_LINKAGE COPWBonus : public CGTownBuilding
 {///used for OPW bonusing structures
 public:
@@ -114,8 +129,9 @@ public:
 	void setProperty(ui8 what, ui32 val) override;
 	void onHeroVisit (const CGHeroInstance * h) const override;
 
-	COPWBonus (BuildingID index, CGTownInstance *TOWN);
-	COPWBonus (){ID = BuildingID::NONE; town = nullptr;};
+	COPWBonus (BuildingID index, BuildingSubID::EBuildingSubID subId, CGTownInstance *TOWN);
+	COPWBonus () {};
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & static_cast<CGTownBuilding&>(*this);
@@ -133,7 +149,8 @@ public:
 	void onHeroVisit (const CGHeroInstance * h) const override;
 
 	CTownBonus (BuildingID index, CGTownInstance *TOWN);
-	CTownBonus (){ID = BuildingID::NONE; town = nullptr;};
+	CTownBonus () {};
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & static_cast<CGTownBuilding&>(*this);
@@ -231,6 +248,7 @@ public:
 	void updateMoraleBonusFromArmy() override;
 	void deserializationFix();
 	void recreateBuildingsBonuses();
+	bool addBonusIfBuilt(BuildingSubID::EBuildingSubID building, 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);
@@ -262,9 +280,13 @@ public:
 	GrowthInfo getGrowthInfo(int level) const;
 	bool hasFort() const;
 	bool hasCapitol() const;
+	const CGTownBuilding * getBonusingBuilding(BuildingSubID::EBuildingSubID subId) const;
+	//checks if special building with type buildingID is constructed
+	bool hasBuilt(BuildingSubID::EBuildingSubID buildingID) const;
 	//checks if building is constructed and town has same subID
 	bool hasBuilt(BuildingID buildingID) const;
 	bool hasBuilt(BuildingID buildingID, int townID) const;
+
 	TResources getBuildingCost(BuildingID buildingID) const;
 	TResources dailyIncome() const; //calculates daily income of this town
 	int spellsAtLevel(int level, bool checkGuild) const; //levels are counted from 1 (1 - 5)
@@ -276,7 +298,8 @@ public:
 	void mergeGarrisonOnSiege() const; // merge garrison into army of visiting hero
 	void removeCapitols (PlayerColor owner) const;
 	void clearArmy() const;
-	void addHeroToStructureVisitors(const CGHeroInstance *h, si32 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
+	bool townEnvisagesSpecialBuilding(BuildingSubID::EBuildingSubID bid) const;
 
 	const CTown * getTown() const ;
 

+ 1 - 1
lib/serializer/CSerializer.h

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

+ 13 - 7
server/CGameHandler.cpp

@@ -2361,9 +2361,14 @@ bool CGameHandler::teleportHero(ObjectInstanceID hid, ObjectInstanceID dstid, ui
 	const CGTownInstance *from = h->visitedTown;
 	if (((h->getOwner() != t->getOwner())
 		&& complain("Cannot teleport hero to another player"))
-	|| ((!from || !from->hasBuilt(BuildingID::CASTLE_GATE, ETownType::INFERNO))
+
+	|| (from->town->faction->index != t->town->faction->index
+		&& complain("Source town and destination town should belong to the same faction"))
+
+	|| ((!from || !from->hasBuilt(BuildingSubID::CASTLE_GATE))
 		&& complain("Hero must be in town with Castle gate for teleporting"))
-	|| (!t->hasBuilt(BuildingID::CASTLE_GATE, ETownType::INFERNO)
+
+	|| (!t->hasBuilt(BuildingSubID::CASTLE_GATE)
 		&& complain("Cannot teleport hero to town without Castle gate in it")))
 			return false;
 	int3 pos = t->visitablePos();
@@ -2511,7 +2516,6 @@ void CGameHandler::heroVisitCastle(const CGTownInstance * obj, const CGHeroInsta
 	sendAndApply(&vc);
 	visitCastleObjects(obj, hero);
 	giveSpells(obj, hero);
-
 	checkVictoryLossConditionsForPlayer(hero->tempOwner); //transported artifact?
 }
 
@@ -3082,9 +3086,11 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
 	//Performs stuff that has to be done after new building is built
 	auto processAfterBuiltStructure = [t, this](const BuildingID buildingID)
 	{
-		if (buildingID <= BuildingID::MAGES_GUILD_5 || //it's mage guild
-			(t->subID == ETownType::TOWER && buildingID == BuildingID::LIBRARY) ||
-			(t->subID == ETownType::CONFLUX && buildingID == BuildingID::GRAIL))
+		auto isMageGuild = (buildingID <= BuildingID::MAGES_GUILD_5 && buildingID >= BuildingID::MAGES_GUILD_1);
+		auto isLibrary = isMageGuild ? false 
+			: t->town->buildings.at(buildingID)->subId == BuildingSubID::EBuildingSubID::LIBRARY;
+
+		if(isMageGuild || isLibrary || (t->subID == ETownType::CONFLUX && buildingID == BuildingID::GRAIL))
 		{
 			if (t->visitingHero)
 				giveSpells(t,t->visitingHero);
@@ -3830,7 +3836,7 @@ bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, PlayerColor pl
 	if (t)
 	{
 		visitCastleObjects(t, nh);
-		giveSpells (t,nh);
+		giveSpells(t,nh);
 	}
 	return true;
 }