Pārlūkot izejas kodu

Merge pull request #4462 from IvanSavenko/building_refactor

Town buildings refactoring
Ivan Savenko 1 gadu atpakaļ
vecāks
revīzija
28789bc691
47 mainītis faili ar 1088 papildinājumiem un 1407 dzēšanām
  1. 0 11
      Mods/vcmi/config/vcmi/chinese.json
  2. 0 11
      Mods/vcmi/config/vcmi/czech.json
  3. 0 11
      Mods/vcmi/config/vcmi/english.json
  4. 0 11
      Mods/vcmi/config/vcmi/french.json
  5. 0 11
      Mods/vcmi/config/vcmi/german.json
  6. 0 11
      Mods/vcmi/config/vcmi/polish.json
  7. 2 12
      Mods/vcmi/config/vcmi/portuguese.json
  8. 0 11
      Mods/vcmi/config/vcmi/russian.json
  9. 0 11
      Mods/vcmi/config/vcmi/spanish.json
  10. 0 11
      Mods/vcmi/config/vcmi/ukrainian.json
  11. 0 11
      Mods/vcmi/config/vcmi/vietnamese.json
  12. 6 22
      client/windows/CCastleInterface.cpp
  13. 202 0
      config/buildingsLibrary.json
  14. 37 19
      config/factions/castle.json
  15. 17 17
      config/factions/conflux.json
  16. 16 18
      config/factions/dungeon.json
  17. 35 18
      config/factions/fortress.json
  18. 26 18
      config/factions/inferno.json
  19. 17 17
      config/factions/necropolis.json
  20. 25 17
      config/factions/rampart.json
  21. 14 15
      config/factions/stronghold.json
  22. 17 18
      config/factions/tower.json
  23. 3 0
      config/schemas/objectType.json
  24. 1 0
      config/schemas/rewardable.json
  25. 7 17
      config/schemas/townBuilding.json
  26. 113 57
      docs/modders/Entities_Format/Town_Building_Format.md
  27. 2 2
      lib/CMakeLists.txt
  28. 1 0
      lib/IGameCallback.cpp
  29. 1 16
      lib/constants/Enumerations.h
  30. 1 15
      lib/constants/StringConstants.h
  31. 1 19
      lib/entities/building/CBuilding.h
  32. 0 10
      lib/entities/faction/CTown.cpp
  33. 0 6
      lib/entities/faction/CTown.h
  34. 59 131
      lib/entities/faction/CTownHandler.cpp
  35. 5 8
      lib/entities/faction/CTownHandler.h
  36. 0 536
      lib/mapObjects/CGTownBuilding.cpp
  37. 0 149
      lib/mapObjects/CGTownBuilding.h
  38. 48 89
      lib/mapObjects/CGTownInstance.cpp
  39. 26 26
      lib/mapObjects/CGTownInstance.h
  40. 0 1
      lib/mapObjects/CRewardableObject.h
  41. 279 0
      lib/mapObjects/TownBuildingInstance.cpp
  42. 109 0
      lib/mapObjects/TownBuildingInstance.h
  43. 7 16
      lib/networkPacks/NetPacksLib.cpp
  44. 5 5
      lib/registerTypes/RegisterTypesMapObjects.h
  45. 2 1
      lib/serializer/ESerializationVersion.h
  46. 3 2
      server/CGameHandler.cpp
  47. 1 0
      test/spells/effects/CatapultTest.cpp

+ 0 - 11
Mods/vcmi/config/vcmi/chinese.json

@@ -285,17 +285,6 @@
 
 	"vcmi.townHall.missingBase"             : "必须先建造基础建筑 %s",
 	"vcmi.townHall.noCreaturesToRecruit"    : "没有可供招募的生物。",
-	"vcmi.townHall.greetingManaVortex"      : "接近%s时,你会全身充满活力,并且你的魔法值会加倍。",
-	"vcmi.townHall.greetingKnowledge"       : "你研究了%s的浮雕,洞察了魔法的秘密(知识+1)。",
-	"vcmi.townHall.greetingSpellPower"      : "%s教你如何运用魔法力量(力量+1)。",
-	"vcmi.townHall.greetingExperience"      : "参观%s可以让你学会许多新的技能(经验值+1000)。",
-	"vcmi.townHall.greetingAttack"          : "在%s中稍待片刻可以让你学会更有效的战斗技巧(攻击力+1)。",
-	"vcmi.townHall.greetingDefence"         : "在%s中稍待片刻,富有战斗经验的战士会教你防御技巧(防御力+1)。",
-	"vcmi.townHall.hasNotProduced"          : "本周%s并没有产生什么资源。",
-	"vcmi.townHall.hasProduced"             : "本周%s产生了%d个%s。",
-	"vcmi.townHall.greetingCustomBonus"     : "%s 给予英雄 +%d %s%s。",
-	"vcmi.townHall.greetingCustomUntil"     : "直到下一场战斗。",
-	"vcmi.townHall.greetingInTownMagicWell" : "%s使你的魔法值恢复到最大值。",
 
 	"vcmi.logicalExpressions.anyOf"  : "以下任一前提:",
 	"vcmi.logicalExpressions.allOf"  : "以下所有前提:",

+ 0 - 11
Mods/vcmi/config/vcmi/czech.json

@@ -278,17 +278,6 @@
 
 	"vcmi.townHall.missingBase"             : "Základní budova %s musí být postavena jako první",
 	"vcmi.townHall.noCreaturesToRecruit"    : "Žádné jednotky k vycvičení!",
-	"vcmi.townHall.greetingManaVortex"      : "Při pobytu u místa %s se vaše tělo naplnilo novou energií. Máte dvojnásobné množství maximální magické energie.",
-	"vcmi.townHall.greetingKnowledge"       : "Studujete glyfy na the %s a porozumíte fungování různých magií (+1 Znalosti).",
-	"vcmi.townHall.greetingSpellPower"      : "%s vás učí nové cesty zaměření vaší magické síly (+1 Síla kouzel).",
-	"vcmi.townHall.greetingExperience"      : "Návštěva %s vás naučila spoustu nových dovedností (+1000 zkušeností).",
-	"vcmi.townHall.greetingAttack"          : "Čas strávený poblíž místa zvaného %s vám dovolil se naučit efektivnější bojové dovednosti (+1 Útočná síla).",
-	"vcmi.townHall.greetingDefence"         : "Trávíte čas na místě zvaném %s, zkušení bojovníci vás u toho naučili nové metody obrany (+1 Obranná síla).",
-	"vcmi.townHall.hasNotProduced"          : "%s - zatím nic nevyrobeno.",
-	"vcmi.townHall.hasProduced"             : "%s - vyrobeno %d %s tento týden.",
-	"vcmi.townHall.greetingCustomBonus"     : "%s vám dává +%d %s%s",
-	"vcmi.townHall.greetingCustomUntil"     : " do další bitvy.",
-	"vcmi.townHall.greetingInTownMagicWell" : "%s - obnoveno na maximum vaši magickou energii.",
 
 	"vcmi.logicalExpressions.anyOf"  : "Něco z následujících:",
 	"vcmi.logicalExpressions.allOf"  : "Všechny následující:",

+ 0 - 11
Mods/vcmi/config/vcmi/english.json

@@ -292,17 +292,6 @@
 
 	"vcmi.townHall.missingBase"             : "Base building %s must be built first",
 	"vcmi.townHall.noCreaturesToRecruit"    : "There are no creatures to recruit!",
-	"vcmi.townHall.greetingManaVortex"      : "As you near the %s your body is filled with new energy. You have doubled your normal spell points.",
-	"vcmi.townHall.greetingKnowledge"       : "You study the glyphs on the %s and gain insight into the workings of various magics (+1 Knowledge).",
-	"vcmi.townHall.greetingSpellPower"      : "The %s teaches you new ways to focus your magical powers (+1 Power).",
-	"vcmi.townHall.greetingExperience"      : "A visit to the %s teaches you many new skills (+1000 Experience).",
-	"vcmi.townHall.greetingAttack"          : "Some time spent at the %s allows you to learn more effective combat skills (+1 Attack Skill).",
-	"vcmi.townHall.greetingDefence"         : "Spending time in the %s, the experienced warriors therein teach you additional defensive skills (+1 Defense).",
-	"vcmi.townHall.hasNotProduced"          : "The %s has not produced anything yet.",
-	"vcmi.townHall.hasProduced"             : "The %s produced %d %s this week.",
-	"vcmi.townHall.greetingCustomBonus"     : "%s gives you +%d %s%s",
-	"vcmi.townHall.greetingCustomUntil"     : " until next battle.",
-	"vcmi.townHall.greetingInTownMagicWell" : "%s has restored your spell points to maximum.",
 
 	"vcmi.townStructure.bank.borrow" : "You enter the bank. A banker sees you and says: \"We have made a special offer for you. You can take a loan of 2500 gold from us for 5 days. You will have to repay 500 gold every day.\"",
 	"vcmi.townStructure.bank.payBack" : "You enter the bank. A banker sees you and says: \"You have already got your loan. Pay it back before taking a new one.\"",

+ 0 - 11
Mods/vcmi/config/vcmi/french.json

@@ -135,17 +135,6 @@
 
 	"vcmi.townHall.missingBase"             : "Le bâtiment de base %s doit être construit avant",
 	"vcmi.townHall.noCreaturesToRecruit"    : "Il n'y a aucune créature à recruter !",
-	"vcmi.townHall.greetingManaVortex"      : "Alors que vous approchez du %s, votre corps est rempli d'une nouvelle énergie. Vous avez doublé vos points de sort normaux.",
-	"vcmi.townHall.greetingKnowledge"       : "Vous étudiez les glyphes sur le %s et découvrez le fonctionnement de diverses magies (+1 Connaissance).",
-	"vcmi.townHall.greetingSpellPower"      : "Le %s vous apprend de nouvelles façons de concentrer vos pouvoirs magiques (+1 Pouvoir).",
-	"vcmi.townHall.greetingExperience"      : "Une visite au %s vous apprend de nombreuses nouvelles compétences (+1000 Expérience).",
-	"vcmi.townHall.greetingAttack"          : "Un peu de temps passé au %s vous permet d'apprendre des compétences de combat plus efficaces (+1 compétence d'attaque).",
-	"vcmi.townHall.greetingDefence"         : "En passant du temps dans le %s, les guerriers expérimentés qui s'y trouvent vous enseignent des compétences défensives supplémentaires (+1 Défense).",
-	"vcmi.townHall.hasNotProduced"          : "Le %s n'a encore rien produit.",
-	"vcmi.townHall.hasProduced"             : "Le %s a produit %d %s cette semaine.",
-	"vcmi.townHall.greetingCustomBonus"     : "%s vous offre +%d %s%s",
-	"vcmi.townHall.greetingCustomUntil"     : " jusqu'à la prochaine bataille.",
-	"vcmi.townHall.greetingInTownMagicWell" : "%s a restauré vos points de sort au maximum.",
 
 	"vcmi.logicalExpressions.anyOf"  : "L'un des éléments suivants :",
 	"vcmi.logicalExpressions.allOf"  : "Tous les éléments suivants :",

+ 0 - 11
Mods/vcmi/config/vcmi/german.json

@@ -292,17 +292,6 @@
 
 	"vcmi.townHall.missingBase"             : "Basis Gebäude %s muss als erstes gebaut werden",
 	"vcmi.townHall.noCreaturesToRecruit"    : "Es gibt keine Kreaturen zu rekrutieren!",
-	"vcmi.townHall.greetingManaVortex"      : "Wenn Ihr Euch den %s nähert, wird Euer Körper mit neuer Energie gefüllt. Ihr habt Eure normalen Zauberpunkte verdoppelt.",
-	"vcmi.townHall.greetingKnowledge"       : "Ihr studiert die Glyphen auf dem %s und erhaltet Einblick in die Funktionsweise verschiedener Magie (+1 Wissen).",
-	"vcmi.townHall.greetingSpellPower"      : "Der %s lehrt Euch neue Wege, Eure magischen Kräfte zu bündeln (+1 Kraft).",
-	"vcmi.townHall.greetingExperience"      : "Ein Besuch bei den %s bringt Euch viele neue Fähigkeiten bei (+1000 Erfahrung).",
-	"vcmi.townHall.greetingAttack"          : "Nach einiger Zeit im %s könnt Ihr effizientere Kampffertigkeiten erlernen (+1 Angriffsfertigkeit).",
-	"vcmi.townHall.greetingDefence"         : "Wenn Ihr Zeit im %s verbringt, bringen Euch die erfahrenen Krieger dort zusätzliche Verteidigungsfähigkeiten bei (+1 Verteidigung).",
-	"vcmi.townHall.hasNotProduced"          : "Die %s hat noch nichts produziert.",
-	"vcmi.townHall.hasProduced"             : "Die %s hat diese Woche %d %s produziert.",
-	"vcmi.townHall.greetingCustomBonus"     : "%s gibt Ihnen +%d %s%s",
-	"vcmi.townHall.greetingCustomUntil"     : " bis zur nächsten Schlacht.",
-	"vcmi.townHall.greetingInTownMagicWell" : "%s hat Eure Zauberpunkte wieder auf das Maximum erhöht.",
 
 	"vcmi.logicalExpressions.anyOf"  : "Eines der folgenden:",
 	"vcmi.logicalExpressions.allOf"  : "Alles der folgenden:",

+ 0 - 11
Mods/vcmi/config/vcmi/polish.json

@@ -292,17 +292,6 @@
 
 	"vcmi.townHall.missingBase"             : "Podstawowy budynek %s musi zostać najpierw wybudowany",
 	"vcmi.townHall.noCreaturesToRecruit"    : "Brak stworzeń do rekrutacji!",
-	"vcmi.townHall.greetingManaVortex"      : "Zbliżając się do %s czujesz jak twoje ciało wypełnia energia. Ilość pkt. magii, które posiadasz, zwiększa się dwukrotnie.",
-	"vcmi.townHall.greetingKnowledge"       : "Studiując napisy na %s odkrywasz nowe aspekty stosowania magii (wiedza +1).",
-	"vcmi.townHall.greetingSpellPower"      : "Odwiedzając %s dowiadujesz się, jak zwiększyć potęgę swojej mocy magicznej (moc +1).",
-	"vcmi.townHall.greetingExperience"      : "Wizyta w %s zwiększa twoje doświadczenie (doświadczenie +1000).",
-	"vcmi.townHall.greetingAttack"          : "Krótka wizyka w %s umożliwia ci polepszenie technik walki (atak +1).",
-	"vcmi.townHall.greetingDefence"         : "Odwiedzasz %s. Doświadczeni żołnierze, którzy tam przebywają, uczą cię sztuki skutecznej obrony (obrona +1).",
-	"vcmi.townHall.hasNotProduced"          : "%s nic jeszcze nie wyprodukował.",
-	"vcmi.townHall.hasProduced"             : "%s wyprodukował w tym tygodniu: %d %s.",
-	"vcmi.townHall.greetingCustomBonus"     : "%s daje tobie +%d %s%s",
-	"vcmi.townHall.greetingCustomUntil"     : " do następnej bitwy.",
-	"vcmi.townHall.greetingInTownMagicWell" : "%s przywraca ci wszystkie punkty magii.",
 
 	"vcmi.townStructure.bank.borrow" : "Wchodzisz do banku. Bankier cię widzi i mówi: \"Złożyliśmy ci specjalną ofertę. Możesz wziąć od nas pożyczkę w wysokości 2500 złota na 5 dni. Będziesz musiał spłacać 500 złota każdego dnia.\"",
 	"vcmi.townStructure.bank.payBack" : "Wchodzisz do banku. Bankier cię widzi i mówi: „Już dostałeś pożyczkę. Spłać ją zanim weźmiesz nową.\"",

+ 2 - 12
Mods/vcmi/config/vcmi/portuguese.json

@@ -291,18 +291,8 @@
 	"vcmi.otherOptions.compactTownCreatureInfo.help" : "{Informações Compactas de Criaturas}\n\nMostra informações menores para criaturas da cidade no resumo da cidade (canto inferior esquerdo da tela da cidade).",
 
 	"vcmi.townHall.missingBase"             : "A construção base %s deve ser construída primeiro",
-	"vcmi.townHall.noCreaturesToRecruit"	: "Não há criaturas para recrutar!",
-	"vcmi.townHall.greetingManaVortex"	: "Ao se aproximar de %s, seu corpo é preenchido com nova energia. Você dobrou seus pontos de mana normais.",
-	"vcmi.townHall.greetingKnowledge"	: "Estudando os glifos de %s, você adquire uma visão dos segredos sobre o funcionamento de várias magias (+1 de Conhecimento).",
-	"vcmi.townHall.greetingSpellPower"	: "%s ensina novas maneiras de concentrar seus poderes mágicos (+1 de Força).",
-	"vcmi.townHall.greetingExperience"	: "Uma visita em %s ensina muitas habilidades novas (+1000 de Experiência).",
-	"vcmi.townHall.greetingAttack"		: "Algum tempo passado em %s permite que você aprenda habilidades de combate mais eficazes (+1 de Ataque).",
-	"vcmi.townHall.greetingDefence"		: "Ao passar um tempo em %s, os guerreiros experientes lá dentro te ensinam habilidades defensivas adicionais (+1 de Defesa).",
-	"vcmi.townHall.hasNotProduced"		: "%s ainda não produziu nada.",
-	"vcmi.townHall.hasProduced"		: "%s produziu %d %s nesta semana.",
-	"vcmi.townHall.greetingCustomBonus"	: "%s dá +%d %s%s",
-	"vcmi.townHall.greetingCustomUntil"	: " até a próxima batalha.",
-	"vcmi.townHall.greetingInTownMagicWell" : "%s restaurou seus pontos de mana para o máximo.",
+	"vcmi.townHall.noCreaturesToRecruit"    : "Não há criaturas para recrutar!",
+
 
 	"vcmi.logicalExpressions.anyOf"  : "Qualquer um dos seguintes:",
 	"vcmi.logicalExpressions.allOf"  : "Todos os seguintes:",

+ 0 - 11
Mods/vcmi/config/vcmi/russian.json

@@ -162,17 +162,6 @@
 
 	"vcmi.townHall.missingBase"             : "Сначала необходимо построить: %s",
 	"vcmi.townHall.noCreaturesToRecruit"    : "Нет существ для найма!",
-	"vcmi.townHall.greetingManaVortex"      : "Близ %s ваше тело наполняется новой силой. Ваша обычная магическая энергия ныне удвоена.",
-	"vcmi.townHall.greetingKnowledge"       : "Вы изучили знаки %s, на вас снизошло прозрение в деле магии (+1 Знания).",
-	"vcmi.townHall.greetingSpellPower"      : "В %s вас научили новым способам концентрации магической силы (+1 Силы)",
-	"vcmi.townHall.greetingExperience"      : "Посетив %s, вы узнали много нового (+1000 опыта).",
-	"vcmi.townHall.greetingAttack"          : "Пребывание в %s позволило вам лучше использовать боевые навыки (+1 Атаки).",
-	"vcmi.townHall.greetingDefence"         : "В %s искушенные воины преподали вам свои защитные умения (+1 Защиты).",
-	"vcmi.townHall.hasNotProduced"          : "В %s еще ничего не произведено.",
-	"vcmi.townHall.hasProduced"             : "В %s на этой неделе произведено: %d %s",
-	"vcmi.townHall.greetingCustomBonus"     : "%s дает вам +%d %s%s",
-	"vcmi.townHall.greetingCustomUntil"     : " до следующей битвы.",
-	"vcmi.townHall.greetingInTownMagicWell" : "%s восстанавливает ваши очки заклинаний до максимума.",
 
 	"vcmi.logicalExpressions.anyOf"  : "Любое из:",
 	"vcmi.logicalExpressions.allOf"  : "Все перечисленное:",

+ 0 - 11
Mods/vcmi/config/vcmi/spanish.json

@@ -219,17 +219,6 @@
 
 	"vcmi.townHall.missingBase"             : "Primero se debe construir el edificio base %s",
 	"vcmi.townHall.noCreaturesToRecruit"    : "¡No hay criaturas para reclutar!",
-	"vcmi.townHall.greetingManaVortex"      : "Al acercarte a %s, tu cuerpo se llena de nueva energía. Has duplicado tus puntos de hechizo normales.",
-	"vcmi.townHall.greetingKnowledge"       : "Estudias los glifos en %s y obtienes una visión de los entresijos de varias magias (+1 conocimiento).",
-	"vcmi.townHall.greetingSpellPower"      : "El %s te enseña nuevas formas de enfocar tus poderes mágicos (+1 Poder).",
-	"vcmi.townHall.greetingExperience"      : "Una visita a %s te enseña muchas habilidades nuevas (+1000 Experiencia).",
-	"vcmi.townHall.greetingAttack"          : "El tiempo dedicado en %s te permite aprender habilidades de combate más efectivas (+1 habilidad de ataque).",
-	"vcmi.townHall.greetingDefence"         : "Pasando tiempo en %s, los guerreros experimentados allí te enseñan habilidades defensivas adicionales (+1 Defensa).",
-	"vcmi.townHall.hasNotProduced"          : "%s aún no ha producido nada.",
-	"vcmi.townHall.hasProduced"             : "%s ha producido %d %s esta semana.",
-	"vcmi.townHall.greetingCustomBonus"     : "%s te da +%d %s%s",
-	"vcmi.townHall.greetingCustomUntil"     : " hasta la próxima batalla.",
-	"vcmi.townHall.greetingInTownMagicWell" : "%s ha restaurado tus puntos de hechizo al máximo.",
 
 	"vcmi.logicalExpressions.anyOf"  : "Cualquiera de lo siguiente:",
 	"vcmi.logicalExpressions.allOf"  : "Todo lo siguiente:",

+ 0 - 11
Mods/vcmi/config/vcmi/ukrainian.json

@@ -285,17 +285,6 @@
 
 	"vcmi.townHall.missingBase"             : "Спочатку необхідно звести початкову будівлю: %s",
 	"vcmi.townHall.noCreaturesToRecruit"    : "Немає істот, яких можна завербувати!",
-	"vcmi.townHall.greetingManaVortex"      : "Неподалік %s ваше тіло наповнюється новою силою. Ваша звична магічна енергія сьогодні подвоєна.",
-	"vcmi.townHall.greetingKnowledge"       : "Ви вивчили знаки на %s, і на вас зійшло прозріння у справах магії. (+1 Knowledge).",
-	"vcmi.townHall.greetingSpellPower"      : "В %s вас навчили новим методам концентрації магічної сили. (+1 Power).",
-	"vcmi.townHall.greetingExperience"      : "Відвідавши %s, ви дізналися багато нового. (+1000 Experience).",
-	"vcmi.townHall.greetingAttack"          : "Перебування у %s дозволило вам краще використовувати бойові навички (+1 Attack Skill).",
-	"vcmi.townHall.greetingDefence"         : "У %s досвідчені воїни виклали вам свої захисні вміння. (+1 Defense).",
-	"vcmi.townHall.hasNotProduced"          : "Поки що %s нічого не створило.",
-	"vcmi.townHall.hasProduced"             : "Цього тижня %s створило %d одиниць, цього разу це %s.",
-	"vcmi.townHall.greetingCustomBonus"     : "%s дає вам +%d %s%s",
-	"vcmi.townHall.greetingCustomUntil"     : " до наступної битви.",
-	"vcmi.townHall.greetingInTownMagicWell" : "%s повністю відновлює ваш запас очків магії.",
 
 	"vcmi.logicalExpressions.anyOf"  : "Будь-що з перерахованого:",
 	"vcmi.logicalExpressions.allOf"  : "Все з перерахованого:",

+ 0 - 11
Mods/vcmi/config/vcmi/vietnamese.json

@@ -159,17 +159,6 @@
 
   "vcmi.townHall.missingBase": "Căn cứ %s phải được xây trước",
   "vcmi.townHall.noCreaturesToRecruit": "Không có quái để chiêu mộ!",
-  "vcmi.townHall.greetingManaVortex": "%s giúp cơ thể bạn tràn đầy năng lượng mới. Bạn được gấp đôi năng lượng tối đa.",
-  "vcmi.townHall.greetingKnowledge": "Bạn học chữ khắc trên %s và thấu hiểu cách vận hành của nhiều ma thuật (+1 Trí).",
-  "vcmi.townHall.greetingSpellPower": "%s dạy bạn hướng mới tập trung sức mạnh ma thuật (+1 Lực).",
-  "vcmi.townHall.greetingExperience": "Viếng thăm %s dạy bạn nhiều kĩ năng mới (+1000 Kinh nghiệm).",
-  "vcmi.townHall.greetingAttack": "Thời gian ở %s giúp bạn học nhiều kĩ năng chiến đấu hiệu quả (+1 Công).",
-  "vcmi.townHall.greetingDefence": "Thời gian ở %s, các chiến binh lão luyện tại đó dạy bạn nhiều kĩ năng phòng thủ (+1 Thủ).",
-  "vcmi.townHall.hasNotProduced": "%s chưa tạo được cái gì.",
-  "vcmi.townHall.hasProduced": "%s tạo %d %s tuần này.",
-  "vcmi.townHall.greetingCustomBonus": "%s cho bạn +%d %s%s",
-  "vcmi.townHall.greetingCustomUntil": " đến trận đánh tiếp theo.",
-  "vcmi.townHall.greetingInTownMagicWell": "%s đã hồi phục năng lượng tối đa của bạn.",
 
   "vcmi.logicalExpressions.anyOf": "Bất kì cái sau:",
   "vcmi.logicalExpressions.allOf": "Tất cả cái sau:",

+ 6 - 22
client/windows/CCastleInterface.cpp

@@ -781,13 +781,6 @@ void CCastleBuildings::buildingClicked(BuildingID building, BuildingSubID::EBuil
 							enterBuilding(building);
 						break;
 
-				case BuildingSubID::BROTHERHOOD_OF_SWORD:
-						if(upgrades == BuildingID::TAVERN)
-							LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
-						else
-							enterBuilding(building);
-						break;
-
 				case BuildingSubID::CASTLE_GATE:
 						if (LOCPLINT->makingTurn)
 							enterCastleGate();
@@ -819,8 +812,11 @@ void CCastleBuildings::buildingClicked(BuildingID building, BuildingSubID::EBuil
 						break;
 
 				default:
+					if(upgrades == BuildingID::TAVERN)
+						LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
+					else
 						enterBuilding(building);
-						break;
+					break;
 				}
 				break;
 
@@ -939,20 +935,8 @@ void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID:
 	std::string hasNotProduced;
 	std::string hasProduced;
 
-	if(this->town->town->faction->getIndex() == ETownType::RAMPART)
-	{
-		hasNotProduced = CGI->generaltexth->allTexts[677];
-		hasProduced = CGI->generaltexth->allTexts[678];
-	}
-	else
-	{
-		auto buildingName = town->town->getSpecialBuilding(subID)->getNameTranslated();
-
-		hasNotProduced = std::string(CGI->generaltexth->translate("vcmi.townHall.hasNotProduced"));
-		hasProduced = std::string(CGI->generaltexth->translate("vcmi.townHall.hasProduced"));
-		boost::algorithm::replace_first(hasNotProduced, "%s", buildingName);
-		boost::algorithm::replace_first(hasProduced, "%s", buildingName);
-	}
+	hasNotProduced = CGI->generaltexth->allTexts[677];
+	hasProduced = CGI->generaltexth->allTexts[678];
 
 	bool isMysticPondOrItsUpgrade = subID == BuildingSubID::MYSTIC_POND
 		|| (upgrades != BuildingID::NONE

+ 202 - 0
config/buildingsLibrary.json

@@ -0,0 +1,202 @@
+{
+	"mageGuild1":     { "id" : 0 },
+	"mageGuild2":     { "id" : 1,  "upgrades" : "mageGuild1" },
+	"mageGuild3":     { "id" : 2,  "upgrades" : "mageGuild2" },
+	"mageGuild4":     { "id" : 3,  "upgrades" : "mageGuild3" },
+	"mageGuild5":     { "id" : 4,  "upgrades" : "mageGuild4" },
+	"tavern": {
+		"id" : 5,
+		"bonuses": [
+			{
+				"type": "MORALE",
+				"val": 1
+			}
+		]
+	},
+	"shipyard":       { "id" : 6 },
+	"fort":           { "id" : 7 },
+	"citadel":        { "id" : 8,  "upgrades" : "fort" },
+	"castle":         { "id" : 9,  "upgrades" : "citadel" },
+	
+	"villageHall": {
+		"id" : 10,
+		"mode" : "auto",
+		"produce": { "gold": 500 }
+	},
+
+	"townHall": {
+		"id" : 11, 
+		"upgrades" : "villageHall", 
+		"requires" : [ "tavern" ], 
+		"produce": { "gold": 1000 }
+	},
+	"cityHall": {
+		"id" : 12,
+		"upgrades" : "townHall",
+		"requires" : [ "allOf", [ "mageGuild1" ], [ "marketplace" ], [ "blacksmith" ] ],
+		"produce": { "gold": 2000 }
+	},
+	"capitol": {
+		"id" : 13,
+		"upgrades" : "cityHall",
+		"requires" : [ "castle" ],
+		"produce": { "gold": 4000 }
+	},
+
+	"marketplace":    { "id" : 14 },
+	"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ] },
+	"blacksmith":     { "id" : 16 },
+
+	// Previously hardcoded buildings that might be used by mods
+	// Section 1 - building with bonuses during sieges
+	"brotherhoodOfSword" : {
+		"bonuses": [
+			{
+				"type": "MORALE",
+				"val": 2
+			}
+		]
+	},
+	
+	"fountainOfFortune" : {
+		"bonuses": [
+			{
+				"type": "LUCK",
+				"val": 2
+			}
+		]
+	},
+	
+	"spellPowerGarrisonBonus" : {
+		"bonuses": [
+			{
+				"type": "PRIMARY_SKILL",
+				"subtype": "primarySkill.spellpower",
+				"val": 2
+			}
+		]
+	},
+	
+	"attackGarrisonBonus" : {
+		"bonuses": [
+			{
+				"type": "PRIMARY_SKILL",
+				"subtype": "primarySkill.attack",
+				"val": 2
+			}
+		]
+	},
+	
+	"defenseGarrisonBonus" : {
+		"bonuses": [
+			{
+				"type": "PRIMARY_SKILL",
+				"subtype": "primarySkill.defence",
+				"val": 2
+			}
+		]
+	},
+	
+	"lighthouse" : {
+		"bonuses": [
+			{
+				"propagator": "PLAYER_PROPAGATOR",
+				"type": "MOVEMENT",
+				"subtype": "heroMovementSea",
+				"val": 500
+			}
+		]
+	},
+
+	// Section 2 - buildings that are visitable by hero
+	"stables": {
+		"configuration" : {
+			"visitMode" : "bonus",
+			"rewards" : [
+				{
+					"message" : "@core.genrltxt.580",
+					"movePoints" : 400,
+					"bonuses" : [ { "type" : "MOVEMENT", "subtype" : "heroMovementLand",  "val" : 400, "valueType" : "ADDITIVE_VALUE", "duration" : "ONE_WEEK"} ]
+				}
+			]
+		}
+	},
+	"manaVortex": {
+		"configuration" : {
+			"resetParameters" : {
+				"period" : 7,
+				"visitors" : true
+			},
+			"visitMode" : "hero", // Should be 'once' to match (somewhat buggy) H3 logic
+			"rewards" : [
+				{
+					"limiter" : {
+						"noneOf" : [ { "manaPercentage" : 200 } ]
+					},
+					"message" : "@core.genrltxt.579",
+					"manaPercentage" : 200
+				}
+			]
+		}
+	},
+
+	"attackVisitingBonus": { 
+		"configuration" : {
+			"visitMode" : "hero",
+			"rewards" : [
+				{
+					"message" : "@core.genrltxt.584",
+					"primary" : { "attack" : 1 }
+				}
+			]
+		}
+	},
+	
+	"defenceVisitingBonus": {
+		"configuration" : {
+			"visitMode" : "hero",
+			"rewards" : [
+				{
+					"message" : "@core.genrltxt.585",
+					"primary" : { "defence" : 1 }
+				}
+			]
+		}
+	},
+	
+	"spellPowerVisitingBonus": {
+		"configuration" : {
+			"visitMode" : "hero",
+			"rewards" : [
+				{
+					"message" : "@core.genrltxt.582",
+					"primary" : { "spellpower" : 1 }
+				}
+			]
+		}
+	},
+
+	"knowledgeVisitingBonus": {
+		"configuration" : {
+			"visitMode" : "hero",
+			"rewards" : [
+				{
+					"message" : "@core.genrltxt.581",
+					"primary" : { "knowledge" : 1 }
+				}
+			]
+		}
+	},
+
+	"experienceVisitingBonus": {
+		"configuration" : {
+			"visitMode" : "hero",
+			"rewards" : [
+				{
+					"message" : "@core.genrltxt.583",
+					"heroExperience" : 1000
+				}
+			]
+		}
+	}
+}

+ 37 - 19
config/factions/castle.json

@@ -153,29 +153,38 @@
 
 			"buildings" :
 			{
-				"mageGuild1":     { "id" : 0 },
-				"mageGuild2":     { "id" : 1,  "upgrades" : "mageGuild1" },
-				"mageGuild3":     { "id" : 2,  "upgrades" : "mageGuild2" },
-				"mageGuild4":     { "id" : 3,  "upgrades" : "mageGuild3" },
-				"tavern":         { "id" : 5 },
-				"shipyard":       { "id" : 6 },
-				"fort":           { "id" : 7 },
-				"citadel":        { "id" : 8,  "upgrades" : "fort" },
-				"castle":         { "id" : 9,  "upgrades" : "citadel" },
-				"villageHall":    { "id" : 10, "mode" : "auto", "produce": { "gold": 500 } },
-				"townHall":       { "id" : 11, "upgrades" : "villageHall", "requires" : [ "tavern" ], "produce": { "gold": 1000 } },
-				"cityHall":       { "id" : 12, "upgrades" : "townHall", "requires" : [ "allOf", [ "mageGuild1" ], [ "marketplace" ], [ "blacksmith" ] ], "produce": { "gold": 2000 } },
-				"capitol":        { "id" : 13, "upgrades" : "cityHall", "requires" : [ "castle" ], "produce": { "gold": 4000 } },
-				"marketplace":    { "id" : 14 },
-				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ], "produce": { "ore": 1, "wood": 1 } },
-				"blacksmith":     { "id" : 16 },
+				"mageGuild1":     { },
+				"mageGuild2":     { },
+				"mageGuild3":     { },
+				"mageGuild4":     { },
+				"tavern":         { },
+				"shipyard":       { },
+				"fort":           { },
+				"citadel":        { },
+				"castle":         { },
+				"villageHall":    { },
+				"townHall":       { },
+				"cityHall":       { },
+				"capitol":        { },
+				"marketplace":    { },
+				"resourceSilo":   { "produce": { "ore": 1, "wood": 1 } },
+				"blacksmith":     { },
 
-				"special1":       { "type" : "lighthouse", "requires" : [ "shipyard" ] },
+				"special1":       { 
+					"bonuses": [
+						{
+							"propagator": "PLAYER_PROPAGATOR",
+							"type": "MOVEMENT",
+							"subtype": "heroMovementSea",
+							"val": 500
+						}
+					],
+					"requires" : [ "shipyard" ]
+				},
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl3" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl3", "requires" : [ "horde1" ], "mode" : "auto" },
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },
 				"special2":       {
-					"type" : "configurable",
 					"requires" : [ "dwellingLvl4" ],
 					"configuration" : {
 						"visitMode" : "bonus",
@@ -188,7 +197,16 @@
 						]
 					}
 				},
-				"special3":       { "type" : "brotherhoodOfSword", "upgrades" : "tavern" },
+				"special3": {
+					"upgradeReplacesBonuses" : true,
+					"bonuses": [
+						{
+							"type": "MORALE",
+							"val": 2
+						}
+					],
+					"upgrades" : "tavern"
+				},
 				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }, "bonuses": [ { "type": "MORALE", "val": 2, "propagator": "PLAYER_PROPAGATOR" } ] },
 
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },

+ 17 - 17
config/factions/conflux.json

@@ -157,23 +157,23 @@
 
 			"buildings" :
 			{
-				"mageGuild1":     { "id" : 0 },
-				"mageGuild2":     { "id" : 1,  "upgrades" : "mageGuild1" },
-				"mageGuild3":     { "id" : 2,  "upgrades" : "mageGuild2" },
-				"mageGuild4":     { "id" : 3,  "upgrades" : "mageGuild3" },
-				"mageGuild5":     { "id" : 4,  "upgrades" : "mageGuild4" },
-				"tavern":         { "id" : 5 },
-				"shipyard":       { "id" : 6 },
-				"fort":           { "id" : 7 },
-				"citadel":        { "id" : 8,  "upgrades" : "fort" },
-				"castle":         { "id" : 9,  "upgrades" : "citadel" },
-				"villageHall":    { "id" : 10, "mode" : "auto", "produce": { "gold": 500 } },
-				"townHall":       { "id" : 11, "upgrades" : "villageHall", "requires" : [ "tavern" ], "produce": { "gold": 1000 } },
-				"cityHall":       { "id" : 12, "upgrades" : "townHall", "requires" : [ "allOf", [ "mageGuild1" ], [ "marketplace" ], [ "blacksmith" ] ], "produce": { "gold": 2000 } },
-				"capitol":        { "id" : 13, "upgrades" : "cityHall", "requires" : [ "castle" ], "produce": { "gold": 4000 } },
-				"marketplace":    { "id" : 14 },
-				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ], "produce": { "mercury": 1 } },
-				"blacksmith":     { "id" : 16 },
+				"mageGuild1":     { },
+				"mageGuild2":     { },
+				"mageGuild3":     { },
+				"mageGuild4":     { },
+				"mageGuild5":     { },
+				"tavern":         { },
+				"shipyard":       { },
+				"fort":           { },
+				"citadel":        { },
+				"castle":         { },
+				"villageHall":    { },
+				"townHall":       { },
+				"cityHall":       { },
+				"capitol":        { },
+				"marketplace":    { },
+				"resourceSilo":   { "produce": { "mercury": 1 } },
+				"blacksmith":     { },
 
 				"special1":       { "type" : "artifactMerchant", "requires" : [ "marketplace" ] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1" },

+ 16 - 18
config/factions/dungeon.json

@@ -154,28 +154,27 @@
 
 			"buildings" :
 			{
-				"mageGuild1":     { "id" : 0 },
-				"mageGuild2":     { "id" : 1,  "upgrades" : "mageGuild1" },
-				"mageGuild3":     { "id" : 2,  "upgrades" : "mageGuild2" },
-				"mageGuild4":     { "id" : 3,  "upgrades" : "mageGuild3" },
-				"mageGuild5":     { "id" : 4,  "upgrades" : "mageGuild4" },
-				"tavern":         { "id" : 5 },
-				"fort":           { "id" : 7 },
-				"citadel":        { "id" : 8,  "upgrades" : "fort" },
-				"castle":         { "id" : 9,  "upgrades" : "citadel" },
-				"villageHall":    { "id" : 10, "mode" : "auto", "produce": { "gold": 500 } },
-				"townHall":       { "id" : 11, "upgrades" : "villageHall", "requires" : [ "tavern" ], "produce": { "gold": 1000 } },
-				"cityHall":       { "id" : 12, "upgrades" : "townHall", "requires" : [ "allOf", [ "mageGuild1" ], [ "marketplace" ], [ "blacksmith" ] ], "produce": { "gold": 2000 } },
-				"capitol":        { "id" : 13, "upgrades" : "cityHall", "requires" : [ "castle" ], "produce": { "gold": 4000 } },
-				"marketplace":    { "id" : 14 },
-				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ] },
-				"blacksmith":     { "id" : 16 },
+				"mageGuild1":     { },
+				"mageGuild2":     { },
+				"mageGuild3":     { },
+				"mageGuild4":     { },
+				"mageGuild5":     { },
+				"tavern":         { },
+				"fort":           { },
+				"citadel":        { },
+				"castle":         { },
+				"villageHall":    { },
+				"townHall":       { },
+				"cityHall":       { },
+				"capitol":        { },
+				"marketplace":    { },
+				"resourceSilo":   { "produce": { "sulfur": 1 } },
+				"blacksmith":     { },
 
 				"special1":       { "type" : "artifactMerchant", "requires" : [ "marketplace" ] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
 				"special2":       {
-					"type" : "configurable",
 					"requires" : [ "mageGuild1" ],
 					"configuration" : {
 						"resetParameters" : {
@@ -196,7 +195,6 @@
 				},
 				"special3":       { "type" : "portalOfSummoning" },
 				"special4":       {
-					"type" : "configurable",
 					"configuration" : {
 						"visitMode" : "hero",
 						"rewards" : [

+ 35 - 18
config/factions/fortress.json

@@ -153,24 +153,23 @@
 
 			"buildings" :
 			{
-				"mageGuild1":     { "id" : 0 },
-				"mageGuild2":     { "id" : 1,  "upgrades" : "mageGuild1" },
-				"mageGuild3":     { "id" : 2,  "upgrades" : "mageGuild2" },
-				"tavern":         { "id" : 5 },
-				"shipyard":       { "id" : 6 },
-				"fort":           { "id" : 7 },
-				"citadel":        { "id" : 8,  "upgrades" : "fort" },
-				"castle":         { "id" : 9,  "upgrades" : "citadel" },
-				"villageHall":    { "id" : 10, "mode" : "auto", "produce": { "gold": 500 } },
-				"townHall":       { "id" : 11, "upgrades" : "villageHall", "requires" : [ "tavern" ], "produce": { "gold": 1000 } },
-				"cityHall":       { "id" : 12, "upgrades" : "townHall", "requires" : [ "allOf", [ "mageGuild1" ], [ "marketplace" ], [ "blacksmith" ] ], "produce": { "gold": 2000 } },
-				"capitol":        { "id" : 13, "upgrades" : "cityHall", "requires" : [ "castle" ], "produce": { "gold": 4000 } },
-				"marketplace":    { "id" : 14 },
-				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ], "produce": { "wood": 1, "ore": 1 } },
-				"blacksmith":     { "id" : 16 },
+				"mageGuild1":     { },
+				"mageGuild2":     { },
+				"mageGuild3":     { },
+				"tavern":         { },
+				"shipyard":       { },
+				"fort":           { },
+				"citadel":        { },
+				"castle":         { },
+				"villageHall":    { },
+				"townHall":       { },
+				"cityHall":       { },
+				"capitol":        { },
+				"marketplace":    { },
+				"resourceSilo":   { "produce": { "wood": 1, "ore": 1 } },
+				"blacksmith":     { },
 
 				"special1":       {
-					"type" : "configurable",
 					"requires" : [ "allOf", [ "townHall" ], [ "special2" ] ],
 					"configuration" : {
 						"visitMode" : "hero",
@@ -185,8 +184,26 @@
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },
-				"special2":       { "type" : "defenseGarrisonBonus", "requires" : [ "fort" ] },
-				"special3":       { "type" : "attackGarrisonBonus", "requires" : [ "special2" ] },
+				"special2":       {
+					"bonuses": [
+						{
+							"type": "PRIMARY_SKILL",
+							"subtype": "primarySkill.defence",
+							"val": 2
+						}
+					],
+					"requires" : [ "fort" ]
+				},
+				"special3": {
+					"bonuses": [
+						{
+							"type": "PRIMARY_SKILL",
+							"subtype": "primarySkill.defence",
+							"val": 2
+						}
+					],
+					"requires" : [ "special2" ]
+				},
 				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }, 
 					"bonuses": [
 						{ "type": "PRIMARY_SKILL", "subtype": "primarySkill.attack", "val": 10 },

+ 26 - 18
config/factions/inferno.json

@@ -154,29 +154,37 @@
 
 			"buildings" :
 			{
-				"mageGuild1":     { "id" : 0 },
-				"mageGuild2":     { "id" : 1,  "upgrades" : "mageGuild1" },
-				"mageGuild3":     { "id" : 2,  "upgrades" : "mageGuild2" },
-				"mageGuild4":     { "id" : 3,  "upgrades" : "mageGuild3" },
-				"mageGuild5":     { "id" : 4,  "upgrades" : "mageGuild4" },
-				"tavern":         { "id" : 5 },
-				"fort":           { "id" : 7 },
-				"citadel":        { "id" : 8,  "upgrades" : "fort" },
-				"castle":         { "id" : 9,  "upgrades" : "citadel" },
-				"villageHall":    { "id" : 10, "mode" : "auto", "produce": { "gold": 500 } },
-				"townHall":       { "id" : 11, "upgrades" : "villageHall", "requires" : [ "tavern" ], "produce": { "gold": 1000 } },
-				"cityHall":       { "id" : 12, "upgrades" : "townHall", "requires" : [ "allOf", [ "mageGuild1" ], [ "marketplace" ], [ "blacksmith" ] ], "produce": { "gold": 2000 } },
-				"capitol":        { "id" : 13, "upgrades" : "cityHall", "requires" : [ "castle" ], "produce": { "gold": 4000 } },
-				"marketplace":    { "id" : 14 },
-				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ], "produce": { "mercury": 1 } },
-				"blacksmith":     { "id" : 16 },
+				"mageGuild1":     { },
+				"mageGuild2":     { },
+				"mageGuild3":     { },
+				"mageGuild4":     { },
+				"mageGuild5":     { },
+				"tavern":         { },
+				"fort":           { },
+				"citadel":        { },
+				"castle":         { },
+				"villageHall":    { },
+				"townHall":       { },
+				"cityHall":       { },
+				"capitol":        { },
+				"marketplace":    { },
+				"resourceSilo":   { "produce": { "mercury": 1 } },
+				"blacksmith":     { },
 
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
-				"special2":       { "type" : "spellPowerGarrisonBonus", "requires" : [ "fort" ] },
+				"special2":       {
+					"bonuses": [
+						{
+							"type": "PRIMARY_SKILL",
+							"subtype": "primarySkill.spellpower",
+							"val": 2
+						}
+					],
+					"requires" : [ "fort" ]
+				},
 				"special3":       { "type" : "castleGate", "requires" : [ "citadel" ] },
 				"special4":       {
-					"type" : "configurable",
 					"requires" : [ "mageGuild1" ],
 					"configuration" : {
 						"visitMode" : "hero",

+ 17 - 17
config/factions/necropolis.json

@@ -158,23 +158,23 @@
 
 			"buildings" :
 			{
-				"mageGuild1":     { "id" : 0 },
-				"mageGuild2":     { "id" : 1,  "upgrades" : "mageGuild1" },
-				"mageGuild3":     { "id" : 2,  "upgrades" : "mageGuild2" },
-				"mageGuild4":     { "id" : 3,  "upgrades" : "mageGuild3" },
-				"mageGuild5":     { "id" : 4,  "upgrades" : "mageGuild4" },
-				"tavern":         { "id" : 5 },
-				"shipyard":       { "id" : 6 },
-				"fort":           { "id" : 7 },
-				"citadel":        { "id" : 8,  "upgrades" : "fort" },
-				"castle":         { "id" : 9,  "upgrades" : "citadel" },
-				"villageHall":    { "id" : 10, "mode" : "auto", "produce": { "gold": 500 } },
-				"townHall":       { "id" : 11, "upgrades" : "villageHall", "requires" : [ "tavern" ], "produce": { "gold": 1000 } },
-				"cityHall":       { "id" : 12, "upgrades" : "townHall", "requires" : [ "allOf", [ "mageGuild1" ], [ "marketplace" ], [ "blacksmith" ] ], "produce": { "gold": 2000 } },
-				"capitol":        { "id" : 13, "upgrades" : "cityHall", "requires" : [ "castle" ], "produce": { "gold": 4000 } },
-				"marketplace":    { "id" : 14 },
-				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ], "produce": { "ore": 1, "wood": 1 } },
-				"blacksmith":     { "id" : 16 },
+				"mageGuild1":     { },
+				"mageGuild2":     { },
+				"mageGuild3":     { },
+				"mageGuild4":     { },
+				"mageGuild5":     { },
+				"tavern":         { },
+				"shipyard":       { },
+				"fort":           { },
+				"citadel":        { },
+				"castle":         { },
+				"villageHall":    { },
+				"townHall":       { },
+				"cityHall":       { },
+				"capitol":        { },
+				"marketplace":    { },
+				"resourceSilo":   { "produce": { "ore": 1, "wood": 1 } },
+				"blacksmith":     { },
 
 				"special1":       { "requires" : [ "fort" ], "bonuses": [ { "type": "DARKNESS", "val": 20  } ] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1", "requires" : [ "special3" ] },

+ 25 - 17
config/factions/rampart.json

@@ -157,27 +157,35 @@
 
 			"buildings" :
 			{
-				"mageGuild1":     { "id" : 0 },
-				"mageGuild2":     { "id" : 1,  "upgrades" : "mageGuild1" },
-				"mageGuild3":     { "id" : 2,  "upgrades" : "mageGuild2" },
-				"mageGuild4":     { "id" : 3,  "upgrades" : "mageGuild3" },
-				"mageGuild5":     { "id" : 4,  "upgrades" : "mageGuild4" },
-				"tavern":         { "id" : 5 },
-				"fort":           { "id" : 7 },
-				"citadel":        { "id" : 8,  "upgrades" : "fort" },
-				"castle":         { "id" : 9,  "upgrades" : "citadel" },
-				"villageHall":    { "id" : 10, "mode" : "auto", "produce": { "gold": 500 } },
-				"townHall":       { "id" : 11, "upgrades" : "villageHall", "requires" : [ "tavern" ], "produce": { "gold": 1000 } },
-				"cityHall":       { "id" : 12, "upgrades" : "townHall", "requires" : [ "allOf", [ "mageGuild1" ], [ "marketplace" ], [ "blacksmith" ] ], "produce": { "gold": 2000 } },
-				"capitol":        { "id" : 13, "upgrades" : "cityHall", "requires" : [ "castle" ], "produce": { "gold": 4000 } },
-				"marketplace":    { "id" : 14 },
-				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ], "produce": { "crystal": 1 } },
-				"blacksmith":     { "id" : 16 },
+				"mageGuild1":     { },
+				"mageGuild2":     { },
+				"mageGuild3":     { },
+				"mageGuild4":     { },
+				"mageGuild5":     { },
+				"tavern":         { },
+				"fort":           { },
+				"citadel":        { },
+				"castle":         { },
+				"villageHall":    { },
+				"townHall":       { },
+				"cityHall":       { },
+				"capitol":        { },
+				"marketplace":    { },
+				"resourceSilo":   { "produce": { "crystal": 1 } },
+				"blacksmith":     { },
 
 				"special1":       { "type" : "mysticPond" },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl2" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl2", "requires" : [ "horde1" ], "mode" : "auto" },
-				"special2":       { "type" : "fountainOfFortune", "upgrades" : "special1" },
+				"special2":       { 
+					"bonuses": [
+						{
+							"type": "LUCK",
+							"val": 2
+						}
+					],
+					"upgrades" : "special1"
+				},
 				"special3":       { "type" : "treasury", "requires" : [ "horde1" ] },
 				"horde2":         { "id" : 24, "upgrades" : "dwellingLvl5" },
 				"horde2Upgr":     { "id" : 25, "upgrades" : "dwellingUpLvl5", "requires" : [ "horde2" ], "mode" : "auto" },

+ 14 - 15
config/factions/stronghold.json

@@ -151,20 +151,20 @@
 
 			"buildings" :
 			{
-				"mageGuild1":     { "id" : 0 },
-				"mageGuild2":     { "id" : 1,  "upgrades" : "mageGuild1" },
-				"mageGuild3":     { "id" : 2,  "upgrades" : "mageGuild2" },
-				"tavern":         { "id" : 5 },
-				"fort":           { "id" : 7 },
-				"citadel":        { "id" : 8,  "upgrades" : "fort" },
-				"castle":         { "id" : 9,  "upgrades" : "citadel" },
-				"villageHall":    { "id" : 10, "mode" : "auto", "produce": { "gold": 500 } },
-				"townHall":       { "id" : 11, "upgrades" : "villageHall", "requires" : [ "tavern" ], "produce": { "gold": 1000 } },
-				"cityHall":       { "id" : 12, "upgrades" : "townHall", "requires" : [ "allOf", [ "mageGuild1" ], [ "marketplace" ], [ "blacksmith" ] ], "produce": { "gold": 2000 } },
-				"capitol":        { "id" : 13, "upgrades" : "cityHall", "requires" : [ "castle" ], "produce": { "gold": 4000 } },
-				"marketplace":    { "id" : 14 },
-				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ], "produce": { "ore": 1, "wood": 1 } },
-				"blacksmith":     { "id" : 16 },
+				"mageGuild1":     { },
+				"mageGuild2":     { },
+				"mageGuild3":     { },
+				"tavern":         { },
+				"fort":           { },
+				"citadel":        { },
+				"castle":         { },
+				"villageHall":    { },
+				"townHall":       { },
+				"cityHall":       { },
+				"capitol":        { },
+				"marketplace":    { },
+				"resourceSilo":   { "produce": { "ore": 1, "wood": 1 } },
+				"blacksmith":     { },
 
 				"special1":       { "type" : "escapeTunnel", "requires" : [ "fort" ] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl1" },
@@ -172,7 +172,6 @@
 				"special2":       { "type" : "freelancersGuild", "requires" : [ "marketplace" ] },
 				"special3":       { "type" : "ballistaYard", "requires" : [ "blacksmith" ] },
 				"special4":       { 
-					"type" : "configurable",
 					"requires" : [ "fort" ],
 					"configuration" : {
 						"visitMode" : "hero",

+ 17 - 18
config/factions/tower.json

@@ -152,30 +152,29 @@
 
 			"buildings" :
 			{
-				"mageGuild1":     { "id" : 0 },
-				"mageGuild2":     { "id" : 1,  "upgrades" : "mageGuild1" },
-				"mageGuild3":     { "id" : 2,  "upgrades" : "mageGuild2" },
-				"mageGuild4":     { "id" : 3,  "upgrades" : "mageGuild3" },
-				"mageGuild5":     { "id" : 4,  "upgrades" : "mageGuild4" },
-				"tavern":         { "id" : 5 },
-				"fort":           { "id" : 7 },
-				"citadel":        { "id" : 8,  "upgrades" : "fort" },
-				"castle":         { "id" : 9,  "upgrades" : "citadel" },
-				"villageHall":    { "id" : 10, "mode" : "auto", "produce" : { "gold": 500 } },
-				"townHall":       { "id" : 11, "upgrades" : "villageHall", "requires" : [ "tavern" ], "produce" : { "gold": 1000 } },
-				"cityHall":       { "id" : 12, "upgrades" : "townHall", "requires" : [ "allOf", [ "mageGuild1" ], [ "marketplace" ], [ "blacksmith" ] ], "produce": { "gold": 2000 } },
-				"capitol":        { "id" : 13, "upgrades" : "cityHall", "requires" : [ "castle" ], "produce" : { "gold": 4000 } },
-				"marketplace":    { "id" : 14 },
-				"resourceSilo":   { "id" : 15, "requires" : [ "marketplace" ], "produce" : { "gems": 1 } },
-				"blacksmith":     { "id" : 16 },
+				"mageGuild1":     { },
+				"mageGuild2":     { },
+				"mageGuild3":     { },
+				"mageGuild4":     { },
+				"mageGuild5":     { },
+				"tavern":         { },
+				"fort":           { },
+				"citadel":        { },
+				"castle":         { },
+				"villageHall":    { },
+				"townHall":       { },
+				"cityHall":       { },
+				"capitol":        { },
+				"marketplace":    { },
+				"resourceSilo":   { "produce" : { "gems": 1 } },
+				"blacksmith":     { },
 
 				"special1":       { "type" : "artifactMerchant", "requires" : [ "marketplace" ] },
 				"horde1":         { "id" : 18, "upgrades" : "dwellingLvl2" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl2", "requires" : [ "horde1" ], "mode" : "auto" },
-				"special2":       { "type" : "lookoutTower", "height" : "high", "requires" : [ "fort" ] },
+				"special2":       { "height" : "high", "requires" : [ "fort" ] },
 				"special3":       { "type" : "library", "requires" : [ "mageGuild1" ] },
 				"special4":       {
-					"type" : "configurable",
 					"requires" : [ "mageGuild1" ],
 					"configuration" : {
 						"visitMode" : "hero",

+ 3 - 0
config/schemas/objectType.json

@@ -34,6 +34,9 @@
 				}
 			}
 		},
+		"name" : {
+			"type" : "string"
+		},
 		"templates" : {
 			"type" : "object",
 			"additionalProperties" : {

+ 1 - 0
config/schemas/rewardable.json

@@ -318,6 +318,7 @@
 		"aiValue" : { },
 		"index" : { },
 		"base" : { },
+		"name" : { },
 		"rmg" : { },
 		"templates" : { },
 		"battleground" : { },

+ 7 - 17
config/schemas/townBuilding.json

@@ -36,7 +36,7 @@
 		},
 		"type" : {
 			"type" : "string",
-			"enum" : [ "mysticPond", "artifactMerchant", "freelancersGuild", "magicUniversity", "castleGate", "creatureTransformer", "portalOfSummoning", "ballistaYard", "lookoutTower", "library", "brotherhoodOfSword", "fountainOfFortune", "spellPowerGarrisonBonus", "attackGarrisonBonus", "defenseGarrisonBonus", "escapeTunnel", "lighthouse", "treasury", "thievesGuild", "bank", "configurable" ],
+			"enum" : [ "mysticPond", "artifactMerchant", "freelancersGuild", "magicUniversity", "castleGate", "creatureTransformer", "portalOfSummoning", "ballistaYard", "library", "escapeTunnel", "treasury", "thievesGuild", "bank" ],
 			"description" : "Subtype for some special buildings"
 		},
 		"mode" : {
@@ -57,8 +57,12 @@
 			"description" : "Optional, indicates that this building upgrades another base building",
 			"type" : "string"
 		},
+		"upgradeReplacesBonuses" : {
+			"description" : "If set to true, this building will replace all bonuses from base building, leaving only bonuses defined by this building",
+			"type" : "boolean"
+		},
 		"configuration" : {
-			"description" : "Configuration of building. Only used if 'type' is set to 'configurable'",
+			"description" : "Optional, configuration of building that can be activated by visiting hero",
 			"$ref" : "rewardable.json"
 		},
 		"cost" : {
@@ -89,23 +93,9 @@
 				"gems" :    { "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",
+			"description" : "Bonuses that are provided by this building in any town where this building has been built. Only affects town itself (including siege), to propagate effect to player or team please use bonus propagators",
 			"items" : { "$ref" : "bonus.json" }
 		}
 	}

+ 113 - 57
docs/modders/Entities_Format/Town_Building_Format.md

@@ -1,6 +1,6 @@
 # Town Building Format
 
-# Required data
+## Required data
 
 Each building requires following assets:
 
@@ -9,6 +9,107 @@ Each building requires following assets:
 -   Selection area (1 image)
 -   Town hall icon (1 image)
 
+## Examples
+These are just a couple of examples of what can be done in VCMI. See vcmi configuration files to check how buildings from Heroes III are implemented or other mods for more examples
+####
+
+##### Order of Fire from Inferno:
+```jsonc
+"special4": {
+    "requires" : [ "mageGuild1" ],
+	"name" : "Order of Fire",
+	"description" : "Increases spellpower of visiting hero",
+	"cost" : {
+	    "mercury" : 5,
+		"gold" : 1000
+	},
+	"configuration" : {
+	    "visitMode" : "hero",
+		"rewards" : [
+		    {
+			    // NOTE: this forces vcmi to load string from H3 text file. In order to define own string simply write your own message without '@' symbol
+				"message" : "@core.genrltxt.582", 
+				"primary" : { "spellpower" : 1 }
+			}
+		]
+	}
+}
+``` 
+
+##### Mana Vortex from Dungeon
+```jsonc
+"special2": {
+    "requires" : [ "mageGuild1" ],
+	"name" : "Mana Vortex",
+	"description" : "Doubles mana points of the first visiting hero each week",
+	"cost" : {
+	    "gold" : 5000
+	},
+	"configuration" : {
+	    "resetParameters" : {
+		    "period" : 7,
+			"visitors" : true
+		},
+		"visitMode" : "once",
+		"rewards" : [
+		    {
+			    "limiter" : {
+				    "noneOf" : [ { "manaPercentage" : 200 } ]
+				},
+				"message" : "As you near the mana vortex your body is filled with new energy. You have doubled your normal spell points.",
+				"manaPercentage" : 200
+			}
+		]
+	}
+}
+```
+
+#### Resource Silo with custom production
+```jsonc
+"resourceSilo": {
+    "name" : "Wood Resource Silo",
+	"description" : "Produces 2 wood every day",
+	"cost" : {
+	    "wood" : 10,
+		"gold" : 5000
+	},
+	"produce" : {
+	    "wood": 2
+	}
+},
+```
+
+#### Brotherhood of Sword - bonuses in siege
+```jsonc
+"special3": {
+	// replaces +1 Morale bonus from Tavern
+	"upgradeReplacesBonuses" : true, 
+	// Gives +2 bonus to morale to town (effective only during siege)
+	"bonuses": [
+		{
+			"type": "MORALE",
+			"val": 2
+		}
+	],
+	"upgrades" : "tavern"
+},
+```
+
+#### Lighthouse - bonus to all heroes under player control
+```jsonc
+"special1":       { 
+	"bonuses": [
+		{
+			"propagator": "PLAYER_PROPAGATOR", // bonus affects everything under player control
+			"type": "MOVEMENT",
+			"subtype": "heroMovementSea",
+			"val": 500 // +500 movement points
+		}
+	],
+	"requires" : [ "shipyard" ]
+},
+```
+
 ## Town Building node
 
 ```jsonc
@@ -55,7 +156,7 @@ Each building requires following assets:
 		"gold" : 2000
 	}, 
 
-	//determine how this building can be built. Possible values are:
+    //determine how this building can be built. Possible values are:
 	// normal  - default value. Fulfill requirements, use resources, spend one day
 	// auto    - building appears when all requirements are built
 	// special - building can not be built manually
@@ -65,11 +166,11 @@ Each building requires following assets:
 	// Buildings which bonuses should be overridden with bonuses of the current building
 	"overrides" : [ "anotherBuilding ]
 	
-	// Bonuses, provided by this special building on build using bonus system
-	"bonuses" : BONUS_FORMAT
+    // Bonuses provided by this special building if this building or any of its upgrades are constructed in town
+	"bonuses" : [ BONUS_FORMAT ]
 	
-	// Bonuses, provided by this special building on hero visit and applied to the visiting hero
-	"onVisitBonuses" : BONUS_FORMAT
+    // If set to true, this building will replace all bonuses from base building, leaving only bonuses defined by this building"
+	"upgradeReplacesBonuses" : false,
 }
 ```
 
@@ -94,7 +195,8 @@ Building requirements can be described using logical expressions:
 ```
 ### List of unique town buildings
 
-Following Heroes III buildings can be used as unique buildings for a town. Their functionality should be identical to a corresponding H3 building:
+#### Buildings from Heroes III
+Following Heroes III buildings can be used as unique buildings for a town. Their functionality should be identical to a corresponding H3 building. H3 buildings that are not present in this list contain no hardcoded functionality. See vcmi json configuration to see how such buildings can be implemented in a mod.
 - `mysticPond`
 - `artifactMerchant`
 - `freelancersGuild`
@@ -103,64 +205,18 @@ Following Heroes III buildings can be used as unique buildings for a town. Their
 - `creatureTransformer`
 - `portalOfSummoning`
 - `ballistaYard`
-- `stables`
-- `manaVortex`
-- `lookoutTower`
 - `library`
-- `brotherhoodOfSword`
-- `fountainOfFortune`
 - `escapeTunnel`
-- `lighthouse`
 - `treasury`
-- `spellPowerGarrisonBonus`
-- `attackGarrisonBonus`
-- `defenseGarrisonBonus`
 
+#### Buildings from other Heroes III mods
 Following HotA buildings can be used as unique building for a town. Functionality should match corresponding HotA building:
 - `thievesGuild`
 - `bank`
 
-In addition to above, it is possible to use same format as [Rewardable](../Map_Objects/Rewardable.md) map objects for town buildings. In order to do that, town building type must be set to `configurable` and configuration of a rewardable object must be placed into `configuration` node 
-
-Example 1 - Order of Fire from Inferno:
-```jsonc
-"special4": { // 
-	"type" : "configurable",
-	"requires" : [ "mageGuild1" ],
-	"configuration" : {
-		"visitMode" : "hero",
-		"rewards" : [
-			{
-				"message" : "@core.genrltxt.582", // NOTE: this forces vcmi to load string from H3 text file. In order to define own string simply write your own message without '@' symbol
-				"primary" : { "spellpower" : 1 }
-			}
-		]
-	}
-}
-``` 
+#### Custom buildings
+In addition to above, it is possible to use same format as [Rewardable](../Map_Objects/Rewardable.md) map objects for town buildings. In order to do that, configuration of a rewardable object must be placed into `configuration` json node in building config.
 
-Example 2 - Mana Vortex from Dungeon
-```jsonc
-"special2": {
-	"type" : "configurable",
-	"requires" : [ "mageGuild1" ],
-	"configuration" : {
-		"resetParameters" : {
-			"period" : 7,
-			"visitors" : true
-		},
-		"visitMode" : "once",
-		"rewards" : [
-			{
-				"limiter" : {
-					"noneOf" : [ { "manaPercentage" : 200 } ]
-				},
-				"message" : "@core.genrltxt.579",
-				"manaPercentage" : 200
-			}
-		]
-	}
-}
 ```
 
 ### Town Structure node
@@ -191,4 +247,4 @@ Example 2 - Mana Vortex from Dungeon
 	// If upgrade, this building will replace parent animation but will not alter its behaviour
 	"hidden" : false 
 }
-```
+```

+ 2 - 2
lib/CMakeLists.txt

@@ -122,7 +122,7 @@ set(lib_MAIN_SRCS
 	mapObjects/CGMarket.cpp
 	mapObjects/CGObjectInstance.cpp
 	mapObjects/CGPandoraBox.cpp
-	mapObjects/CGTownBuilding.cpp
+	mapObjects/TownBuildingInstance.cpp
 	mapObjects/CGTownInstance.cpp
 	mapObjects/CObjectHandler.cpp
 	mapObjects/CQuest.cpp
@@ -496,7 +496,7 @@ set(lib_MAIN_HEADERS
 	mapObjects/CGMarket.h
 	mapObjects/CGObjectInstance.h
 	mapObjects/CGPandoraBox.h
-	mapObjects/CGTownBuilding.h
+	mapObjects/TownBuildingInstance.h
 	mapObjects/CGTownInstance.h
 	mapObjects/CObjectHandler.h
 	mapObjects/CQuest.h

+ 1 - 0
lib/IGameCallback.cpp

@@ -27,6 +27,7 @@
 #include "mapObjectConstructors/AObjectTypeHandler.h"
 #include "mapObjectConstructors/CObjectClassesHandler.h"
 #include "mapObjects/CGMarket.h"
+#include "mapObjects/TownBuildingInstance.h"
 #include "mapObjects/CGTownInstance.h"
 #include "mapObjects/CObjectHandler.h"
 #include "mapObjects/CQuest.h"

+ 1 - 16
lib/constants/Enumerations.h

@@ -25,35 +25,20 @@ namespace BuildingSubID
 	{
 		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,
-		ATTACK_VISITING_BONUS,
 		MAGIC_UNIVERSITY,
-		SPELL_POWER_GARRISON_BONUS,
-		ATTACK_GARRISON_BONUS,
-		DEFENSE_GARRISON_BONUS,
-		DEFENSE_VISITING_BONUS,
-		SPELL_POWER_VISITING_BONUS,
-		KNOWLEDGE_VISITING_BONUS,
-		EXPERIENCE_VISITING_BONUS,
-		LIGHTHOUSE,
 		TREASURY,
 		THIEVES_GUILD,
-		BANK,
-		CUSTOM_VISITING_BONUS,
-		CUSTOM_VISITING_REWARD
+		BANK
 	};
 }
 

+ 1 - 15
lib/constants/StringConstants.h

@@ -185,26 +185,12 @@ namespace MappedKeys
 		{ "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 },
-		{ "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 },
 		{ "thievesGuild", BuildingSubID::THIEVES_GUILD },
-		{ "bank", BuildingSubID::BANK },
-		{ "configurable", BuildingSubID::CUSTOM_VISITING_REWARD}
+		{ "bank", BuildingSubID::BANK }
 	};
 
 	static const std::map<std::string, EMarketMode> MARKET_NAMES_TO_TYPES =

+ 1 - 19
lib/entities/building/CBuilding.h

@@ -38,9 +38,8 @@ 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<BuildingID> overrideBids; /// the building which bonuses should be overridden with bonuses of the current building
+	bool upgradeReplacesBonuses = false;
 	BonusList buildingBonuses;
-	BonusList onVisitBonuses;
 
 	Rewardable::Info rewardableObjectInfo; ///configurable rewards for special buildings
 
@@ -89,23 +88,6 @@ 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 ||
-			   subId == BuildingSubID::CUSTOM_VISITING_BONUS;
-	}
-
 	void addNewBonus(const std::shared_ptr<Bonus> & b, BonusList & bonusList) const;
 
 	friend class CTownHandler;

+ 0 - 10
lib/entities/faction/CTown.cpp

@@ -78,14 +78,4 @@ BuildingID CTown::getBuildingType(BuildingSubID::EBuildingSubID subID) const
 	return building == nullptr ? BuildingID::NONE : building->bid.num;
 }
 
-std::string CTown::getGreeting(BuildingSubID::EBuildingSubID subID) const
-{
-	return vstd::find_or(specialMessages, subID, std::string());
-}
-
-void CTown::setGreeting(BuildingSubID::EBuildingSubID subID, const std::string & message) const
-{
-	specialMessages.insert(std::pair<BuildingSubID::EBuildingSubID, const std::string>(subID, message));
-}
-
 VCMI_LIB_NAMESPACE_END

+ 0 - 6
lib/entities/faction/CTown.h

@@ -49,8 +49,6 @@ public:
 	std::string getBuildingScope() const;
 	std::set<si32> getAllBuildings() const;
 	const CBuilding * getSpecialBuilding(BuildingSubID::EBuildingSubID subID) const;
-	std::string getGreeting(BuildingSubID::EBuildingSubID subID) const;
-	void setGreeting(BuildingSubID::EBuildingSubID subID, const std::string & message) const; //may affect only mutable field
 	BuildingID getBuildingType(BuildingSubID::EBuildingSubID subID) const;
 
 	std::string getRandomNameTextID(size_t index) const;
@@ -106,10 +104,6 @@ public:
 		std::string towerIconLarge;
 
 	} clientInfo;
-
-private:
-	///generated bonusing buildings messages for all towns of this type.
-	mutable std::map<BuildingSubID::EBuildingSubID, const std::string> specialMessages; //may be changed by CGTownBuilding::getVisitingBonusGreeting() const
 };
 
 VCMI_LIB_NAMESPACE_END

+ 59 - 131
lib/entities/faction/CTownHandler.cpp

@@ -30,14 +30,16 @@
 #include "../../texts/CGeneralTextHandler.h"
 #include "../../texts/CLegacyConfigParser.h"
 #include "../../json/JsonBonus.h"
+#include "../../json/JsonUtils.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
 const int NAMES_PER_TOWN=16; // number of town names per faction in H3 files. Json can define any number
 
-CTownHandler::CTownHandler():
-	randomTown(new CTown()),
-	randomFaction(new CFaction())
+CTownHandler::CTownHandler()
+	: buildingsLibrary(JsonPath::builtin("config/buildingsLibrary"))
+	, randomTown(new CTown())
+	, randomFaction(new CFaction())
 {
 	randomFaction->town = randomTown;
 	randomTown->faction = randomFaction;
@@ -240,66 +242,7 @@ void CTownHandler::loadBuildingRequirements(CBuilding * building, const JsonNode
 	bidsToLoad.push_back(hlp);
 }
 
-void CTownHandler::addBonusesForVanilaBuilding(CBuilding * building) const
-{
-	std::shared_ptr<Bonus> b;
-	static const TPropagatorPtr playerPropagator = std::make_shared<CPropagatorNodeType>(CBonusSystemNode::ENodeTypes::PLAYER);
-
-	if(building->bid == BuildingID::TAVERN)
-	{
-		b = createBonus(building, BonusType::MORALE, +1);
-	}
-
-	switch(building->subId)
-	{
-	case BuildingSubID::BROTHERHOOD_OF_SWORD:
-		b = createBonus(building, BonusType::MORALE, +2);
-		building->overrideBids.insert(BuildingID::TAVERN);
-		break;
-	case BuildingSubID::FOUNTAIN_OF_FORTUNE:
-		b = createBonus(building, BonusType::LUCK, +2);
-		break;
-	case BuildingSubID::SPELL_POWER_GARRISON_BONUS:
-		b = createBonus(building, BonusType::PRIMARY_SKILL, +2, BonusSubtypeID(PrimarySkill::SPELL_POWER));
-		break;
-	case BuildingSubID::ATTACK_GARRISON_BONUS:
-		b = createBonus(building, BonusType::PRIMARY_SKILL, +2, BonusSubtypeID(PrimarySkill::ATTACK));
-		break;
-	case BuildingSubID::DEFENSE_GARRISON_BONUS:
-		b = createBonus(building, BonusType::PRIMARY_SKILL, +2, BonusSubtypeID(PrimarySkill::DEFENSE));
-		break;
-	case BuildingSubID::LIGHTHOUSE:
-		b = createBonus(building, BonusType::MOVEMENT, +500, BonusCustomSubtype::heroMovementSea, playerPropagator);
-		break;
-	}
-
-	if(b)
-		building->addNewBonus(b, building->buildingBonuses);
-}
-
-std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, BonusType type, int val) const
-{
-	return createBonus(build, type, val, BonusSubtypeID(), emptyPropagator());
-}
-
-std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, BonusType type, int val, BonusSubtypeID subtype) const
-{
-	return createBonus(build, type, val, subtype, emptyPropagator());
-}
-
-std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, BonusType type, int val, BonusSubtypeID subtype, const TPropagatorPtr & prop) const
-{
-	auto b = std::make_shared<Bonus>(BonusDuration::PERMANENT, type, BonusSource::TOWN_STRUCTURE, val, build->getUniqueTypeID(), subtype);
-
-	b->description.appendTextID(build->getNameTextID());
-
-	if(prop)
-		b->addPropagator(prop);
-
-	return b;
-}
-
-void CTownHandler::loadSpecialBuildingBonuses(const JsonNode & source, BonusList & bonusList, CBuilding * building)
+void CTownHandler::loadBuildingBonuses(const JsonNode & source, BonusList & bonusList, CBuilding * building) const
 {
 	for(const auto & b : source.Vector())
 	{
@@ -311,6 +254,8 @@ void CTownHandler::loadSpecialBuildingBonuses(const JsonNode & source, BonusList
 		bonus->description.appendTextID(building->getNameTextID());
 
 		//JsonUtils::parseBuildingBonus produces UNKNOWN type propagator instead of empty.
+		assert(bonus->propagator == nullptr || bonus->propagator->getPropagatorType() != CBonusSystemNode::ENodeTypes::UNKNOWN);
+
 		if(bonus->propagator != nullptr
 			&& bonus->propagator->getPropagatorType() == CBonusSystemNode::ENodeTypes::UNKNOWN)
 				bonus->addPropagator(emptyPropagator());
@@ -350,67 +295,35 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
 	VLC->generaltexth->registerString(source.getModScope(), ret->getNameTextID(), source["name"].String());
 	VLC->generaltexth->registerString(source.getModScope(), ret->getDescriptionTextID(), source["description"].String());
 
+	ret->subId = vstd::find_or(MappedKeys::SPECIAL_BUILDINGS, source["type"].String(), BuildingSubID::NONE);
 	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())
-		{
-			ret->subId = vstd::find_or(MappedKeys::SPECIAL_BUILDINGS, source["type"].String(), BuildingSubID::NONE);
-			addBonusesForVanilaBuilding(ret);
-		}
+	loadBuildingBonuses(source["bonuses"], ret->buildingBonuses, ret);
 
-		loadSpecialBuildingBonuses(source["onVisitBonuses"], ret->onVisitBonuses, ret);
+	if(!source["configuration"].isNull())
+		ret->rewardableObjectInfo.init(source["configuration"], ret->getBaseTextID());
 
-		if(!ret->onVisitBonuses.empty())
+	//MODS COMPATIBILITY FOR pre-1.6
+	if(ret->produce.empty() && ret->bid == BuildingID::RESOURCE_SILO)
+	{
+		logGlobal->warn("Resource silo in town '%s' does not produces any resources!", ret->town->faction->getJsonKey());
+		switch (ret->town->primaryRes.toEnum())
 		{
-			if(ret->subId == BuildingSubID::NONE)
-				ret->subId = BuildingSubID::CUSTOM_VISITING_BONUS;
-
-			for(auto & bonus : ret->onVisitBonuses)
-				bonus->sid = BonusSourceID(ret->getUniqueTypeID());
-		}
-		
-		if(ret->subId == BuildingSubID::CUSTOM_VISITING_REWARD)
-			ret->rewardableObjectInfo.init(source["configuration"], ret->getBaseTextID());
-	}
-	//MODS COMPATIBILITY FOR 0.96
-	if(!ret->produce.nonZero())
-	{
-		switch (ret->bid.toEnum()) {
-			break; case BuildingID::VILLAGE_HALL: ret->produce[EGameResID::GOLD] = 500;
-			break; case BuildingID::TOWN_HALL :   ret->produce[EGameResID::GOLD] = 1000;
-			break; case BuildingID::CITY_HALL :   ret->produce[EGameResID::GOLD] = 2000;
-			break; case BuildingID::CAPITOL :     ret->produce[EGameResID::GOLD] = 4000;
-			break; case BuildingID::GRAIL :       ret->produce[EGameResID::GOLD] = 5000;
-			break; case BuildingID::RESOURCE_SILO :
-			{
-				switch (ret->town->primaryRes.toEnum())
-				{
-					case EGameResID::GOLD:
-						ret->produce[ret->town->primaryRes] = 500;
-						break;
-					case EGameResID::WOOD_AND_ORE:
-						ret->produce[EGameResID::WOOD] = 1;
-						ret->produce[EGameResID::ORE] = 1;
-						break;
-					default:
-						ret->produce[ret->town->primaryRes] = 1;
-						break;
-				}
-			}
+			case EGameResID::GOLD:
+				ret->produce[ret->town->primaryRes] = 500;
+				break;
+			case EGameResID::WOOD_AND_ORE:
+				ret->produce[EGameResID::WOOD] = 1;
+				ret->produce[EGameResID::ORE] = 1;
+				break;
+			default:
+				ret->produce[ret->town->primaryRes] = 1;
+				break;
 		}
 	}
 	loadBuildingRequirements(ret, source["requires"], requirementsToLoad);
 
-	if(ret->bid.IsSpecialOrGrail())
-		loadBuildingRequirements(ret, source["overrides"], overriddenBidsToLoad);
-
 	if (!source["upgrades"].isNull())
 	{
 		// building id and upgrades can't be the same
@@ -895,10 +808,41 @@ void CTownHandler::loadCustom()
 	loadRandomFaction();
 }
 
+void CTownHandler::beforeValidate(JsonNode & object)
+{
+	if (object.Struct().count("town") == 0)
+		return;
+
+	const auto & inheritBuilding = [this](const std::string & name, JsonNode & target)
+	{
+		if (buildingsLibrary.Struct().count(name) == 0)
+			return;
+
+		JsonNode baseCopy(buildingsLibrary[name]);
+		baseCopy.setModScope(target.getModScope());
+		JsonUtils::inherit(target, baseCopy);
+	};
+
+	for (auto & building : object["town"]["buildings"].Struct())
+	{
+		inheritBuilding(building.first, building.second);
+		if (building.second.Struct().count("type"))
+			inheritBuilding(building.second["type"].String(), building.second);
+
+		// MODS COMPATIBILITY FOR pre-1.6
+		// convert old buildigns with onVisitBonuses into configurable building
+		if (building.second.Struct().count("onVisitBonuses"))
+		{
+			building.second["configuration"]["visitMode"] = JsonNode("bonus");
+			building.second["configuration"]["visitMode"]["rewards"][0]["message"] = building.second["description"];
+			building.second["configuration"]["visitMode"]["rewards"][0]["bonuses"] = building.second["onVisitBonuses"];
+		}
+	}
+}
+
 void CTownHandler::afterLoadFinalization()
 {
 	initializeRequirements();
-	initializeOverridden();
 	initializeWarMachines();
 }
 
@@ -929,22 +873,6 @@ void CTownHandler::initializeRequirements()
 	requirementsToLoad.clear();
 }
 
-void CTownHandler::initializeOverridden()
-{
-	for(auto & bidHelper : overriddenBidsToLoad)
-	{
-		auto jsonNode = bidHelper.json;
-		auto scope = bidHelper.town->getBuildingScope();
-
-		for(const auto & b : jsonNode.Vector())
-		{
-			auto bid = BuildingID(VLC->identifiers()->getIdentifier(scope, b).value());
-			bidHelper.building->overrideBids.insert(bid);
-		}
-	}
-	overriddenBidsToLoad.clear();
-}
-
 void CTownHandler::initializeWarMachines()
 {
 	// must be done separately after all objects are loaded

+ 5 - 8
lib/entities/faction/CTownHandler.h

@@ -25,6 +25,8 @@ class CTown;
 
 class DLL_LINKAGE CTownHandler : public CHandlerBase<FactionID, Faction, CFaction, FactionService>
 {
+	JsonNode buildingsLibrary;
+
 	struct BuildingRequirementsHelper
 	{
 		JsonNode json;
@@ -39,7 +41,6 @@ class DLL_LINKAGE CTownHandler : public CHandlerBase<FactionID, Faction, CFactio
 	static const TPropagatorPtr & emptyPropagator();
 
 	void initializeRequirements();
-	void initializeOverridden();
 	void initializeWarMachines();
 
 	/// loads CBuilding's into town
@@ -47,10 +48,6 @@ class DLL_LINKAGE CTownHandler : public CHandlerBase<FactionID, Faction, CFactio
 	void loadBuilding(CTown * town, const std::string & stringID, const JsonNode & source);
 	void loadBuildings(CTown * town, const JsonNode & source);
 
-	std::shared_ptr<Bonus> createBonus(CBuilding * build, BonusType type, int val) const;
-	std::shared_ptr<Bonus> createBonus(CBuilding * build, BonusType type, int val, BonusSubtypeID subtype) const;
-	std::shared_ptr<Bonus> createBonus(CBuilding * build, BonusType type, int val, BonusSubtypeID subtype, const TPropagatorPtr & prop) const;
-
 	/// loads CStructure's into town
 	void loadStructure(CTown & town, const std::string & stringID, const JsonNode & source) const;
 	void loadStructures(CTown & town, const JsonNode & source) const;
@@ -78,17 +75,17 @@ 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) const;
 
 	void loadCustom() override;
 	void afterLoadFinalization() override;
+	void beforeValidate(JsonNode & object) override;
 
 	std::set<FactionID> getDefaultAllowed() const;
 	std::set<FactionID> getAllowedFactions(bool withTown = true) const;
 
-	static void loadSpecialBuildingBonuses(const JsonNode & source, BonusList & bonusList, CBuilding * building);
-
 protected:
+
+	void loadBuildingBonuses(const JsonNode & source, BonusList & bonusList, CBuilding * building) const;
 	const std::vector<std::string> & getTypeNames() const override;
 	std::shared_ptr<CFaction> loadFromJson(const std::string & scope, const JsonNode & data, const std::string & identifier, size_t index) override;
 };

+ 0 - 536
lib/mapObjects/CGTownBuilding.cpp

@@ -1,536 +0,0 @@
-/*
- * CGTownBuilding.cpp, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-
-#include "StdInc.h"
-#include "CGTownBuilding.h"
-#include "CGTownInstance.h"
-#include "../texts/CGeneralTextHandler.h"
-#include "../IGameCallback.h"
-#include "../gameState/CGameState.h"
-#include "../mapObjects/CGHeroInstance.h"
-#include "../networkPacks/PacksForClient.h"
-#include "../entities/building/CBuilding.h"
-
-
-#include <vstd/RNG.h>
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-CGTownBuilding::CGTownBuilding(IGameCallback * cb)
-	: IObjectInterface(cb)
-	, town(nullptr)
-{}
-
-CGTownBuilding::CGTownBuilding(CGTownInstance * town)
-	: IObjectInterface(town->cb)
-	, town(town)
-{}
-
-PlayerColor CGTownBuilding::getOwner() const
-{
-	return town->getOwner();
-}
-
-MapObjectID CGTownBuilding::getObjGroupIndex() const
-{
-	return -1;
-}
-
-MapObjectSubID CGTownBuilding::getObjTypeIndex() const
-{
-	return 0;
-}
-
-int3 CGTownBuilding::visitablePos() const
-{
-	return town->visitablePos();
-}
-
-int3 CGTownBuilding::getPosition() const
-{
-	return town->getPosition();
-}
-
-std::string CGTownBuilding::getVisitingBonusGreeting() const
-{
-	auto bonusGreeting = town->getTown()->getGreeting(bType);
-
-	if(!bonusGreeting.empty())
-		return bonusGreeting;
-
-	switch(bType)
-	{
-	case BuildingSubID::MANA_VORTEX:
-		bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingManaVortex"));
-		break;
-	case BuildingSubID::KNOWLEDGE_VISITING_BONUS:
-		bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingKnowledge"));
-		break;
-	case BuildingSubID::SPELL_POWER_VISITING_BONUS:
-		bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingSpellPower"));
-		break;
-	case BuildingSubID::ATTACK_VISITING_BONUS:
-		bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingAttack"));
-		break;
-	case BuildingSubID::EXPERIENCE_VISITING_BONUS:
-		bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingExperience"));
-		break;
-	case BuildingSubID::DEFENSE_VISITING_BONUS:
-		bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingDefence"));
-		break;
-	}
-	auto buildingName = town->getTown()->getSpecialBuilding(bType)->getNameTranslated();
-
-	if(bonusGreeting.empty())
-	{
-		bonusGreeting = "Error: Bonus greeting for '%s' is not localized.";
-		logGlobal->error("'%s' building of '%s' faction has not localized bonus greeting.", buildingName, town->getTown()->faction->getNameTranslated());
-	}
-	boost::algorithm::replace_first(bonusGreeting, "%s", buildingName);
-	town->getTown()->setGreeting(bType, bonusGreeting);
-	return bonusGreeting;
-}
-
-std::string CGTownBuilding::getCustomBonusGreeting(const Bonus & bonus) const
-{
-	if(bonus.type == BonusType::TOWN_MAGIC_WELL)
-	{
-		MetaString wellGreeting = MetaString::createFromTextID("vcmi.townHall.greetingInTownMagicWell");
-
-		wellGreeting.replaceTextID(town->getTown()->getSpecialBuilding(bType)->getNameTextID());
-		return wellGreeting.toString();
-	}
-
-	MetaString greeting = MetaString::createFromTextID("vcmi.townHall.greetingCustomBonus");
-
-	std::string paramTextID;
-	std::string until;
-
-	if(bonus.type == BonusType::MORALE)
-		paramTextID = "core.genrltxt.384"; // Morale
-
-	if(bonus.type == BonusType::LUCK)
-		paramTextID = "core.genrltxt.385"; // Luck
-
-	greeting.replaceTextID(town->getTown()->getSpecialBuilding(bType)->getNameTextID());
-	greeting.replaceNumber(bonus.val);
-	greeting.replaceTextID(paramTextID);
-
-	if (bonus.duration == BonusDuration::ONE_BATTLE)
-		greeting.replaceTextID("vcmi.townHall.greetingCustomUntil");
-	else
-		greeting.replaceRawString(".");
-
-	return greeting.toString();
-}
-
-COPWBonus::COPWBonus(IGameCallback *cb)
-	: CGTownBuilding(cb)
-{}
-
-COPWBonus::COPWBonus(const BuildingID & bid, BuildingSubID::EBuildingSubID subId, CGTownInstance * cgTown)
-	: CGTownBuilding(cgTown)
-{
-	bID = bid;
-	bType = subId;
-	indexOnTV = static_cast<si32>(town->bonusingBuildings.size());
-}
-
-void COPWBonus::setProperty(ObjProperty what, ObjPropertyID identifier)
-{
-	switch (what)
-	{
-		case ObjProperty::VISITORS:
-			visitors.insert(identifier.as<ObjectInstanceID>());
-			break;
-		case ObjProperty::STRUCTURE_CLEAR_VISITORS:
-			visitors.clear();
-			break;
-	}
-}
-
-void COPWBonus::onHeroVisit (const CGHeroInstance * h) const
-{
-	ObjectInstanceID heroID = h->id;
-	if(town->hasBuilt(bID))
-	{
-		InfoWindow iw;
-		iw.player = h->tempOwner;
-
-		switch (this->bType)
-		{
-		case BuildingSubID::STABLES:
-			if(!h->hasBonusFrom(BonusSource::OBJECT_TYPE, BonusSourceID(Obj(Obj::STABLES)))) //does not stack with advMap Stables
-			{
-				GiveBonus gb;
-				gb.bonus = Bonus(BonusDuration::ONE_WEEK, BonusType::MOVEMENT, BonusSource::OBJECT_TYPE, 600, BonusSourceID(Obj(Obj::STABLES)), BonusCustomSubtype::heroMovementLand);
-				gb.id = heroID;
-				cb->giveHeroBonus(&gb);
-
-				cb->setMovePoints(heroID, 600, false);
-
-				iw.text.appendRawString(VLC->generaltexth->allTexts[580]);
-				cb->showInfoDialog(&iw);
-			}
-			break;
-
-		case BuildingSubID::MANA_VORTEX:
-			if(visitors.empty())
-			{
-				if(h->mana < h->manaLimit() * 2)
-				{
-					cb->setManaPoints (heroID, 2 * h->manaLimit());
-					//TODO: investigate line below
-					//cb->setObjProperty (town->id, ObjProperty::VISITED, true);
-					iw.text.appendRawString(getVisitingBonusGreeting());
-					cb->showInfoDialog(&iw);
-					town->addHeroToStructureVisitors(h, indexOnTV);
-				}
-			}
-			break;
-		}
-	}
-}
-
-CTownBonus::CTownBonus(IGameCallback *cb)
-	: CGTownBuilding(cb)
-{}
-
-CTownBonus::CTownBonus(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * cgTown)
-	: CGTownBuilding(cgTown)
-{
-	bID = index;
-	bType = subId;
-	indexOnTV = static_cast<si32>(town->bonusingBuildings.size());
-}
-
-void CTownBonus::setProperty(ObjProperty what, ObjPropertyID identifier)
-{
-	if(what == ObjProperty::VISITORS)
-		visitors.insert(identifier.as<ObjectInstanceID>());
-}
-
-void CTownBonus::onHeroVisit (const CGHeroInstance * h) const
-{
-	ObjectInstanceID heroID = h->id;
-	if(town->hasBuilt(bID) && visitors.find(heroID) == visitors.end())
-	{
-		si64 val = 0;
-		InfoWindow iw;
-		PrimarySkill what = PrimarySkill::NONE;
-
-		switch(bType)
-		{
-		case BuildingSubID::KNOWLEDGE_VISITING_BONUS: //wall of knowledge
-			what = PrimarySkill::KNOWLEDGE;
-			val = 1;
-			iw.components.emplace_back(ComponentType::PRIM_SKILL, PrimarySkill::KNOWLEDGE, 1);
-			break;
-
-		case BuildingSubID::SPELL_POWER_VISITING_BONUS: //order of fire
-			what = PrimarySkill::SPELL_POWER;
-			val = 1;
-			iw.components.emplace_back(ComponentType::PRIM_SKILL, PrimarySkill::SPELL_POWER, 1);
-			break;
-
-		case BuildingSubID::ATTACK_VISITING_BONUS: //hall of Valhalla
-			what = PrimarySkill::ATTACK;
-			val = 1;
-			iw.components.emplace_back(ComponentType::PRIM_SKILL, PrimarySkill::ATTACK, 1);
-			break;
-
-		case BuildingSubID::EXPERIENCE_VISITING_BONUS: //academy of battle scholars
-			what = PrimarySkill::EXPERIENCE;
-			val = static_cast<int>(h->calculateXp(1000));
-			iw.components.emplace_back(ComponentType::EXPERIENCE, val);
-			break;
-
-		case BuildingSubID::DEFENSE_VISITING_BONUS: //cage of warlords
-			what = PrimarySkill::DEFENSE;
-			val = 1;
-			iw.components.emplace_back(ComponentType::PRIM_SKILL, PrimarySkill::DEFENSE, 1);
-			break;
-
-		case BuildingSubID::CUSTOM_VISITING_BONUS:
-			const auto building = town->getTown()->buildings.at(bID);
-			if(!h->hasBonusFrom(BonusSource::TOWN_STRUCTURE, BonusSourceID(building->getUniqueTypeID())))
-			{
-				const auto & bonuses = building->onVisitBonuses;
-				applyBonuses(const_cast<CGHeroInstance *>(h), bonuses);
-			}
-			break;
-		}
-
-		if(what != PrimarySkill::NONE)
-		{
-			iw.player = cb->getOwner(heroID);
-				iw.text.appendRawString(getVisitingBonusGreeting());
-			cb->showInfoDialog(&iw);
-			if (what == PrimarySkill::EXPERIENCE)
-				cb->giveExperience(cb->getHero(heroID), val);
-			else
-				cb->changePrimSkill(cb->getHero(heroID), what, val);
-
-			town->addHeroToStructureVisitors(h, indexOnTV);
-		}
-	}
-}
-
-void CTownBonus::applyBonuses(CGHeroInstance * h, const BonusList & bonuses) const
-{
-	auto addToVisitors = false;
-
-	for(const auto & bonus : bonuses)
-	{
-		GiveBonus gb;
-		InfoWindow iw;
-
-		if(bonus->type == BonusType::TOWN_MAGIC_WELL)
-		{
-			if(h->mana >= h->manaLimit())
-				return;
-			cb->setManaPoints(h->id, h->manaLimit());
-			bonus->duration = BonusDuration::ONE_DAY;
-		}
-		gb.bonus = * bonus;
-		gb.id = h->id;
-		cb->giveHeroBonus(&gb);
-
-		if(bonus->duration == BonusDuration::PERMANENT)
-			addToVisitors = true;
-
-		iw.player = cb->getOwner(h->id);
-		iw.text.appendRawString(getCustomBonusGreeting(gb.bonus));
-		cb->showInfoDialog(&iw);
-	}
-	if(addToVisitors)
-		town->addHeroToStructureVisitors(h, indexOnTV);
-}
-
-CTownRewardableBuilding::CTownRewardableBuilding(IGameCallback *cb)
-	: CGTownBuilding(cb)
-{}
-
-CTownRewardableBuilding::CTownRewardableBuilding(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * cgTown, vstd::RNG & rand)
-	: CGTownBuilding(cgTown)
-{
-	bID = index;
-	bType = subId;
-	indexOnTV = static_cast<si32>(town->bonusingBuildings.size());
-	initObj(rand);
-}
-
-void CTownRewardableBuilding::initObj(vstd::RNG & rand)
-{
-	assert(town && town->town);
-	configuration = generateConfiguration(rand);
-}
-
-Rewardable::Configuration CTownRewardableBuilding::generateConfiguration(vstd::RNG & rand) const
-{
-	Rewardable::Configuration result;
-	auto building = town->town->buildings.at(bID);
-
-	building->rewardableObjectInfo.configureObject(result, rand, cb);
-	for(auto & rewardInfo : result.info)
-	{
-		for (auto & bonus : rewardInfo.reward.bonuses)
-		{
-			bonus.source = BonusSource::TOWN_STRUCTURE;
-			bonus.sid = BonusSourceID(building->getUniqueTypeID());
-		}
-	}
-	return result;
-}
-
-void CTownRewardableBuilding::newTurn(vstd::RNG & rand) const
-{
-	if (configuration.resetParameters.period != 0 && cb->getDate(Date::DAY) > 1 && ((cb->getDate(Date::DAY)-1) % configuration.resetParameters.period) == 0)
-	{
-		auto newConfiguration = generateConfiguration(rand);
-		cb->setRewardableObjectConfiguration(town->id, bID, newConfiguration);
-
-		if(configuration.resetParameters.visitors)
-		{
-			cb->setObjPropertyValue(town->id, ObjProperty::STRUCTURE_CLEAR_VISITORS, indexOnTV);
-		}
-	}
-}
-
-void CTownRewardableBuilding::setProperty(ObjProperty what, ObjPropertyID identifier)
-{
-	switch (what)
-	{
-		case ObjProperty::VISITORS:
-			visitors.insert(identifier.as<ObjectInstanceID>());
-			break;
-		case ObjProperty::STRUCTURE_CLEAR_VISITORS:
-			visitors.clear();
-			break;
-		case ObjProperty::REWARD_SELECT:
-			selectedReward = identifier.getNum();
-			break;
-	}
-}
-
-void CTownRewardableBuilding::heroLevelUpDone(const CGHeroInstance *hero) const
-{
-	grantRewardAfterLevelup(cb, configuration.info.at(selectedReward), town, hero);
-}
-
-void CTownRewardableBuilding::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const
-{
-	if(answer == 0)
-		return; // player refused
-	
-	if(visitors.find(hero->id) != visitors.end())
-		return; // query not for this building
-
-	if(answer > 0 && answer-1 < configuration.info.size())
-	{
-		auto list = getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT);
-		grantReward(list[answer - 1], hero);
-	}
-	else
-	{
-		throw std::runtime_error("Unhandled choice");
-	}
-}
-
-void CTownRewardableBuilding::grantReward(ui32 rewardID, const CGHeroInstance * hero) const
-{
-	town->addHeroToStructureVisitors(hero, indexOnTV);
-	
-	grantRewardBeforeLevelup(cb, configuration.info.at(rewardID), hero);
-	
-	// hero is not blocked by levelup dialog - grant remainder immediately
-	if(!cb->isVisitCoveredByAnotherQuery(town, hero))
-	{
-		grantRewardAfterLevelup(cb, configuration.info.at(rewardID), town, hero);
-	}
-}
-
-bool CTownRewardableBuilding::wasVisitedBefore(const CGHeroInstance * contextHero) const
-{
-	switch (configuration.visitMode)
-	{
-		case Rewardable::VISIT_UNLIMITED:
-			return false;
-		case Rewardable::VISIT_ONCE:
-			return !visitors.empty();
-		case Rewardable::VISIT_PLAYER:
-			return false; //not supported
-		case Rewardable::VISIT_BONUS:
-		{
-			const auto building = town->getTown()->buildings.at(bID);
-			return contextHero->hasBonusFrom(BonusSource::TOWN_STRUCTURE, BonusSourceID(building->getUniqueTypeID()));
-		}
-		case Rewardable::VISIT_HERO:
-			return visitors.find(contextHero->id) != visitors.end();
-		case Rewardable::VISIT_LIMITER:
-			return configuration.visitLimiter.heroAllowed(contextHero);
-		default:
-			return false;
-	}
-}
-
-void CTownRewardableBuilding::onHeroVisit(const CGHeroInstance *h) const
-{
-	auto grantRewardWithMessage = [&](int index) -> void
-	{
-		auto vi = configuration.info.at(index);
-		logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString());
-		
-		town->addHeroToStructureVisitors(h, indexOnTV); //adding to visitors
-
-		InfoWindow iw;
-		iw.player = h->tempOwner;
-		iw.text = vi.message;
-		vi.reward.loadComponents(iw.components, h);
-		iw.type = EInfoWindowMode::MODAL;
-		if(!iw.components.empty() || !iw.text.toString().empty())
-			cb->showInfoDialog(&iw);
-		
-		grantReward(index, h);
-	};
-	auto selectRewardsMessage = [&](const std::vector<ui32> & rewards, const MetaString & dialog) -> void
-	{
-		BlockingDialog sd(configuration.canRefuse, rewards.size() > 1);
-		sd.player = h->tempOwner;
-		sd.text = dialog;
-
-		if (rewards.size() > 1)
-			for (auto index : rewards)
-				sd.components.push_back(configuration.info.at(index).reward.getDisplayedComponent(h));
-
-		if (rewards.size() == 1)
-			configuration.info.at(rewards.front()).reward.loadComponents(sd.components, h);
-
-		cb->showBlockingDialog(&sd);
-	};
-	
-	if(!town->hasBuilt(bID))
-		return;
-
-	if(!wasVisitedBefore(h))
-	{
-		auto rewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT);
-
-		logGlobal->debug("Visiting object with %d possible rewards", rewards.size());
-		switch (rewards.size())
-		{
-			case 0: // no available rewards, e.g. visiting School of War without gold
-			{
-				auto emptyRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_NOT_AVAILABLE);
-				if (!emptyRewards.empty())
-					grantRewardWithMessage(emptyRewards[0]);
-				else
-					logMod->warn("No applicable message for visiting empty object!");
-				break;
-			}
-			case 1: // one reward. Just give it with message
-			{
-				if (configuration.canRefuse)
-					selectRewardsMessage(rewards, configuration.info.at(rewards.front()).message);
-				else
-					grantRewardWithMessage(rewards.front());
-				break;
-			}
-			default: // multiple rewards. Act according to select mode
-			{
-				switch (configuration.selectMode) {
-					case Rewardable::SELECT_PLAYER: // player must select
-						selectRewardsMessage(rewards, configuration.onSelect);
-						break;
-					case Rewardable::SELECT_FIRST: // give first available
-						grantRewardWithMessage(rewards.front());
-						break;
-					case Rewardable::SELECT_RANDOM: // give random
-						grantRewardWithMessage(*RandomGeneratorUtil::nextItem(rewards, cb->gameState()->getRandomGenerator()));
-						break;
-				}
-				break;
-			}
-		}
-	}
-	else
-	{
-		logGlobal->debug("Revisiting already visited object");
-
-		auto visitedRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_ALREADY_VISITED);
-		if (!visitedRewards.empty())
-			grantRewardWithMessage(visitedRewards[0]);
-		else
-			logMod->debug("No applicable message for visiting already visited object!");
-	}
-}
-
-
-VCMI_LIB_NAMESPACE_END

+ 0 - 149
lib/mapObjects/CGTownBuilding.h

@@ -1,149 +0,0 @@
-/*
- * CGTownBuilding.h, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-
-#pragma once
-
-#include "IObjectInterface.h"
-#include "../rewardable/Interface.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-class CGTownInstance;
-class CBuilding;
-
-class DLL_LINKAGE CGTownBuilding : public IObjectInterface
-{
-///basic class for town structures handled as map objects
-public:
-	CGTownBuilding(CGTownInstance * town);
-	CGTownBuilding(IGameCallback *cb);
-
-	si32 indexOnTV = 0; //identifies its index on towns vector
-	
-	CGTownInstance * town;
-
-	STRONG_INLINE
-	BuildingSubID::EBuildingSubID getBuildingSubtype() const
-	{
-		return bType;
-	}
-
-	STRONG_INLINE
-	const BuildingID & getBuildingType() const
-	{
-		return bID;
-	}
-
-	STRONG_INLINE
-	void setBuildingSubtype(BuildingSubID::EBuildingSubID subId)
-	{
-		bType = subId;
-	}
-
-	PlayerColor getOwner() const override;
-	MapObjectID getObjGroupIndex() const override;
-	MapObjectSubID getObjTypeIndex() const override;
-
-	int3 visitablePos() const override;
-	int3 getPosition() const override;
-
-	template <typename Handler> void serialize(Handler &h)
-	{
-		h & bID;
-		h & indexOnTV;
-		h & bType;
-	}
-
-protected:
-	BuildingID bID; //from buildig list
-	BuildingSubID::EBuildingSubID bType = BuildingSubID::NONE;
-
-	std::string getVisitingBonusGreeting() const;
-	std::string getCustomBonusGreeting(const Bonus & bonus) const;
-};
-
-class DLL_LINKAGE COPWBonus : public CGTownBuilding
-{///used for OPW bonusing structures
-public:
-	std::set<ObjectInstanceID> visitors;
-	void setProperty(ObjProperty what, ObjPropertyID identifier) override;
-	void onHeroVisit (const CGHeroInstance * h) const override;
-
-	COPWBonus(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * TOWN);
-	COPWBonus(IGameCallback *cb);
-
-	template <typename Handler> void serialize(Handler &h)
-	{
-		h & static_cast<CGTownBuilding&>(*this);
-		h & visitors;
-	}
-};
-
-class DLL_LINKAGE CTownBonus : public CGTownBuilding
-{
-///used for one-time bonusing structures
-///feel free to merge inheritance tree
-public:
-	std::set<ObjectInstanceID> visitors;
-	void setProperty(ObjProperty what, ObjPropertyID identifier) override;
-	void onHeroVisit (const CGHeroInstance * h) const override;
-
-	CTownBonus(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * TOWN);
-	CTownBonus(IGameCallback *cb);
-
-	template <typename Handler> void serialize(Handler &h)
-	{
-		h & static_cast<CGTownBuilding&>(*this);
-		h & visitors;
-	}
-
-private:
-	void applyBonuses(CGHeroInstance * h, const BonusList & bonuses) const;
-};
-
-class DLL_LINKAGE CTownRewardableBuilding : public CGTownBuilding, public Rewardable::Interface
-{
-	/// reward selected by player, no serialize
-	ui16 selectedReward = 0;
-		
-	std::set<ObjectInstanceID> visitors;
-	
-	bool wasVisitedBefore(const CGHeroInstance * contextHero) const;
-	
-	void grantReward(ui32 rewardID, const CGHeroInstance * hero) const;
-
-	Rewardable::Configuration generateConfiguration(vstd::RNG & rand) const;
-	
-public:
-	void setProperty(ObjProperty what, ObjPropertyID identifier) override;
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	
-	void newTurn(vstd::RNG & rand) const override;
-	
-	/// gives second part of reward after hero level-ups for proper granting of spells/mana
-	void heroLevelUpDone(const CGHeroInstance *hero) const override;
-	
-	void initObj(vstd::RNG & rand) override;
-	
-	/// applies player selection of reward
-	void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override;
-	
-	CTownRewardableBuilding(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * town, vstd::RNG & rand);
-	CTownRewardableBuilding(IGameCallback *cb);
-	
-	template <typename Handler> void serialize(Handler &h)
-	{
-		h & static_cast<CGTownBuilding&>(*this);
-		h & static_cast<Rewardable::Interface&>(*this);
-		h & visitors;
-	}
-};
-
-VCMI_LIB_NAMESPACE_END

+ 48 - 89
lib/mapObjects/CGTownInstance.cpp

@@ -10,7 +10,8 @@
 
 #include "StdInc.h"
 #include "CGTownInstance.h"
-#include "CGTownBuilding.h"
+
+#include "TownBuildingInstance.h"
 #include "../spells/CSpellHandler.h"
 #include "../bonuses/Bonus.h"
 #include "../battle/IBattleInfoCallback.h"
@@ -59,13 +60,13 @@ void CGTownInstance::setPropertyDer(ObjProperty what, ObjPropertyID identifier)
 	switch (what)
 	{
 		case ObjProperty::STRUCTURE_ADD_VISITING_HERO:
-			bonusingBuildings[identifier.getNum()]->setProperty(ObjProperty::VISITORS, visitingHero->id);
+			rewardableBuildings.at(identifier.getNum())->setProperty(ObjProperty::VISITORS, visitingHero->id);
 			break;
 		case ObjProperty::STRUCTURE_CLEAR_VISITORS:
-			bonusingBuildings[identifier.getNum()]->setProperty(ObjProperty::STRUCTURE_CLEAR_VISITORS, NumericID(0));
+			rewardableBuildings.at(identifier.getNum())->setProperty(ObjProperty::STRUCTURE_CLEAR_VISITORS, NumericID(0));
 			break;
 		case ObjProperty::STRUCTURE_ADD_GARRISONED_HERO: //add garrisoned hero to visitors
-			bonusingBuildings[identifier.getNum()]->setProperty(ObjProperty::VISITORS, garrisonHero->id);
+			rewardableBuildings.at(identifier.getNum())->setProperty(ObjProperty::VISITORS, garrisonHero->id);
 			break;
 		case ObjProperty::BONUS_VALUE_FIRST:
 			bonusValue.first = identifier.getNum();
@@ -254,8 +255,8 @@ CGTownInstance::CGTownInstance(IGameCallback *cb):
 
 CGTownInstance::~CGTownInstance()
 {
-	for (auto & elem : bonusingBuildings)
-		delete elem;
+	for (auto & elem : rewardableBuildings)
+		delete elem.second;
 }
 
 int CGTownInstance::spellsAtLevel(int level, bool checkGuild) const
@@ -283,8 +284,8 @@ void CGTownInstance::setOwner(const PlayerColor & player) const
 
 void CGTownInstance::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const
 {
-	for (auto building : bonusingBuildings)
-		building->blockingDialogAnswered(hero, answer);
+	for (auto building : rewardableBuildings)
+		building.second->blockingDialogAnswered(hero, answer); // FIXME: why call for every building?
 }
 
 void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const
@@ -369,42 +370,12 @@ bool CGTownInstance::townEnvisagesBuilding(BuildingSubID::EBuildingSubID subId)
 	return town->getBuildingType(subId) != BuildingID::NONE;
 }
 
-void CGTownInstance::initOverriddenBids()
-{
-	for(const auto & bid : builtBuildings)
-	{
-		const auto & overrideThem = town->buildings.at(bid)->overrideBids;
-
-		for(const auto & overrideIt : overrideThem)
-			overriddenBuildings.insert(overrideIt);
-	}
-}
-
-bool CGTownInstance::isBonusingBuildingAdded(BuildingID bid) const
-{
-	auto present = std::find_if(bonusingBuildings.begin(), bonusingBuildings.end(), [&](CGTownBuilding* building)
-		{
-			return building->getBuildingType() == bid;
-		});
-
-	return present != bonusingBuildings.end();
-}
-
-void CGTownInstance::addTownBonuses(vstd::RNG & rand)
+void CGTownInstance::initializeConfigurableBuildings(vstd::RNG & rand)
 {
 	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));
-
-		if(kvp.second->IsWeekBonus())
-			bonusingBuildings.push_back(new COPWBonus(kvp.second->bid, kvp.second->subId, this));
-		
-		if(kvp.second->subId == BuildingSubID::CUSTOM_VISITING_REWARD)
-			bonusingBuildings.push_back(new CTownRewardableBuilding(kvp.second->bid, kvp.second->subId, this, rand));
+		if(!kvp.second->rewardableObjectInfo.getParameters().isNull())
+			rewardableBuildings[kvp.first] = new TownRewardableBuildingInstance(this, kvp.second->bid, rand);
 	}
 }
 
@@ -444,34 +415,6 @@ DamageRange CGTownInstance::getKeepDamageRange() const
 	};
 }
 
-void CGTownInstance::deleteTownBonus(BuildingID 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);
-
-	delete freeIt;
-}
-
 FactionID CGTownInstance::randomizeFaction(vstd::RNG & rand)
 {
 	if(getOwner().isValidPlayer())
@@ -526,8 +469,7 @@ void CGTownInstance::initObj(vstd::RNG & rand) ///initialize town structures
 				creatures[level].second.push_back(town->creatures[level][upgradeNum]);
 		}
 	}
-	initOverriddenBids();
-	addTownBonuses(rand); //add special bonuses from buildings to the bonusingBuildings vector.
+	initializeConfigurableBuildings(rand);
 	recreateBuildingsBonuses();
 	updateAppearance();
 }
@@ -549,9 +491,6 @@ void CGTownInstance::newTurn(vstd::RNG & rand) const
 			cb->setObjPropertyValue(id, ObjProperty::BONUS_VALUE_FIRST, resID);
 			cb->setObjPropertyValue(id, ObjProperty::BONUS_VALUE_SECOND, resVal);
 		}
-		
-		for(const auto * manaVortex : getBonusingBuildings(BuildingSubID::MANA_VORTEX))
-			cb->setObjPropertyValue(id, ObjProperty::STRUCTURE_CLEAR_VISITORS, manaVortex->indexOnTV); //reset visitors for Mana Vortex
 
 		if (tempOwner == PlayerColor::NEUTRAL) //garrison growth for neutral towns
 		{
@@ -606,8 +545,8 @@ void CGTownInstance::newTurn(vstd::RNG & rand) const
 		}
 	}
 	
-	for(const auto * rewardableBuilding : getBonusingBuildings(BuildingSubID::CUSTOM_VISITING_REWARD))
-		rewardableBuilding->newTurn(rand);
+	for(const auto & building : rewardableBuildings)
+		building.second->newTurn(rand);
 		
 	if(hasBuilt(BuildingSubID::BANK) && bonusValue.second > 0)
 	{
@@ -852,9 +791,21 @@ void CGTownInstance::recreateBuildingsBonuses()
 	for(const auto & b : bl)
 		removeBonus(b);
 
+
+
 	for(const auto & bid : builtBuildings)
 	{
-		if(vstd::contains(overriddenBuildings, bid)) //tricky! -> checks tavern only if no bratherhood of sword
+		bool bonusesReplacedByUpgrade = false;
+
+		for(const auto & upgradeID : builtBuildings)
+		{
+			const auto & upgrade = town->buildings.at(upgradeID);
+			if (upgrade->getBase() == bid && upgrade->upgradeReplacesBonuses)
+				bonusesReplacedByUpgrade = true;
+		}
+
+		// bonuses from this building are disabled and replaced by bonuses from an upgrade
+		if (bonusesReplacedByUpgrade)
 			continue;
 
 		auto building = town->buildings.at(bid);
@@ -979,18 +930,6 @@ const CArmedInstance * CGTownInstance::getUpperArmy() const
 	return this;
 }
 
-std::vector<const CGTownBuilding *> CGTownInstance::getBonusingBuildings(BuildingSubID::EBuildingSubID subId) const
-{
-	std::vector<const CGTownBuilding *> ret;
-	for(auto * const building : bonusingBuildings)
-	{
-		if(building->getBuildingSubtype() == subId)
-			ret.push_back(building);
-	}
-	return ret;
-}
-
-
 bool CGTownInstance::hasBuiltSomeTradeBuilding() const
 {
 	for(const auto & bid : builtBuildings)
@@ -1295,4 +1234,24 @@ void CGTownInstance::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &s
 	}
 }
 
+void CGTownInstance::postDeserialize()
+{
+	setNodeType(CBonusSystemNode::TOWN);
+	for(auto & building : rewardableBuildings)
+		building.second->town = this;
+}
+
+std::map<BuildingID, TownRewardableBuildingInstance*> CGTownInstance::convertOldBuildings(std::vector<TownRewardableBuildingInstance*> oldVector)
+{
+	std::map<BuildingID, TownRewardableBuildingInstance*> result;
+
+	for(auto & building : oldVector)
+	{
+		result[building->getBuildingType()] = new TownRewardableBuildingInstance(*building);
+		delete building;
+	}
+
+	return result;
+}
+
 VCMI_LIB_NAMESPACE_END

+ 26 - 26
lib/mapObjects/CGTownInstance.h

@@ -11,16 +11,19 @@
 
 #include "IMarket.h"
 #include "CGDwelling.h"
-#include "CGTownBuilding.h"
-#include "../LogicalExpression.h"
 #include "../entities/faction/CFaction.h" // TODO: remove
 #include "../entities/faction/CTown.h" // TODO: remove
 
 VCMI_LIB_NAMESPACE_BEGIN
 
 class CCastleEvent;
+class CTown;
+class TownBuildingInstance;
+class TownRewardableBuildingInstance;
 struct DamageRange;
 
+template<typename ContainedClass>
+class LogicalExpression;
 
 class DLL_LINKAGE CTownAndVisitingHero : public CBonusSystemNode
 {
@@ -47,6 +50,8 @@ struct DLL_LINKAGE GrowthInfo
 class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public IMarket, public INativeTerrainProvider, public ICreatureUpgrader
 {
 	std::string nameTextId; // name of town
+
+	std::map<BuildingID, TownRewardableBuildingInstance*> convertOldBuildings(std::vector<TownRewardableBuildingInstance*> oldVector);
 public:
 	using CGDwelling::getPosition;
 
@@ -61,8 +66,7 @@ public:
 	PlayerColor alignmentToPlayer; // if set to non-neutral, random town will have same faction as specified player
 	std::set<BuildingID> forbiddenBuildings;
 	std::set<BuildingID> builtBuildings;
-	std::set<BuildingID> overriddenBuildings; ///buildings which bonuses are overridden and should not be applied
-	std::vector<CGTownBuilding*> bonusingBuildings;
+	std::map<BuildingID, TownRewardableBuildingInstance*> rewardableBuildings;
 	std::vector<SpellID> possibleSpells, obligatorySpells;
 	std::vector<std::vector<SpellID> > spells; //spells[level] -> vector of spells, first will be available in guild
 	std::vector<CCastleEvent> events;
@@ -86,11 +90,18 @@ public:
 		h & obligatorySpells;
 		h & spells;
 		h & events;
-		h & bonusingBuildings;
-		
-		for(auto * bonusingBuilding : bonusingBuildings)
-			bonusingBuilding->town = this;
-		
+
+		if (h.version >= Handler::Version::NEW_TOWN_BUILDINGS)
+		{
+			h & rewardableBuildings;
+		}
+		else
+		{
+			std::vector<TownRewardableBuildingInstance*> oldVector;
+			h & oldVector;
+			rewardableBuildings = convertOldBuildings(oldVector);
+		}
+
 		if (h.saving)
 		{
 			CFaction * faction = town ? town->faction : nullptr;
@@ -106,23 +117,14 @@ public:
 		h & townAndVis;
 		BONUS_TREE_DESERIALIZATION_FIX
 
-		if(town)
+		if (h.version < Handler::Version::NEW_TOWN_BUILDINGS)
 		{
-			vstd::erase_if(builtBuildings, [this](BuildingID building) -> bool
-			{
-				if(!town->buildings.count(building) || !town->buildings.at(building))
-				{
-					logGlobal->error("#1444-like issue in CGTownInstance::serialize. From town %s at %s removing the bogus builtBuildings item %s", nameTextId, pos.toString(), building);
-					return true;
-				}
-				return false;
-			});
+			std::set<BuildingID> overriddenBuildings;
+			h & overriddenBuildings;
 		}
 
-		h & overriddenBuildings;
-
 		if(!h.saving)
-			this->setNodeType(CBonusSystemNode::TOWN);
+			postDeserialize();
 	}
 	//////////////////////////////////////////////////////////////////////////
 
@@ -130,6 +132,7 @@ public:
 	std::string nodeName() const override;
 	void updateMoraleBonusFromArmy() override;
 	void deserializationFix();
+	void postDeserialize();
 	void recreateBuildingsBonuses();
 	void setVisitingHero(CGHeroInstance *h);
 	void setGarrisonedHero(CGHeroInstance *h);
@@ -165,7 +168,6 @@ public:
 	GrowthInfo getGrowthInfo(int level) const;
 	bool hasFort() const;
 	bool hasCapitol() const;
-	std::vector<const CGTownBuilding *> getBonusingBuildings(BuildingSubID::EBuildingSubID subId) const;
 	bool hasBuiltSomeTradeBuilding() const;
 	//checks if special building with type buildingID is constructed
 	bool hasBuilt(BuildingSubID::EBuildingSubID buildingID) const;
@@ -231,9 +233,7 @@ private:
 	void onTownCaptured(const PlayerColor & winner) const;
 	int getDwellingBonus(const std::vector<CreatureID>& creatureIds, const std::vector<ConstTransitivePtr<CGDwelling> >& dwellings) const;
 	bool townEnvisagesBuilding(BuildingSubID::EBuildingSubID bid) const;
-	bool isBonusingBuildingAdded(BuildingID bid) const;
-	void initOverriddenBids();
-	void addTownBonuses(vstd::RNG & rand);
+	void initializeConfigurableBuildings(vstd::RNG & rand);
 };
 
 VCMI_LIB_NAMESPACE_END

+ 0 - 1
lib/mapObjects/CRewardableObject.h

@@ -95,7 +95,6 @@ public:
 // class DLL_LINKAGE CGPyramid : public CBank
 
 // EXTRA
-// class DLL_LINKAGE COPWBonus : public CGTownBuilding
 // class DLL_LINKAGE CTownBonus : public CGTownBuilding
 // class DLL_LINKAGE CGKeys : public CGObjectInstance //Base class for Keymaster and guards
 // class DLL_LINKAGE CGKeymasterTent : public CGKeys

+ 279 - 0
lib/mapObjects/TownBuildingInstance.cpp

@@ -0,0 +1,279 @@
+/*
+ * TownBuildingInstance.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "TownBuildingInstance.h"
+
+#include "CGTownInstance.h"
+#include "../texts/CGeneralTextHandler.h"
+#include "../IGameCallback.h"
+#include "../gameState/CGameState.h"
+#include "../mapObjects/CGHeroInstance.h"
+#include "../networkPacks/PacksForClient.h"
+#include "../entities/building/CBuilding.h"
+
+
+#include <vstd/RNG.h>
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+TownBuildingInstance::TownBuildingInstance(IGameCallback * cb)
+	: IObjectInterface(cb)
+	, town(nullptr)
+{}
+
+TownBuildingInstance::TownBuildingInstance(CGTownInstance * town, const BuildingID & index)
+	: IObjectInterface(town->cb)
+	, town(town)
+	, bID(index)
+{}
+
+PlayerColor TownBuildingInstance::getOwner() const
+{
+	return town->getOwner();
+}
+
+MapObjectID TownBuildingInstance::getObjGroupIndex() const
+{
+	return -1;
+}
+
+MapObjectSubID TownBuildingInstance::getObjTypeIndex() const
+{
+	return 0;
+}
+
+int3 TownBuildingInstance::visitablePos() const
+{
+	return town->visitablePos();
+}
+
+int3 TownBuildingInstance::getPosition() const
+{
+	return town->getPosition();
+}
+
+TownRewardableBuildingInstance::TownRewardableBuildingInstance(IGameCallback *cb)
+	: TownBuildingInstance(cb)
+{}
+
+TownRewardableBuildingInstance::TownRewardableBuildingInstance(CGTownInstance * town, const BuildingID & index, vstd::RNG & rand)
+	: TownBuildingInstance(town, index)
+{
+	initObj(rand);
+}
+
+void TownRewardableBuildingInstance::initObj(vstd::RNG & rand)
+{
+	assert(town && town->town);
+	configuration = generateConfiguration(rand);
+}
+
+Rewardable::Configuration TownRewardableBuildingInstance::generateConfiguration(vstd::RNG & rand) const
+{
+	Rewardable::Configuration result;
+	auto building = town->town->buildings.at(getBuildingType());
+
+	building->rewardableObjectInfo.configureObject(result, rand, cb);
+	for(auto & rewardInfo : result.info)
+	{
+		for (auto & bonus : rewardInfo.reward.bonuses)
+		{
+			bonus.source = BonusSource::TOWN_STRUCTURE;
+			bonus.sid = BonusSourceID(building->getUniqueTypeID());
+		}
+	}
+	return result;
+}
+
+void TownRewardableBuildingInstance::newTurn(vstd::RNG & rand) const
+{
+	if (configuration.resetParameters.period != 0 && cb->getDate(Date::DAY) > 1 && ((cb->getDate(Date::DAY)-1) % configuration.resetParameters.period) == 0)
+	{
+		auto newConfiguration = generateConfiguration(rand);
+		cb->setRewardableObjectConfiguration(town->id, getBuildingType(), newConfiguration);
+
+		if(configuration.resetParameters.visitors)
+		{
+			cb->setObjPropertyValue(town->id, ObjProperty::STRUCTURE_CLEAR_VISITORS, getBuildingType());
+		}
+	}
+}
+
+void TownRewardableBuildingInstance::setProperty(ObjProperty what, ObjPropertyID identifier)
+{
+	switch (what)
+	{
+		case ObjProperty::VISITORS:
+			visitors.insert(identifier.as<ObjectInstanceID>());
+			break;
+		case ObjProperty::STRUCTURE_CLEAR_VISITORS:
+			visitors.clear();
+			break;
+		case ObjProperty::REWARD_SELECT:
+			selectedReward = identifier.getNum();
+			break;
+	}
+}
+
+void TownRewardableBuildingInstance::heroLevelUpDone(const CGHeroInstance *hero) const
+{
+	grantRewardAfterLevelup(cb, configuration.info.at(selectedReward), town, hero);
+}
+
+void TownRewardableBuildingInstance::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const
+{
+	if(answer == 0)
+		return; // player refused
+	
+	if(visitors.find(hero->id) != visitors.end())
+		return; // query not for this building
+
+	if(answer > 0 && answer-1 < configuration.info.size())
+	{
+		auto list = getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT);
+		grantReward(list[answer - 1], hero);
+	}
+	else
+	{
+		throw std::runtime_error("Unhandled choice");
+	}
+}
+
+void TownRewardableBuildingInstance::grantReward(ui32 rewardID, const CGHeroInstance * hero) const
+{
+	town->addHeroToStructureVisitors(hero, getBuildingType());
+	
+	grantRewardBeforeLevelup(cb, configuration.info.at(rewardID), hero);
+	
+	// hero is not blocked by levelup dialog - grant remainder immediately
+	if(!cb->isVisitCoveredByAnotherQuery(town, hero))
+	{
+		grantRewardAfterLevelup(cb, configuration.info.at(rewardID), town, hero);
+	}
+}
+
+bool TownRewardableBuildingInstance::wasVisitedBefore(const CGHeroInstance * contextHero) const
+{
+	switch (configuration.visitMode)
+	{
+		case Rewardable::VISIT_UNLIMITED:
+			return false;
+		case Rewardable::VISIT_ONCE:
+			return !visitors.empty();
+		case Rewardable::VISIT_PLAYER:
+			return false; //not supported
+		case Rewardable::VISIT_BONUS:
+		{
+			const auto building = town->getTown()->buildings.at(getBuildingType());
+			return contextHero->hasBonusFrom(BonusSource::TOWN_STRUCTURE, BonusSourceID(building->getUniqueTypeID()));
+		}
+		case Rewardable::VISIT_HERO:
+			return visitors.find(contextHero->id) != visitors.end();
+		case Rewardable::VISIT_LIMITER:
+			return configuration.visitLimiter.heroAllowed(contextHero);
+		default:
+			return false;
+	}
+}
+
+void TownRewardableBuildingInstance::onHeroVisit(const CGHeroInstance *h) const
+{
+	auto grantRewardWithMessage = [&](int index) -> void
+	{
+		auto vi = configuration.info.at(index);
+		logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString());
+		
+		town->addHeroToStructureVisitors(h, getBuildingType()); //adding to visitors
+
+		InfoWindow iw;
+		iw.player = h->tempOwner;
+		iw.text = vi.message;
+		vi.reward.loadComponents(iw.components, h);
+		iw.type = EInfoWindowMode::MODAL;
+		if(!iw.components.empty() || !iw.text.toString().empty())
+			cb->showInfoDialog(&iw);
+		
+		grantReward(index, h);
+	};
+	auto selectRewardsMessage = [&](const std::vector<ui32> & rewards, const MetaString & dialog) -> void
+	{
+		BlockingDialog sd(configuration.canRefuse, rewards.size() > 1);
+		sd.player = h->tempOwner;
+		sd.text = dialog;
+
+		if (rewards.size() > 1)
+			for (auto index : rewards)
+				sd.components.push_back(configuration.info.at(index).reward.getDisplayedComponent(h));
+
+		if (rewards.size() == 1)
+			configuration.info.at(rewards.front()).reward.loadComponents(sd.components, h);
+
+		cb->showBlockingDialog(&sd);
+	};
+	
+	if(!town->hasBuilt(getBuildingType()))
+		return;
+
+	if(!wasVisitedBefore(h))
+	{
+		auto rewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT);
+
+		logGlobal->debug("Visiting object with %d possible rewards", rewards.size());
+		switch (rewards.size())
+		{
+			case 0: // no available rewards, e.g. visiting School of War without gold
+			{
+				auto emptyRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_NOT_AVAILABLE);
+				if (!emptyRewards.empty())
+					grantRewardWithMessage(emptyRewards[0]);
+				else
+					logMod->warn("No applicable message for visiting empty object!");
+				break;
+			}
+			case 1: // one reward. Just give it with message
+			{
+				if (configuration.canRefuse)
+					selectRewardsMessage(rewards, configuration.info.at(rewards.front()).message);
+				else
+					grantRewardWithMessage(rewards.front());
+				break;
+			}
+			default: // multiple rewards. Act according to select mode
+			{
+				switch (configuration.selectMode) {
+					case Rewardable::SELECT_PLAYER: // player must select
+						selectRewardsMessage(rewards, configuration.onSelect);
+						break;
+					case Rewardable::SELECT_FIRST: // give first available
+						grantRewardWithMessage(rewards.front());
+						break;
+					case Rewardable::SELECT_RANDOM: // give random
+						grantRewardWithMessage(*RandomGeneratorUtil::nextItem(rewards, cb->gameState()->getRandomGenerator()));
+						break;
+				}
+				break;
+			}
+		}
+	}
+	else
+	{
+		logGlobal->debug("Revisiting already visited object");
+
+		auto visitedRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_ALREADY_VISITED);
+		if (!visitedRewards.empty())
+			grantRewardWithMessage(visitedRewards[0]);
+		else
+			logMod->debug("No applicable message for visiting already visited object!");
+	}
+}
+
+
+VCMI_LIB_NAMESPACE_END

+ 109 - 0
lib/mapObjects/TownBuildingInstance.h

@@ -0,0 +1,109 @@
+/*
+ * TownBuildingInstance.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "IObjectInterface.h"
+#include "../rewardable/Interface.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CGTownInstance;
+class CBuilding;
+
+class DLL_LINKAGE TownBuildingInstance : public IObjectInterface
+{
+///basic class for town structures handled as map objects
+public:
+	TownBuildingInstance(CGTownInstance * town, const BuildingID & index);
+	TownBuildingInstance(IGameCallback *cb);
+
+	CGTownInstance * town;
+
+	const BuildingID & getBuildingType() const
+	{
+		return bID;
+	}
+
+	PlayerColor getOwner() const override;
+	MapObjectID getObjGroupIndex() const override;
+	MapObjectSubID getObjTypeIndex() const override;
+
+	int3 visitablePos() const override;
+	int3 getPosition() const override;
+
+	template <typename Handler> void serialize(Handler &h)
+	{
+		h & bID;
+		if (h.version < Handler::Version::NEW_TOWN_BUILDINGS)
+		{
+			// compatibility code
+			si32 indexOnTV = 0; //identifies its index on towns vector
+			BuildingSubID::EBuildingSubID bType = BuildingSubID::NONE;
+			h & indexOnTV;
+			h & bType;
+		}
+	}
+
+private:
+	BuildingID bID; //from building list
+};
+
+class DLL_LINKAGE TownRewardableBuildingInstance : public TownBuildingInstance, public Rewardable::Interface
+{
+	/// reward selected by player, no serialize
+	ui16 selectedReward = 0;
+	std::set<ObjectInstanceID> visitors;
+
+	bool wasVisitedBefore(const CGHeroInstance * contextHero) const;
+	void grantReward(ui32 rewardID, const CGHeroInstance * hero) const;
+	Rewardable::Configuration generateConfiguration(vstd::RNG & rand) const;
+
+public:
+	void setProperty(ObjProperty what, ObjPropertyID identifier) override;
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	
+	void newTurn(vstd::RNG & rand) const override;
+	
+	/// gives second part of reward after hero level-ups for proper granting of spells/mana
+	void heroLevelUpDone(const CGHeroInstance *hero) const override;
+	
+	void initObj(vstd::RNG & rand) override;
+	
+	/// applies player selection of reward
+	void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override;
+	
+	TownRewardableBuildingInstance(CGTownInstance * town, const BuildingID & index, vstd::RNG & rand);
+	TownRewardableBuildingInstance(IGameCallback *cb);
+	
+	template <typename Handler> void serialize(Handler &h)
+	{
+		h & static_cast<TownBuildingInstance&>(*this);
+		if (h.version >= Handler::Version::NEW_TOWN_BUILDINGS)
+			h & static_cast<Rewardable::Interface&>(*this);
+		h & visitors;
+	}
+};
+
+/// Compatibility for old code
+class DLL_LINKAGE CTownCompatBuilding1 : public TownRewardableBuildingInstance
+{
+public:
+	using TownRewardableBuildingInstance::TownRewardableBuildingInstance;
+};
+
+/// Compatibility for old code
+class DLL_LINKAGE CTownCompatBuilding2 : public TownRewardableBuildingInstance
+{
+public:
+	using TownRewardableBuildingInstance::TownRewardableBuildingInstance;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 7 - 16
lib/networkPacks/NetPacksLib.cpp

@@ -36,6 +36,7 @@
 #include "mapObjects/CBank.h"
 #include "mapObjects/CGCreature.h"
 #include "mapObjects/CGMarket.h"
+#include "mapObjects/TownBuildingInstance.h"
 #include "mapObjects/CGTownInstance.h"
 #include "mapObjects/CQuest.h"
 #include "mapObjects/MiscObjects.h"
@@ -1332,18 +1333,8 @@ void NewStructures::applyGs(CGameState *gs)
 	{
 		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(const auto & overrideBid : currentBuilding->overrideBids)
-		{
-			t->overriddenBuildings.insert(overrideBid);
-			t->deleteTownBonus(overrideBid);
-		}
 	}
+	t->updateAppearance();
 	t->built = built;
 	t->recreateBuildingsBonuses();
 }
@@ -2441,13 +2432,13 @@ void SetRewardableConfiguration::applyGs(CGameState * gs)
 	else
 	{
 		auto * townPtr = dynamic_cast<CGTownInstance*>(objectPtr);
-		CGTownBuilding * buildingPtr = nullptr;
+		TownBuildingInstance * buildingPtr = nullptr;
 
-		for (CGTownBuilding * building : townPtr->bonusingBuildings)
-			if (building->getBuildingType() == buildingID)
-				buildingPtr = building;
+		for (auto & building : townPtr->rewardableBuildings)
+			if (building.second->getBuildingType() == buildingID)
+				buildingPtr = building.second;
 
-		auto * rewardablePtr = dynamic_cast<CTownRewardableBuilding *>(buildingPtr);
+		auto * rewardablePtr = dynamic_cast<TownRewardableBuildingInstance *>(buildingPtr);
 		assert(rewardablePtr);
 		rewardablePtr->configuration = configuration;
 	}

+ 5 - 5
lib/registerTypes/RegisterTypesMapObjects.h

@@ -12,7 +12,7 @@
 #include "../mapObjectConstructors/CBankInstanceConstructor.h"
 #include "../mapObjects/MapObjects.h"
 #include "../mapObjects/CGCreature.h"
-#include "../mapObjects/CGTownBuilding.h"
+#include "../mapObjects/TownBuildingInstance.h"
 #include "../mapObjects/ObjectTemplate.h"
 #include "../battle/BattleInfo.h"
 #include "../battle/CObstacleInstance.h"
@@ -86,10 +86,10 @@ void registerTypesMapObjects(Serializer &s)
 	//order of type registration is critical for loading old savegames
 
 	//Other object-related
-	s.template registerType<IObjectInterface, CGTownBuilding>();
-		s.template registerType<CGTownBuilding, CTownBonus>();
-		s.template registerType<CGTownBuilding, COPWBonus>();
-		s.template registerType<CGTownBuilding, CTownRewardableBuilding>();
+	s.template registerType<IObjectInterface, TownBuildingInstance>();
+	s.template registerType<TownBuildingInstance, TownRewardableBuildingInstance>();
+		s.template registerType<TownBuildingInstance, CTownCompatBuilding1>();
+		s.template registerType<TownBuildingInstance, CTownCompatBuilding2>();
 
 	s.template registerType<CGObjectInstance, CRewardableObject>();
 

+ 2 - 1
lib/serializer/ESerializationVersion.h

@@ -63,6 +63,7 @@ enum class ESerializationVersion : int32_t
 	STATISTICS, // 852 - removed random number generators from library classes
 	CAMPAIGN_REGIONS, // 853 - configurable campaign regions
 	EVENTS_PLAYER_SET, // 854 - map & town events use std::set instead of bitmask to store player list
+	NEW_TOWN_BUILDINGS, // 855 - old bonusing buildings have been removed
 
-	CURRENT = EVENTS_PLAYER_SET
+	CURRENT = NEW_TOWN_BUILDINGS
 };

+ 3 - 2
server/CGameHandler.cpp

@@ -57,6 +57,7 @@
 
 #include "../lib/mapObjects/CGCreature.h"
 #include "../lib/mapObjects/CGMarket.h"
+#include "../lib/mapObjects/TownBuildingInstance.h"
 #include "../lib/mapObjects/CGTownInstance.h"
 #include "../lib/mapObjects/MiscObjects.h"
 #include "../lib/mapObjectConstructors/AObjectTypeHandler.h"
@@ -1535,8 +1536,8 @@ void CGameHandler::heroVisitCastle(const CGTownInstance * obj, const CGHeroInsta
 
 void CGameHandler::visitCastleObjects(const CGTownInstance * t, const CGHeroInstance * h)
 {
-	for (auto building : t->bonusingBuildings)
-		building->onHeroVisit(h);
+	for (auto & building : t->rewardableBuildings)
+		building.second->onHeroVisit(h);
 }
 
 void CGameHandler::stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero)

+ 1 - 0
test/spells/effects/CatapultTest.cpp

@@ -14,6 +14,7 @@
 #include <vstd/RNG.h>
 
 #include "../../../lib/mapObjects/CGTownInstance.h"
+#include "../../../lib/json/JsonNode.h"
 
 
 namespace test