diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 4ae7f54a7..9a475c6f3 100644 --- a/client/windows/CCastleInterface.cpp +++ b/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(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(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(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(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); } diff --git a/client/windows/CCastleInterface.h b/client/windows/CCastleInterface.h index 3ddbe485f..303af627c 100644 --- a/client/windows/CCastleInterface.h +++ b/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!!! }; diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index bf43055cf..08c4f38af 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1781,9 +1781,6 @@ void CObjectListWindow::init(std::shared_ptr titleWidget_, std::stri title = std::make_shared(152, 27, FONT_BIG, CENTER, Colors::YELLOW, _title); descr = std::make_shared(145, 133, FONT_SMALL, CENTER, Colors::WHITE, _descr); - - ok = std::make_shared(Point(15, 402), "IOKAY.DEF", CButton::tooltip(), std::bind(&CObjectListWindow::elementSelected, this), SDLK_RETURN); - ok->block(true); exit = std::make_shared( 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 titleWidget_, std::stri list = std::make_shared(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(Point(15, 402), "IOKAY.DEF", CButton::tooltip(), std::bind(&CObjectListWindow::elementSelected, this), SDLK_RETURN); + ok->block(!list->size()); } std::shared_ptr CObjectListWindow::genItem(size_t index) diff --git a/config/factions/castle.json b/config/factions/castle.json index 87c062b78..2c06e2a96 100644 --- a/config/factions/castle.json +++ b/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" ] }, diff --git a/config/factions/conflux.json b/config/factions/conflux.json index 4d60f237d..e63a770ad 100644 --- a/config/factions/conflux.json +++ b/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" }, diff --git a/config/factions/dungeon.json b/config/factions/dungeon.json index cb57ab9c6..551dc2d22 100644 --- a/config/factions/dungeon.json +++ b/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" ] }, diff --git a/config/factions/fortress.json b/config/factions/fortress.json index 931da6c68..3b47d2d01 100644 --- a/config/factions/fortress.json +++ b/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" }, diff --git a/config/factions/inferno.json b/config/factions/inferno.json index 9b517e52b..1ea70c8a5 100644 --- a/config/factions/inferno.json +++ b/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 }}, diff --git a/config/factions/necropolis.json b/config/factions/necropolis.json index 4d3350cff..0c7963741 100644 --- a/config/factions/necropolis.json +++ b/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" }, diff --git a/config/factions/rampart.json b/config/factions/rampart.json index 3738f4103..680498dc8 100644 --- a/config/factions/rampart.json +++ b/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 }}, diff --git a/config/factions/stronghold.json b/config/factions/stronghold.json index afa21d5c7..67ce19204 100644 --- a/config/factions/stronghold.json +++ b/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" ] }, diff --git a/config/factions/tower.json b/config/factions/tower.json index 760514635..c5a65ddc9 100644 --- a/config/factions/tower.json +++ b/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" ] }, diff --git a/config/schemas/townBuilding.json b/config/schemas/townBuilding.json index 9f6d043b4..cf3976cc0 100644 --- a/config/schemas/townBuilding.json +++ b/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" ], diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index e62daa417..b0d0a1b11 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -343,28 +343,98 @@ void CTownHandler::loadBuildingRequirements(CBuilding * building, const JsonNode requirementsToLoad.push_back(hlp); } +template +R CTownHandler::getMappedValue(const std::string key, const R defval, const std::map & 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 +R CTownHandler::getMappedValue(const JsonNode & node, const R defval, const std::map & map, bool required) const +{ + if(!node.isNull() && node.getType() == JsonNode::JsonType::DATA_STRING) + return getMappedValue(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::vector MODES = + static const std::map MODES = { - "normal", "auto", "special", "grail" + { "normal", CBuilding::BUILD_NORMAL }, + { "auto", CBuilding::BUILD_AUTO }, + { "special", CBuilding::BUILD_SPECIAL }, + { "grail", CBuilding::BUILD_GRAIL } }; - ret->mode = CBuilding::BUILD_NORMAL; + static const std::map BUILDING_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(rawMode); - } - } + { "special1", BuildingID::SPECIAL_1 }, + { "special2", BuildingID::SPECIAL_2 }, + { "special3", BuildingID::SPECIAL_3 }, + { "special4", BuildingID::SPECIAL_4 }, + { "grail", BuildingID::GRAIL } + }; + + static const std::map LOOKOUT_TYPES = + { + { "low", CBuilding::HEIGHT_LOW }, + { "average", CBuilding::HEIGHT_AVERAGE }, + { "high", CBuilding::HEIGHT_HIGH }, + { "skyship", CBuilding::HEIGHT_SKYSHIP } + }; + + static const std::map 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(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(source["mode"], CBuilding::BUILD_NORMAL, MODES); + + ret->subId = getMappedValue(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(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"]); diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index 435b8a5a5..3f9d9af91 100644 --- a/lib/CTownHandler.h +++ b/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::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 + R getMappedValue(const std::string key, const R defval, const std::map & map, bool required = true) const; + template + R getMappedValue(const JsonNode& node, const R defval, const std::map & map, bool required = true) const; + public: std::vector > factions; diff --git a/lib/GameConstants.h b/lib/GameConstants.h index f2d80904d..36a1a7fc2 100644 --- a/lib/GameConstants.h +++ b/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 diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index 11e688b88..25aa67a27 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -62,7 +62,7 @@ void CPrivilegedInfoCallback::getTilesInRange(std::unordered_seterror("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 { diff --git a/lib/NetPacksBase.h b/lib/NetPacksBase.h index 1404fc3f8..16d062f75 100644 --- a/lib/NetPacksBase.h +++ b/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 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) { } diff --git a/lib/battle/CBattleInfoEssentials.cpp b/lib/battle/CBattleInfoEssentials.cpp index 366d19bca..31ca1072b 100644 --- a/lib/battle/CBattleInfoEssentials.cpp +++ b/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; } diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index cb607edcf..a1bc816ec 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/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 &offsets ) const void CGTownInstance::mergeGarrisonOnSiege() const { - auto getWeakestStackSlot = [&](int powerLimit) + auto getWeakestStackSlot = [&](ui64 powerLimit) { std::vector weakSlots; auto stacksList = visitingHero->stacks; @@ -909,7 +931,8 @@ void CGTownInstance::mergeGarrisonOnSiege() const return SlotID(); }; - int count = static_cast(stacks.size()); + auto count = static_cast(stacks.size()); + for(int i = 0; i < count; i++) { auto pair = *vstd::maxElementByFun(stacks, [&](std::pair 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::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(town->bonusingBuildings.size()); + indexOnTV = static_cast(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(town->bonusingBuildings.size()); + indexOnTV = static_cast(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); } } diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 4b5f5801d..c44fcb7bf 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/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 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 void serialize(Handler &h, const int version) { h & static_cast(*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 void serialize(Handler &h, const int version) { h & static_cast(*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 ; diff --git a/lib/serializer/CSerializer.h b/lib/serializer/CSerializer.h index 9ff22e957..6750e2ed8 100644 --- a/lib/serializer/CSerializer.h +++ b/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"; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 1ce17e4ba..2cd6f2126 100644 --- a/server/CGameHandler.cpp +++ b/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; }