From 854a2e6c39ab4c5c5b13a28fec8f27a3a1033148 Mon Sep 17 00:00:00 2001 From: Dmitry Orlov Date: Thu, 14 Jan 2021 01:02:13 +0300 Subject: [PATCH] Feature: Mods system improvement, Part III. Bunusing buildings customization. --- client/lobby/OptionsTab.cpp | 2 +- config/factions/castle.json | 2 +- config/factions/dungeon.json | 3 +- config/factions/fortress.json | 9 +- config/factions/necropolis.json | 9 +- config/factions/rampart.json | 3 +- config/factions/stronghold.json | 3 +- config/factions/tower.json | 2 +- config/schemas/townBuilding.json | 20 ++- config/translate.json | 4 +- lib/CCreatureHandler.cpp | 5 + lib/CCreatureHandler.h | 1 + lib/CTownHandler.cpp | 268 ++++++++++++++++++++++++++---- lib/CTownHandler.h | 40 ++++- lib/GameConstants.h | 5 +- lib/HeroBonus.cpp | 28 +++- lib/HeroBonus.h | 60 ++++--- lib/JsonNode.cpp | 20 ++- lib/JsonNode.h | 2 + lib/NetPacksLib.cpp | 28 +++- lib/VCMI_Lib.cpp | 5 + lib/VCMI_Lib.h | 4 + lib/mapObjects/CGTownInstance.cpp | 252 ++++++++++++++++------------ lib/mapObjects/CGTownInstance.h | 27 +-- lib/mapObjects/CObjectHandler.cpp | 2 +- lib/serializer/CSerializer.h | 2 +- server/CGameHandler.cpp | 13 +- 27 files changed, 596 insertions(+), 223 deletions(-) diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index 44e093870..1c0de4ce6 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -88,7 +88,7 @@ size_t OptionsTab::CPlayerSettingsHelper::getImageIndex() switch(type) { case TOWN: - switch (settings.castle) + switch(settings.castle) { case PlayerSettings::NONE: return TOWN_NONE; diff --git a/config/factions/castle.json b/config/factions/castle.json index 2846904b0..33a0006d8 100644 --- a/config/factions/castle.json +++ b/config/factions/castle.json @@ -176,7 +176,7 @@ "ship": { "id" : 20, "upgrades" : "shipyard" }, "special2": { "type" : "stables", "requires" : [ "dwellingLvl4" ] }, "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" ] }, "dwellingLvl2": { "id" : 31, "requires" : [ "dwellingLvl1" ] }, diff --git a/config/factions/dungeon.json b/config/factions/dungeon.json index 624f42918..6e9235a3c 100644 --- a/config/factions/dungeon.json +++ b/config/factions/dungeon.json @@ -175,7 +175,8 @@ "special2": { "type" : "manaVortex", "requires" : [ "mageGuild1" ] }, "special3": { "type" : "portalOfSummoning" }, "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" ] }, "dwellingLvl2": { "id" : 31, "requires" : [ "dwellingLvl1" ] }, diff --git a/config/factions/fortress.json b/config/factions/fortress.json index 50e6d0a54..cdfd9fb8f 100644 --- a/config/factions/fortress.json +++ b/config/factions/fortress.json @@ -175,9 +175,14 @@ "ship": { "id" : 20, "upgrades" : "shipyard" }, "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" }, + "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" ] }, "dwellingLvl2": { "id" : 31, "requires" : [ "dwellingLvl1" ] }, "dwellingLvl3": { "id" : 32, "requires" : [ "dwellingLvl1" ] }, diff --git a/config/factions/necropolis.json b/config/factions/necropolis.json index 0c7963741..05b2cf291 100644 --- a/config/factions/necropolis.json +++ b/config/factions/necropolis.json @@ -175,13 +175,16 @@ "resourceSilo": { "id" : 15, "requires" : [ "marketplace" ], "produce": { "ore": 1, "wood": 1 } }, "blacksmith": { "id" : 16 }, - "special1": { "requires" : [ "fort" ] }, + "special1": { "requires" : [ "fort" ], "bonuses": [ { "type": "DARKNESS", "val": 20 } ] }, "horde1": { "id" : 18, "upgrades" : "dwellingLvl1", "requires" : [ "special3" ] }, "horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" }, "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" ] }, - "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" }, "extraCityHall": { "id" : 28, "requires" : [ "cityHall" ], "mode" : "auto" }, "extraCapitol": { "id" : 29, "requires" : [ "capitol" ], "mode" : "auto" }, diff --git a/config/factions/rampart.json b/config/factions/rampart.json index 796969b3a..c25deaec6 100644 --- a/config/factions/rampart.json +++ b/config/factions/rampart.json @@ -181,7 +181,8 @@ "special3": { "type" : "treasury", "requires" : [ "horde1" ] }, "horde2": { "id" : 24, "upgrades" : "dwellingLvl5" }, "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" }, "extraCityHall": { "id" : 28, "requires" : [ "cityHall" ], "mode" : "auto" }, "extraCapitol": { "id" : 29, "requires" : [ "capitol" ], "mode" : "auto" }, diff --git a/config/factions/stronghold.json b/config/factions/stronghold.json index 74f981fbc..d4c7fba71 100644 --- a/config/factions/stronghold.json +++ b/config/factions/stronghold.json @@ -172,7 +172,8 @@ "special2": { "type" : "freelancersGuild", "requires" : [ "marketplace" ] }, "special3": { "type" : "ballistaYard", "requires" : [ "blacksmith" ] }, "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" ] }, "dwellingLvl2": { "id" : 31, "requires" : [ "dwellingLvl1" ] }, diff --git a/config/factions/tower.json b/config/factions/tower.json index 0f2e167fd..89e93b34a 100644 --- a/config/factions/tower.json +++ b/config/factions/tower.json @@ -175,7 +175,7 @@ "special2": { "type" : "lookoutTower", "height" : "high", "requires" : [ "fort" ] }, "special3": { "type" : "library", "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" ] }, "dwellingLvl2": { "id" : 31, "requires" : [ "dwellingLvl1" ] }, diff --git a/config/schemas/townBuilding.json b/config/schemas/townBuilding.json index cf3976cc0..4ad0a1aa5 100644 --- a/config/schemas/townBuilding.json +++ b/config/schemas/townBuilding.json @@ -85,7 +85,25 @@ "gems": { "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" } } - } } diff --git a/config/translate.json b/config/translate.json index cdb5d4809..ecbb0e044 100644 --- a/config/translate.json +++ b/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).", "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.", - "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" : { diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 9180e9efa..3b4c330d0 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -1210,6 +1210,11 @@ void CCreatureHandler::removeBonusesFromAllCreatures() allCreatures.removeBonuses(Selector::all); } +void CCreatureHandler::restoreAllCreaturesNodeType794() +{ + allCreatures.setNodeType(CBonusSystemNode::ENodeTypes::ALL_CREATURES); +} + void CCreatureHandler::buildBonusTreeForTiers() { for(CCreature *c : creatures) diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index dd8fd3e6d..68328c1ab 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -249,6 +249,7 @@ public: void addBonusForTier(int tier, std::shared_ptr b); //tier must be <1-7> void addBonusForAllCreatures(std::shared_ptr b); void removeBonusesFromAllCreatures(); + void restoreAllCreaturesNodeType794(); //restore ALL_CREATURES node type for old saves CCreatureHandler(); ~CCreatureHandler(); diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 526504e7e..0ff7869cd 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -22,6 +22,7 @@ #include "filesystem/Filesystem.h" #include "mapObjects/CObjectClassesHandler.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 @@ -93,7 +94,37 @@ void CBuilding::deserializeFix() } } -void CBuilding::update792(const BuildingID & bid, BuildingSubID::EBuildingSubID & subId, ETowerHeight & height) +void CBuilding::addNewBonus(std::shared_ptr b, BonusList & bonusList) +{ + bonusList.push_back(b); +} + +const JsonNode & CBuilding::getCurrentFactionForUpdateRoutine() const +{ + const auto & faction = town->faction->identifier; + 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()) + { + const auto index = faction.find(':'); + const std::string factionDir = index == std::string::npos ? faction : faction.substr(0, index); + const auto it = factionsContent.modData.find(factionDir); + + if(it == factionsContent.modData.end()) + { + logMod->warn("Warning: Update old save failed: Faction: '%s' is not found.", factionDir); + return currentFaction; + } + const std::string modFaction = index == std::string::npos ? faction : faction.substr(index + 1); + return it->second.modData[modFaction]; + } + return currentFaction; +} + +void CBuilding::update792() { subId = BuildingSubID::NONE; height = ETowerHeight::HEIGHT_NO_TOWER; @@ -106,37 +137,72 @@ void CBuilding::update792(const BuildingID & bid, BuildingSubID::EBuildingSubID if(buildingName.empty()) return; - 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]; + auto & currentFaction = getCurrentFactionForUpdateRoutine(); - if (currentFaction.isNull()) - { - const auto index = faction.find(':'); - const std::string factionDir = index == std::string::npos ? faction : faction.substr(0, index); - const auto it = factionsContent.modData.find(factionDir); - - if (it == factionsContent.modData.end()) - { - logMod->warn("Warning: Update old save failed: Faction: '%s' is not found.", factionDir); - return; - } - const std::string modFaction = index == std::string::npos ? faction : faction.substr(index + 1); - currentFaction = it->second.modData[modFaction]; - } - - 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 & currentBuilding = buildings[buildingName]; subId = CTownHandler::getMappedValue(currentBuilding["type"], BuildingSubID::NONE, MappedKeys::SPECIAL_BUILDINGS); - height = CBuilding::HEIGHT_NO_TOWER; + height = subId == BuildingSubID::LOOKOUT_TOWER || bid == BuildingID::GRAIL + ? CTownHandler::getMappedValue(currentBuilding["height"], CBuilding::HEIGHT_NO_TOWER, CBuilding::TOWER_TYPES) + : height = CBuilding::HEIGHT_NO_TOWER; + } +} - if (subId == BuildingSubID::LOOKOUT_TOWER || bid == BuildingID::GRAIL) - height = CTownHandler::getMappedValue(currentBuilding["height"], CBuilding::HEIGHT_NO_TOWER, CBuilding::TOWER_TYPES); +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(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; + + 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; } +TPropagatorPtr CTownHandler::emptyPropagator = std::make_shared(); + std::vector CTownHandler::loadLegacyData(size_t dataSize) { std::vector dest(dataSize); @@ -412,7 +480,7 @@ std::vector CTownHandler::loadLegacyData(size_t dataSize) return dest; } -void CTownHandler::loadBuildingRequirements(CBuilding * building, const JsonNode & source) +void CTownHandler::loadBuildingRequirements(CBuilding * building, const JsonNode & source, std::vector & bidsToLoad) { if (source.isNull()) return; @@ -421,7 +489,7 @@ void CTownHandler::loadBuildingRequirements(CBuilding * building, const JsonNode hlp.building = building; hlp.town = building->town; hlp.json = source; - requirementsToLoad.push_back(hlp); + bidsToLoad.push_back(hlp); } template @@ -445,6 +513,100 @@ R CTownHandler::getMappedValue(const JsonNode & node, const R defval, const std: return defval; } +void CTownHandler::addBonusesForVanilaBuilding(CBuilding * building) +{ + std::shared_ptr b; + static TPropagatorPtr playerPropagator = std::make_shared(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(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 CTownHandler::createBonus(CBuilding * build, Bonus::BonusType type, int val, int subtype) +{ + return createBonus(build, type, val, emptyPropagator, subtype); +} + +std::shared_ptr 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 CTownHandler::createBonusImpl(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, const std::string & description, int subtype) +{ + auto b = std::make_shared(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(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) { auto ret = new CBuilding(); @@ -474,6 +636,26 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons ret->resources = TResources(source["cost"]); 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 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()) { @@ -828,9 +1012,10 @@ ETerrainType::EETerrainType CTownHandler::getDefaultTerrainForAlignment(EAlignme 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(); + faction->index = index; faction->name = source["name"].String(); 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) { - auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); + auto index = static_cast(factions.size()); + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name), index); - object->index = static_cast(factions.size()); factions.push_back(object); 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) { - auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); - object->index = static_cast(index); + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name), static_cast(index)); + if (factions.size() > index) assert(factions[index] == nullptr); // ensure that this id was not loaded before else @@ -957,6 +1142,7 @@ void CTownHandler::loadCustom() void CTownHandler::afterLoadFinalization() { initializeRequirements(); + initializeOverridden(); initializeWarMachines(); } @@ -979,6 +1165,22 @@ void CTownHandler::initializeRequirements() 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() { // must be done separately after all objects are loaded diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index 1197ae383..539b0261d 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -16,6 +16,7 @@ #include "IHandlerBase.h" #include "LogicalExpression.h" #include "battle/BattleHex.h" +#include "HeroBonus.h" class CLegacyConfigParser; class JsonNode; @@ -47,6 +48,9 @@ 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 + std::set overrideBids; /// the building which bonuses should be overridden with bonuses of the current building + BonusList buildingBonuses; + BonusList onVisitBonuses; enum EBuildMode { @@ -95,14 +99,16 @@ public: bool IsVisitingBonus() const { return subId == BuildingSubID::ATTACK_VISITING_BONUS || - subId == BuildingSubID::DEFENSE_VISITING_BONUS || + subId == BuildingSubID::DEFENSE_VISITING_BONUS || subId == BuildingSubID::SPELL_POWER_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 b, BonusList & bonusList); + void update792(); + void update794(); template void serialize(Handler &h, const int version) { @@ -123,9 +129,17 @@ public: h & height; } 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) deserializeFix(); } @@ -133,8 +147,8 @@ public: friend class CTownHandler; private: - void deserializeFix(); + const JsonNode & getCurrentFactionForUpdateRoutine() const; }; /// This is structure used only by client @@ -354,19 +368,27 @@ class DLL_LINKAGE CTownHandler : public IHandlerBase std::map warMachinesToLoad; std::vector requirementsToLoad; + std::vector overriddenBidsToLoad; //list of buildings, which bonuses should be overridden. const static ETerrainType::EETerrainType defaultGoodTerrain = ETerrainType::EETerrainType::GRASS; const static ETerrainType::EETerrainType defaultEvilTerrain = ETerrainType::EETerrainType::LAVA; const static ETerrainType::EETerrainType defaultNeutralTerrain = ETerrainType::EETerrainType::ROUGH; + static TPropagatorPtr emptyPropagator; + void initializeRequirements(); + void initializeOverridden(); void initializeWarMachines(); /// loads CBuilding's into town - void loadBuildingRequirements(CBuilding * building, const JsonNode & source); + void loadBuildingRequirements(CBuilding * building, const JsonNode & source, std::vector & bidsToLoad); void loadBuilding(CTown * town, const std::string & stringID, const JsonNode & source); void loadBuildings(CTown * town, const JsonNode & source); + std::shared_ptr createBonus(CBuilding * build, Bonus::BonusType type, int val, int subtype = -1); + std::shared_ptr createBonus(CBuilding * build, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype = -1); + std::shared_ptr createBonusImpl(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, const std::string & description, int subtype = -1); + /// loads CStructure's into town void loadStructure(CTown &town, const std::string & stringID, 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; - CFaction * loadFromJson(const JsonNode & data, const std::string & identifier); + CFaction * loadFromJson(const JsonNode & data, const std::string & identifier, TFaction index); 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, size_t index) override; + void addBonusesForVanilaBuilding(CBuilding * building); void loadCustom() override; void afterLoadFinalization() override; @@ -416,6 +439,7 @@ public: //json serialization helper static std::string encodeFaction(const si32 index); + static void loadSpecialBuildingBonuses(const JsonNode & source, BonusList & bonusList, CBuilding * building); template void serialize(Handler &h, const int version) { diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 7a6754d17..eea10f358 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -297,7 +297,7 @@ class TeleportChannelID : public BaseForID // Enum declarations 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 } @@ -452,7 +452,8 @@ namespace BuildingSubID KNOWLEDGE_VISITING_BONUS, EXPERIENCE_VISITING_BONUS, LIGHTHOUSE, - TREASURY + TREASURY, + CUSTOM_VISITING_BONUS }; } diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index 54ad9fb10..19c56c4db 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -71,7 +71,8 @@ const std::map bonusLimiterMap = {"SHOOTER_ONLY", std::make_shared(Bonus::SHOOTER)}, {"DRAGON_NATURE", std::make_shared(Bonus::DRAGON_NATURE)}, {"IS_UNDEAD", std::make_shared(Bonus::UNDEAD)}, - {"CREATURE_NATIVE_TERRAIN", std::make_shared()} + {"CREATURE_NATIVE_TERRAIN", std::make_shared()}, + {"CREATURE_FACTION", std::make_shared()} }; const std::map bonusPropagatorMap = @@ -81,7 +82,8 @@ const std::map bonusPropagatorMap = {"PLAYER_PROPAGATOR", std::make_shared(CBonusSystemNode::PLAYER)}, {"HERO", std::make_shared(CBonusSystemNode::HERO)}, {"TEAM_PROPAGATOR", std::make_shared(CBonusSystemNode::TEAM)}, //untested - {"GLOBAL_EFFECT", std::make_shared(CBonusSystemNode::GLOBAL_EFFECTS)} + {"GLOBAL_EFFECT", std::make_shared(CBonusSystemNode::GLOBAL_EFFECTS)}, + {"ALL_CREATURES", std::make_shared(CBonusSystemNode::ALL_CREATURES)} }; //untested const std::map 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; valType = ADDITIVE_VALUE; @@ -1579,8 +1581,8 @@ Bonus::Bonus(ui16 Dur, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std:: 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; effectRange = NO_LIMIT; @@ -1922,17 +1924,27 @@ bool IPropagator::shouldBeAttached(CBonusSystemNode *dest) return false; } +CBonusSystemNode::ENodeTypes IPropagator::getPropagatorType() const +{ + return CBonusSystemNode::ENodeTypes::NONE; +} + CPropagatorNodeType::CPropagatorNodeType() - :nodeType(0) + :nodeType(CBonusSystemNode::ENodeTypes::UNKNOWN) { } -CPropagatorNodeType::CPropagatorNodeType(int NodeType) +CPropagatorNodeType::CPropagatorNodeType(CBonusSystemNode::ENodeTypes NodeType) : nodeType(NodeType) { } +CBonusSystemNode::ENodeTypes CPropagatorNodeType::getPropagatorType() const +{ + return nodeType; +} + bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest) { return nodeType == dest->getNodeType(); diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index 642298245..501b3d939 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -418,8 +418,8 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this 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(); template void serialize(Handler &h, const int version) @@ -508,6 +508,10 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this { val += Val; } + STRONG_INLINE static ui32 getSid32(ui32 high, ui32 low) + { + return (high << 16) + low; + } std::string Description() 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); -class DLL_LINKAGE IPropagator -{ -public: - virtual ~IPropagator(); - virtual bool shouldBeAttached(CBonusSystemNode *dest); - - template 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 void serialize(Handler &h, const int version) - { - h & nodeType; - } -}; - struct BonusLimitationContext { std::shared_ptr b; @@ -756,6 +736,7 @@ class DLL_LINKAGE CBonusSystemNode : public virtual IBonusBearer, public boost:: public: enum ENodeTypes { + NONE = -1, UNKNOWN, STACK_INSTANCE, STACK_BATTLE, SPECIALTY, ARTIFACT, CREATURE, ARTIFACT_INSTANCE, HERO, PLAYER, TEAM, TOWN_AND_VISITOR, BATTLE, COMMANDER, GLOBAL_EFFECTS, ALL_CREATURES }; @@ -857,6 +838,33 @@ public: friend class CBonusProxy; }; +class DLL_LINKAGE IPropagator +{ +public: + virtual ~IPropagator(); + virtual bool shouldBeAttached(CBonusSystemNode *dest); + virtual CBonusSystemNode::ENodeTypes getPropagatorType() const; + + template 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 void serialize(Handler &h, const int version) + { + h & nodeType; + } +}; + namespace NBonus { //set of methods that may be safely called with nullptr objs diff --git a/lib/JsonNode.cpp b/lib/JsonNode.cpp index 48fc01c5f..7af159dc6 100644 --- a/lib/JsonNode.cpp +++ b/lib/JsonNode.cpp @@ -697,6 +697,19 @@ std::shared_ptr JsonUtils::parseBonus(const JsonNode &ability) return b; } +std::shared_ptr 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::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) { const JsonNode *value; @@ -726,7 +739,8 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b) b->sid = static_cast(ability["sourceID"].Float()); - b->description = ability["description"].String(); + if(!ability["description"].isNull()) + b->description = ability["description"].String(); value = &ability["effectRange"]; if (!value->isNull()) @@ -738,7 +752,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b) switch (value->getType()) { case JsonNode::JsonType::DATA_STRING: - b->duration = parseByMap(bonusDurationMap, value, "duration type "); + b->duration = (Bonus::BonusDuration)parseByMap(bonusDurationMap, value, "duration type "); break; case JsonNode::JsonType::DATA_VECTOR: { @@ -747,7 +761,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b) { dur |= parseByMap(bonusDurationMap, &d, "duration type "); } - b->duration = dur; + b->duration = (Bonus::BonusDuration)dur; } break; default: diff --git a/lib/JsonNode.h b/lib/JsonNode.h index 10912baa8..e73f99ad1 100644 --- a/lib/JsonNode.h +++ b/lib/JsonNode.h @@ -8,6 +8,7 @@ * */ #pragma once +#include "GameConstants.h" class JsonNode; typedef std::map JsonMap; @@ -168,6 +169,7 @@ namespace JsonUtils /// DLL_LINKAGE std::shared_ptr parseBonus(const JsonVector &ability_vec); DLL_LINKAGE std::shared_ptr parseBonus(const JsonNode &ability); + DLL_LINKAGE std::shared_ptr parseBuildingBonus(const JsonNode &ability, BuildingID building, std::string description); DLL_LINKAGE bool parseBonus(const JsonNode &ability, Bonus *placement); DLL_LINKAGE std::shared_ptr parseLimiter(const JsonNode & limiter); DLL_LINKAGE void resolveIdentifier(si32 &var, const JsonNode &node, std::string name); diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 90969d130..fad0f1eab 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -245,10 +245,21 @@ DLL_LINKAGE void GiveBonus::applyGs(CGameState *gs) std::string &descr = b->description; if(!bdescr.message.size() - && bonus.source == Bonus::OBJECT && (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 { @@ -556,16 +567,27 @@ void TryMoveHero::applyGs(CGameState *gs) DLL_LINKAGE void NewStructures::applyGs(CGameState *gs) { CGTownInstance *t = gs->getTown(tid); + for(const auto & id : bid) { assert(t->town->buildings.at(id) != nullptr); t->builtBuildings.insert(id); - 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->recreateBuildingsBonuses(); } + DLL_LINKAGE void RazeStructures::applyGs(CGameState *gs) { CGTownInstance *t = gs->getTown(tid); diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index f92844324..a8f506bea 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -196,3 +196,8 @@ void LibClasses::setContent(std::shared_ptr content) { modh->content = content; } + +void LibClasses::restoreAllCreaturesNodeType794() +{ + creh->restoreAllCreaturesNodeType794(); +} diff --git a/lib/VCMI_Lib.h b/lib/VCMI_Lib.h index f82992e96..ff4d6a104 100644 --- a/lib/VCMI_Lib.h +++ b/lib/VCMI_Lib.h @@ -36,6 +36,7 @@ class DLL_LINKAGE LibClasses void makeNull(); //sets all handler pointers to null std::shared_ptr getContent() const; void setContent(std::shared_ptr content); + void restoreAllCreaturesNodeType794(); public: bool IS_AI_ENABLED; //unused? @@ -69,6 +70,9 @@ public: h & heroh; h & arth; h & creh; + if(!h.saving && version < 794) + restoreAllCreaturesNodeType794(); + h & townh; h & objh; h & objtypeh; diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 6c8389a31..a1210c5ed 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -21,6 +21,7 @@ #include "../mapping/CMap.h" #include "../CPlayerState.h" #include "../serializer/JsonSerializeFormat.h" +#include "../HeroBonus.h" std::vector CGTownInstance::merchantArtifacts; std::vector CGTownInstance::universitySkills; @@ -435,8 +436,6 @@ void CGDwelling::serializeJsonOptions(JsonSerializeFormat & handler) } } -TPropagatorPtr CGTownInstance::emptyPropagator = TPropagatorPtr(); - int CGTownInstance::getSightRadius() const //returns sight distance { auto ret = CBuilding::HEIGHT_NO_TOWER; @@ -753,6 +752,17 @@ void CGTownInstance::tryAddOnePerWeekBonus(BuildingSubID::EBuildingSubID subID) 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) { auto bid = town->getBuildingType(subID); @@ -765,6 +775,9 @@ void CGTownInstance::addTownBonuses() { for(const auto & kvp : town->buildings) { + if(vstd::contains(overriddenBuildings, kvp.first)) + continue; + if(kvp.second->IsVisitingBonus()) 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 { @@ -794,17 +837,18 @@ void CGTownInstance::initObj(CRandomGenerator & rand) ///initialize town structu creatures[level].second.push_back(town->creatures[level][upgradeNum]); } } + initOverriddenBids(); addTownBonuses(); //add special bonuses from buildings to the bonusingBuildings vector. recreateBuildingsBonuses(); 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 - 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) { @@ -838,7 +882,7 @@ void CGTownInstance::updateBonusingBuildings() } } //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; @@ -864,6 +908,25 @@ void CGTownInstance::updateBonusingBuildings() 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 { return (this->town->faction != nullptr && this->town->faction->index == type && hasBuilt(bid)); @@ -1184,112 +1247,30 @@ void CGTownInstance::updateMoraleBonusFromArmy() void CGTownInstance::recreateBuildingsBonuses() { - static TPropagatorPtr playerProp(new CPropagatorNodeType(PLAYER)); - BonusList bl; getExportedBonusList().getBonuses(bl, Selector::sourceType()(Bonus::TOWN_STRUCTURE)); + for(auto b : bl) 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); + for(auto bid : builtBuildings) + { + if(vstd::contains(overriddenBuildings, bid)) //tricky! -> checks tavern only if no bratherhood of sword + continue; - 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); + auto building = town->buildings.at(bid); - 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 - { - 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(this->town->faction->index); - auto b = std::make_shared(Bonus::PERMANENT, Bonus::NO_TERRAIN_PENALTY, Bonus::TOWN_STRUCTURE, 0, BuildingID::GRAIL, "", -1); + if(building->buildingBonuses.empty()) + continue; - b->addLimiter(factionLimiter); - b->addPropagator(lsProp); - VLC->creh->addBonusForAllCreatures(b); - } -} - -bool CGTownInstance::addBonusIfBuilt(BuildingSubID::EBuildingSubID subId, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype) -{ - BuildingID currentBid = BuildingID::NONE; - std::ostringstream descr; - - 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::PERMANENT, type, Bonus::TOWN_STRUCTURE, val, building, description, subtype); - - if(prop) - b->addPropagator(prop); - addNewBonus(b); - return true; } void CGTownInstance::setVisitingHero(CGHeroInstance *h) @@ -1738,11 +1719,11 @@ void CTownBonus::setProperty (ui8 what, ui32 val) void CTownBonus::onHeroVisit(const CGHeroInstance * h) const { 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; InfoWindow iw; - PrimarySkill::PrimarySkill what = PrimarySkill::ATTACK; + PrimarySkill::PrimarySkill what = PrimarySkill::NONE; switch (bType) { @@ -1775,15 +1756,51 @@ void CTownBonus::onHeroVisit(const CGHeroInstance * h) const val = 1; iw.components.push_back(Component(Component::PRIM_SKILL, 1, 1, 0)); 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(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(); - cb->showInfoDialog(&iw); - cb->changePrimSkill(cb->getHero(heroID), what, val); - town->addHeroToStructureVisitors(h, indexOnTV); } } +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); + } + if(addToVisitors) + town->addHeroToStructureVisitors(h, indexOnTV); +} + GrowthInfo::Entry::Entry(const std::string &format, int _count) : count(_count) { @@ -1851,3 +1868,22 @@ const std::string CGTownBuilding::getVisitingBonusGreeting() const town->town->setGreeting(bType, 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; +} diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 5d3843214..f41812cff 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -133,6 +133,7 @@ protected: BuildingSubID::EBuildingSubID bType; const std::string getVisitingBonusGreeting() const; + static const std::string getCustomBonusGreeting(const Bonus & bonus); }; class DLL_LINKAGE COPWBonus : public CGTownBuilding @@ -169,6 +170,9 @@ public: h & static_cast(*this); h & visitors; } + +private: + void applyBonuses(CGHeroInstance * h, const BonusList & bonuses) const; }; class DLL_LINKAGE CTownAndVisitingHero : public CBonusSystemNode @@ -205,7 +209,9 @@ public: ConstTransitivePtr garrisonHero, visitingHero; ui32 identifier; //special identifier from h3m (only > RoE maps) si32 alignment; - std::set forbiddenBuildings, builtBuildings; + std::set forbiddenBuildings; + std::set builtBuildings; + std::set overriddenBuildings; ///buildings which bonuses are overridden and should not be applied std::vector bonusingBuildings; std::vector possibleSpells, obligatorySpells; std::vector > spells; //spells[level] -> vector of spells, first will be available in guild @@ -237,8 +243,8 @@ public: h & events; h & bonusingBuildings; - for (std::vector::iterator i = bonusingBuildings.begin(); i!=bonusingBuildings.end(); i++) - (*i)->town = this; + for(auto * bonusingBuilding : bonusingBuildings) + bonusingBuilding->town = this; h & town; h & townAndVis; @@ -256,6 +262,11 @@ public: if(!h.saving && version < 793) updateBonusingBuildings(); + + if(version >= 794) + h & overriddenBuildings; + else if(!h.saving) + updateTown794(); } ////////////////////////////////////////////////////////////////////////// @@ -264,11 +275,6 @@ public: void updateMoraleBonusFromArmy() override; void deserializationFix(); 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 setGarrisonedHero(CGHeroInstance *h); const CArmedInstance *getUpperArmy() const; //garrisoned hero if present or the town itself @@ -318,6 +324,7 @@ public: void removeCapitols (PlayerColor owner) const; void clearArmy() const; 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 ; @@ -336,7 +343,6 @@ public: static void reset(); protected: - static TPropagatorPtr emptyPropagator; void setPropertyDer(ui8 what, ui32 val) override; void serializeJsonOptions(JsonSerializeFormat & handler) override; @@ -344,9 +350,10 @@ private: int getDwellingBonus(const std::vector& creatureIds, const std::vector >& dwellings) const; void updateBonusingBuildings(); 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; void tryAddOnePerWeekBonus(BuildingSubID::EBuildingSubID subID); void tryAddVisitingBonus(BuildingSubID::EBuildingSubID subID); + void initOverriddenBids(); void addTownBonuses(); + void updateTown794(); //populate overriddenBuildings and vanila bonuses for old saves }; diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index e76321790..05060f890 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -260,7 +260,7 @@ void CGObjectInstance::giveDummyBonus(ObjectInstanceID heroID, ui8 duration) con GiveBonus gbonus; gbonus.bonus.type = Bonus::NONE; gbonus.id = heroID.getNum(); - gbonus.bonus.duration = duration; + gbonus.bonus.duration = (Bonus::BonusDuration)duration; gbonus.bonus.source = Bonus::OBJECT; gbonus.bonus.sid = ID; cb->giveHeroBonus(&gbonus); diff --git a/lib/serializer/CSerializer.h b/lib/serializer/CSerializer.h index 6cdf550a0..6622fe7d9 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 = 793; +const ui32 SERIALIZATION_VERSION = 794; const ui32 MINIMAL_SERIALIZATION_VERSION = 753; const std::string SAVEGAME_MAGIC = "VCMISVG"; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 95d05e6f2..5d16437bf 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3147,16 +3147,14 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, processBeforeBuiltStructure(builtID); //Take cost - if (!force) - { + if(!force) 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); //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); // 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); sendAndApply(&fw); - if (t->visitingHero) + if(t->visitingHero) visitCastleObjects(t, t->visitingHero); - if (t->garrisonHero) + if(t->garrisonHero) visitCastleObjects(t, t->garrisonHero); checkVictoryLossConditionsForPlayer(t->tempOwner); return true; } + bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid) { ///incomplete, simply erases target building