diff --git a/config/factions/castle.json b/config/factions/castle.json index 2c06e2a96..2846904b0 100644 --- a/config/factions/castle.json +++ b/config/factions/castle.json @@ -170,7 +170,7 @@ "resourceSilo": { "id" : 15, "requires" : [ "marketplace" ], "produce": { "ore": 1, "wood": 1 } }, "blacksmith": { "id" : 16 }, - "special1": { "requires" : [ "shipyard" ] }, + "special1": { "type" : "lighthouse", "requires" : [ "shipyard" ] }, "horde1": { "id" : 18, "upgrades" : "dwellingLvl3" }, "horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl3", "requires" : [ "horde1" ], "mode" : "auto" }, "ship": { "id" : 20, "upgrades" : "shipyard" }, diff --git a/config/factions/dungeon.json b/config/factions/dungeon.json index 551dc2d22..624f42918 100644 --- a/config/factions/dungeon.json +++ b/config/factions/dungeon.json @@ -174,7 +174,7 @@ "horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" }, "special2": { "type" : "manaVortex", "requires" : [ "mageGuild1" ] }, "special3": { "type" : "portalOfSummoning" }, - "special4": { }, + "special4": { "type" : "experienceVisitingBonus" }, "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 3b47d2d01..50e6d0a54 100644 --- a/config/factions/fortress.json +++ b/config/factions/fortress.json @@ -169,7 +169,7 @@ "resourceSilo": { "id" : 15, "requires" : [ "marketplace" ], "produce": { "wood": 1, "ore": 1 } }, "blacksmith": { "id" : 16 }, - "special1": { "requires" : [ "allOf", [ "townHall" ], [ "special2" ] ] }, + "special1": { "type" : "defenceVisitingBonus", "requires" : [ "allOf", [ "townHall" ], [ "special2" ] ] }, "horde1": { "id" : 18, "upgrades" : "dwellingLvl1" }, "horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" }, "ship": { "id" : 20, "upgrades" : "shipyard" }, diff --git a/config/factions/inferno.json b/config/factions/inferno.json index 1ea70c8a5..505f209fd 100644 --- a/config/factions/inferno.json +++ b/config/factions/inferno.json @@ -174,7 +174,7 @@ "horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" }, "special2": { "type" : "spellPowerGarrisonBonus", "requires" : [ "fort" ] }, "special3": { "type" : "castleGate", "requires" : [ "citadel" ] }, - "special4": { "requires" : [ "mageGuild1" ] }, + "special4": { "type" : "spellPowerVisitingBonus", "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/rampart.json b/config/factions/rampart.json index d08f15757..796969b3a 100644 --- a/config/factions/rampart.json +++ b/config/factions/rampart.json @@ -178,7 +178,7 @@ "horde1": { "id" : 18, "upgrades" : "dwellingLvl2" }, "horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl2", "requires" : [ "horde1" ], "mode" : "auto" }, "special2": { "type" : "fountainOfFortune", "upgrades" : "special1" }, - "special3": { "requires" : [ "horde1" ] }, + "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 }}, diff --git a/config/factions/stronghold.json b/config/factions/stronghold.json index 67ce19204..74f981fbc 100644 --- a/config/factions/stronghold.json +++ b/config/factions/stronghold.json @@ -171,7 +171,7 @@ "horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" }, "special2": { "type" : "freelancersGuild", "requires" : [ "marketplace" ] }, "special3": { "type" : "ballistaYard", "requires" : [ "blacksmith" ] }, - "special4": { "requires" : [ "fort" ] }, + "special4": { "type" : "attackVisitingBonus", "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 c5a65ddc9..0f2e167fd 100644 --- a/config/factions/tower.json +++ b/config/factions/tower.json @@ -174,7 +174,7 @@ "horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl2", "requires" : [ "horde1" ], "mode" : "auto" }, "special2": { "type" : "lookoutTower", "height" : "high", "requires" : [ "fort" ] }, "special3": { "type" : "library", "requires" : [ "mageGuild1" ] }, - "special4": { "requires" : [ "mageGuild1" ] }, + "special4": { "type" : "knowledgeVisitingBonus", "requires" : [ "mageGuild1" ] }, "grail": { "height" : "skyship", "produce" : { "gold": 5000 } }, "dwellingLvl1": { "id" : 30, "requires" : [ "fort" ] }, diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index 2944f3c8f..7685fcf39 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -85,6 +85,22 @@ public: return bid == BuildingID::MARKETPLACE || subId == BuildingSubID::ARTIFACT_MERCHANT || subId == BuildingSubID::FREELANCERS_GUILD; } + STRONG_INLINE + bool IsWeekBonus() const + { + return subId == BuildingSubID::STABLES || subId == BuildingSubID::MANA_VORTEX; + } + + STRONG_INLINE + bool IsVisitingBonus() const + { + return subId == BuildingSubID::ATTACK_VISITING_BONUS || + subId == BuildingSubID::DEFENSE_VISITING_BONUS || + subId == BuildingSubID::SPELL_POWER_VISITING_BONUS || + subId == BuildingSubID::KNOWLEDGE_VISITING_BONUS || + subId == BuildingSubID::EXPERIENCE_VISITING_BONUS; + } + /// input: faction, bid; output: subId, height; void update792(const BuildingID & bid, BuildingSubID::EBuildingSubID & subId, ETowerHeight & height); diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 5c5471c12..8902dc83b 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -442,11 +442,17 @@ namespace BuildingSubID ESCAPE_TUNNEL, FREELANCERS_GUILD, BALLISTA_YARD, - HALL_OF_VALHALLA, + ATTACK_VISITING_BONUS, MAGIC_UNIVERSITY, SPELL_POWER_GARRISON_BONUS, ATTACK_GARRISON_BONUS, - DEFENSE_GARRISON_BONUS + DEFENSE_GARRISON_BONUS, + DEFENSE_VISITING_BONUS, + SPELL_POWER_VISITING_BONUS, + KNOWLEDGE_VISITING_BONUS, + EXPERIENCE_VISITING_BONUS, + LIGHTHOUSE, + TREASURY }; } @@ -490,7 +496,14 @@ namespace MappedKeys { "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 } + { "escapeTunnel", BuildingSubID::ESCAPE_TUNNEL }, + { "attackVisitingBonus", BuildingSubID::ATTACK_VISITING_BONUS }, + { "defenceVisitingBonus", BuildingSubID::DEFENSE_VISITING_BONUS }, + { "spellPowerVisitingBonus", BuildingSubID::SPELL_POWER_VISITING_BONUS }, + { "knowledgeVisitingBonus", BuildingSubID::KNOWLEDGE_VISITING_BONUS }, + { "experienceVisitingBonus", BuildingSubID::EXPERIENCE_VISITING_BONUS }, + { "lighthouse", BuildingSubID::LIGHTHOUSE }, + { "treasury", BuildingSubID::TREASURY } }; } diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 6ba79fed1..612120d66 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -744,18 +744,36 @@ bool CGTownInstance::townEnvisagesBuilding(BuildingSubID::EBuildingSubID subId) return town->getBuildingType(subId) != BuildingID::NONE; } -//it does not check hasBuilt(...) because this check is in the OnHeroVisit handler -bool CGTownInstance::tryAddOnePerWeekBonus(BuildingSubID::EBuildingSubID subID) +//it does not check hasBuilt because this check is in the OnHeroVisit handler +void CGTownInstance::tryAddOnePerWeekBonus(BuildingSubID::EBuildingSubID subID) { auto bid = town->getBuildingType(subID); - if(bid == BuildingID::NONE) - return false; - - bonusingBuildings.push_back(new COPWBonus(bid, subID, this)); - return true; + if(bid != BuildingID::NONE) + bonusingBuildings.push_back(new COPWBonus(bid, subID, this)); } +void CGTownInstance::tryAddVisitingBonus(BuildingSubID::EBuildingSubID subID) +{ + auto bid = town->getBuildingType(subID); + + if(bid != BuildingID::NONE) + bonusingBuildings.push_back(new CTownBonus(bid, subID, this)); +} + +void CGTownInstance::addTownBonuses() +{ + for(const auto & kvp : town->buildings) + { + if(kvp.second->IsVisitingBonus()) + bonusingBuildings.push_back(new CTownBonus(kvp.second->bid, kvp.second->subId, this)); + + if(kvp.second->IsWeekBonus()) + bonusingBuildings.push_back(new COPWBonus(kvp.second->bid, kvp.second->subId, this)); + } +} + + void CGTownInstance::initObj(CRandomGenerator & rand) ///initialize town structures { blockVisit = true; @@ -776,24 +794,7 @@ void CGTownInstance::initObj(CRandomGenerator & rand) ///initialize town structu creatures[level].second.push_back(town->creatures[level][upgradeNum]); } } - tryAddOnePerWeekBonus(BuildingSubID::STABLES); - tryAddOnePerWeekBonus(BuildingSubID::MANA_VORTEX); - - switch (subID) - { - //add new visitable objects - case ETownType::DUNGEON: - case ETownType::TOWER: - case ETownType::INFERNO: - case ETownType::STRONGHOLD: - bonusingBuildings.push_back (new CTownBonus(BuildingID::SPECIAL_4, this)); - break; - case ETownType::FORTRESS: - bonusingBuildings.push_back (new CTownBonus(BuildingID::SPECIAL_1, this)); - break; - } - //add special bonuses from buildings - + addTownBonuses(); //add special bonuses from buildings to the bonusingBuildings vector. recreateBuildingsBonuses(); updateAppearance(); } @@ -803,18 +804,35 @@ void CGTownInstance::updateBonusingBuildings() if (this->town->faction != nullptr) { //firstly, update subtype for the Bonusing objects, which are already stored in the bonusing list - for (auto building : bonusingBuildings) + for (auto building : bonusingBuildings) //no garrison bonuses here, only week and visiting bonuses { switch (this->town->faction->index) { case ETownType::CASTLE: - if (building->getBuildingType() == BuildingID::SPECIAL_2) - building->setBuildingSubtype(BuildingSubID::STABLES); + building->setBuildingSubtype(BuildingSubID::STABLES); break; case ETownType::DUNGEON: if(building->getBuildingType() == BuildingID::SPECIAL_2) building->setBuildingSubtype(BuildingSubID::MANA_VORTEX); + else if(building->getBuildingType() == BuildingID::SPECIAL_4) + building->setBuildingSubtype(BuildingSubID::EXPERIENCE_VISITING_BONUS); + break; + + case ETownType::TOWER: + building->setBuildingSubtype(BuildingSubID::KNOWLEDGE_VISITING_BONUS); + break; + + case ETownType::STRONGHOLD: + building->setBuildingSubtype(BuildingSubID::ATTACK_VISITING_BONUS); + break; + + case ETownType::INFERNO: + building->setBuildingSubtype(BuildingSubID::SPELL_POWER_VISITING_BONUS); + break; + + case ETownType::FORTRESS: + building->setBuildingSubtype(BuildingSubID::DEFENSE_VISITING_BONUS); break; } } @@ -824,23 +842,24 @@ void CGTownInstance::updateBonusingBuildings() { auto & building = kvp.second; - switch (building->subId) + if(building->subId == BuildingSubID::PORTAL_OF_SUMMONING) { - case BuildingSubID::PORTAL_OF_SUMMONING: if(!hasBuiltInOldWay(ETownType::DUNGEON, BuildingID::PORTAL_OF_SUMMON)) creatures.resize(GameConstants::CREATURES_PER_TOWN + 1); - break; - ///'hasBuilt' checking for COPW bonuses is in the COPWBonus::onHeroVisit - case BuildingSubID::STABLES: - if(getBonusingBuilding(building->subId) == nullptr) - tryAddOnePerWeekBonus(BuildingSubID::STABLES); - break; - - case BuildingSubID::MANA_VORTEX: - if(getBonusingBuilding(building->subId) == nullptr) - tryAddOnePerWeekBonus(BuildingSubID::MANA_VORTEX); - break; + continue; } + if(!building->IsVisitingBonus() && !building->IsWeekBonus()) //it's not bonusing => nothing to handle + continue; + + if(getBonusingBuilding(building->subId) != nullptr) //it's already added => already handled + continue; + + ///'hasBuilt' checking for bonuses is in the onHeroVisit handler + if(building->IsWeekBonus()) + tryAddOnePerWeekBonus(building->subId); + + if(building->IsVisitingBonus()) + tryAddVisitingBonus(building->subId); } recreateBuildingsBonuses(); ///Clear all bonuses and recreate } @@ -1180,11 +1199,11 @@ void CGTownInstance::recreateBuildingsBonuses() addBonusIfBuilt(BuildingSubID::SPELL_POWER_GARRISON_BONUS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER);//works as Brimstone Clouds addBonusIfBuilt(BuildingSubID::ATTACK_GARRISON_BONUS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::ATTACK);//works as Blood Obelisk addBonusIfBuilt(BuildingSubID::DEFENSE_GARRISON_BONUS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE);//works as Glyphs of Fear + addBonusIfBuilt(BuildingSubID::LIGHTHOUSE, Bonus::SEA_MOVEMENT, +500, playerProp); if(subID == ETownType::CASTLE) //castle { - addBonusIfBuilt(BuildingID::LIGHTHOUSE, Bonus::SEA_MOVEMENT, +500, playerProp); - addBonusIfBuilt(BuildingID::GRAIL, Bonus::MORALE, +2, playerProp); //colossus + addBonusIfBuilt(BuildingID::GRAIL, Bonus::MORALE, +2, playerProp); //colossus } else if(subID == ETownType::RAMPART) //rampart { @@ -1215,14 +1234,14 @@ void CGTownInstance::recreateBuildingsBonuses() } } -bool CGTownInstance::addBonusIfBuilt(BuildingSubID::EBuildingSubID subId, Bonus::BonusType type, int val, int subtype) +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) + if(town->buildings.at(bid)->subId == subId) { descr << town->buildings.at(bid)->Name(); currentBid = bid; @@ -1230,12 +1249,12 @@ bool CGTownInstance::addBonusIfBuilt(BuildingSubID::EBuildingSubID subId, Bonus: } } return currentBid == BuildingID::NONE ? false - : addBonusImpl(currentBid, type, val, emptyPropagator, descr.str(), subtype); + : addBonusImpl(currentBid, type, val, prop, descr.str(), subtype); } -bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype) +bool CGTownInstance::addBonusIfBuilt(BuildingSubID::EBuildingSubID subId, Bonus::BonusType type, int val, int subtype) { - return addBonusIfBuilt(building, type, val, emptyPropagator, subtype); + return addBonusIfBuilt(subId, type, val, emptyPropagator, subtype); } bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype) @@ -1248,6 +1267,11 @@ bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, 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); @@ -1686,9 +1710,10 @@ void COPWBonus::onHeroVisit(const CGHeroInstance * h) const } } } -CTownBonus::CTownBonus (BuildingID index, CGTownInstance *TOWN) +CTownBonus::CTownBonus (BuildingID index, BuildingSubID::EBuildingSubID subId, CGTownInstance *TOWN) { bID = index; + bType = subId; town = TOWN; indexOnTV = static_cast(town->bonusingBuildings.size()); } @@ -1699,64 +1724,58 @@ void CTownBonus::setProperty (ui8 what, ui32 val) visitors.insert(ObjectInstanceID(val)); } -void CTownBonus::onHeroVisit (const CGHeroInstance * h) const +void CTownBonus::onHeroVisit(const CGHeroInstance * h) const { ObjectInstanceID heroID = h->id; if (town->hasBuilt(bID) && visitors.find(heroID) == visitors.end()) { - si32 mid=0; + si32 mid = 0; si64 val = 0; InfoWindow iw; PrimarySkill::PrimarySkill what = PrimarySkill::ATTACK; - switch (bID) + switch (bType) { - case BuildingID::SPECIAL_4: - switch(town->subID) - { - case ETownType::TOWER: //wall - what = PrimarySkill::KNOWLEDGE; - val = 1; - mid = 581; - iw.components.push_back (Component(Component::PRIM_SKILL, 3, 1, 0)); - break; - case ETownType::INFERNO: //order of fire - what = PrimarySkill::SPELL_POWER; - val = 1; - mid = 582; - iw.components.push_back (Component(Component::PRIM_SKILL, 2, 1, 0)); - break; - case ETownType::STRONGHOLD://hall of Valhalla - what = PrimarySkill::ATTACK; - val = 1; - mid = 584; - iw.components.push_back (Component(Component::PRIM_SKILL, 0, 1, 0)); - break; - case ETownType::DUNGEON://academy of battle scholars - what = PrimarySkill::EXPERIENCE; - val = static_cast(h->calculateXp(1000)); - mid = 583; - iw.components.push_back (Component(Component::EXPERIENCE, 0, val, 0)); - break; - } - break; - case BuildingID::SPECIAL_1: - switch(town->subID) - { - case ETownType::FORTRESS: //cage of warlords - what = PrimarySkill::DEFENSE; - val = 1; - mid = 585; - iw.components.push_back (Component(Component::PRIM_SKILL, 1, 1, 0)); - break; - } - break; + case BuildingSubID::KNOWLEDGE_VISITING_BONUS: //wall of knowledge + what = PrimarySkill::KNOWLEDGE; + val = 1; + mid = 581; + iw.components.push_back(Component(Component::PRIM_SKILL, 3, 1, 0)); + break; + + case BuildingSubID::SPELL_POWER_VISITING_BONUS: //order of fire + what = PrimarySkill::SPELL_POWER; + val = 1; + mid = 582; + iw.components.push_back(Component(Component::PRIM_SKILL, 2, 1, 0)); + break; + + case BuildingSubID::ATTACK_VISITING_BONUS: //hall of Valhalla + what = PrimarySkill::ATTACK; + val = 1; + mid = 584; + iw.components.push_back(Component(Component::PRIM_SKILL, 0, 1, 0)); + break; + + case BuildingSubID::EXPERIENCE_VISITING_BONUS: //academy of battle scholars + what = PrimarySkill::EXPERIENCE; + val = static_cast(h->calculateXp(1000)); + mid = 583; + iw.components.push_back(Component(Component::EXPERIENCE, 0, val, 0)); + break; + + case BuildingSubID::DEFENSE_VISITING_BONUS: //cage of warlords + what = PrimarySkill::DEFENSE; + val = 1; + mid = 585; + iw.components.push_back(Component(Component::PRIM_SKILL, 1, 1, 0)); + break; } assert(mid); iw.player = cb->getOwner(heroID); iw.text << VLC->generaltexth->allTexts[mid]; cb->showInfoDialog(&iw); - cb->changePrimSkill (cb->getHero(heroID), what, val); + cb->changePrimSkill(cb->getHero(heroID), what, val); town->addHeroToStructureVisitors(h, indexOnTV); } } diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 1d3645e64..b49b86e91 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -158,7 +158,7 @@ public: void setProperty(ui8 what, ui32 val) override; void onHeroVisit (const CGHeroInstance * h) const override; - CTownBonus (BuildingID index, CGTownInstance *TOWN); + CTownBonus (BuildingID index, BuildingSubID::EBuildingSubID subId, CGTownInstance *TOWN); CTownBonus () {}; template void serialize(Handler &h, const int version) @@ -262,6 +262,7 @@ public: 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 @@ -342,5 +343,7 @@ private: bool hasBuiltInOldWay(ETownType::ETownType type, BuildingID bid) const; bool addBonusImpl(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, const std::string & description, int subtype = -1); bool townEnvisagesBuilding(BuildingSubID::EBuildingSubID bid) const; - bool tryAddOnePerWeekBonus(BuildingSubID::EBuildingSubID subID); + void tryAddOnePerWeekBonus(BuildingSubID::EBuildingSubID subID); + void tryAddVisitingBonus(BuildingSubID::EBuildingSubID subID); + void addTownBonuses(); };