Prechádzať zdrojové kódy

Merge remote-tracking branch 'upstream/develop' into develop

Xilmi 1 rok pred
rodič
commit
9a40577994
100 zmenil súbory, kde vykonal 1375 pridanie a 869 odobranie
  1. 1 1
      AI/BattleAI/BattleEvaluator.cpp
  2. 15 15
      AI/BattleAI/StackWithBonuses.cpp
  3. 9 9
      AI/BattleAI/StackWithBonuses.h
  4. 62 38
      CCallback.cpp
  5. 9 1
      CCallback.h
  6. 5 5
      CMakeLists.txt
  7. BIN
      Mods/vcmi/Data/spellResearch/accept.png
  8. BIN
      Mods/vcmi/Data/spellResearch/close.png
  9. BIN
      Mods/vcmi/Data/spellResearch/reroll.png
  10. 3 1
      Mods/vcmi/config/vcmi/chinese.json
  11. 17 1
      Mods/vcmi/config/vcmi/english.json
  12. 10 1
      Mods/vcmi/config/vcmi/german.json
  13. 9 4
      client/CServerHandler.cpp
  14. 0 1
      client/CServerHandler.h
  15. 12 14
      client/Client.cpp
  16. 4 4
      client/Client.h
  17. 1 0
      client/ClientNetPackVisitors.h
  18. 7 0
      client/NetPacksClient.cpp
  19. 5 6
      client/lobby/OptionsTab.cpp
  20. 59 18
      client/media/CVideoHandler.cpp
  21. 14 9
      client/render/AssetGenerator.cpp
  22. 7 0
      client/render/ColorFilter.cpp
  23. 1 0
      client/render/ColorFilter.h
  24. 3 1
      client/widgets/CComponent.cpp
  25. 1 0
      client/widgets/CComponent.h
  26. 80 8
      client/windows/CCastleInterface.cpp
  27. 5 1
      client/windows/CCastleInterface.h
  28. 28 5
      client/windows/CHeroBackpackWindow.cpp
  29. 2 1
      client/windows/CHeroBackpackWindow.h
  30. 3 3
      client/windows/CKingdomInterface.cpp
  31. 1 1
      client/windows/CMessage.cpp
  32. 2 1
      client/windows/GUIClasses.cpp
  33. 2 0
      client/windows/QuickRecruitmentWindow.cpp
  34. 32 2
      config/gameConfig.json
  35. 13 2
      config/schemas/gameSettings.json
  36. 40 40
      config/schemas/mod.json
  37. 18 1
      config/schemas/settings.json
  38. 3 2
      docs/modders/Mod_File_Format.md
  39. 9 9
      include/vcmi/ServerCallback.h
  40. 1 5
      launcher/CMakeLists.txt
  41. 3 19
      launcher/StdInc.h
  42. 1 1
      launcher/modManager/cdownloadmanager_moc.cpp
  43. 2 2
      launcher/modManager/cmodlistview_moc.cpp
  44. 2 2
      launcher/modManager/cmodmanager.cpp
  45. 1 1
      launcher/prepare.cpp
  46. 26 1
      launcher/settingsView/csettingsview_moc.cpp
  47. 7 6
      launcher/settingsView/csettingsview_moc.h
  48. 377 382
      launcher/settingsView/csettingsview_moc.ui
  49. 3 3
      lib/CArtHandler.cpp
  50. 5 4
      lib/CBonusTypeHandler.cpp
  51. 3 3
      lib/CCreatureHandler.cpp
  52. 1 1
      lib/CCreatureSet.cpp
  53. 5 5
      lib/CHeroHandler.cpp
  54. 2 2
      lib/CSkillHandler.cpp
  55. 1 1
      lib/CStack.cpp
  56. 69 64
      lib/GameSettings.cpp
  57. 2 1
      lib/IGameCallback.h
  58. 5 0
      lib/IGameSettings.h
  59. 1 1
      lib/RiverHandler.cpp
  60. 1 1
      lib/RoadHandler.cpp
  61. 1 1
      lib/TerrainHandler.cpp
  62. 1 1
      lib/battle/CBattleInfoCallback.cpp
  63. 5 5
      lib/entities/faction/CTownHandler.cpp
  64. 2 2
      lib/gameState/CGameState.cpp
  65. 1 1
      lib/gameState/CGameState.h
  66. 45 0
      lib/json/JsonUtils.cpp
  67. 8 0
      lib/json/JsonUtils.h
  68. 1 1
      lib/mapObjectConstructors/CBankInstanceConstructor.cpp
  69. 1 1
      lib/mapObjectConstructors/CObjectClassesHandler.cpp
  70. 7 4
      lib/mapObjectConstructors/CRewardableConstructor.cpp
  71. 1 1
      lib/mapObjectConstructors/CRewardableConstructor.h
  72. 1 1
      lib/mapObjectConstructors/DwellingInstanceConstructor.cpp
  73. 1 1
      lib/mapObjects/CBank.cpp
  74. 20 7
      lib/mapObjects/CGCreature.cpp
  75. 1 1
      lib/mapObjects/CGCreature.h
  76. 7 7
      lib/mapObjects/CGDwelling.cpp
  77. 1 1
      lib/mapObjects/CGHeroInstance.cpp
  78. 1 1
      lib/mapObjects/CGMarket.cpp
  79. 6 3
      lib/mapObjects/CGTownInstance.cpp
  80. 10 0
      lib/mapObjects/CGTownInstance.h
  81. 4 4
      lib/mapObjects/CQuest.cpp
  82. 5 5
      lib/mapObjects/CRewardableObject.cpp
  83. 1 1
      lib/mapObjects/IObjectInterface.cpp
  84. 7 7
      lib/mapObjects/MiscObjects.cpp
  85. 2 2
      lib/mapping/CMapHeader.cpp
  86. 1 4
      lib/mapping/MapFormatH3M.cpp
  87. 42 43
      lib/modding/CModHandler.cpp
  88. 1 2
      lib/modding/CModHandler.h
  89. 78 21
      lib/modding/ContentTypeHandler.cpp
  90. 5 2
      lib/modding/ContentTypeHandler.h
  91. 2 0
      lib/networkPacks/NetPackVisitor.h
  92. 23 1
      lib/networkPacks/NetPacksLib.cpp
  93. 20 0
      lib/networkPacks/PacksForClient.h
  94. 22 0
      lib/networkPacks/PacksForServer.h
  95. 1 1
      lib/rewardable/Info.cpp
  96. 6 6
      lib/serializer/Connection.cpp
  97. 2 2
      lib/serializer/Connection.h
  98. 2 1
      lib/serializer/ESerializationVersion.h
  99. 2 0
      lib/serializer/RegisterTypes.h
  100. 18 18
      lib/spells/AdventureSpellMechanics.cpp

+ 1 - 1
AI/BattleAI/BattleEvaluator.cpp

@@ -394,7 +394,7 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
 	{
 	{
 		std::set<BattleHex> obstacleHexes;
 		std::set<BattleHex> obstacleHexes;
 
 
-		auto insertAffected = [](const CObstacleInstance & spellObst, std::set<BattleHex> obstacleHexes) {
+		auto insertAffected = [](const CObstacleInstance & spellObst, std::set<BattleHex> & obstacleHexes) {
 			auto affectedHexes = spellObst.getAffectedTiles();
 			auto affectedHexes = spellObst.getAffectedTiles();
 			obstacleHexes.insert(affectedHexes.cbegin(), affectedHexes.cend());
 			obstacleHexes.insert(affectedHexes.cbegin(), affectedHexes.cend());
 		};
 		};

+ 15 - 15
AI/BattleAI/StackWithBonuses.cpp

@@ -531,44 +531,44 @@ vstd::RNG * HypotheticBattle::HypotheticServerCallback::getRNG()
 	return &rngStub;
 	return &rngStub;
 }
 }
 
 
-void HypotheticBattle::HypotheticServerCallback::apply(CPackForClient * pack)
+void HypotheticBattle::HypotheticServerCallback::apply(CPackForClient & pack)
 {
 {
 	logAi->error("Package of type %s is not allowed in battle evaluation", typeid(pack).name());
 	logAi->error("Package of type %s is not allowed in battle evaluation", typeid(pack).name());
 }
 }
 
 
-void HypotheticBattle::HypotheticServerCallback::apply(BattleLogMessage * pack)
+void HypotheticBattle::HypotheticServerCallback::apply(BattleLogMessage & pack)
 {
 {
-	pack->applyBattle(owner);
+	pack.applyBattle(owner);
 }
 }
 
 
-void HypotheticBattle::HypotheticServerCallback::apply(BattleStackMoved * pack)
+void HypotheticBattle::HypotheticServerCallback::apply(BattleStackMoved & pack)
 {
 {
-	pack->applyBattle(owner);
+	pack.applyBattle(owner);
 }
 }
 
 
-void HypotheticBattle::HypotheticServerCallback::apply(BattleUnitsChanged * pack)
+void HypotheticBattle::HypotheticServerCallback::apply(BattleUnitsChanged & pack)
 {
 {
-	pack->applyBattle(owner);
+	pack.applyBattle(owner);
 }
 }
 
 
-void HypotheticBattle::HypotheticServerCallback::apply(SetStackEffect * pack)
+void HypotheticBattle::HypotheticServerCallback::apply(SetStackEffect & pack)
 {
 {
-	pack->applyBattle(owner);
+	pack.applyBattle(owner);
 }
 }
 
 
-void HypotheticBattle::HypotheticServerCallback::apply(StacksInjured * pack)
+void HypotheticBattle::HypotheticServerCallback::apply(StacksInjured & pack)
 {
 {
-	pack->applyBattle(owner);
+	pack.applyBattle(owner);
 }
 }
 
 
-void HypotheticBattle::HypotheticServerCallback::apply(BattleObstaclesChanged * pack)
+void HypotheticBattle::HypotheticServerCallback::apply(BattleObstaclesChanged & pack)
 {
 {
-	pack->applyBattle(owner);
+	pack.applyBattle(owner);
 }
 }
 
 
-void HypotheticBattle::HypotheticServerCallback::apply(CatapultAttack * pack)
+void HypotheticBattle::HypotheticServerCallback::apply(CatapultAttack & pack)
 {
 {
-	pack->applyBattle(owner);
+	pack.applyBattle(owner);
 }
 }
 
 
 HypotheticBattle::HypotheticEnvironment::HypotheticEnvironment(HypotheticBattle * owner_, const Environment * upperEnvironment)
 HypotheticBattle::HypotheticEnvironment::HypotheticEnvironment(HypotheticBattle * owner_, const Environment * upperEnvironment)

+ 9 - 9
AI/BattleAI/StackWithBonuses.h

@@ -189,15 +189,15 @@ private:
 
 
 		vstd::RNG * getRNG() override;
 		vstd::RNG * getRNG() override;
 
 
-		void apply(CPackForClient * pack) override;
-
-		void apply(BattleLogMessage * pack) override;
-		void apply(BattleStackMoved * pack) override;
-		void apply(BattleUnitsChanged * pack) override;
-		void apply(SetStackEffect * pack) override;
-		void apply(StacksInjured * pack) override;
-		void apply(BattleObstaclesChanged * pack) override;
-		void apply(CatapultAttack * pack) override;
+		void apply(CPackForClient & pack) override;
+
+		void apply(BattleLogMessage & pack) override;
+		void apply(BattleStackMoved & pack) override;
+		void apply(BattleUnitsChanged & pack) override;
+		void apply(SetStackEffect & pack) override;
+		void apply(StacksInjured & pack) override;
+		void apply(BattleObstaclesChanged & pack) override;
+		void apply(CatapultAttack & pack) override;
 	private:
 	private:
 		HypotheticBattle * owner;
 		HypotheticBattle * owner;
 		RNGStub rngStub;
 		RNGStub rngStub;

+ 62 - 38
CCallback.cpp

@@ -29,20 +29,20 @@
 bool CCallback::teleportHero(const CGHeroInstance *who, const CGTownInstance *where)
 bool CCallback::teleportHero(const CGHeroInstance *who, const CGTownInstance *where)
 {
 {
 	CastleTeleportHero pack(who->id, where->id, 1);
 	CastleTeleportHero pack(who->id, where->id, 1);
-	sendRequest(&pack);
+	sendRequest(pack);
 	return true;
 	return true;
 }
 }
 
 
 void CCallback::moveHero(const CGHeroInstance *h, const int3 & destination, bool transit)
 void CCallback::moveHero(const CGHeroInstance *h, const int3 & destination, bool transit)
 {
 {
 	MoveHero pack({destination}, h->id, transit);
 	MoveHero pack({destination}, h->id, transit);
-	sendRequest(&pack);
+	sendRequest(pack);
 }
 }
 
 
 void CCallback::moveHero(const CGHeroInstance *h, const std::vector<int3> & path, bool transit)
 void CCallback::moveHero(const CGHeroInstance *h, const std::vector<int3> & path, bool transit)
 {
 {
 	MoveHero pack(path, h->id, transit);
 	MoveHero pack(path, h->id, transit);
-	sendRequest(&pack);
+	sendRequest(pack);
 }
 }
 
 
 int CCallback::selectionMade(int selection, QueryID queryID)
 int CCallback::selectionMade(int selection, QueryID queryID)
@@ -61,7 +61,7 @@ int CCallback::sendQueryReply(std::optional<int32_t> reply, QueryID queryID)
 
 
 	QueryReply pack(queryID, reply);
 	QueryReply pack(queryID, reply);
 	pack.player = *player;
 	pack.player = *player;
-	return sendRequest(&pack);
+	return sendRequest(pack);
 }
 }
 
 
 void CCallback::recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level)
 void CCallback::recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level)
@@ -71,7 +71,7 @@ void CCallback::recruitCreatures(const CGDwelling * obj, const CArmedInstance *
 		return;
 		return;
 
 
 	RecruitCreatures pack(obj->id, dst->id, ID, amount, level);
 	RecruitCreatures pack(obj->id, dst->id, ID, amount, level);
-	sendRequest(&pack);
+	sendRequest(pack);
 }
 }
 
 
 bool CCallback::dismissCreature(const CArmedInstance *obj, SlotID stackPos)
 bool CCallback::dismissCreature(const CArmedInstance *obj, SlotID stackPos)
@@ -80,14 +80,14 @@ bool CCallback::dismissCreature(const CArmedInstance *obj, SlotID stackPos)
 		return false;
 		return false;
 
 
 	DisbandCreature pack(stackPos,obj->id);
 	DisbandCreature pack(stackPos,obj->id);
-	sendRequest(&pack);
+	sendRequest(pack);
 	return true;
 	return true;
 }
 }
 
 
 bool CCallback::upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID)
 bool CCallback::upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID)
 {
 {
 	UpgradeCreature pack(stackPos,obj->id,newID);
 	UpgradeCreature pack(stackPos,obj->id,newID);
-	sendRequest(&pack);
+	sendRequest(pack);
 	return false;
 	return false;
 }
 }
 
 
@@ -95,54 +95,54 @@ void CCallback::endTurn()
 {
 {
 	logGlobal->trace("Player %d ended his turn.", player->getNum());
 	logGlobal->trace("Player %d ended his turn.", player->getNum());
 	EndTurn pack;
 	EndTurn pack;
-	sendRequest(&pack);
+	sendRequest(pack);
 }
 }
 int CCallback::swapCreatures(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
 int CCallback::swapCreatures(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
 {
 {
 	ArrangeStacks pack(1,p1,p2,s1->id,s2->id,0);
 	ArrangeStacks pack(1,p1,p2,s1->id,s2->id,0);
-	sendRequest(&pack);
+	sendRequest(pack);
 	return 0;
 	return 0;
 }
 }
 
 
 int CCallback::mergeStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
 int CCallback::mergeStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
 {
 {
 	ArrangeStacks pack(2,p1,p2,s1->id,s2->id,0);
 	ArrangeStacks pack(2,p1,p2,s1->id,s2->id,0);
-	sendRequest(&pack);
+	sendRequest(pack);
 	return 0;
 	return 0;
 }
 }
 
 
 int CCallback::splitStack(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2, int val)
 int CCallback::splitStack(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2, int val)
 {
 {
 	ArrangeStacks pack(3,p1,p2,s1->id,s2->id,val);
 	ArrangeStacks pack(3,p1,p2,s1->id,s2->id,val);
-	sendRequest(&pack);
+	sendRequest(pack);
 	return 0;
 	return 0;
 }
 }
 
 
 int CCallback::bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot)
 int CCallback::bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot)
 {
 {
 	BulkMoveArmy pack(srcArmy, destArmy, srcSlot);
 	BulkMoveArmy pack(srcArmy, destArmy, srcSlot);
-	sendRequest(&pack);
+	sendRequest(pack);
 	return 0;
 	return 0;
 }
 }
 
 
 int CCallback::bulkSplitStack(ObjectInstanceID armyId, SlotID srcSlot, int howMany)
 int CCallback::bulkSplitStack(ObjectInstanceID armyId, SlotID srcSlot, int howMany)
 {
 {
 	BulkSplitStack pack(armyId, srcSlot, howMany);
 	BulkSplitStack pack(armyId, srcSlot, howMany);
-	sendRequest(&pack);
+	sendRequest(pack);
 	return 0;
 	return 0;
 }
 }
 
 
 int CCallback::bulkSmartSplitStack(ObjectInstanceID armyId, SlotID srcSlot)
 int CCallback::bulkSmartSplitStack(ObjectInstanceID armyId, SlotID srcSlot)
 {
 {
 	BulkSmartSplitStack pack(armyId, srcSlot);
 	BulkSmartSplitStack pack(armyId, srcSlot);
-	sendRequest(&pack);
+	sendRequest(pack);
 	return 0;
 	return 0;
 }
 }
 
 
 int CCallback::bulkMergeStacks(ObjectInstanceID armyId, SlotID srcSlot)
 int CCallback::bulkMergeStacks(ObjectInstanceID armyId, SlotID srcSlot)
 {
 {
 	BulkMergeStacks pack(armyId, srcSlot);
 	BulkMergeStacks pack(armyId, srcSlot);
-	sendRequest(&pack);
+	sendRequest(pack);
 	return 0;
 	return 0;
 }
 }
 
 
@@ -151,7 +151,7 @@ bool CCallback::dismissHero(const CGHeroInstance *hero)
 	if(player!=hero->tempOwner) return false;
 	if(player!=hero->tempOwner) return false;
 
 
 	DismissHero pack(hero->id);
 	DismissHero pack(hero->id);
-	sendRequest(&pack);
+	sendRequest(pack);
 	return true;
 	return true;
 }
 }
 
 
@@ -160,7 +160,7 @@ bool CCallback::swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation
 	ExchangeArtifacts ea;
 	ExchangeArtifacts ea;
 	ea.src = l1;
 	ea.src = l1;
 	ea.dst = l2;
 	ea.dst = l2;
-	sendRequest(&ea);
+	sendRequest(ea);
 	return true;
 	return true;
 }
 }
 
 
@@ -175,13 +175,13 @@ bool CCallback::swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation
 void CCallback::assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)
 void CCallback::assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)
 {
 {
 	AssembleArtifacts aa(heroID, artifactSlot, assemble, assembleTo);
 	AssembleArtifacts aa(heroID, artifactSlot, assemble, assembleTo);
-	sendRequest(&aa);
+	sendRequest(aa);
 }
 }
 
 
 void CCallback::bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap, bool equipped, bool backpack)
 void CCallback::bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap, bool equipped, bool backpack)
 {
 {
 	BulkExchangeArtifacts bma(srcHero, dstHero, swap, equipped, backpack);
 	BulkExchangeArtifacts bma(srcHero, dstHero, swap, equipped, backpack);
-	sendRequest(&bma);
+	sendRequest(bma);
 }
 }
 
 
 void CCallback::scrollBackpackArtifacts(ObjectInstanceID hero, bool left)
 void CCallback::scrollBackpackArtifacts(ObjectInstanceID hero, bool left)
@@ -189,19 +189,37 @@ void CCallback::scrollBackpackArtifacts(ObjectInstanceID hero, bool left)
 	ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SCROLL_RIGHT);
 	ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SCROLL_RIGHT);
 	if(left)
 	if(left)
 		mba.cmd = ManageBackpackArtifacts::ManageCmd::SCROLL_LEFT;
 		mba.cmd = ManageBackpackArtifacts::ManageCmd::SCROLL_LEFT;
-	sendRequest(&mba);
+	sendRequest(mba);
+}
+
+void CCallback::sortBackpackArtifactsBySlot(const ObjectInstanceID hero)
+{
+	ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SORT_BY_SLOT);
+	sendRequest(mba);
+}
+
+void CCallback::sortBackpackArtifactsByCost(const ObjectInstanceID hero)
+{
+	ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SORT_BY_COST);
+	sendRequest(mba);
+}
+
+void CCallback::sortBackpackArtifactsByClass(const ObjectInstanceID hero)
+{
+	ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SORT_BY_CLASS);
+	sendRequest(mba);
 }
 }
 
 
 void CCallback::manageHeroCostume(ObjectInstanceID hero, size_t costumeIndex, bool saveCostume)
 void CCallback::manageHeroCostume(ObjectInstanceID hero, size_t costumeIndex, bool saveCostume)
 {
 {
 	ManageEquippedArtifacts mea(hero, costumeIndex, saveCostume);
 	ManageEquippedArtifacts mea(hero, costumeIndex, saveCostume);
-	sendRequest(&mea);
+	sendRequest(mea);
 }
 }
 
 
 void CCallback::eraseArtifactByClient(const ArtifactLocation & al)
 void CCallback::eraseArtifactByClient(const ArtifactLocation & al)
 {
 {
 	EraseArtifactByClient ea(al);
 	EraseArtifactByClient ea(al);
-	sendRequest(&ea);
+	sendRequest(ea);
 }
 }
 
 
 bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
 bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
@@ -213,7 +231,7 @@ bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
 		return false;
 		return false;
 
 
 	BuildStructure pack(town->id,buildingID);
 	BuildStructure pack(town->id,buildingID);
-	sendRequest(&pack);
+	sendRequest(pack);
 	return true;
 	return true;
 }
 }
 
 
@@ -223,7 +241,7 @@ bool CCallback::visitTownBuilding(const CGTownInstance *town, BuildingID buildin
 		return false;
 		return false;
 
 
 	VisitTownBuilding pack(town->id, buildingID);
 	VisitTownBuilding pack(town->id, buildingID);
-	sendRequest(&pack);
+	sendRequest(pack);
 	return true;
 	return true;
 }
 }
 
 
@@ -232,10 +250,10 @@ void CBattleCallback::battleMakeSpellAction(const BattleID & battleID, const Bat
 	assert(action.actionType == EActionType::HERO_SPELL);
 	assert(action.actionType == EActionType::HERO_SPELL);
 	MakeAction mca(action);
 	MakeAction mca(action);
 	mca.battleID = battleID;
 	mca.battleID = battleID;
-	sendRequest(&mca);
+	sendRequest(mca);
 }
 }
 
 
-int CBattleCallback::sendRequest(const CPackForServer * request)
+int CBattleCallback::sendRequest(const CPackForServer & request)
 {
 {
 	int requestID = cl->sendRequest(request, *getPlayerID());
 	int requestID = cl->sendRequest(request, *getPlayerID());
 	if(waitTillRealize)
 	if(waitTillRealize)
@@ -249,12 +267,18 @@ int CBattleCallback::sendRequest(const CPackForServer * request)
 	return requestID;
 	return requestID;
 }
 }
 
 
+void CCallback::spellResearch( const CGTownInstance *town, SpellID spellAtSlot, bool accepted )
+{
+	SpellResearch pack(town->id, spellAtSlot, accepted);
+	sendRequest(pack);
+}
+
 void CCallback::swapGarrisonHero( const CGTownInstance *town )
 void CCallback::swapGarrisonHero( const CGTownInstance *town )
 {
 {
 	if(town->tempOwner == *player || (town->garrisonHero && town->garrisonHero->tempOwner == *player ))
 	if(town->tempOwner == *player || (town->garrisonHero && town->garrisonHero->tempOwner == *player ))
 	{
 	{
 		GarrisonHeroSwap pack(town->id);
 		GarrisonHeroSwap pack(town->id);
-		sendRequest(&pack);
+		sendRequest(pack);
 	}
 	}
 }
 }
 
 
@@ -263,7 +287,7 @@ void CCallback::buyArtifact(const CGHeroInstance *hero, ArtifactID aid)
 	if(hero->tempOwner != *player) return;
 	if(hero->tempOwner != *player) return;
 
 
 	BuyArtifact pack(hero->id,aid);
 	BuyArtifact pack(hero->id,aid);
-	sendRequest(&pack);
+	sendRequest(pack);
 }
 }
 
 
 void CCallback::trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero)
 void CCallback::trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero)
@@ -280,13 +304,13 @@ void CCallback::trade(const ObjectInstanceID marketId, EMarketMode mode, const s
 	pack.r1 = id1;
 	pack.r1 = id1;
 	pack.r2 = id2;
 	pack.r2 = id2;
 	pack.val = val1;
 	pack.val = val1;
-	sendRequest(&pack);
+	sendRequest(pack);
 }
 }
 
 
 void CCallback::setFormation(const CGHeroInstance * hero, EArmyFormation mode)
 void CCallback::setFormation(const CGHeroInstance * hero, EArmyFormation mode)
 {
 {
 	SetFormation pack(hero->id, mode);
 	SetFormation pack(hero->id, mode);
-	sendRequest(&pack);
+	sendRequest(pack);
 }
 }
 
 
 void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero)
 void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero)
@@ -296,7 +320,7 @@ void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroIn
 
 
 	HireHero pack(hero->getHeroType(), townOrTavern->id, nextHero);
 	HireHero pack(hero->getHeroType(), townOrTavern->id, nextHero);
 	pack.player = *player;
 	pack.player = *player;
-	sendRequest(&pack);
+	sendRequest(pack);
 }
 }
 
 
 void CCallback::save( const std::string &fname )
 void CCallback::save( const std::string &fname )
@@ -310,7 +334,7 @@ void CCallback::gamePause(bool pause)
 	{
 	{
 		GamePause pack;
 		GamePause pack;
 		pack.player = *player;
 		pack.player = *player;
-		sendRequest(&pack);
+		sendRequest(pack);
 	}
 	}
 	else
 	else
 	{
 	{
@@ -324,14 +348,14 @@ void CCallback::sendMessage(const std::string &mess, const CGObjectInstance * cu
 	PlayerMessage pm(mess, currentObject? currentObject->id : ObjectInstanceID(-1));
 	PlayerMessage pm(mess, currentObject? currentObject->id : ObjectInstanceID(-1));
 	if(player)
 	if(player)
 		pm.player = *player;
 		pm.player = *player;
-	sendRequest(&pm);
+	sendRequest(pm);
 }
 }
 
 
 void CCallback::buildBoat( const IShipyard *obj )
 void CCallback::buildBoat( const IShipyard *obj )
 {
 {
 	BuildBoat bb;
 	BuildBoat bb;
 	bb.objid = dynamic_cast<const CGObjectInstance*>(obj)->id;
 	bb.objid = dynamic_cast<const CGObjectInstance*>(obj)->id;
-	sendRequest(&bb);
+	sendRequest(bb);
 }
 }
 
 
 CCallback::CCallback(CGameState * GS, std::optional<PlayerColor> Player, CClient * C)
 CCallback::CCallback(CGameState * GS, std::optional<PlayerColor> Player, CClient * C)
@@ -373,7 +397,7 @@ void CCallback::dig( const CGObjectInstance *hero )
 {
 {
 	DigWithHero dwh;
 	DigWithHero dwh;
 	dwh.id = hero->id;
 	dwh.id = hero->id;
-	sendRequest(&dwh);
+	sendRequest(dwh);
 }
 }
 
 
 void CCallback::castSpell(const CGHeroInstance *hero, SpellID spellID, const int3 &pos)
 void CCallback::castSpell(const CGHeroInstance *hero, SpellID spellID, const int3 &pos)
@@ -382,7 +406,7 @@ void CCallback::castSpell(const CGHeroInstance *hero, SpellID spellID, const int
 	cas.hid = hero->id;
 	cas.hid = hero->id;
 	cas.sid = spellID;
 	cas.sid = spellID;
 	cas.pos = pos;
 	cas.pos = pos;
-	sendRequest(&cas);
+	sendRequest(cas);
 }
 }
 
 
 int CCallback::mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
 int CCallback::mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
@@ -415,7 +439,7 @@ void CBattleCallback::battleMakeUnitAction(const BattleID & battleID, const Batt
 	MakeAction ma;
 	MakeAction ma;
 	ma.ba = action;
 	ma.ba = action;
 	ma.battleID = battleID;
 	ma.battleID = battleID;
-	sendRequest(&ma);
+	sendRequest(ma);
 }
 }
 
 
 void CBattleCallback::battleMakeTacticAction(const BattleID & battleID, const BattleAction & action )
 void CBattleCallback::battleMakeTacticAction(const BattleID & battleID, const BattleAction & action )
@@ -424,7 +448,7 @@ void CBattleCallback::battleMakeTacticAction(const BattleID & battleID, const Ba
 	MakeAction ma;
 	MakeAction ma;
 	ma.ba = action;
 	ma.ba = action;
 	ma.battleID = battleID;
 	ma.battleID = battleID;
-	sendRequest(&ma);
+	sendRequest(ma);
 }
 }
 
 
 std::optional<BattleAction> CBattleCallback::makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState)
 std::optional<BattleAction> CBattleCallback::makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState)

+ 9 - 1
CCallback.h

@@ -78,6 +78,7 @@ public:
 	virtual bool visitTownBuilding(const CGTownInstance *town, BuildingID buildingID)=0;
 	virtual bool visitTownBuilding(const CGTownInstance *town, BuildingID buildingID)=0;
 	virtual void recruitCreatures(const CGDwelling *obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1)=0;
 	virtual void recruitCreatures(const CGDwelling *obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1)=0;
 	virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made
 	virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made
+	virtual void spellResearch(const CGTownInstance *town, SpellID spellAtSlot, bool accepted)=0;
 	virtual void swapGarrisonHero(const CGTownInstance *town)=0;
 	virtual void swapGarrisonHero(const CGTownInstance *town)=0;
 
 
 	virtual void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero)=0; //mode==0: sell val1 units of id1 resource for id2 resiurce
 	virtual void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero)=0; //mode==0: sell val1 units of id1 resource for id2 resiurce
@@ -92,6 +93,9 @@ public:
 	//virtual bool swapArtifacts(const CGHeroInstance * hero1, ui16 pos1, const CGHeroInstance * hero2, ui16 pos2)=0; //swaps artifacts between two given heroes
 	//virtual bool swapArtifacts(const CGHeroInstance * hero1, ui16 pos1, const CGHeroInstance * hero2, ui16 pos2)=0; //swaps artifacts between two given heroes
 	virtual bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2)=0;
 	virtual bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2)=0;
 	virtual void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) = 0;
 	virtual void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) = 0;
+	virtual void sortBackpackArtifactsBySlot(const ObjectInstanceID hero) = 0;
+	virtual void sortBackpackArtifactsByCost(const ObjectInstanceID hero) = 0;
+	virtual void sortBackpackArtifactsByClass(const ObjectInstanceID hero) = 0;
 	virtual void manageHeroCostume(ObjectInstanceID hero, size_t costumeIndex, bool saveCostume) = 0;
 	virtual void manageHeroCostume(ObjectInstanceID hero, size_t costumeIndex, bool saveCostume) = 0;
 	virtual void assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0;
 	virtual void assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0;
 	virtual void eraseArtifactByClient(const ArtifactLocation & al)=0;
 	virtual void eraseArtifactByClient(const ArtifactLocation & al)=0;
@@ -123,7 +127,7 @@ class CBattleCallback : public IBattleCallback
 	std::optional<PlayerColor> player;
 	std::optional<PlayerColor> player;
 
 
 protected:
 protected:
-	int sendRequest(const CPackForServer * request); //returns requestID (that'll be matched to requestID in PackageApplied)
+	int sendRequest(const CPackForServer & request); //returns requestID (that'll be matched to requestID in PackageApplied)
 	CClient *cl;
 	CClient *cl;
 
 
 public:
 public:
@@ -179,6 +183,9 @@ public:
 	void assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override;
 	void assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override;
 	void bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap, bool equipped = true, bool backpack = true) override;
 	void bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap, bool equipped = true, bool backpack = true) override;
 	void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) override;
 	void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) override;
+	void sortBackpackArtifactsBySlot(const ObjectInstanceID hero) override;
+	void sortBackpackArtifactsByCost(const ObjectInstanceID hero) override;
+	void sortBackpackArtifactsByClass(const ObjectInstanceID hero) override;
 	void manageHeroCostume(ObjectInstanceID hero, size_t costumeIdx, bool saveCostume) override;
 	void manageHeroCostume(ObjectInstanceID hero, size_t costumeIdx, bool saveCostume) override;
 	void eraseArtifactByClient(const ArtifactLocation & al) override;
 	void eraseArtifactByClient(const ArtifactLocation & al) override;
 	bool buildBuilding(const CGTownInstance *town, BuildingID buildingID) override;
 	bool buildBuilding(const CGTownInstance *town, BuildingID buildingID) override;
@@ -187,6 +194,7 @@ public:
 	bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override;
 	bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override;
 	bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override;
 	bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override;
 	void endTurn() override;
 	void endTurn() override;
+	void spellResearch(const CGTownInstance *town, SpellID spellAtSlot, bool accepted) override;
 	void swapGarrisonHero(const CGTownInstance *town) override;
 	void swapGarrisonHero(const CGTownInstance *town) override;
 	void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override;
 	void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override;
 	void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override;
 	void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override;

+ 5 - 5
CMakeLists.txt

@@ -486,11 +486,7 @@ if(NOT FORCE_BUNDLED_MINIZIP)
 endif()
 endif()
 
 
 if (ENABLE_CLIENT)
 if (ENABLE_CLIENT)
-	set(FFMPEG_COMPONENTS avutil swscale avformat avcodec)
-	if(APPLE_IOS AND NOT USING_CONAN)
-		list(APPEND FFMPEG_COMPONENTS swresample)
-	endif()
-	find_package(ffmpeg COMPONENTS ${FFMPEG_COMPONENTS})
+	find_package(ffmpeg COMPONENTS avutil swscale avformat avcodec swresample)
 
 
 	find_package(SDL2 REQUIRED)
 	find_package(SDL2 REQUIRED)
 	find_package(SDL2_image REQUIRED)
 	find_package(SDL2_image REQUIRED)
@@ -666,6 +662,10 @@ if(NOT TARGET minizip::minizip)
 	add_library(minizip::minizip ALIAS minizip)
 	add_library(minizip::minizip ALIAS minizip)
 endif()
 endif()
 
 
+if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
+	add_subdirectory(vcmiqt)
+endif()
+
 if(ENABLE_LAUNCHER)
 if(ENABLE_LAUNCHER)
 	add_subdirectory(launcher)
 	add_subdirectory(launcher)
 endif()
 endif()

BIN
Mods/vcmi/Data/spellResearch/accept.png


BIN
Mods/vcmi/Data/spellResearch/close.png


BIN
Mods/vcmi/Data/spellResearch/reroll.png


+ 3 - 1
Mods/vcmi/config/vcmi/chinese.json

@@ -12,7 +12,9 @@
 	"vcmi.adventureMap.monsterThreat.levels.9"  : "压倒性的",
 	"vcmi.adventureMap.monsterThreat.levels.9"  : "压倒性的",
 	"vcmi.adventureMap.monsterThreat.levels.10" : "致命的",
 	"vcmi.adventureMap.monsterThreat.levels.10" : "致命的",
 	"vcmi.adventureMap.monsterThreat.levels.11" : "无法取胜",
 	"vcmi.adventureMap.monsterThreat.levels.11" : "无法取胜",
-	"vcmi.adventureMap.monsterLevel"            : "\n\n%TOWN%LEVEL级生物",
+	"vcmi.adventureMap.monsterLevel"            : "\n\n%TOWN%LEVEL级%ATTACK_TYPE生物",
+	"vcmi.adventureMap.monsterMeleeType"        : "近战",
+	"vcmi.adventureMap.monsterRangedType"       : "远程",
 
 
 	"vcmi.adventureMap.confirmRestartGame"     : "你想要重新开始游戏吗?",
 	"vcmi.adventureMap.confirmRestartGame"     : "你想要重新开始游戏吗?",
 	"vcmi.adventureMap.noTownWithMarket"       : "没有足够的市场。",
 	"vcmi.adventureMap.noTownWithMarket"       : "没有足够的市场。",

+ 17 - 1
Mods/vcmi/config/vcmi/english.json

@@ -12,7 +12,9 @@
 	"vcmi.adventureMap.monsterThreat.levels.9"  : "Overpowering",
 	"vcmi.adventureMap.monsterThreat.levels.9"  : "Overpowering",
 	"vcmi.adventureMap.monsterThreat.levels.10" : "Deadly",
 	"vcmi.adventureMap.monsterThreat.levels.10" : "Deadly",
 	"vcmi.adventureMap.monsterThreat.levels.11" : "Impossible",
 	"vcmi.adventureMap.monsterThreat.levels.11" : "Impossible",
-	"vcmi.adventureMap.monsterLevel"            : "\n\nLevel %LEVEL %TOWN unit",
+	"vcmi.adventureMap.monsterLevel"            : "\n\nLevel %LEVEL %TOWN %ATTACK_TYPE unit",
+	"vcmi.adventureMap.monsterMeleeType"        : "melee",
+	"vcmi.adventureMap.monsterRangedType"       : "ranged",
 
 
 	"vcmi.adventureMap.confirmRestartGame"               : "Are you sure you want to restart the game?",
 	"vcmi.adventureMap.confirmRestartGame"               : "Are you sure you want to restart the game?",
 	"vcmi.adventureMap.noTownWithMarket"                 : "There are no available marketplaces!",
 	"vcmi.adventureMap.noTownWithMarket"                 : "There are no available marketplaces!",
@@ -59,6 +61,13 @@
 
 
 	"vcmi.spellBook.search" : "search...",
 	"vcmi.spellBook.search" : "search...",
 
 
+	"vcmi.spellResearch.canNotAfford" : "You can't afford to replace {%SPELL1} with {%SPELL2}. But you can still discard this spell and continue spell research.",
+	"vcmi.spellResearch.comeAgain" : "Research has already been done today. Come back tomorrow.",
+	"vcmi.spellResearch.pay" : "Would you like to replace {%SPELL1} with {%SPELL2}? Or discard this spell and continue spell research?",
+	"vcmi.spellResearch.research" : "Research this Spell",
+	"vcmi.spellResearch.skip" : "Skip this Spell",
+	"vcmi.spellResearch.abort" : "Abort",
+
 	"vcmi.mainMenu.serverConnecting" : "Connecting...",
 	"vcmi.mainMenu.serverConnecting" : "Connecting...",
 	"vcmi.mainMenu.serverAddressEnter" : "Enter address:",
 	"vcmi.mainMenu.serverAddressEnter" : "Enter address:",
 	"vcmi.mainMenu.serverConnectionFailed" : "Failed to connect",
 	"vcmi.mainMenu.serverConnectionFailed" : "Failed to connect",
@@ -143,6 +152,7 @@
 	"vcmi.client.errors.invalidMap" : "{Invalid map or campaign}\n\nFailed to start game! Selected map or campaign might be invalid or corrupted. Reason:\n%s",
 	"vcmi.client.errors.invalidMap" : "{Invalid map or campaign}\n\nFailed to start game! Selected map or campaign might be invalid or corrupted. Reason:\n%s",
 	"vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.",
 	"vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.",
 	"vcmi.server.errors.disconnected" : "{Network Error}\n\nConnection to game server has been lost!",
 	"vcmi.server.errors.disconnected" : "{Network Error}\n\nConnection to game server has been lost!",
+	"vcmi.server.errors.playerLeft" : "{Player Left}\n\n%s player have disconnected from the game!", //%s -> player color
 	"vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.",
 	"vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.",
 	"vcmi.server.errors.modsToEnable"    : "{Following mods are required}",
 	"vcmi.server.errors.modsToEnable"    : "{Following mods are required}",
 	"vcmi.server.errors.modsToDisable"   : "{Following mods must be disabled}",
 	"vcmi.server.errors.modsToDisable"   : "{Following mods must be disabled}",
@@ -340,6 +350,12 @@
 	"vcmi.heroWindow.openCommander.help"  : "Shows details about the commander of this hero.",
 	"vcmi.heroWindow.openCommander.help"  : "Shows details about the commander of this hero.",
 	"vcmi.heroWindow.openBackpack.hover" : "Open artifact backpack window",
 	"vcmi.heroWindow.openBackpack.hover" : "Open artifact backpack window",
 	"vcmi.heroWindow.openBackpack.help"  : "Opens window that allows easier artifact backpack management.",
 	"vcmi.heroWindow.openBackpack.help"  : "Opens window that allows easier artifact backpack management.",
+	"vcmi.heroWindow.sortBackpackByCost.hover"  : "Sort by cost",
+	"vcmi.heroWindow.sortBackpackByCost.help"  : "Sort artifacts in backpack by cost.",
+	"vcmi.heroWindow.sortBackpackBySlot.hover"  : "Sort by slot",
+	"vcmi.heroWindow.sortBackpackBySlot.help"  : "Sort artifacts in backpack by equipped slot.",
+	"vcmi.heroWindow.sortBackpackByClass.hover"  : "Sort by class",
+	"vcmi.heroWindow.sortBackpackByClass.help"  : "Sort artifacts in backpack by artifact class. Treasure, Minor, Major, Relic",
 
 
 	"vcmi.tavernWindow.inviteHero"  : "Invite hero",
 	"vcmi.tavernWindow.inviteHero"  : "Invite hero",
 
 

+ 10 - 1
Mods/vcmi/config/vcmi/german.json

@@ -12,7 +12,9 @@
 	"vcmi.adventureMap.monsterThreat.levels.9"  : "Überwältigend",
 	"vcmi.adventureMap.monsterThreat.levels.9"  : "Überwältigend",
 	"vcmi.adventureMap.monsterThreat.levels.10" : "Tödlich",
 	"vcmi.adventureMap.monsterThreat.levels.10" : "Tödlich",
 	"vcmi.adventureMap.monsterThreat.levels.11" : "Unmöglich",
 	"vcmi.adventureMap.monsterThreat.levels.11" : "Unmöglich",
-	"vcmi.adventureMap.monsterLevel"            : "\n\nStufe %LEVEL %TOWN-Einheit",
+	"vcmi.adventureMap.monsterLevel"            : "\n\nStufe %LEVEL %TOWN-Einheit (%ATTACK_TYPE)",
+	"vcmi.adventureMap.monsterMeleeType"        : "Nahkampf",
+	"vcmi.adventureMap.monsterRangedType"       : "Fernkampf",
 
 
 	"vcmi.adventureMap.confirmRestartGame"               : "Seid Ihr sicher, dass Ihr das Spiel neu starten wollt?",
 	"vcmi.adventureMap.confirmRestartGame"               : "Seid Ihr sicher, dass Ihr das Spiel neu starten wollt?",
 	"vcmi.adventureMap.noTownWithMarket"                 : "Kein Marktplatz verfügbar!",
 	"vcmi.adventureMap.noTownWithMarket"                 : "Kein Marktplatz verfügbar!",
@@ -58,6 +60,13 @@
 
 
 	"vcmi.spellBook.search" : "suchen...",
 	"vcmi.spellBook.search" : "suchen...",
 
 
+	"vcmi.spellResearch.canNotAfford" : "Ihr könnt es Euch nicht leisten, {%SPELL1} durch {%SPELL2} zu ersetzen. Aber Ihr könnt diesen Zauberspruch trotzdem verwerfen und die Zauberspruchforschung fortsetzen.",
+	"vcmi.spellResearch.comeAgain" : "Die Forschung wurde heute bereits abgeschlossen. Kommt morgen wieder.",
+	"vcmi.spellResearch.pay" : "Möchtet Ihr {%SPELL1} durch {%SPELL2} ersetzen? Oder diesen Zauberspruch verwerfen und die Zauberspruchforschung fortsetzen?",
+	"vcmi.spellResearch.research" : "Erforsche diesen Zauberspruch",
+	"vcmi.spellResearch.skip" : "Überspringe diesen Zauberspruch",
+	"vcmi.spellResearch.abort" : "Abbruch",
+
 	"vcmi.mainMenu.serverConnecting" : "Verbinde...",
 	"vcmi.mainMenu.serverConnecting" : "Verbinde...",
 	"vcmi.mainMenu.serverAddressEnter" : "Addresse eingeben:",
 	"vcmi.mainMenu.serverAddressEnter" : "Addresse eingeben:",
 	"vcmi.mainMenu.serverConnectionFailed" : "Verbindung fehlgeschlagen",
 	"vcmi.mainMenu.serverConnectionFailed" : "Verbindung fehlgeschlagen",

+ 9 - 4
client/CServerHandler.cpp

@@ -141,7 +141,12 @@ void CServerHandler::resetStateForLobby(EStartMode mode, ESelectionScreen screen
 	if(!playerNames.empty()) //if have custom set of player names - use it
 	if(!playerNames.empty()) //if have custom set of player names - use it
 		localPlayerNames = playerNames;
 		localPlayerNames = playerNames;
 	else
 	else
-		localPlayerNames.push_back(settings["general"]["playerName"].String());
+	{
+		std::string playerName = settings["general"]["playerName"].String();
+		if(playerName == "Player")
+			playerName = CGI->generaltexth->translate("core.genrltxt.434");
+		localPlayerNames.push_back(playerName);
+	}
 
 
 	gameChat->resetMatchState();
 	gameChat->resetMatchState();
 	lobbyClient->resetMatchState();
 	lobbyClient->resetMatchState();
@@ -853,7 +858,7 @@ void CServerHandler::onPacketReceived(const std::shared_ptr<INetworkConnection>
 	if(getState() == EClientState::DISCONNECTING)
 	if(getState() == EClientState::DISCONNECTING)
 		return;
 		return;
 
 
-	CPack * pack = logicConnection->retrievePack(message);
+	auto pack = logicConnection->retrievePack(message);
 	ServerHandlerCPackVisitor visitor(*this);
 	ServerHandlerCPackVisitor visitor(*this);
 	pack->visit(visitor);
 	pack->visit(visitor);
 }
 }
@@ -938,14 +943,14 @@ void CServerHandler::visitForLobby(CPackForLobby & lobbyPack)
 
 
 void CServerHandler::visitForClient(CPackForClient & clientPack)
 void CServerHandler::visitForClient(CPackForClient & clientPack)
 {
 {
-	client->handlePack(&clientPack);
+	client->handlePack(clientPack);
 }
 }
 
 
 
 
 void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const
 void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const
 {
 {
 	if(getState() != EClientState::STARTING)
 	if(getState() != EClientState::STARTING)
-		logicConnection->sendPack(&pack);
+		logicConnection->sendPack(pack);
 }
 }
 
 
 bool CServerHandler::inLobbyRoom() const
 bool CServerHandler::inLobbyRoom() const

+ 0 - 1
client/CServerHandler.h

@@ -25,7 +25,6 @@ struct TurnTimerInfo;
 class CMapInfo;
 class CMapInfo;
 class CGameState;
 class CGameState;
 struct ClientPlayer;
 struct ClientPlayer;
-struct CPack;
 struct CPackForLobby;
 struct CPackForLobby;
 struct CPackForClient;
 struct CPackForClient;
 
 

+ 12 - 14
client/Client.cpp

@@ -163,7 +163,7 @@ void CClient::save(const std::string & fname)
 	}
 	}
 
 
 	SaveGame save_game(fname);
 	SaveGame save_game(fname);
-	sendRequest(&save_game, PlayerColor::NEUTRAL);
+	sendRequest(save_game, PlayerColor::NEUTRAL);
 }
 }
 
 
 void CClient::endNetwork()
 void CClient::endNetwork()
@@ -348,37 +348,35 @@ void CClient::installNewBattleInterface(std::shared_ptr<CBattleGameInterface> ba
 	}
 	}
 }
 }
 
 
-void CClient::handlePack(CPackForClient * pack)
+void CClient::handlePack(CPackForClient & pack)
 {
 {
 	ApplyClientNetPackVisitor afterVisitor(*this, *gameState());
 	ApplyClientNetPackVisitor afterVisitor(*this, *gameState());
 	ApplyFirstClientNetPackVisitor beforeVisitor(*this, *gameState());
 	ApplyFirstClientNetPackVisitor beforeVisitor(*this, *gameState());
 
 
-	pack->visit(beforeVisitor);
-	logNetwork->trace("\tMade first apply on cl: %s", typeid(*pack).name());
+	pack.visit(beforeVisitor);
+	logNetwork->trace("\tMade first apply on cl: %s", typeid(pack).name());
 	{
 	{
 		boost::unique_lock lock(CGameState::mutex);
 		boost::unique_lock lock(CGameState::mutex);
 		gs->apply(pack);
 		gs->apply(pack);
 	}
 	}
-	logNetwork->trace("\tApplied on gs: %s", typeid(*pack).name());
-	pack->visit(afterVisitor);
-	logNetwork->trace("\tMade second apply on cl: %s", typeid(*pack).name());
-
-	delete pack;
+	logNetwork->trace("\tApplied on gs: %s", typeid(pack).name());
+	pack.visit(afterVisitor);
+	logNetwork->trace("\tMade second apply on cl: %s", typeid(pack).name());
 }
 }
 
 
-int CClient::sendRequest(const CPackForServer * request, PlayerColor player)
+int CClient::sendRequest(const CPackForServer & request, PlayerColor player)
 {
 {
 	static ui32 requestCounter = 1;
 	static ui32 requestCounter = 1;
 
 
 	ui32 requestID = requestCounter++;
 	ui32 requestID = requestCounter++;
-	logNetwork->trace("Sending a request \"%s\". It'll have an ID=%d.", typeid(*request).name(), requestID);
+	logNetwork->trace("Sending a request \"%s\". It'll have an ID=%d.", typeid(request).name(), requestID);
 
 
 	waitingRequest.pushBack(requestID);
 	waitingRequest.pushBack(requestID);
-	request->requestID = requestID;
-	request->player = player;
+	request.requestID = requestID;
+	request.player = player;
 	CSH->logicConnection->sendPack(request);
 	CSH->logicConnection->sendPack(request);
 	if(vstd::contains(playerint, player))
 	if(vstd::contains(playerint, player))
-		playerint[player]->requestSent(request, requestID);
+		playerint[player]->requestSent(&request, requestID);
 
 
 	return requestID;
 	return requestID;
 }
 }

+ 4 - 4
client/Client.h

@@ -16,7 +16,6 @@
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
-struct CPack;
 struct CPackForServer;
 struct CPackForServer;
 class IBattleEventsReceiver;
 class IBattleEventsReceiver;
 class CBattleGameInterface;
 class CBattleGameInterface;
@@ -143,8 +142,8 @@ public:
 
 
 	static ThreadSafeVector<int> waitingRequest; //FIXME: make this normal field (need to join all threads before client destruction)
 	static ThreadSafeVector<int> waitingRequest; //FIXME: make this normal field (need to join all threads before client destruction)
 
 
-	void handlePack(CPackForClient * pack); //applies the given pack and deletes it
-	int sendRequest(const CPackForServer * request, PlayerColor player); //returns ID given to that request
+	void handlePack(CPackForClient & pack); //applies the given pack and deletes it
+	int sendRequest(const CPackForServer & request, PlayerColor player); //returns ID given to that request
 
 
 	void battleStarted(const BattleInfo * info);
 	void battleStarted(const BattleInfo * info);
 	void battleFinished(const BattleID & battleID);
 	void battleFinished(const BattleID & battleID);
@@ -159,6 +158,7 @@ public:
 	friend class CBattleCallback; //handling players actions
 	friend class CBattleCallback; //handling players actions
 
 
 	void changeSpells(const CGHeroInstance * hero, bool give, const std::set<SpellID> & spells) override {};
 	void changeSpells(const CGHeroInstance * hero, bool give, const std::set<SpellID> & spells) override {};
+	void setResearchedSpells(const CGTownInstance * town, int level, const std::vector<SpellID> spells, bool accepted) override {};
 	bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;};
 	bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;};
 	void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override {};
 	void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override {};
 	void setOwner(const CGObjectInstance * obj, PlayerColor owner) override {};
 	void setOwner(const CGObjectInstance * obj, PlayerColor owner) override {};
@@ -204,7 +204,7 @@ public:
 	void setManaPoints(ObjectInstanceID hid, int val) override {};
 	void setManaPoints(ObjectInstanceID hid, int val) override {};
 	void giveHero(ObjectInstanceID id, PlayerColor player, ObjectInstanceID boatId = ObjectInstanceID()) override {};
 	void giveHero(ObjectInstanceID id, PlayerColor player, ObjectInstanceID boatId = ObjectInstanceID()) override {};
 	void changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator) override {};
 	void changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator) override {};
-	void sendAndApply(CPackForClient * pack) override {};
+	void sendAndApply(CPackForClient & pack) override {};
 	void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) override {};
 	void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) override {};
 	void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) override {};
 	void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) override {};
 
 

+ 1 - 0
client/ClientNetPackVisitors.h

@@ -37,6 +37,7 @@ public:
 	void visitHeroVisitCastle(HeroVisitCastle & pack) override;
 	void visitHeroVisitCastle(HeroVisitCastle & pack) override;
 	void visitSetMana(SetMana & pack) override;
 	void visitSetMana(SetMana & pack) override;
 	void visitSetMovePoints(SetMovePoints & pack) override;
 	void visitSetMovePoints(SetMovePoints & pack) override;
+	void visitSetResearchedSpells(SetResearchedSpells & pack) override;
 	void visitFoWChange(FoWChange & pack) override;
 	void visitFoWChange(FoWChange & pack) override;
 	void visitChangeStackCount(ChangeStackCount & pack) override;
 	void visitChangeStackCount(ChangeStackCount & pack) override;
 	void visitSetStackType(SetStackType & pack) override;
 	void visitSetStackType(SetStackType & pack) override;

+ 7 - 0
client/NetPacksClient.cpp

@@ -14,6 +14,7 @@
 #include "CPlayerInterface.h"
 #include "CPlayerInterface.h"
 #include "CGameInfo.h"
 #include "CGameInfo.h"
 #include "windows/GUIClasses.h"
 #include "windows/GUIClasses.h"
+#include "windows/CCastleInterface.h"
 #include "mapView/mapHandler.h"
 #include "mapView/mapHandler.h"
 #include "adventureMap/AdventureMapInterface.h"
 #include "adventureMap/AdventureMapInterface.h"
 #include "adventureMap/CInGameConsole.h"
 #include "adventureMap/CInGameConsole.h"
@@ -172,6 +173,12 @@ void ApplyClientNetPackVisitor::visitSetMovePoints(SetMovePoints & pack)
 	callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroMovePointsChanged, h);
 	callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroMovePointsChanged, h);
 }
 }
 
 
+void ApplyClientNetPackVisitor::visitSetResearchedSpells(SetResearchedSpells & pack)
+{
+	for(const auto & win : GH.windows().findWindows<CMageGuildScreen>())
+		win->updateSpells(pack.tid);
+}
+
 void ApplyClientNetPackVisitor::visitFoWChange(FoWChange & pack)
 void ApplyClientNetPackVisitor::visitFoWChange(FoWChange & pack)
 {
 {
 	for(auto &i : cl.playerint)
 	for(auto &i : cl.playerint)

+ 5 - 6
client/lobby/OptionsTab.cpp

@@ -835,9 +835,9 @@ OptionsTab::HandicapWindow::HandicapWindow()
 			if(i == 0)
 			if(i == 0)
 			{
 			{
 				if(isIncome)
 				if(isIncome)
-					labels.push_back(std::make_shared<CLabel>(xPos, 35, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("core.jktext.32")));
+					labels.push_back(std::make_shared<CLabel>(xPos, 38, FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("core.jktext.32")));
 				else if(isGrowth)
 				else if(isGrowth)
-					labels.push_back(std::make_shared<CLabel>(xPos, 35, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.194")));
+					labels.push_back(std::make_shared<CLabel>(xPos, 38, FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.194")));
 				else
 				else
 					anim.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SMALRES"), GameResID(resource), 0, 15 + xPos + (j == 0 ? 10 : 0), 35));
 					anim.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SMALRES"), GameResID(resource), 0, 15 + xPos + (j == 0 ? 10 : 0), 35));
 			}
 			}
@@ -1035,14 +1035,13 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S, con
 		labelPlayerNameEdit = std::make_shared<CTextInput>(Rect(6, 3, 95, 15), EFonts::FONT_SMALL, ETextAlignment::CENTER, false);
 		labelPlayerNameEdit = std::make_shared<CTextInput>(Rect(6, 3, 95, 15), EFonts::FONT_SMALL, ETextAlignment::CENTER, false);
 		labelPlayerNameEdit->setText(name);
 		labelPlayerNameEdit->setText(name);
 	}
 	}
-	const auto & font = GH.renderHandler().loadFont(FONT_SMALL);
 
 
-	labelWhoCanPlay = std::make_shared<CMultiLineLabel>(Rect(6, 23, 45, font->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]);
+	labelWhoCanPlay = std::make_shared<CMultiLineLabel>(Rect(6, 21, 45, 26), EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]);
 
 
 	auto hasHandicap = [this](){ return s->handicap.startBonus.empty() && s->handicap.percentIncome == 100 && s->handicap.percentGrowth == 100; };
 	auto hasHandicap = [this](){ return s->handicap.startBonus.empty() && s->handicap.percentIncome == 100 && s->handicap.percentGrowth == 100; };
 	std::string labelHandicapText = hasHandicap() ? CGI->generaltexth->arraytxt[210] : MetaString::createFromTextID("vcmi.lobby.handicap").toString();
 	std::string labelHandicapText = hasHandicap() ? CGI->generaltexth->arraytxt[210] : MetaString::createFromTextID("vcmi.lobby.handicap").toString();
-	labelHandicap = std::make_shared<CMultiLineLabel>(Rect(57, 24, 47, font->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::TOPCENTER, Colors::WHITE, labelHandicapText);
-	handicap = std::make_shared<LRClickableArea>(Rect(56, 24, 49, font->getLineHeight()*2), [](){
+	labelHandicap = std::make_shared<CMultiLineLabel>(Rect(55, 23, 46, 24), EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, labelHandicapText);
+	handicap = std::make_shared<LRClickableArea>(Rect(53, 23, 50, 24), [](){
 		if(!CSH->isHost())
 		if(!CSH->isHost())
 			return;
 			return;
 		
 		

+ 59 - 18
client/media/CVideoHandler.cpp

@@ -33,7 +33,9 @@ extern "C" {
 #include <libavformat/avformat.h>
 #include <libavformat/avformat.h>
 #include <libavcodec/avcodec.h>
 #include <libavcodec/avcodec.h>
 #include <libavutil/imgutils.h>
 #include <libavutil/imgutils.h>
+#include <libavutil/opt.h>
 #include <libswscale/swscale.h>
 #include <libswscale/swscale.h>
+#include <libswresample/swresample.h>
 }
 }
 
 
 // Define a set of functions to read data
 // Define a set of functions to read data
@@ -501,32 +503,71 @@ std::pair<std::unique_ptr<ui8 []>, si64> CAudioInstance::extractAudio(const Vide
 	int numChannels = codecpar->ch_layout.nb_channels;
 	int numChannels = codecpar->ch_layout.nb_channels;
 #endif
 #endif
 
 
-	samples.reserve(44100 * 5); // arbitrary 5-second buffer
+	samples.reserve(44100 * 5); // arbitrary 5-second buffer to reduce reallocations
 
 
-	for (;;)
+	if (formatProperties.isPlanar && numChannels > 1)
 	{
 	{
-		decodeNextFrame();
-		const AVFrame * frame = getCurrentFrame();
+		// Format is 'planar', which is not supported by wav / SDL
+		// Use swresample part of ffmpeg to deplanarize audio into format supported by wav / SDL
 
 
-		if (!frame)
-			break;
+		auto sourceFormat = static_cast<AVSampleFormat>(codecpar->format);
+		auto targetFormat = av_get_alt_sample_fmt(sourceFormat, false);
 
 
-		int samplesToRead = frame->nb_samples * numChannels;
-		int bytesToRead = samplesToRead * formatProperties.sampleSizeBytes;
+		SwrContext * swr_ctx = swr_alloc();
 
 
-		if (formatProperties.isPlanar && numChannels > 1)
+#if (LIBAVUTIL_VERSION_MAJOR < 58)
+		av_opt_set_channel_layout(swr_ctx, "in_chlayout", codecpar->channel_layout, 0);
+		av_opt_set_channel_layout(swr_ctx, "out_chlayout", codecpar->channel_layout, 0);
+#else
+		av_opt_set_chlayout(swr_ctx, "in_chlayout", &codecpar->ch_layout, 0);
+		av_opt_set_chlayout(swr_ctx, "out_chlayout", &codecpar->ch_layout, 0);
+#endif
+		av_opt_set_int(swr_ctx, "in_sample_rate", codecpar->sample_rate, 0);
+		av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", sourceFormat, 0);
+		av_opt_set_int(swr_ctx, "out_sample_rate", codecpar->sample_rate, 0);
+		av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", targetFormat, 0);
+
+		int initResult = swr_init(swr_ctx);
+		if (initResult < 0)
+			throwFFmpegError(initResult);
+
+		std::vector<uint8_t> frameSamplesBuffer;
+		for (;;)
 		{
 		{
-			// Workaround for lack of resampler
-			// Currently, ffmpeg on conan systems is built without sws resampler
-			// Because of that, and because wav format does not supports 'planar' formats from ffmpeg
-			// we need to de-planarize it and convert to "normal" (non-planar / interleaved) stream
-			samples.reserve(samples.size() + bytesToRead);
-			for (int sm = 0; sm < frame->nb_samples; ++sm)
-				for (int ch = 0; ch < numChannels; ++ch)
-					samples.insert(samples.end(), frame->data[ch] + sm * formatProperties.sampleSizeBytes, frame->data[ch] + (sm+1) * formatProperties.sampleSizeBytes );
+			decodeNextFrame();
+			const AVFrame * frame = getCurrentFrame();
+
+			if (!frame)
+				break;
+
+			size_t samplesToRead = frame->nb_samples * numChannels;
+			size_t bytesToRead = samplesToRead * formatProperties.sampleSizeBytes;
+			frameSamplesBuffer.resize(std::max(frameSamplesBuffer.size(), bytesToRead));
+			uint8_t * frameSamplesPtr = frameSamplesBuffer.data();
+
+			int result = swr_convert(swr_ctx, &frameSamplesPtr, frame->nb_samples, (const uint8_t **)frame->data, frame->nb_samples);
+
+			if (result < 0)
+				throwFFmpegError(result);
+
+			size_t samplesToCopy = result * numChannels;
+			size_t bytesToCopy = samplesToCopy * formatProperties.sampleSizeBytes;
+			samples.insert(samples.end(), frameSamplesBuffer.begin(), frameSamplesBuffer.begin() + bytesToCopy);
 		}
 		}
-		else
+		swr_free(&swr_ctx);
+	}
+	else
+	{
+		for (;;)
 		{
 		{
+			decodeNextFrame();
+			const AVFrame * frame = getCurrentFrame();
+
+			if (!frame)
+				break;
+
+			size_t samplesToRead = frame->nb_samples * numChannels;
+			size_t bytesToRead = samplesToRead * formatProperties.sampleSizeBytes;
 			samples.insert(samples.end(), frame->data[0], frame->data[0] + bytesToRead);
 			samples.insert(samples.end(), frame->data[0], frame->data[0] + bytesToRead);
 		}
 		}
 	}
 	}

+ 14 - 9
client/render/AssetGenerator.cpp

@@ -18,6 +18,10 @@
 #include "../render/IRenderHandler.h"
 #include "../render/IRenderHandler.h"
 
 
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/Filesystem.h"
+#include "../lib/GameSettings.h"
+#include "../lib/IGameSettings.h"
+#include "../lib/json/JsonNode.h"
+#include "../lib/VCMI_Lib.h"
 
 
 void AssetGenerator::generateAll()
 void AssetGenerator::generateAll()
 {
 {
@@ -138,16 +142,17 @@ void AssetGenerator::createPlayerColoredBackground(const PlayerColor & player)
 
 
 	std::shared_ptr<IImage> texture = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE);
 	std::shared_ptr<IImage> texture = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE);
 
 
-	// Color transform to make color of brown DIBOX.PCX texture match color of specified player
+	// transform to make color of brown DIBOX.PCX texture match color of specified player
+	auto filterSettings = VLC->settingsHandler->getFullConfig()["interface"]["playerColoredBackground"];
 	static const std::array<ColorFilter, PlayerColor::PLAYER_LIMIT_I> filters = {
 	static const std::array<ColorFilter, PlayerColor::PLAYER_LIMIT_I> filters = {
-		ColorFilter::genRangeShifter(  0.25,  0,     0,     1.25, 0.00, 0.00 ), // red
-		ColorFilter::genRangeShifter(  0,     0,     0,     0.45, 1.20, 4.50 ), // blue
-		ColorFilter::genRangeShifter(  0.40,  0.27,  0.23,  1.10, 1.20, 1.15 ), // tan
-		ColorFilter::genRangeShifter( -0.27,  0.10, -0.27,  0.70, 1.70, 0.70 ), // green
-		ColorFilter::genRangeShifter(  0.47,  0.17, -0.27,  1.60, 1.20, 0.70 ), // orange
-		ColorFilter::genRangeShifter(  0.12, -0.1,   0.25,  1.15, 1.20, 2.20 ), // purple
-		ColorFilter::genRangeShifter( -0.13,  0.23,  0.23,  0.90, 1.20, 2.20 ), // teal
-		ColorFilter::genRangeShifter(  0.44,  0.15,  0.25,  1.00, 1.00, 1.75 )  // pink
+		ColorFilter::genRangeShifter( filterSettings["red"   ].convertTo<std::vector<float>>() ),
+		ColorFilter::genRangeShifter( filterSettings["blue"  ].convertTo<std::vector<float>>() ),
+		ColorFilter::genRangeShifter( filterSettings["tan"   ].convertTo<std::vector<float>>() ),
+		ColorFilter::genRangeShifter( filterSettings["green" ].convertTo<std::vector<float>>() ),
+		ColorFilter::genRangeShifter( filterSettings["orange"].convertTo<std::vector<float>>() ),
+		ColorFilter::genRangeShifter( filterSettings["purple"].convertTo<std::vector<float>>() ),
+		ColorFilter::genRangeShifter( filterSettings["teal"  ].convertTo<std::vector<float>>() ),
+		ColorFilter::genRangeShifter( filterSettings["pink"  ].convertTo<std::vector<float>>() )
 	};
 	};
 
 
 	assert(player.isValidPlayer());
 	assert(player.isValidPlayer());

+ 7 - 0
client/render/ColorFilter.cpp

@@ -70,6 +70,13 @@ ColorFilter ColorFilter::genRangeShifter( float minR, float minG, float minB, fl
 				  1.f);
 				  1.f);
 }
 }
 
 
+ColorFilter ColorFilter::genRangeShifter( std::vector<float> parameters )
+{
+	assert(std::size(parameters) == 6);
+
+	return genRangeShifter(parameters[0], parameters[1], parameters[2], parameters[3], parameters[4], parameters[5]);
+}
+
 ColorFilter ColorFilter::genMuxerShifter( ChannelMuxer r, ChannelMuxer g, ChannelMuxer b, float a )
 ColorFilter ColorFilter::genMuxerShifter( ChannelMuxer r, ChannelMuxer g, ChannelMuxer b, float a )
 {
 {
 	return ColorFilter(r, g, b, a);
 	return ColorFilter(r, g, b, a);

+ 1 - 0
client/render/ColorFilter.h

@@ -44,6 +44,7 @@ public:
 
 
 	/// Generates object that transforms each channel independently
 	/// Generates object that transforms each channel independently
 	static ColorFilter genRangeShifter( float minR, float minG, float minB, float maxR, float maxG, float maxB );
 	static ColorFilter genRangeShifter( float minR, float minG, float minB, float maxR, float maxG, float maxB );
+	static ColorFilter genRangeShifter( std::vector<float> parameters );
 
 
 	/// Generates object that performs arbitrary mixing between any channels
 	/// Generates object that performs arbitrary mixing between any channels
 	static ColorFilter genMuxerShifter( ChannelMuxer r, ChannelMuxer g, ChannelMuxer b, float a );
 	static ColorFilter genMuxerShifter( ChannelMuxer r, ChannelMuxer g, ChannelMuxer b, float a );

+ 3 - 1
client/widgets/CComponent.cpp

@@ -70,6 +70,7 @@ void CComponent::init(ComponentType Type, ComponentSubType Subtype, std::optiona
 	customSubtitle = ValText;
 	customSubtitle = ValText;
 	size = imageSize;
 	size = imageSize;
 	font = fnt;
 	font = fnt;
+	newLine = false;
 
 
 	assert(size < sizeInvalid);
 	assert(size < sizeInvalid);
 
 
@@ -471,7 +472,8 @@ void CComponentBox::placeComponents(bool selectable)
 
 
 		//start next row
 		//start next row
 		if ((pos.w != 0 && rows.back().width + comp->pos.w + distance > pos.w) // row is full
 		if ((pos.w != 0 && rows.back().width + comp->pos.w + distance > pos.w) // row is full
-			|| rows.back().comps >= componentsInRow)
+			|| rows.back().comps >= componentsInRow
+			|| (prevComp && prevComp->newLine))
 		{
 		{
 			prevComp = nullptr;
 			prevComp = nullptr;
 			rows.push_back (RowData (0,0,0));
 			rows.push_back (RowData (0,0,0));

+ 1 - 0
client/widgets/CComponent.h

@@ -52,6 +52,7 @@ public:
 	std::string customSubtitle;
 	std::string customSubtitle;
 	ESize size; //component size.
 	ESize size; //component size.
 	EFonts font; //Font size of label
 	EFonts font; //Font size of label
+	bool newLine; //Line break after component
 
 
 	std::string getDescription() const;
 	std::string getDescription() const;
 	std::string getSubtitle() const;
 	std::string getSubtitle() const;

+ 80 - 8
client/windows/CCastleInterface.cpp

@@ -1782,6 +1782,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town):
 	ui32 fortSize = static_cast<ui32>(town->creatures.size());
 	ui32 fortSize = static_cast<ui32>(town->creatures.size());
 	if(fortSize > town->town->creatures.size() && town->creatures.back().second.empty())
 	if(fortSize > town->town->creatures.size() && town->creatures.back().second.empty())
 		fortSize--;
 		fortSize--;
+	fortSize = std::min(fortSize, static_cast<ui32>(GameConstants::CREATURES_PER_TOWN)); // for 8 creatures + portal of summoning
 
 
 	const CBuilding * fortBuilding = town->town->buildings.at(BuildingID(town->fortLevel()+6));
 	const CBuilding * fortBuilding = town->town->buildings.at(BuildingID(town->fortLevel()+6));
 	title = std::make_shared<CLabel>(400, 12, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE, fortBuilding->getNameTranslated());
 	title = std::make_shared<CLabel>(400, 12, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE, fortBuilding->getNameTranslated());
@@ -1840,6 +1841,7 @@ ImagePath CFortScreen::getBgName(const CGTownInstance * town)
 	ui32 fortSize = static_cast<ui32>(town->creatures.size());
 	ui32 fortSize = static_cast<ui32>(town->creatures.size());
 	if(fortSize > town->town->creatures.size() && town->creatures.back().second.empty())
 	if(fortSize > town->town->creatures.size() && town->creatures.back().second.empty())
 		fortSize--;
 		fortSize--;
+	fortSize = std::min(fortSize, static_cast<ui32>(GameConstants::CREATURES_PER_TOWN)); // for 8 creatures + portal of summoning
 
 
 	if(fortSize == GameConstants::CREATURES_PER_TOWN)
 	if(fortSize == GameConstants::CREATURES_PER_TOWN)
 		return ImagePath::builtin("TPCASTL8");
 		return ImagePath::builtin("TPCASTL8");
@@ -1966,7 +1968,7 @@ void CFortScreen::RecruitArea::showPopupWindow(const Point & cursorPosition)
 }
 }
 
 
 CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & imagename)
 CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & imagename)
-	: CWindowObject(BORDERED, imagename)
+	: CWindowObject(BORDERED, imagename), townId(owner->town->id)
 {
 {
 	OBJECT_CONSTRUCTION;
 	OBJECT_CONSTRUCTION;
 
 
@@ -1982,6 +1984,15 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & i
 
 
 	exit = std::make_shared<CButton>(Point(748, 556), AnimationPath::builtin("TPMAGE1.DEF"), CButton::tooltip(CGI->generaltexth->allTexts[593]), [&](){ close(); }, EShortcut::GLOBAL_RETURN);
 	exit = std::make_shared<CButton>(Point(748, 556), AnimationPath::builtin("TPMAGE1.DEF"), CButton::tooltip(CGI->generaltexth->allTexts[593]), [&](){ close(); }, EShortcut::GLOBAL_RETURN);
 
 
+	updateSpells(townId);
+}
+
+void CMageGuildScreen::updateSpells(ObjectInstanceID tID)
+{
+	if(tID != townId)
+		return;
+
+	OBJECT_CONSTRUCTION;
 	static const std::vector<std::vector<Point> > positions =
 	static const std::vector<std::vector<Point> > positions =
 	{
 	{
 		{Point(222,445), Point(312,445), Point(402,445), Point(520,445), Point(610,445), Point(700,445)},
 		{Point(222,445), Point(312,445), Point(402,445), Point(520,445), Point(610,445), Point(700,445)},
@@ -1991,21 +2002,28 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & i
 		{Point(491,325), Point(591,325)}
 		{Point(491,325), Point(591,325)}
 	};
 	};
 
 
-	for(size_t i=0; i<owner->town->town->mageLevel; i++)
+	spells.clear();
+	emptyScrolls.clear();
+
+	const CGTownInstance * town = LOCPLINT->cb->getTown(townId);
+
+	for(size_t i=0; i<town->town->mageLevel; i++)
 	{
 	{
-		size_t spellCount = owner->town->spellsAtLevel((int)i+1,false); //spell at level with -1 hmmm?
+		size_t spellCount = town->spellsAtLevel((int)i+1,false); //spell at level with -1 hmmm?
 		for(size_t j=0; j<spellCount; j++)
 		for(size_t j=0; j<spellCount; j++)
 		{
 		{
-			if(i<owner->town->mageGuildLevel() && owner->town->spells[i].size()>j)
-				spells.push_back(std::make_shared<Scroll>(positions[i][j], owner->town->spells[i][j].toSpell()));
+			if(i<town->mageGuildLevel() && town->spells[i].size()>j)
+				spells.push_back(std::make_shared<Scroll>(positions[i][j], town->spells[i][j].toSpell(), townId));
 			else
 			else
 				emptyScrolls.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("TPMAGES.DEF"), 1, 0, positions[i][j].x, positions[i][j].y));
 				emptyScrolls.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("TPMAGES.DEF"), 1, 0, positions[i][j].x, positions[i][j].y));
 		}
 		}
 	}
 	}
+
+	redraw();
 }
 }
 
 
-CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell)
-	: spell(Spell)
+CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell, ObjectInstanceID townId)
+	: spell(Spell), townId(townId)
 {
 {
 	OBJECT_CONSTRUCTION;
 	OBJECT_CONSTRUCTION;
 
 
@@ -2017,7 +2035,61 @@ CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell)
 
 
 void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition)
 void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition)
 {
 {
-	LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared<CComponent>(ComponentType::SPELL, spell->id));
+	const CGTownInstance * town = LOCPLINT->cb->getTown(townId);
+	if(LOCPLINT->cb->getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && town->spellResearchAllowed)
+	{
+		int level = -1;
+		for(int i = 0; i < town->spells.size(); i++)
+			if(vstd::find_pos(town->spells[i], spell->id) != -1)
+				level = i;
+				
+		if(town->spellResearchCounterDay >= LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float())
+		{
+			LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.comeAgain"));
+			return;
+		}
+
+		auto costBase = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST).Vector()[level]);
+		auto costExponent = LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH).Vector()[level].Float();
+		auto cost = costBase * std::pow(town->spellResearchAcceptedCounter + 1, costExponent);
+
+		std::vector<std::shared_ptr<CComponent>> resComps;
+		auto newSpell = town->spells[level].at(town->spellsAtLevel(level, false));
+		resComps.push_back(std::make_shared<CComponent>(ComponentType::SPELL, spell->id));
+		resComps.push_back(std::make_shared<CComponent>(ComponentType::SPELL, newSpell));
+		resComps.back()->newLine = true;
+		for(TResources::nziterator i(cost); i.valid(); i++)
+		{
+			resComps.push_back(std::make_shared<CComponent>(ComponentType::RESOURCE, i->resType, i->resVal, CComponent::ESize::medium));
+		}
+
+		auto showSpellResearchDialog = [this, resComps, town, cost, newSpell](){
+			std::vector<std::pair<AnimationPath, CFunctionList<void()>>> pom;
+			for(int i = 0; i < 3; i++)
+				pom.emplace_back(AnimationPath::builtin("settingsWindow/button80"), nullptr);
+
+			auto text = CGI->generaltexth->translate(LOCPLINT->cb->getResourceAmount().canAfford(cost) ? "vcmi.spellResearch.pay" : "vcmi.spellResearch.canNotAfford");
+			boost::replace_first(text, "%SPELL1", spell->id.toSpell()->getNameTranslated());
+			boost::replace_first(text, "%SPELL2", newSpell.toSpell()->getNameTranslated());
+			auto temp = std::make_shared<CInfoWindow>(text, LOCPLINT->playerID, resComps, pom);
+
+			temp->buttons[0]->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("spellResearch/accept")));
+			temp->buttons[0]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, true); });
+			temp->buttons[0]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.research")); });
+			temp->buttons[0]->setEnabled(LOCPLINT->cb->getResourceAmount().canAfford(cost));
+			temp->buttons[1]->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("spellResearch/reroll")));
+			temp->buttons[1]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, false); });
+			temp->buttons[1]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.skip")); });
+			temp->buttons[2]->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("spellResearch/close")));
+			temp->buttons[2]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.abort")); });
+
+			GH.windows().pushWindow(temp);
+		};
+
+		showSpellResearchDialog();
+	}
+	else
+		LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared<CComponent>(ComponentType::SPELL, spell->id));
 }
 }
 
 
 void CMageGuildScreen::Scroll::showPopupWindow(const Point & cursorPosition)
 void CMageGuildScreen::Scroll::showPopupWindow(const Point & cursorPosition)

+ 5 - 1
client/windows/CCastleInterface.h

@@ -379,9 +379,10 @@ class CMageGuildScreen : public CStatusbarWindow
 	{
 	{
 		const CSpell * spell;
 		const CSpell * spell;
 		std::shared_ptr<CAnimImage> image;
 		std::shared_ptr<CAnimImage> image;
+		ObjectInstanceID townId;
 
 
 	public:
 	public:
-		Scroll(Point position, const CSpell *Spell);
+		Scroll(Point position, const CSpell *Spell, ObjectInstanceID townId);
 		void clickPressed(const Point & cursorPosition) override;
 		void clickPressed(const Point & cursorPosition) override;
 		void showPopupWindow(const Point & cursorPosition) override;
 		void showPopupWindow(const Point & cursorPosition) override;
 		void hover(bool on) override;
 		void hover(bool on) override;
@@ -393,8 +394,11 @@ class CMageGuildScreen : public CStatusbarWindow
 
 
 	std::shared_ptr<CMinorResDataBar> resdatabar;
 	std::shared_ptr<CMinorResDataBar> resdatabar;
 
 
+	ObjectInstanceID townId;
+
 public:
 public:
 	CMageGuildScreen(CCastleInterface * owner, const ImagePath & image);
 	CMageGuildScreen(CCastleInterface * owner, const ImagePath & image);
+	void updateSpells(ObjectInstanceID tID);
 };
 };
 
 
 /// The blacksmith window where you can buy available in town war machine
 /// The blacksmith window where you can buy available in town war machine

+ 28 - 5
client/windows/CHeroBackpackWindow.cpp

@@ -20,6 +20,8 @@
 #include "render/Canvas.h"
 #include "render/Canvas.h"
 #include "CPlayerInterface.h"
 #include "CPlayerInterface.h"
 
 
+#include "../../CCallback.h"
+
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/networkPacks/ArtifactLocation.h"
 #include "../../lib/networkPacks/ArtifactLocation.h"
 
 
@@ -41,17 +43,38 @@ CHeroBackpackWindow::CHeroBackpackWindow(const CGHeroInstance * hero, const std:
 	};
 	};
 	addSet(arts);
 	addSet(arts);
 	arts->setHero(hero);
 	arts->setHero(hero);
-	quitButton = std::make_shared<CButton>(Point(), AnimationPath::builtin("IOKAY32.def"), CButton::tooltip(""),
-		[this]() { WindowBase::close(); }, EShortcut::GLOBAL_RETURN);
+	
+	buttons.emplace_back(std::make_unique<CButton>(Point(), AnimationPath::builtin("ALTFILL.DEF"),
+		CButton::tooltipLocalized("vcmi.heroWindow.sortBackpackByCost"),
+		[hero]() { LOCPLINT->cb->sortBackpackArtifactsByCost(hero->id); }));
+	buttons.emplace_back(std::make_unique<CButton>(Point(), AnimationPath::builtin("ALTFILL.DEF"),
+		CButton::tooltipLocalized("vcmi.heroWindow.sortBackpackBySlot"),
+		[hero]() { LOCPLINT->cb->sortBackpackArtifactsBySlot(hero->id); }));
+	buttons.emplace_back(std::make_unique<CButton>(Point(), AnimationPath::builtin("ALTFILL.DEF"),
+		CButton::tooltipLocalized("vcmi.heroWindow.sortBackpackByClass"),
+		[hero]() { LOCPLINT->cb->sortBackpackArtifactsByClass(hero->id); }));
+
 	pos.w = stretchedBackground->pos.w = arts->pos.w + 2 * windowMargin;
 	pos.w = stretchedBackground->pos.w = arts->pos.w + 2 * windowMargin;
-	pos.h = stretchedBackground->pos.h = arts->pos.h + quitButton->pos.h + 3 * windowMargin;
-	quitButton->moveTo(Point(pos.x + pos.w / 2 - quitButton->pos.w / 2, pos.y + arts->pos.h + 2 * windowMargin));
+	pos.h = stretchedBackground->pos.h = arts->pos.h + buttons.back()->pos.h + 3 * windowMargin;
+	
+	auto buttonPos = Point(pos.x + windowMargin, pos.y + arts->pos.h + 2 * windowMargin);
+	for(const auto & button : buttons)
+	{
+		button->moveTo(buttonPos);
+		buttonPos += Point(button->pos.w + 10, 0);
+	}
+
 	statusbar = CGStatusBar::create(0, pos.h, ImagePath::builtin("ADROLLVR.bmp"), pos.w);
 	statusbar = CGStatusBar::create(0, pos.h, ImagePath::builtin("ADROLLVR.bmp"), pos.w);
 	pos.h += statusbar->pos.h;
 	pos.h += statusbar->pos.h;
-
+	addUsedEvents(LCLICK);
 	center();
 	center();
 }
 }
 
 
+void CHeroBackpackWindow::notFocusedClick()
+{
+	close();
+}
+
 void CHeroBackpackWindow::showAll(Canvas & to)
 void CHeroBackpackWindow::showAll(Canvas & to)
 {
 {
 	CIntObject::showAll(to);
 	CIntObject::showAll(to);

+ 2 - 1
client/windows/CHeroBackpackWindow.h

@@ -17,10 +17,11 @@ class CHeroBackpackWindow : public CStatusbarWindow, public CWindowWithArtifacts
 {
 {
 public:
 public:
 	CHeroBackpackWindow(const CGHeroInstance * hero, const std::vector<CArtifactsOfHeroPtr> & artsSets);
 	CHeroBackpackWindow(const CGHeroInstance * hero, const std::vector<CArtifactsOfHeroPtr> & artsSets);
+	void notFocusedClick() override;
 	
 	
 protected:
 protected:
 	std::shared_ptr<CArtifactsOfHeroBackpack> arts;
 	std::shared_ptr<CArtifactsOfHeroBackpack> arts;
-	std::shared_ptr<CButton> quitButton;
+	std::vector<std::unique_ptr<CButton>> buttons;
 	std::shared_ptr<CFilledTexture> stretchedBackground;
 	std::shared_ptr<CFilledTexture> stretchedBackground;
 	const int windowMargin = 5;
 	const int windowMargin = 5;
 
 

+ 3 - 3
client/windows/CKingdomInterface.cpp

@@ -473,7 +473,7 @@ CKingdomInterface::CKingdomInterface()
 	generateButtons();
 	generateButtons();
 
 
 	statusbar = CGStatusBar::create(std::make_shared<CPicture>(ImagePath::builtin("KSTATBAR"), 10,pos.h - 45));
 	statusbar = CGStatusBar::create(std::make_shared<CPicture>(ImagePath::builtin("KSTATBAR"), 10,pos.h - 45));
-	resdatabar = std::make_shared<CResDataBar>(ImagePath::builtin("KRESBAR"), 7, 111+footerPos, 29, 5, 76, 81);
+	resdatabar = std::make_shared<CResDataBar>(ImagePath::builtin("KRESBAR"), 7, 111+footerPos, 29, 3, 76, 81);
 
 
 	activateTab(persistentStorage["gui"]["lastKindomInterface"].Integer());
 	activateTab(persistentStorage["gui"]["lastKindomInterface"].Integer());
 }
 }
@@ -805,7 +805,7 @@ CTownItem::CTownItem(const CGTownInstance * Town)
 	picture = std::make_shared<CAnimImage>(AnimationPath::builtin("ITPT"), iconIndex, 0, 5, 6);
 	picture = std::make_shared<CAnimImage>(AnimationPath::builtin("ITPT"), iconIndex, 0, 5, 6);
 	openTown = std::make_shared<LRClickableAreaOpenTown>(Rect(5, 6, 58, 64), town);
 	openTown = std::make_shared<LRClickableAreaOpenTown>(Rect(5, 6, 58, 64), town);
 
 
-	for(size_t i=0; i<town->creatures.size(); i++)
+	for(size_t i=0; i<town->creatures.size() && i<GameConstants::CREATURES_PER_TOWN; i++)
 	{
 	{
 		growth.push_back(std::make_shared<CCreaInfo>(Point(401+37*(int)i, 78), town, (int)i, true, true));
 		growth.push_back(std::make_shared<CCreaInfo>(Point(401+37*(int)i, 78), town, (int)i, true, true));
 		available.push_back(std::make_shared<CCreaInfo>(Point(48+37*(int)i, 78), town, (int)i, true, false));
 		available.push_back(std::make_shared<CCreaInfo>(Point(48+37*(int)i, 78), town, (int)i, true, false));
@@ -866,7 +866,7 @@ void CTownItem::update()
 
 
 	heroes->update();
 	heroes->update();
 
 
-	for (size_t i=0; i<town->creatures.size(); i++)
+	for (size_t i=0; i<std::min(static_cast<int>(town->creatures.size()), GameConstants::CREATURES_PER_TOWN); i++)
 	{
 	{
 		growth[i]->update();
 		growth[i]->update();
 		available[i]->update();
 		available[i]->update();

+ 1 - 1
client/windows/CMessage.cpp

@@ -129,7 +129,7 @@ std::vector<std::string> CMessage::breakText(std::string text, size_t maxLineWid
 			if(wordBreak != ui32(-1))
 			if(wordBreak != ui32(-1))
 			{
 			{
 				currPos = wordBreak;
 				currPos = wordBreak;
-				if(text.substr(0, currPos).find('{') == std::string::npos)
+				if(boost::count(text.substr(0, currPos), '{') == boost::count(text.substr(0, currPos), '}'))
 				{
 				{
 					opened = false;
 					opened = false;
 					color = "";
 					color = "";

+ 2 - 1
client/windows/GUIClasses.cpp

@@ -548,11 +548,12 @@ void CTavernWindow::addInvite()
 
 
 	if(!inviteableHeroes.empty())
 	if(!inviteableHeroes.empty())
 	{
 	{
+		int imageIndex = heroToInvite ? (*CGI->heroh)[heroToInvite->getHeroType()]->imageIndex : 156; // 156 => special id for random
 		if(!heroToInvite)
 		if(!heroToInvite)
 			heroToInvite = (*RandomGeneratorUtil::nextItem(inviteableHeroes, CRandomGenerator::getDefault())).second;
 			heroToInvite = (*RandomGeneratorUtil::nextItem(inviteableHeroes, CRandomGenerator::getDefault())).second;
 
 
 		inviteHero = std::make_shared<CLabel>(170, 444, EFonts::FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("vcmi.tavernWindow.inviteHero"));
 		inviteHero = std::make_shared<CLabel>(170, 444, EFonts::FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("vcmi.tavernWindow.inviteHero"));
-		inviteHeroImage = std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[heroToInvite->getHeroType()]->imageIndex, 0, 245, 428);
+		inviteHeroImage = std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsSmall"), imageIndex, 0, 245, 428);
 		inviteHeroImageArea = std::make_shared<LRClickableArea>(Rect(245, 428, 48, 32), [this](){ GH.windows().createAndPushWindow<HeroSelector>(inviteableHeroes, [this](CGHeroInstance* h){ heroToInvite = h; addInvite(); }); }, [this](){ GH.windows().createAndPushWindow<CRClickPopupInt>(std::make_shared<CHeroWindow>(heroToInvite)); });
 		inviteHeroImageArea = std::make_shared<LRClickableArea>(Rect(245, 428, 48, 32), [this](){ GH.windows().createAndPushWindow<HeroSelector>(inviteableHeroes, [this](CGHeroInstance* h){ heroToInvite = h; addInvite(); }); }, [this](){ GH.windows().createAndPushWindow<CRClickPopupInt>(std::make_shared<CHeroWindow>(heroToInvite)); });
 	}
 	}
 }
 }

+ 2 - 0
client/windows/QuickRecruitmentWindow.cpp

@@ -166,4 +166,6 @@ QuickRecruitmentWindow::QuickRecruitmentWindow(const CGTownInstance * townd, Rec
 	setButtons();
 	setButtons();
 	setCreaturePurchaseCards();
 	setCreaturePurchaseCards();
 	maxAllCards(cards);
 	maxAllCards(cards);
+
+	center();
 }
 }

+ 32 - 2
config/gameConfig.json

@@ -302,7 +302,7 @@
 			"backpackSize"		: -1,
 			"backpackSize"		: -1,
 			// if heroes are invitable in tavern
 			// if heroes are invitable in tavern
 			"tavernInvite"            : false,
 			"tavernInvite"            : false,
-			// minimai primary skills for heroes
+			// minimal primary skills for heroes
 			"minimalPrimarySkills": [ 0, 0, 1, 1]
 			"minimalPrimarySkills": [ 0, 0, 1, 1]
 		},
 		},
 
 
@@ -311,7 +311,21 @@
 			// How many new building can be built in a town per day
 			// How many new building can be built in a town per day
 			"buildingsPerTurnCap" : 1,
 			"buildingsPerTurnCap" : 1,
 			// Chances for a town with default buildings to receive corresponding dwelling level built in start
 			// Chances for a town with default buildings to receive corresponding dwelling level built in start
-			"startingDwellingChances": [100, 50] 
+			"startingDwellingChances": [100, 50],
+			// Enable spell research in mage guild
+			"spellResearch": false,
+			// Cost for an spell research (array index is spell tier)
+			"spellResearchCost": [
+				{ "gold": 1000, "wood" : 2, "mercury": 2, "ore": 2, "sulfur": 2, "crystal": 2, "gems": 2 },
+				{ "gold": 1000, "wood" : 4, "mercury": 4, "ore": 4, "sulfur": 4, "crystal": 4, "gems": 4 },
+				{ "gold": 1000, "wood" : 6, "mercury": 6, "ore": 6, "sulfur": 6, "crystal": 6, "gems": 6 },
+				{ "gold": 1000, "wood" : 8, "mercury": 8, "ore": 8, "sulfur": 8, "crystal": 8, "gems": 8 },
+				{ "gold": 1000, "wood" : 10, "mercury": 10, "ore": 10, "sulfur": 10, "crystal": 10, "gems": 10 }
+			],
+			// How much researchs/skips per day are possible? (array index is spell tier)
+			"spellResearchPerDay": [ 2, 2, 2, 2, 1 ],
+			// Exponent for increasing cost for each research (factor 1 disables this; array index is spell tier)
+			"spellResearchCostExponentPerResearch": [ 1.25, 1.25, 1.25, 1.25, 1.25 ]
 		},
 		},
 
 
 		"combat":
 		"combat":
@@ -555,6 +569,22 @@
 					"valueType" : "BASE_NUMBER"
 					"valueType" : "BASE_NUMBER"
 				}
 				}
 			}
 			}
+		},
+
+		"interface" :
+		{
+			// Color transform to make color of brown DIBOX.PCX texture match color of specified player
+			"playerColoredBackground" :
+			{
+				"red" :    [  0.25,  0,     0,     1.25, 0.00, 0.00 ],
+				"blue" :   [  0,     0,     0,     0.45, 1.20, 4.50 ],
+				"tan" :    [  0.40,  0.27,  0.23,  1.10, 1.20, 1.15 ],
+				"green" :  [ -0.27,  0.10, -0.27,  0.70, 1.70, 0.70 ],
+				"orange" : [  0.47,  0.17, -0.27,  1.60, 1.20, 0.70 ],
+				"purple" : [  0.12, -0.1,   0.25,  1.15, 1.20, 2.20 ],
+				"teal" :   [ -0.13,  0.23,  0.23,  0.90, 1.20, 2.20 ],
+				"pink" :   [  0.44,  0.15,  0.25,  1.00, 1.00, 1.75 ]
+			}
 		}
 		}
 	}
 	}
 }
 }

+ 13 - 2
config/schemas/gameSettings.json

@@ -51,8 +51,12 @@
 			"type" : "object",
 			"type" : "object",
 			"additionalProperties" : false,
 			"additionalProperties" : false,
 			"properties" : {
 			"properties" : {
-				"buildingsPerTurnCap"  :    { "type" : "number" },
-				"startingDwellingChances" : { "type" : "array" }
+				"buildingsPerTurnCap"  :                 { "type" : "number" },
+				"startingDwellingChances" :              { "type" : "array" },
+				"spellResearch" :                        { "type" : "boolean" },
+				"spellResearchCost" :                    { "type" : "array" },
+				"spellResearchPerDay" :                  { "type" : "array" },
+				"spellResearchCostExponentPerResearch" : { "type" : "array" }
 			}
 			}
 		},
 		},
 		"combat": {
 		"combat": {
@@ -148,5 +152,12 @@
 				"perHero" : { "type" : "object" }
 				"perHero" : { "type" : "object" }
 			}
 			}
 		},
 		},
+		"interface": {
+			"type" : "object",
+			"additionalProperties" : false,
+			"properties" : {
+				"playerColoredBackground" : { "type" : "object" }
+			}
+		}
 	}
 	}
 }
 }

+ 40 - 40
config/schemas/mod.json

@@ -5,6 +5,17 @@
 	"description" : "Format used to define main mod file (mod.json) in VCMI",
 	"description" : "Format used to define main mod file (mod.json) in VCMI",
 	"required" : [ "name", "description", "modType", "version", "author", "contact" ],
 	"required" : [ "name", "description", "modType", "version", "author", "contact" ],
 	"definitions" : {
 	"definitions" : {
+		"fileListOrObject" : {
+			"oneOf" : [
+				{
+					"type" : "array",
+					"items" : { "type" : "string", "format" : "textFile" }
+				},
+				{
+					"type" : "object"
+				}
+			]
+		},
 		"localizable" : {
 		"localizable" : {
 			"type" : "object",
 			"type" : "object",
 			"additionalProperties" : false,
 			"additionalProperties" : false,
@@ -35,9 +46,8 @@
 					"description" : "If set to true, vcmi will skip validation of current translation json files"
 					"description" : "If set to true, vcmi will skip validation of current translation json files"
 				},
 				},
 				"translations" : {
 				"translations" : {
-					"type" : "array",
 					"description" : "List of files with translations for this language",
 					"description" : "List of files with translations for this language",
-					"items" : { "type" : "string", "format" : "textFile" }
+					"$ref" : "#/definitions/fileListOrObject"
 				}
 				}
 			}
 			}
 		}
 		}
@@ -122,9 +132,17 @@
 			"description" : "If set to true, mod will not be enabled automatically on install"
 			"description" : "If set to true, mod will not be enabled automatically on install"
 		},
 		},
 		"settings" : {
 		"settings" : {
-			"type" : "object",
 			"description" : "List of changed game settings by mod",
 			"description" : "List of changed game settings by mod",
-			"$ref" : "gameSettings.json"
+			"oneOf" : [
+				{
+					"type" : "object",
+					"$ref" : "gameSettings.json"
+				},
+				{
+					"type" : "array",
+					"items" : { "type" : "string", "format" : "textFile" }
+				},
+			]
 		},
 		},
 		"filesystem" : {
 		"filesystem" : {
 			"type" : "object",
 			"type" : "object",
@@ -206,94 +224,76 @@
 			"$ref" : "#/definitions/localizable"
 			"$ref" : "#/definitions/localizable"
 		},
 		},
 		"translations" : {
 		"translations" : {
-			"type" : "array",
 			"description" : "List of files with translations for this language",
 			"description" : "List of files with translations for this language",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		},
 		},
 		"factions" : {
 		"factions" : {
-			"type" : "array",
 			"description" : "List of configuration files for towns/factions",
 			"description" : "List of configuration files for towns/factions",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		},
 		},
 		"heroClasses" : {
 		"heroClasses" : {
-			"type" : "array",
 			"description" : "List of configuration files for hero classes",
 			"description" : "List of configuration files for hero classes",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		},
 		},
 		"heroes" : {
 		"heroes" : {
-			"type" : "array",
 			"description" : "List of configuration files for heroes",
 			"description" : "List of configuration files for heroes",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		},
 		},
 		"skills" : {
 		"skills" : {
-			"type" : "array",
 			"description" : "List of configuration files for skills",
 			"description" : "List of configuration files for skills",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		},
 		},
 		"creatures" : {
 		"creatures" : {
-			"type" : "array",
 			"description" : "List of configuration files for creatures",
 			"description" : "List of configuration files for creatures",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		},
 		},
 		"artifacts" : {
 		"artifacts" : {
-			"type" : "array",
 			"description" : "List of configuration files for artifacts",
 			"description" : "List of configuration files for artifacts",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		},
 		},
 		"spells" : {
 		"spells" : {
-			"type" : "array",
 			"description" : "List of configuration files for spells",
 			"description" : "List of configuration files for spells",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		},
 		},
 		"objects" : {
 		"objects" : {
-			"type" : "array",
 			"description" : "List of configuration files for objects",
 			"description" : "List of configuration files for objects",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		},
 		},
 		"biomes" : {
 		"biomes" : {
-			"type" : "array",
 			"description" : "List of configuration files for biomes",
 			"description" : "List of configuration files for biomes",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		},
 		},
 		"bonuses" : {
 		"bonuses" : {
-			"type" : "array",
 			"description" : "List of configuration files for bonuses",
 			"description" : "List of configuration files for bonuses",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		},
 		},
 		"terrains" : {
 		"terrains" : {
-			"type" : "array",
 			"description" : "List of configuration files for terrains",
 			"description" : "List of configuration files for terrains",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		},
 		},
 		"roads" : {
 		"roads" : {
-			"type" : "array",
 			"description" : "List of configuration files for roads",
 			"description" : "List of configuration files for roads",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		},
 		},
 		"rivers" : {
 		"rivers" : {
-			"type" : "array",
 			"description" : "List of configuration files for rivers",
 			"description" : "List of configuration files for rivers",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		},
 		},
 		"battlefields" : {
 		"battlefields" : {
-			"type" : "array",
 			"description" : "List of configuration files for battlefields",
 			"description" : "List of configuration files for battlefields",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		},
 		},
 		"obstacles" : {
 		"obstacles" : {
-			"type" : "array",
 			"description" : "List of configuration files for obstacles",
 			"description" : "List of configuration files for obstacles",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		},
 		},
 		"templates" : {
 		"templates" : {
-			"type" : "array",
 			"description" : "List of configuration files for RMG templates",
 			"description" : "List of configuration files for RMG templates",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		},
 		},
 		"scripts" : {
 		"scripts" : {
-			"type" : "array",
 			"description" : "List of configuration files for scripts",
 			"description" : "List of configuration files for scripts",
-			"items" : { "type" : "string", "format" : "textFile" }
+			"$ref" : "#/definitions/fileListOrObject"
 		}
 		}
 	}
 	}
 }
 }

+ 18 - 1
config/schemas/settings.json

@@ -3,7 +3,7 @@
 {
 {
 	"type" : "object",
 	"type" : "object",
 	"$schema" : "http://json-schema.org/draft-04/schema",
 	"$schema" : "http://json-schema.org/draft-04/schema",
-	"required" : [ "general", "video", "adventure", "battle", "input", "server", "logging", "launcher", "lobby", "gameTweaks" ],
+	"required" : [ "general", "video", "adventure", "battle", "input", "server", "logging", "launcher", "lobby", "gameTweaks", "mods" ],
 	"definitions" : {
 	"definitions" : {
 		"logLevelEnum" : {
 		"logLevelEnum" : {
 			"type" : "string",
 			"type" : "string",
@@ -149,6 +149,23 @@
 				}
 				}
 			}
 			}
 		},
 		},
+		
+		"mods" : {
+			"type" : "object",
+			"additionalProperties" : false,
+			"default" : {},
+			"required" : [ 
+				"validation"
+			],
+			"properties" : {
+				"validation" : {
+					"type" : "string",
+					"enum" : [ "off", "basic", "full" ],
+					"default" : "basic"
+				}
+			}
+		},
+		
 		"video" : {
 		"video" : {
 			"type" : "object",
 			"type" : "object",
 			"additionalProperties" : false,
 			"additionalProperties" : false,

+ 3 - 2
docs/modders/Mod_File_Format.md

@@ -90,8 +90,9 @@ These are fields that are present only in local mod.json file
 {
 {
 	// Following section describes configuration files with content added by mod
 	// Following section describes configuration files with content added by mod
 	// It can be split into several files in any way you want but recommended organization is
 	// It can be split into several files in any way you want but recommended organization is
-	// to keep one file per object (creature/hero/etc) and, if applicable, add separate file
-	// with translatable strings for each type of content
+	// to keep one file per object (creature/hero/etc)
+	// Alternatively, for small changes you can embed changes to content directly in here, e.g.
+	// "creatures" : { "core:imp" : { "health" : 5 }}
 
 
 	// list of factions/towns configuration files
 	// list of factions/towns configuration files
 	"factions" :
 	"factions" :

+ 9 - 9
include/vcmi/ServerCallback.h

@@ -36,15 +36,15 @@ public:
 
 
 	virtual vstd::RNG * getRNG() = 0;
 	virtual vstd::RNG * getRNG() = 0;
 
 
-	virtual void apply(CPackForClient * pack) = 0;
-
-	virtual void apply(BattleLogMessage * pack) = 0;
-	virtual void apply(BattleStackMoved * pack) = 0;
-	virtual void apply(BattleUnitsChanged * pack) = 0;
-	virtual void apply(SetStackEffect * pack) = 0;
-	virtual void apply(StacksInjured * pack) = 0;
-	virtual void apply(BattleObstaclesChanged * pack) = 0;
-	virtual void apply(CatapultAttack * pack) = 0;
+	virtual void apply(CPackForClient & pack) = 0;
+
+	virtual void apply(BattleLogMessage & pack) = 0;
+	virtual void apply(BattleStackMoved & pack) = 0;
+	virtual void apply(BattleUnitsChanged & pack) = 0;
+	virtual void apply(SetStackEffect & pack) = 0;
+	virtual void apply(StacksInjured & pack) = 0;
+	virtual void apply(BattleObstaclesChanged & pack) = 0;
+	virtual void apply(CatapultAttack & pack) = 0;
 };
 };
 
 
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 1 - 5
launcher/CMakeLists.txt

@@ -20,8 +20,6 @@ set(launcher_SRCS
 		innoextract.cpp
 		innoextract.cpp
 		mainwindow_moc.cpp
 		mainwindow_moc.cpp
 		languages.cpp
 		languages.cpp
-		launcherdirs.cpp
-		jsonutils.cpp
 		updatedialog_moc.cpp
 		updatedialog_moc.cpp
 		prepare.cpp
 		prepare.cpp
 )
 )
@@ -49,8 +47,6 @@ set(launcher_HEADERS
 		firstLaunch/firstlaunch_moc.h
 		firstLaunch/firstlaunch_moc.h
 		mainwindow_moc.h
 		mainwindow_moc.h
 		languages.h
 		languages.h
-		launcherdirs.h
-		jsonutils.h
 		updatedialog_moc.h
 		updatedialog_moc.h
 		main.h
 		main.h
 		helper.h
 		helper.h
@@ -206,7 +202,7 @@ elseif(NOT APPLE_IOS)
 	target_link_libraries(vcmilauncher SDL2::SDL2)
 	target_link_libraries(vcmilauncher SDL2::SDL2)
 endif()
 endif()
 
 
-target_link_libraries(vcmilauncher vcmi Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network)
+target_link_libraries(vcmilauncher vcmi vcmiqt Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network)
 target_include_directories(vcmilauncher
 target_include_directories(vcmilauncher
 	PUBLIC	${CMAKE_CURRENT_SOURCE_DIR}
 	PUBLIC	${CMAKE_CURRENT_SOURCE_DIR}
 )
 )

+ 3 - 19
launcher/StdInc.h

@@ -18,24 +18,8 @@
 #include <QList>
 #include <QList>
 #include <QString>
 #include <QString>
 #include <QFile>
 #include <QFile>
-#include <QTemporaryDir> 
+#include <QTemporaryDir>
 
 
-VCMI_LIB_USING_NAMESPACE
-
-inline QString pathToQString(const boost::filesystem::path & path)
-{
-#ifdef VCMI_WINDOWS
-	return QString::fromStdWString(path.wstring());
-#else
-	return QString::fromStdString(path.string());
-#endif
-}
+#include "../vcmiqt/convpathqstring.h"
 
 
-inline boost::filesystem::path qstringToPath(const QString & path)
-{
-#ifdef VCMI_WINDOWS
-	return boost::filesystem::path(path.toStdWString());
-#else
-	return boost::filesystem::path(path.toUtf8().data());
-#endif
-}
+VCMI_LIB_USING_NAMESPACE

+ 1 - 1
launcher/modManager/cdownloadmanager_moc.cpp

@@ -10,7 +10,7 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "cdownloadmanager_moc.h"
 #include "cdownloadmanager_moc.h"
 
 
-#include "../launcherdirs.h"
+#include "../vcmiqt/launcherdirs.h"
 
 
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CConfigHandler.h"
 
 

+ 2 - 2
launcher/modManager/cmodlistview_moc.cpp

@@ -22,8 +22,8 @@
 #include "cdownloadmanager_moc.h"
 #include "cdownloadmanager_moc.h"
 #include "chroniclesextractor.h"
 #include "chroniclesextractor.h"
 #include "../settingsView/csettingsview_moc.h"
 #include "../settingsView/csettingsview_moc.h"
-#include "../launcherdirs.h"
-#include "../jsonutils.h"
+#include "../vcmiqt/launcherdirs.h"
+#include "../vcmiqt/jsonutils.h"
 #include "../helper.h"
 #include "../helper.h"
 
 
 #include "../../lib/VCMIDirs.h"
 #include "../../lib/VCMIDirs.h"

+ 2 - 2
launcher/modManager/cmodmanager.cpp

@@ -17,8 +17,8 @@
 #include "../../lib/modding/CModInfo.h"
 #include "../../lib/modding/CModInfo.h"
 #include "../../lib/modding/IdentifierStorage.h"
 #include "../../lib/modding/IdentifierStorage.h"
 
 
-#include "../jsonutils.h"
-#include "../launcherdirs.h"
+#include "../vcmiqt/jsonutils.h"
+#include "../vcmiqt/launcherdirs.h"
 
 
 #include <future>
 #include <future>
 
 

+ 1 - 1
launcher/prepare.cpp

@@ -9,7 +9,7 @@
  */
  */
 #include "StdInc.h"
 #include "StdInc.h"
 #include "prepare.h"
 #include "prepare.h"
-#include "launcherdirs.h"
+#include "../vcmiqt/launcherdirs.h"
 
 
 #include <QDir>
 #include <QDir>
 #include <QFile>
 #include <QFile>

+ 26 - 1
launcher/settingsView/csettingsview_moc.cpp

@@ -15,7 +15,7 @@
 
 
 #include "../modManager/cmodlistview_moc.h"
 #include "../modManager/cmodlistview_moc.h"
 #include "../helper.h"
 #include "../helper.h"
-#include "../jsonutils.h"
+#include "../vcmiqt/jsonutils.h"
 #include "../languages.h"
 #include "../languages.h"
 
 
 #include <QFileInfo>
 #include <QFileInfo>
@@ -182,6 +182,13 @@ void CSettingsView::loadSettings()
 	else
 	else
 		ui->buttonFontScalable->setChecked(true);
 		ui->buttonFontScalable->setChecked(true);
 
 
+	if (settings["mods"]["validation"].String() == "off")
+		ui->buttonValidationOff->setChecked(true);
+	else if (settings["mods"]["validation"].String() == "basic")
+		ui->buttonValidationBasic->setChecked(true);
+	else
+		ui->buttonValidationFull->setChecked(true);
+
 	loadToggleButtonSettings();
 	loadToggleButtonSettings();
 }
 }
 
 
@@ -791,3 +798,21 @@ void CSettingsView::on_buttonFontOriginal_clicked(bool checked)
 	Settings node = settings.write["video"]["fontsType"];
 	Settings node = settings.write["video"]["fontsType"];
 	node->String() = "original";
 	node->String() = "original";
 }
 }
+
+void CSettingsView::on_buttonValidationOff_clicked(bool checked)
+{
+	Settings node = settings.write["mods"]["validation"];
+	node->String() = "off";
+}
+
+void CSettingsView::on_buttonValidationBasic_clicked(bool checked)
+{
+	Settings node = settings.write["mods"]["validation"];
+	node->String() = "basic";
+}
+
+void CSettingsView::on_buttonValidationFull_clicked(bool checked)
+{
+	Settings node = settings.write["mods"]["validation"];
+	node->String() = "full";
+}

+ 7 - 6
launcher/settingsView/csettingsview_moc.h

@@ -83,19 +83,20 @@ private slots:
 	void on_sliderToleranceDistanceController_valueChanged(int value);
 	void on_sliderToleranceDistanceController_valueChanged(int value);
 	void on_lineEditGameLobbyHost_textChanged(const QString &arg1);
 	void on_lineEditGameLobbyHost_textChanged(const QString &arg1);
 	void on_spinBoxNetworkPortLobby_valueChanged(int arg1);
 	void on_spinBoxNetworkPortLobby_valueChanged(int arg1);
-
 	void on_sliderControllerSticksAcceleration_valueChanged(int value);
 	void on_sliderControllerSticksAcceleration_valueChanged(int value);
-
 	void on_sliderControllerSticksSensitivity_valueChanged(int value);
 	void on_sliderControllerSticksSensitivity_valueChanged(int value);
-
-	//void on_buttonTtfFont_toggled(bool value);
-
 	void on_sliderScalingFont_valueChanged(int value);
 	void on_sliderScalingFont_valueChanged(int value);
-
 	void on_buttonFontAuto_clicked(bool checked);
 	void on_buttonFontAuto_clicked(bool checked);
 	void on_buttonFontScalable_clicked(bool checked);
 	void on_buttonFontScalable_clicked(bool checked);
 	void on_buttonFontOriginal_clicked(bool checked);
 	void on_buttonFontOriginal_clicked(bool checked);
 
 
+
+	void on_buttonValidationOff_clicked(bool checked);
+
+	void on_buttonValidationBasic_clicked(bool checked);
+
+	void on_buttonValidationFull_clicked(bool checked);
+
 private:
 private:
 	Ui::CSettingsView * ui;
 	Ui::CSettingsView * ui;
 
 

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 377 - 382
launcher/settingsView/csettingsview_moc.ui


+ 3 - 3
lib/CArtHandler.cpp

@@ -431,9 +431,9 @@ std::shared_ptr<CArtifact> CArtHandler::loadFromJson(const std::string & scope,
 
 
 	const JsonNode & text = node["text"];
 	const JsonNode & text = node["text"];
 
 
-	VLC->generaltexth->registerString(scope, art->getNameTextID(), text["name"].String());
-	VLC->generaltexth->registerString(scope, art->getDescriptionTextID(), text["description"].String());
-	VLC->generaltexth->registerString(scope, art->getEventTextID(), text["event"].String());
+	VLC->generaltexth->registerString(scope, art->getNameTextID(), text["name"]);
+	VLC->generaltexth->registerString(scope, art->getDescriptionTextID(), text["description"]);
+	VLC->generaltexth->registerString(scope, art->getEventTextID(), text["event"]);
 
 
 	const JsonNode & graphics = node["graphics"];
 	const JsonNode & graphics = node["graphics"];
 	art->image = graphics["image"].String();
 	art->image = graphics["image"].String();

+ 5 - 4
lib/CBonusTypeHandler.cpp

@@ -200,8 +200,9 @@ ImagePath CBonusTypeHandler::bonusToGraphics(const std::shared_ptr<Bonus> & bonu
 
 
 void CBonusTypeHandler::load()
 void CBonusTypeHandler::load()
 {
 {
-	const JsonNode gameConf(JsonPath::builtin("config/gameConfig.json"));
-	const JsonNode config(JsonUtils::assembleFromFiles(gameConf["bonuses"].convertTo<std::vector<std::string>>()));
+	JsonNode gameConf(JsonPath::builtin("config/gameConfig.json"));
+	JsonNode config(JsonUtils::assembleFromFiles(gameConf["bonuses"].convertTo<std::vector<std::string>>()));
+	config.setModScope("vcmi");
 	load(config);
 	load(config);
 }
 }
 
 
@@ -240,8 +241,8 @@ void CBonusTypeHandler::loadItem(const JsonNode & source, CBonusType & dest, con
 
 
 	if (!dest.hidden)
 	if (!dest.hidden)
 	{
 	{
-		VLC->generaltexth->registerString( "vcmi", dest.getNameTextID(), source["name"].String());
-		VLC->generaltexth->registerString( "vcmi", dest.getDescriptionTextID(), source["description"].String());
+		VLC->generaltexth->registerString( "vcmi", dest.getNameTextID(), source["name"]);
+		VLC->generaltexth->registerString( "vcmi", dest.getDescriptionTextID(), source["description"]);
 	}
 	}
 
 
 	const JsonNode & graphics = source["graphics"];
 	const JsonNode & graphics = source["graphics"];

+ 3 - 3
lib/CCreatureHandler.cpp

@@ -617,9 +617,9 @@ std::shared_ptr<CCreature> CCreatureHandler::loadFromJson(const std::string & sc
 
 
 	cre->cost = ResourceSet(node["cost"]);
 	cre->cost = ResourceSet(node["cost"]);
 
 
-	VLC->generaltexth->registerString(scope, cre->getNameSingularTextID(), node["name"]["singular"].String());
-	VLC->generaltexth->registerString(scope, cre->getNamePluralTextID(), node["name"]["plural"].String());
-	VLC->generaltexth->registerString(scope, cre->getDescriptionTextID(), node["description"].String());
+	VLC->generaltexth->registerString(scope, cre->getNameSingularTextID(), node["name"]["singular"]);
+	VLC->generaltexth->registerString(scope, cre->getNamePluralTextID(), node["name"]["plural"]);
+	VLC->generaltexth->registerString(scope, cre->getDescriptionTextID(), node["description"]);
 
 
 	cre->addBonus(node["hitPoints"].Integer(), BonusType::STACK_HEALTH);
 	cre->addBonus(node["hitPoints"].Integer(), BonusType::STACK_HEALTH);
 	cre->addBonus(node["speed"].Integer(), BonusType::STACKS_SPEED);
 	cre->addBonus(node["speed"].Integer(), BonusType::STACKS_SPEED);

+ 1 - 1
lib/CCreatureSet.cpp

@@ -863,7 +863,7 @@ std::string CStackInstance::getName() const
 ui64 CStackInstance::getPower() const
 ui64 CStackInstance::getPower() const
 {
 {
 	assert(type);
 	assert(type);
-	return type->getAIValue() * count;
+	return static_cast<ui64>(type->getAIValue()) * count;
 }
 }
 
 
 ui64 CStackInstance::getMarketValue() const
 ui64 CStackInstance::getMarketValue() const

+ 5 - 5
lib/CHeroHandler.cpp

@@ -459,11 +459,11 @@ std::shared_ptr<CHero> CHeroHandler::loadFromJson(const std::string & scope, con
 	hero->onlyOnWaterMap = node["onlyOnWaterMap"].Bool();
 	hero->onlyOnWaterMap = node["onlyOnWaterMap"].Bool();
 	hero->onlyOnMapWithoutWater = node["onlyOnMapWithoutWater"].Bool();
 	hero->onlyOnMapWithoutWater = node["onlyOnMapWithoutWater"].Bool();
 
 
-	VLC->generaltexth->registerString(scope, hero->getNameTextID(), node["texts"]["name"].String());
-	VLC->generaltexth->registerString(scope, hero->getBiographyTextID(), node["texts"]["biography"].String());
-	VLC->generaltexth->registerString(scope, hero->getSpecialtyNameTextID(), node["texts"]["specialty"]["name"].String());
-	VLC->generaltexth->registerString(scope, hero->getSpecialtyTooltipTextID(), node["texts"]["specialty"]["tooltip"].String());
-	VLC->generaltexth->registerString(scope, hero->getSpecialtyDescriptionTextID(), node["texts"]["specialty"]["description"].String());
+	VLC->generaltexth->registerString(scope, hero->getNameTextID(), node["texts"]["name"]);
+	VLC->generaltexth->registerString(scope, hero->getBiographyTextID(), node["texts"]["biography"]);
+	VLC->generaltexth->registerString(scope, hero->getSpecialtyNameTextID(), node["texts"]["specialty"]["name"]);
+	VLC->generaltexth->registerString(scope, hero->getSpecialtyTooltipTextID(), node["texts"]["specialty"]["tooltip"]);
+	VLC->generaltexth->registerString(scope, hero->getSpecialtyDescriptionTextID(), node["texts"]["specialty"]["description"]);
 
 
 	hero->iconSpecSmall = node["images"]["specialtySmall"].String();
 	hero->iconSpecSmall = node["images"]["specialtySmall"].String();
 	hero->iconSpecLarge = node["images"]["specialtyLarge"].String();
 	hero->iconSpecLarge = node["images"]["specialtyLarge"].String();

+ 2 - 2
lib/CSkillHandler.cpp

@@ -212,7 +212,7 @@ std::shared_ptr<CSkill> CSkillHandler::loadFromJson(const std::string & scope, c
 
 
 	skill->onlyOnWaterMap = json["onlyOnWaterMap"].Bool();
 	skill->onlyOnWaterMap = json["onlyOnWaterMap"].Bool();
 
 
-	VLC->generaltexth->registerString(scope, skill->getNameTextID(), json["name"].String());
+	VLC->generaltexth->registerString(scope, skill->getNameTextID(), json["name"]);
 	switch(json["gainChance"].getType())
 	switch(json["gainChance"].getType())
 	{
 	{
 	case JsonNode::JsonType::DATA_INTEGER:
 	case JsonNode::JsonType::DATA_INTEGER:
@@ -237,7 +237,7 @@ std::shared_ptr<CSkill> CSkillHandler::loadFromJson(const std::string & scope, c
 			skill->addNewBonus(bonus, level);
 			skill->addNewBonus(bonus, level);
 		}
 		}
 		CSkill::LevelInfo & skillAtLevel = skill->at(level);
 		CSkill::LevelInfo & skillAtLevel = skill->at(level);
-		VLC->generaltexth->registerString(scope, skill->getDescriptionTextID(level), levelNode["description"].String());
+		VLC->generaltexth->registerString(scope, skill->getDescriptionTextID(level), levelNode["description"]);
 		skillAtLevel.iconSmall = levelNode["images"]["small"].String();
 		skillAtLevel.iconSmall = levelNode["images"]["small"].String();
 		skillAtLevel.iconMedium = levelNode["images"]["medium"].String();
 		skillAtLevel.iconMedium = levelNode["images"]["medium"].String();
 		skillAtLevel.iconLarge = levelNode["images"]["large"].String();
 		skillAtLevel.iconLarge = levelNode["images"]["large"].String();

+ 1 - 1
lib/CStack.cpp

@@ -401,7 +401,7 @@ void CStack::spendMana(ServerCallback * server, const int spellCost) const
 	ssp.which = BattleSetStackProperty::CASTS;
 	ssp.which = BattleSetStackProperty::CASTS;
 	ssp.val = -spellCost;
 	ssp.val = -spellCost;
 	ssp.absolute = false;
 	ssp.absolute = false;
-	server->apply(&ssp);
+	server->apply(ssp);
 }
 }
 
 
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 69 - 64
lib/GameSettings.cpp

@@ -37,70 +37,75 @@ GameSettings::GameSettings() = default;
 GameSettings::~GameSettings() = default;
 GameSettings::~GameSettings() = default;
 
 
 const std::vector<GameSettings::SettingOption> GameSettings::settingProperties = {
 const std::vector<GameSettings::SettingOption> GameSettings::settingProperties = {
-		{EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION,          "banks",     "showGuardsComposition"            },
-		{EGameSettings::BONUSES_GLOBAL,                         "bonuses",   "global"                           },
-		{EGameSettings::BONUSES_PER_HERO,                       "bonuses",   "perHero"                          },
-		{EGameSettings::COMBAT_AREA_SHOT_CAN_TARGET_EMPTY_HEX,  "combat",    "areaShotCanTargetEmptyHex"        },
-		{EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR,      "combat",    "attackPointDamageFactor"          },
-		{EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP,  "combat",    "attackPointDamageFactorCap"       },
-		{EGameSettings::COMBAT_BAD_LUCK_DICE,                   "combat",    "badLuckDice"                      },
-		{EGameSettings::COMBAT_BAD_MORALE_DICE,                 "combat",    "badMoraleDice"                    },
-		{EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR,     "combat",    "defensePointDamageFactor"         },
-		{EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP, "combat",    "defensePointDamageFactorCap"      },
-		{EGameSettings::COMBAT_GOOD_LUCK_DICE,                  "combat",    "goodLuckDice"                     },
-		{EGameSettings::COMBAT_GOOD_MORALE_DICE,                "combat",    "goodMoraleDice"                   },
-		{EGameSettings::COMBAT_LAYOUTS,                         "combat",    "layouts"                          },
-		{EGameSettings::COMBAT_ONE_HEX_TRIGGERS_OBSTACLES,      "combat",    "oneHexTriggersObstacles"          },
-		{EGameSettings::CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH,   "creatures", "allowAllForDoubleMonth"           },
-		{EGameSettings::CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS,   "creatures", "allowRandomSpecialWeeks"          },
-		{EGameSettings::CREATURES_DAILY_STACK_EXPERIENCE,       "creatures", "dailyStackExperience"             },
-		{EGameSettings::CREATURES_WEEKLY_GROWTH_CAP,            "creatures", "weeklyGrowthCap"                  },
-		{EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT,        "creatures", "weeklyGrowthPercent"              },
-		{EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE,    "spells",    "dimensionDoorExposesTerrainType"  },
-		{EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS,   "spells",    "dimensionDoorFailureSpendsPoints" },
-		{EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES, "spells",    "dimensionDoorOnlyToUncoveredTiles"},
-		{EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT,  "spells",    "dimensionDoorTournamentRulesLimit"},
-		{EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS,         "spells",    "dimensionDoorTriggersGuards"      },
-		{EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL,      "dwellings", "accumulateWhenNeutral"            },
-		{EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED,        "dwellings", "accumulateWhenOwned"              },
-		{EGameSettings::DWELLINGS_MERGE_ON_RECRUIT,             "dwellings", "mergeOnRecruit"                   },
-		{EGameSettings::HEROES_BACKPACK_CAP,                    "heroes",    "backpackSize"                     },
-		{EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS,          "heroes",    "minimalPrimarySkills"             },
-		{EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP,           "heroes",    "perPlayerOnMapCap"                },
-		{EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP,            "heroes",    "perPlayerTotalCap"                },
-		{EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS,   "heroes",    "retreatOnWinWithoutTroops"        },
-		{EGameSettings::HEROES_STARTING_STACKS_CHANCES,         "heroes",    "startingStackChances"             },
-		{EGameSettings::HEROES_TAVERN_INVITE,                   "heroes",    "tavernInvite"                     },
-		{EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE,           "mapFormat", "armageddonsBlade"                 },
-		{EGameSettings::MAP_FORMAT_CHRONICLES,                  "mapFormat", "chronicles"                       },
-		{EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS,           "mapFormat", "hornOfTheAbyss"                   },
-		{EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS,         "mapFormat", "inTheWakeOfGods"                  },
-		{EGameSettings::MAP_FORMAT_JSON_VCMI,                   "mapFormat", "jsonVCMI"                         },
-		{EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA,      "mapFormat", "restorationOfErathia"             },
-		{EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH,             "mapFormat", "shadowOfDeath"                    },
-		{EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD,    "markets",   "blackMarketRestockPeriod"         },
-		{EGameSettings::MODULE_COMMANDERS,                      "modules",   "commanders"                       },
-		{EGameSettings::MODULE_STACK_ARTIFACT,                  "modules",   "stackArtifact"                    },
-		{EGameSettings::MODULE_STACK_EXPERIENCE,                "modules",   "stackExperience"                  },
-		{EGameSettings::PATHFINDER_IGNORE_GUARDS,               "pathfinder", "ignoreGuards"                    },
-		{EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES,          "pathfinder", "originalFlyRules"                },
-		{EGameSettings::PATHFINDER_USE_BOAT,                    "pathfinder", "useBoat"                         },
-		{EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM, "pathfinder", "useMonolithOneWayRandom"         },
-		{EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE, "pathfinder", "useMonolithOneWayUnique"         },
-		{EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY,        "pathfinder", "useMonolithTwoWay"               },
-		{EGameSettings::PATHFINDER_USE_WHIRLPOOL,               "pathfinder", "useWhirlpool"                    },
-		{EGameSettings::TEXTS_ARTIFACT,                         "textData",  "artifact"                         },
-		{EGameSettings::TEXTS_CREATURE,                         "textData",  "creature"                         },
-		{EGameSettings::TEXTS_FACTION,                          "textData",  "faction"                          },
-		{EGameSettings::TEXTS_HERO,                             "textData",  "hero"                             },
-		{EGameSettings::TEXTS_HERO_CLASS,                       "textData",  "heroClass"                        },
-		{EGameSettings::TEXTS_OBJECT,                           "textData",  "object"                           },
-		{EGameSettings::TEXTS_RIVER,                            "textData",  "river"                            },
-		{EGameSettings::TEXTS_ROAD,                             "textData",  "road"                             },
-		{EGameSettings::TEXTS_SPELL,                            "textData",  "spell"                            },
-		{EGameSettings::TEXTS_TERRAIN,                          "textData",  "terrain"                          },
-		{EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP,           "towns",     "buildingsPerTurnCap"              },
-		{EGameSettings::TOWNS_STARTING_DWELLING_CHANCES,        "towns",     "startingDwellingChances"          },
+		{EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION,                    "banks",     "showGuardsComposition"                },
+		{EGameSettings::BONUSES_GLOBAL,                                   "bonuses",   "global"                               },
+		{EGameSettings::BONUSES_PER_HERO,                                 "bonuses",   "perHero"                              },
+		{EGameSettings::COMBAT_AREA_SHOT_CAN_TARGET_EMPTY_HEX,            "combat",    "areaShotCanTargetEmptyHex"            },
+		{EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR,                "combat",    "attackPointDamageFactor"              },
+		{EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP,            "combat",    "attackPointDamageFactorCap"           },
+		{EGameSettings::COMBAT_BAD_LUCK_DICE,                             "combat",    "badLuckDice"                          },
+		{EGameSettings::COMBAT_BAD_MORALE_DICE,                           "combat",    "badMoraleDice"                        },
+		{EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR,               "combat",    "defensePointDamageFactor"             },
+		{EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP,           "combat",    "defensePointDamageFactorCap"          },
+		{EGameSettings::COMBAT_GOOD_LUCK_DICE,                            "combat",    "goodLuckDice"                         },
+		{EGameSettings::COMBAT_GOOD_MORALE_DICE,                          "combat",    "goodMoraleDice"                       },
+		{EGameSettings::COMBAT_LAYOUTS,                                   "combat",    "layouts"                              },
+		{EGameSettings::COMBAT_ONE_HEX_TRIGGERS_OBSTACLES,                "combat",    "oneHexTriggersObstacles"              },
+		{EGameSettings::CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH,             "creatures", "allowAllForDoubleMonth"               },
+		{EGameSettings::CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS,             "creatures", "allowRandomSpecialWeeks"              },
+		{EGameSettings::CREATURES_DAILY_STACK_EXPERIENCE,                 "creatures", "dailyStackExperience"                 },
+		{EGameSettings::CREATURES_WEEKLY_GROWTH_CAP,                      "creatures", "weeklyGrowthCap"                      },
+		{EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT,                  "creatures", "weeklyGrowthPercent"                  },
+		{EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE,              "spells",    "dimensionDoorExposesTerrainType"      },
+		{EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS,             "spells",    "dimensionDoorFailureSpendsPoints"     },
+		{EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES,           "spells",    "dimensionDoorOnlyToUncoveredTiles"    },
+		{EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT,            "spells",    "dimensionDoorTournamentRulesLimit"    },
+		{EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS,                   "spells",    "dimensionDoorTriggersGuards"          },
+		{EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL,                "dwellings", "accumulateWhenNeutral"                },
+		{EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED,                  "dwellings", "accumulateWhenOwned"                  },
+		{EGameSettings::DWELLINGS_MERGE_ON_RECRUIT,                       "dwellings", "mergeOnRecruit"                       },
+		{EGameSettings::HEROES_BACKPACK_CAP,                              "heroes",    "backpackSize"                         },
+		{EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS,                    "heroes",    "minimalPrimarySkills"                 },
+		{EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP,                     "heroes",    "perPlayerOnMapCap"                    },
+		{EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP,                      "heroes",    "perPlayerTotalCap"                    },
+		{EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS,             "heroes",    "retreatOnWinWithoutTroops"            },
+		{EGameSettings::HEROES_STARTING_STACKS_CHANCES,                   "heroes",    "startingStackChances"                 },
+		{EGameSettings::HEROES_TAVERN_INVITE,                             "heroes",    "tavernInvite"                         },
+		{EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE,                     "mapFormat", "armageddonsBlade"                     },
+		{EGameSettings::MAP_FORMAT_CHRONICLES,                            "mapFormat", "chronicles"                           },
+		{EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS,                     "mapFormat", "hornOfTheAbyss"                       },
+		{EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS,                   "mapFormat", "inTheWakeOfGods"                      },
+		{EGameSettings::MAP_FORMAT_JSON_VCMI,                             "mapFormat", "jsonVCMI"                             },
+		{EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA,                "mapFormat", "restorationOfErathia"                 },
+		{EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH,                       "mapFormat", "shadowOfDeath"                        },
+		{EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD,              "markets",   "blackMarketRestockPeriod"             },
+		{EGameSettings::MODULE_COMMANDERS,                                "modules",   "commanders"                           },
+		{EGameSettings::MODULE_STACK_ARTIFACT,                            "modules",   "stackArtifact"                        },
+		{EGameSettings::MODULE_STACK_EXPERIENCE,                          "modules",   "stackExperience"                      },
+		{EGameSettings::PATHFINDER_IGNORE_GUARDS,                         "pathfinder", "ignoreGuards"                        },
+		{EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES,                    "pathfinder", "originalFlyRules"                    },
+		{EGameSettings::PATHFINDER_USE_BOAT,                              "pathfinder", "useBoat"                             },
+		{EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM,           "pathfinder", "useMonolithOneWayRandom"             },
+		{EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE,           "pathfinder", "useMonolithOneWayUnique"             },
+		{EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY,                  "pathfinder", "useMonolithTwoWay"                   },
+		{EGameSettings::PATHFINDER_USE_WHIRLPOOL,                         "pathfinder", "useWhirlpool"                        },
+		{EGameSettings::TEXTS_ARTIFACT,                                   "textData",  "artifact"                             },
+		{EGameSettings::TEXTS_CREATURE,                                   "textData",  "creature"                             },
+		{EGameSettings::TEXTS_FACTION,                                    "textData",  "faction"                              },
+		{EGameSettings::TEXTS_HERO,                                       "textData",  "hero"                                 },
+		{EGameSettings::TEXTS_HERO_CLASS,                                 "textData",  "heroClass"                            },
+		{EGameSettings::TEXTS_OBJECT,                                     "textData",  "object"                               },
+		{EGameSettings::TEXTS_RIVER,                                      "textData",  "river"                                },
+		{EGameSettings::TEXTS_ROAD,                                       "textData",  "road"                                 },
+		{EGameSettings::TEXTS_SPELL,                                      "textData",  "spell"                                },
+		{EGameSettings::TEXTS_TERRAIN,                                    "textData",  "terrain"                              },
+		{EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP,                     "towns",     "buildingsPerTurnCap"                  },
+		{EGameSettings::TOWNS_STARTING_DWELLING_CHANCES,                  "towns",     "startingDwellingChances"              },
+		{EGameSettings::TOWNS_SPELL_RESEARCH,                             "towns",     "spellResearch"                        },
+		{EGameSettings::TOWNS_SPELL_RESEARCH_COST,                        "towns",     "spellResearchCost"                    },
+		{EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY,                     "towns",     "spellResearchPerDay"                  },
+		{EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH,  "towns",     "spellResearchCostExponentPerResearch" },
+		{EGameSettings::INTERFACE_PLAYER_COLORED_BACKGROUND,              "interface", "playerColoredBackground"              },
 	};
 	};
 
 
 void GameSettings::loadBase(const JsonNode & input)
 void GameSettings::loadBase(const JsonNode & input)

+ 2 - 1
lib/IGameCallback.h

@@ -94,6 +94,7 @@ public:
 	virtual void showInfoDialog(InfoWindow * iw) = 0;
 	virtual void showInfoDialog(InfoWindow * iw) = 0;
 
 
 	virtual void changeSpells(const CGHeroInstance * hero, bool give, const std::set<SpellID> &spells)=0;
 	virtual void changeSpells(const CGHeroInstance * hero, bool give, const std::set<SpellID> &spells)=0;
+	virtual void setResearchedSpells(const CGTownInstance * town, int level, const std::vector<SpellID> spells, bool accepted)=0;
 	virtual bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) = 0;
 	virtual bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) = 0;
 	virtual void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) = 0;
 	virtual void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) = 0;
 	virtual void setOwner(const CGObjectInstance * objid, PlayerColor owner)=0;
 	virtual void setOwner(const CGObjectInstance * objid, PlayerColor owner)=0;
@@ -139,7 +140,7 @@ public:
 	virtual void setManaPoints(ObjectInstanceID hid, int val)=0;
 	virtual void setManaPoints(ObjectInstanceID hid, int val)=0;
 	virtual void giveHero(ObjectInstanceID id, PlayerColor player, ObjectInstanceID boatId = ObjectInstanceID()) = 0;
 	virtual void giveHero(ObjectInstanceID id, PlayerColor player, ObjectInstanceID boatId = ObjectInstanceID()) = 0;
 	virtual void changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator)=0;
 	virtual void changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator)=0;
-	virtual void sendAndApply(CPackForClient * pack) = 0;
+	virtual void sendAndApply(CPackForClient & pack) = 0;
 	virtual void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)=0; //when two heroes meet on adventure map
 	virtual void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)=0; //when two heroes meet on adventure map
 	virtual void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, ETileVisibility mode) = 0;
 	virtual void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, ETileVisibility mode) = 0;
 	virtual void changeFogOfWar(const std::unordered_set<int3> &tiles, PlayerColor player, ETileVisibility mode) = 0;
 	virtual void changeFogOfWar(const std::unordered_set<int3> &tiles, PlayerColor player, ETileVisibility mode) = 0;

+ 5 - 0
lib/IGameSettings.h

@@ -79,6 +79,11 @@ enum class EGameSettings
 	TEXTS_TERRAIN,
 	TEXTS_TERRAIN,
 	TOWNS_BUILDINGS_PER_TURN_CAP,
 	TOWNS_BUILDINGS_PER_TURN_CAP,
 	TOWNS_STARTING_DWELLING_CHANCES,
 	TOWNS_STARTING_DWELLING_CHANCES,
+	INTERFACE_PLAYER_COLORED_BACKGROUND,
+	TOWNS_SPELL_RESEARCH,
+	TOWNS_SPELL_RESEARCH_COST,
+	TOWNS_SPELL_RESEARCH_PER_DAY,
+	TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH,
 
 
 	OPTIONS_COUNT,
 	OPTIONS_COUNT,
 	OPTIONS_BEGIN = BONUSES_GLOBAL
 	OPTIONS_BEGIN = BONUSES_GLOBAL

+ 1 - 1
lib/RiverHandler.cpp

@@ -50,7 +50,7 @@ std::shared_ptr<RiverType> RiverTypeHandler::loadFromJson(
 		info->paletteAnimation.push_back(element);
 		info->paletteAnimation.push_back(element);
 	}
 	}
 
 
-	VLC->generaltexth->registerString(scope, info->getNameTextID(), json["text"].String());
+	VLC->generaltexth->registerString(scope, info->getNameTextID(), json["text"]);
 
 
 	return info;
 	return info;
 }
 }

+ 1 - 1
lib/RoadHandler.cpp

@@ -41,7 +41,7 @@ std::shared_ptr<RoadType> RoadTypeHandler::loadFromJson(
 	info->shortIdentifier = json["shortIdentifier"].String();
 	info->shortIdentifier = json["shortIdentifier"].String();
 	info->movementCost    = json["moveCost"].Integer();
 	info->movementCost    = json["moveCost"].Integer();
 
 
-	VLC->generaltexth->registerString(scope,info->getNameTextID(), json["text"].String());
+	VLC->generaltexth->registerString(scope,info->getNameTextID(), json["text"]);
 
 
 	return info;
 	return info;
 }
 }

+ 1 - 1
lib/TerrainHandler.cpp

@@ -45,7 +45,7 @@ std::shared_ptr<TerrainType> TerrainTypeHandler::loadFromJson( const std::string
 	info->transitionRequired = json["transitionRequired"].Bool();
 	info->transitionRequired = json["transitionRequired"].Bool();
 	info->terrainViewPatterns = json["terrainViewPatterns"].String();
 	info->terrainViewPatterns = json["terrainViewPatterns"].String();
 
 
-	VLC->generaltexth->registerString(scope, info->getNameTextID(), json["text"].String());
+	VLC->generaltexth->registerString(scope, info->getNameTextID(), json["text"]);
 
 
 	const JsonVector & unblockedVec = json["minimapUnblocked"].Vector();
 	const JsonVector & unblockedVec = json["minimapUnblocked"].Vector();
 	info->minimapUnblocked =
 	info->minimapUnblocked =

+ 1 - 1
lib/battle/CBattleInfoCallback.cpp

@@ -927,7 +927,7 @@ bool CBattleInfoCallback::handleObstacleTriggersForUnit(SpellCastEnvironment & s
 				bocp.battleID = getBattle()->getBattleID();
 				bocp.battleID = getBattle()->getBattleID();
 				bocp.changes.emplace_back(spellObstacle.uniqueID, operation);
 				bocp.changes.emplace_back(spellObstacle.uniqueID, operation);
 				changedObstacle.toInfo(bocp.changes.back(), operation);
 				changedObstacle.toInfo(bocp.changes.back(), operation);
-				spellEnv.apply(&bocp);
+				spellEnv.apply(bocp);
 			};
 			};
 			const auto side = unit.unitSide();
 			const auto side = unit.unitSide();
 			auto shouldReveal = !spellObstacle->hidden || !battleIsObstacleVisibleForSide(*obstacle, side);
 			auto shouldReveal = !spellObstacle->hidden || !battleIsObstacleVisibleForSide(*obstacle, side);

+ 5 - 5
lib/entities/faction/CTownHandler.cpp

@@ -292,8 +292,8 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
 	ret->modScope = source.getModScope();
 	ret->modScope = source.getModScope();
 	ret->town = town;
 	ret->town = town;
 
 
-	VLC->generaltexth->registerString(source.getModScope(), ret->getNameTextID(), source["name"].String());
-	VLC->generaltexth->registerString(source.getModScope(), ret->getDescriptionTextID(), source["description"].String());
+	VLC->generaltexth->registerString(source.getModScope(), ret->getNameTextID(), source["name"]);
+	VLC->generaltexth->registerString(source.getModScope(), ret->getDescriptionTextID(), source["description"]);
 
 
 	ret->subId = vstd::find_or(MappedKeys::SPECIAL_BUILDINGS, source["type"].String(), BuildingSubID::NONE);
 	ret->subId = vstd::find_or(MappedKeys::SPECIAL_BUILDINGS, source["type"].String(), BuildingSubID::NONE);
 	ret->resources = TResources(source["cost"]);
 	ret->resources = TResources(source["cost"]);
@@ -603,7 +603,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source)
 	town->namesCount = 0;
 	town->namesCount = 0;
 	for(const auto & name : source["names"].Vector())
 	for(const auto & name : source["names"].Vector())
 	{
 	{
-		VLC->generaltexth->registerString(town->faction->modScope, town->getRandomNameTextID(town->namesCount), name.String());
+		VLC->generaltexth->registerString(town->faction->modScope, town->getRandomNameTextID(town->namesCount), name);
 		town->namesCount += 1;
 		town->namesCount += 1;
 	}
 	}
 
 
@@ -718,8 +718,8 @@ std::shared_ptr<CFaction> CTownHandler::loadFromJson(const std::string & scope,
 	faction->modScope = scope;
 	faction->modScope = scope;
 	faction->identifier = identifier;
 	faction->identifier = identifier;
 
 
-	VLC->generaltexth->registerString(scope, faction->getNameTextID(), source["name"].String());
-	VLC->generaltexth->registerString(scope, faction->getDescriptionTextID(), source["description"].String());
+	VLC->generaltexth->registerString(scope, faction->getNameTextID(), source["name"]);
+	VLC->generaltexth->registerString(scope, faction->getDescriptionTextID(), source["description"]);
 
 
 	faction->creatureBg120 = ImagePath::fromJson(source["creatureBackground"]["120px"]);
 	faction->creatureBg120 = ImagePath::fromJson(source["creatureBackground"]["120px"]);
 	faction->creatureBg130 = ImagePath::fromJson(source["creatureBackground"]["130px"]);
 	faction->creatureBg130 = ImagePath::fromJson(source["creatureBackground"]["130px"]);

+ 2 - 2
lib/gameState/CGameState.cpp

@@ -1143,9 +1143,9 @@ PlayerRelations CGameState::getPlayerRelations( PlayerColor color1, PlayerColor
 	return PlayerRelations::ENEMIES;
 	return PlayerRelations::ENEMIES;
 }
 }
 
 
-void CGameState::apply(CPackForClient *pack)
+void CGameState::apply(CPackForClient & pack)
 {
 {
-	pack->applyGs(this);
+	pack.applyGs(this);
 }
 }
 
 
 void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out)
 void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out)

+ 1 - 1
lib/gameState/CGameState.h

@@ -98,7 +98,7 @@ public:
 	/// picks next free hero type of the H3 hero init sequence -> chosen starting hero, then unused hero type randomly
 	/// picks next free hero type of the H3 hero init sequence -> chosen starting hero, then unused hero type randomly
 	HeroTypeID pickNextHeroType(const PlayerColor & owner);
 	HeroTypeID pickNextHeroType(const PlayerColor & owner);
 
 
-	void apply(CPackForClient *pack);
+	void apply(CPackForClient & pack);
 	BattleField battleGetBattlefieldType(int3 tile, vstd::RNG & rand);
 	BattleField battleGetBattlefieldType(int3 tile, vstd::RNG & rand);
 
 
 	void fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const override;
 	void fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const override;

+ 45 - 0
lib/json/JsonUtils.cpp

@@ -230,6 +230,27 @@ void JsonUtils::inherit(JsonNode & descendant, const JsonNode & base)
 	std::swap(descendant, inheritedNode);
 	std::swap(descendant, inheritedNode);
 }
 }
 
 
+JsonNode JsonUtils::assembleFromFiles(const JsonNode & files, bool & isValid)
+{
+	if (files.isVector())
+	{
+		auto configList = files.convertTo<std::vector<std::string> >();
+		JsonNode result = JsonUtils::assembleFromFiles(configList, isValid);
+
+		return result;
+	}
+	else
+	{
+		return files;
+	}
+}
+
+JsonNode JsonUtils::assembleFromFiles(const JsonNode & files)
+{
+	bool isValid = false;
+	return assembleFromFiles(files, isValid);
+}
+
 JsonNode JsonUtils::assembleFromFiles(const std::vector<std::string> & files)
 JsonNode JsonUtils::assembleFromFiles(const std::vector<std::string> & files)
 {
 {
 	bool isValid = false;
 	bool isValid = false;
@@ -275,4 +296,28 @@ JsonNode JsonUtils::assembleFromFiles(const std::string & filename)
 	return result;
 	return result;
 }
 }
 
 
+void JsonUtils::detectConflicts(JsonNode & result, const JsonNode & left, const JsonNode & right, const std::string & keyName)
+{
+	switch (left.getType())
+	{
+		case JsonNode::JsonType::DATA_NULL:
+		case JsonNode::JsonType::DATA_BOOL:
+		case JsonNode::JsonType::DATA_FLOAT:
+		case JsonNode::JsonType::DATA_INTEGER:
+		case JsonNode::JsonType::DATA_STRING:
+		case JsonNode::JsonType::DATA_VECTOR: // NOTE: comparing vectors as whole - since merge will overwrite it in its entirety
+		{
+			result[keyName][left.getModScope()] = left;
+			result[keyName][right.getModScope()] = right;
+			return;
+		}
+		case JsonNode::JsonType::DATA_STRUCT:
+		{
+			for(const auto & node : left.Struct())
+				if (!right[node.first].isNull())
+					detectConflicts(result, node.second, right[node.first], keyName + "/" + node.first);
+		}
+	}
+}
+
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 8 - 0
lib/json/JsonUtils.h

@@ -44,6 +44,8 @@ namespace JsonUtils
 	 * @brief generate one Json structure from multiple files
 	 * @brief generate one Json structure from multiple files
 	 * @param files - list of filenames with parts of json structure
 	 * @param files - list of filenames with parts of json structure
 	 */
 	 */
+	DLL_LINKAGE JsonNode assembleFromFiles(const JsonNode & files);
+	DLL_LINKAGE JsonNode assembleFromFiles(const JsonNode & files, bool & isValid);
 	DLL_LINKAGE JsonNode assembleFromFiles(const std::vector<std::string> & files);
 	DLL_LINKAGE JsonNode assembleFromFiles(const std::vector<std::string> & files);
 	DLL_LINKAGE JsonNode assembleFromFiles(const std::vector<std::string> & files, bool & isValid);
 	DLL_LINKAGE JsonNode assembleFromFiles(const std::vector<std::string> & files, bool & isValid);
 
 
@@ -72,6 +74,12 @@ namespace JsonUtils
 	/// get schema by json URI: vcmi:<name of file in schemas directory>#<entry in file, optional>
 	/// get schema by json URI: vcmi:<name of file in schemas directory>#<entry in file, optional>
 	/// example: schema "vcmi:settings" is used to check user settings
 	/// example: schema "vcmi:settings" is used to check user settings
 	DLL_LINKAGE const JsonNode & getSchema(const std::string & URI);
 	DLL_LINKAGE const JsonNode & getSchema(const std::string & URI);
+
+	/// detects potential conflicts - json entries present in both nodes
+	/// returns JsonNode that contains list of conflicting keys
+	/// For each conflict - list of conflicting mods and list of conflicting json values
+	/// result[pathToKey][modID] -> node that was conflicting
+	DLL_LINKAGE void detectConflicts(JsonNode & result, const JsonNode & left, const JsonNode & right, const std::string & keyName);
 }
 }
 
 
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/mapObjectConstructors/CBankInstanceConstructor.cpp

@@ -28,7 +28,7 @@ void CBankInstanceConstructor::initTypeData(const JsonNode & input)
 	if (input.Struct().count("name") == 0)
 	if (input.Struct().count("name") == 0)
 		logMod->warn("Bank %s missing name!", getJsonKey());
 		logMod->warn("Bank %s missing name!", getJsonKey());
 
 
-	VLC->generaltexth->registerString(input.getModScope(), getNameTextID(), input["name"].String());
+	VLC->generaltexth->registerString(input.getModScope(), getNameTextID(), input["name"]);
 
 
 	levels = input["levels"].Vector();
 	levels = input["levels"].Vector();
 	bankResetDuration = static_cast<si32>(input["resetDuration"].Float());
 	bankResetDuration = static_cast<si32>(input["resetDuration"].Float());

+ 1 - 1
lib/mapObjectConstructors/CObjectClassesHandler.cpp

@@ -278,7 +278,7 @@ std::unique_ptr<ObjectClass> CObjectClassesHandler::loadFromJson(const std::stri
 	newObject->base = json["base"];
 	newObject->base = json["base"];
 	newObject->id = index;
 	newObject->id = index;
 
 
-	VLC->generaltexth->registerString(scope, newObject->getNameTextID(), json["name"].String());
+	VLC->generaltexth->registerString(scope, newObject->getNameTextID(), json["name"]);
 
 
 	newObject->objectTypeHandlers.resize(json["lastReservedIndex"].Float() + 1);
 	newObject->objectTypeHandlers.resize(json["lastReservedIndex"].Float() + 1);
 
 

+ 7 - 4
lib/mapObjectConstructors/CRewardableConstructor.cpp

@@ -14,6 +14,7 @@
 #include "../mapObjects/CRewardableObject.h"
 #include "../mapObjects/CRewardableObject.h"
 #include "../texts/CGeneralTextHandler.h"
 #include "../texts/CGeneralTextHandler.h"
 #include "../IGameCallback.h"
 #include "../IGameCallback.h"
+#include "../CConfigHandler.h"
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
@@ -23,9 +24,10 @@ void CRewardableConstructor::initTypeData(const JsonNode & config)
 	blockVisit = config["blockedVisitable"].Bool();
 	blockVisit = config["blockedVisitable"].Bool();
 
 
 	if (!config["name"].isNull())
 	if (!config["name"].isNull())
-		VLC->generaltexth->registerString( config.getModScope(), getNameTextID(), config["name"].String());
+		VLC->generaltexth->registerString( config.getModScope(), getNameTextID(), config["name"]);
 
 
-	JsonUtils::validate(config, "vcmi:rewardable", getJsonKey());
+	if (settings["mods"]["validation"].String() != "off")
+		JsonUtils::validate(config, "vcmi:rewardable", getJsonKey());
 	
 	
 }
 }
 
 
@@ -43,9 +45,10 @@ CGObjectInstance * CRewardableConstructor::create(IGameCallback * cb, std::share
 	return ret;
 	return ret;
 }
 }
 
 
-Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID) const
+Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID, const std::map<std::string, JsonNode> & presetVariables) const
 {
 {
 	Rewardable::Configuration result;
 	Rewardable::Configuration result;
+	result.variables.preset = presetVariables;
 	objectInfo.configureObject(result, rand, cb);
 	objectInfo.configureObject(result, rand, cb);
 
 
 	for(auto & rewardInfo : result.info)
 	for(auto & rewardInfo : result.info)
@@ -67,7 +70,7 @@ void CRewardableConstructor::configureObject(CGObjectInstance * object, vstd::RN
 	if (!rewardableObject)
 	if (!rewardableObject)
 		throw std::runtime_error("Object " + std::to_string(object->getObjGroupIndex()) + ", " + std::to_string(object->getObjTypeIndex()) + " is not a rewardable object!" );
 		throw std::runtime_error("Object " + std::to_string(object->getObjGroupIndex()) + ", " + std::to_string(object->getObjTypeIndex()) + " is not a rewardable object!" );
 
 
-	rewardableObject->configuration = generateConfiguration(object->cb, rng, object->ID);
+	rewardableObject->configuration = generateConfiguration(object->cb, rng, object->ID, rewardableObject->configuration.variables.preset);
 	rewardableObject->initializeGuards();
 	rewardableObject->initializeGuards();
 
 
 	if (rewardableObject->configuration.info.empty())
 	if (rewardableObject->configuration.info.empty())

+ 1 - 1
lib/mapObjectConstructors/CRewardableConstructor.h

@@ -31,7 +31,7 @@ public:
 
 
 	std::unique_ptr<IObjectInfo> getObjectInfo(std::shared_ptr<const ObjectTemplate> tmpl) const override;
 	std::unique_ptr<IObjectInfo> getObjectInfo(std::shared_ptr<const ObjectTemplate> tmpl) const override;
 
 
-	Rewardable::Configuration generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID) const;
+	Rewardable::Configuration generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID, const std::map<std::string, JsonNode> & presetVariables) const;
 };
 };
 
 
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/mapObjectConstructors/DwellingInstanceConstructor.cpp

@@ -29,7 +29,7 @@ void DwellingInstanceConstructor::initTypeData(const JsonNode & input)
 	if (input.Struct().count("name") == 0)
 	if (input.Struct().count("name") == 0)
 		logMod->warn("Dwelling %s missing name!", getJsonKey());
 		logMod->warn("Dwelling %s missing name!", getJsonKey());
 
 
-	VLC->generaltexth->registerString( input.getModScope(), getNameTextID(), input["name"].String());
+	VLC->generaltexth->registerString( input.getModScope(), getNameTextID(), input["name"]);
 
 
 	const JsonVector & levels = input["creatures"].Vector();
 	const JsonVector & levels = input["creatures"].Vector();
 	const auto totalLevels = levels.size();
 	const auto totalLevels = levels.size();

+ 1 - 1
lib/mapObjects/CBank.cpp

@@ -138,7 +138,7 @@ bool CBank::wasVisited (PlayerColor player) const
 void CBank::onHeroVisit(const CGHeroInstance * h) const
 void CBank::onHeroVisit(const CGHeroInstance * h) const
 {
 {
 	ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, h->id);
 	ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, h->id);
-	cb->sendAndApply(&cov);
+	cb->sendAndApply(cov);
 
 
 	BlockingDialog bd(true, false);
 	BlockingDialog bd(true, false);
 	bd.player = h->getOwner();
 	bd.player = h->getOwner();

+ 20 - 7
lib/mapObjects/CGCreature.cpp

@@ -66,6 +66,18 @@ std::string CGCreature::getHoverText(const CGHeroInstance * hero) const
 	}
 	}
 }
 }
 
 
+std::string CGCreature::getMonsterLevelText() const
+{
+	std::string monsterLevel = VLC->generaltexth->translate("vcmi.adventureMap.monsterLevel");
+	bool isRanged = VLC->creatures()->getById(getCreature())->getBonusBearer()->hasBonusOfType(BonusType::SHOOTER);
+	std::string attackTypeKey = isRanged ? "vcmi.adventureMap.monsterRangedType" : "vcmi.adventureMap.monsterMeleeType";
+	std::string attackType = VLC->generaltexth->translate(attackTypeKey);
+	boost::replace_first(monsterLevel, "%TOWN", (*VLC->townh)[VLC->creatures()->getById(getCreature())->getFaction()]->getNameTranslated());
+	boost::replace_first(monsterLevel, "%LEVEL", std::to_string(VLC->creatures()->getById(getCreature())->getLevel()));
+	boost::replace_first(monsterLevel, "%ATTACK_TYPE", attackType);
+	return monsterLevel;
+}
+
 std::string CGCreature::getPopupText(const CGHeroInstance * hero) const
 std::string CGCreature::getPopupText(const CGHeroInstance * hero) const
 {
 {
 	std::string hoverName;
 	std::string hoverName;
@@ -102,15 +114,13 @@ std::string CGCreature::getPopupText(const CGHeroInstance * hero) const
 
 
 	if (settings["general"]["enableUiEnhancements"].Bool())
 	if (settings["general"]["enableUiEnhancements"].Bool())
 	{
 	{
-		std::string monsterLevel = VLC->generaltexth->translate("vcmi.adventureMap.monsterLevel");
-		boost::replace_first(monsterLevel, "%TOWN", (*VLC->townh)[VLC->creatures()->getById(getCreature())->getFaction()]->getNameTranslated());
-		boost::replace_first(monsterLevel, "%LEVEL", std::to_string(VLC->creatures()->getById(getCreature())->getLevel()));
-		hoverName += monsterLevel;
-
+		hoverName += getMonsterLevelText();
 		hoverName += VLC->generaltexth->translate("vcmi.adventureMap.monsterThreat.title");
 		hoverName += VLC->generaltexth->translate("vcmi.adventureMap.monsterThreat.title");
 
 
 		int choice;
 		int choice;
-		double ratio = (static_cast<double>(getArmyStrength()) / hero->getTotalStrength());
+		uint64_t armyStrength = getArmyStrength();
+		uint64_t heroStrength = hero->getTotalStrength();
+		double ratio = static_cast<double>(armyStrength) / heroStrength;
 		if (ratio < 0.1)  choice = 0;
 		if (ratio < 0.1)  choice = 0;
 		else if (ratio < 0.25) choice = 1;
 		else if (ratio < 0.25) choice = 1;
 		else if (ratio < 0.6)  choice = 2;
 		else if (ratio < 0.6)  choice = 2;
@@ -131,7 +141,10 @@ std::string CGCreature::getPopupText(const CGHeroInstance * hero) const
 
 
 std::string CGCreature::getPopupText(PlayerColor player) const
 std::string CGCreature::getPopupText(PlayerColor player) const
 {
 {
-	return getHoverText(player);
+	std::string hoverName = getHoverText(player);
+	if (settings["general"]["enableUiEnhancements"].Bool())
+		hoverName += getMonsterLevelText();
+	return hoverName;
 }
 }
 
 
 std::vector<Component> CGCreature::getPopupComponents(PlayerColor player) const
 std::vector<Component> CGCreature::getPopupComponents(PlayerColor player) const

+ 1 - 1
lib/mapObjects/CGCreature.h

@@ -81,7 +81,7 @@ private:
 
 
 	int takenAction(const CGHeroInstance *h, bool allowJoin=true) const; //action on confrontation: -2 - fight, -1 - flee, >=0 - will join for given value of gold (may be 0)
 	int takenAction(const CGHeroInstance *h, bool allowJoin=true) const; //action on confrontation: -2 - fight, -1 - flee, >=0 - will join for given value of gold (may be 0)
 	void giveReward(const CGHeroInstance * h) const;
 	void giveReward(const CGHeroInstance * h) const;
-
+	std::string getMonsterLevelText() const;
 };
 };
 
 
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 7 - 7
lib/mapObjects/CGDwelling.cpp

@@ -224,7 +224,7 @@ void CGDwelling::onHeroVisit( const CGHeroInstance * h ) const
 		iw.player = h->tempOwner;
 		iw.player = h->tempOwner;
 		iw.text.appendLocalString(EMetaText::ADVOB_TXT, 44); //{%s} \n\n The camp is deserted.  Perhaps you should try next week.
 		iw.text.appendLocalString(EMetaText::ADVOB_TXT, 44); //{%s} \n\n The camp is deserted.  Perhaps you should try next week.
 		iw.text.replaceName(ID);
 		iw.text.replaceName(ID);
-		cb->sendAndApply(&iw);
+		cb->sendAndApply(iw);
 		return;
 		return;
 	}
 	}
 
 
@@ -324,7 +324,7 @@ void CGDwelling::newTurn(vstd::RNG & rand) const
 	}
 	}
 
 
 	if(change)
 	if(change)
-		cb->sendAndApply(&sac);
+		cb->sendAndApply(sac);
 
 
 	updateGuards();
 	updateGuards();
 }
 }
@@ -392,7 +392,7 @@ void CGDwelling::updateGuards() const
 				csc.slot = slot;
 				csc.slot = slot;
 				csc.count = crea->getGrowth() * 3;
 				csc.count = crea->getGrowth() * 3;
 				csc.absoluteValue = true;
 				csc.absoluteValue = true;
-				cb->sendAndApply(&csc);
+				cb->sendAndApply(csc);
 			}
 			}
 			else //slot is empty, create whole new stack
 			else //slot is empty, create whole new stack
 			{
 			{
@@ -401,7 +401,7 @@ void CGDwelling::updateGuards() const
 				ns.slot = slot;
 				ns.slot = slot;
 				ns.type = crea->getId();
 				ns.type = crea->getId();
 				ns.count = crea->getGrowth() * 3;
 				ns.count = crea->getGrowth() * 3;
-				cb->sendAndApply(&ns);
+				cb->sendAndApply(ns);
 			}
 			}
 		}
 		}
 	}
 	}
@@ -458,7 +458,7 @@ void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const
 				iw.text.replaceNamePlural(crid);
 				iw.text.replaceNamePlural(crid);
 
 
 				cb->showInfoDialog(&iw);
 				cb->showInfoDialog(&iw);
-				cb->sendAndApply(&sac);
+				cb->sendAndApply(sac);
 				cb->addToSlot(StackLocation(h, slot), crs, count);
 				cb->addToSlot(StackLocation(h, slot), crs, count);
 			}
 			}
 		}
 		}
@@ -469,7 +469,7 @@ void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const
 			iw.text.appendLocalString(EMetaText::GENERAL_TXT, 422); //There are no %s here to recruit.
 			iw.text.appendLocalString(EMetaText::GENERAL_TXT, 422); //There are no %s here to recruit.
 			iw.text.replaceNamePlural(crid);
 			iw.text.replaceNamePlural(crid);
 			iw.player = h->tempOwner;
 			iw.player = h->tempOwner;
-			cb->sendAndApply(&iw);
+			cb->sendAndApply(iw);
 		}
 		}
 	}
 	}
 	else
 	else
@@ -483,7 +483,7 @@ void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const
 			sac.creatures[0].first = !h->getArt(ArtifactPosition::MACH1); //ballista
 			sac.creatures[0].first = !h->getArt(ArtifactPosition::MACH1); //ballista
 			sac.creatures[1].first = !h->getArt(ArtifactPosition::MACH3); //first aid tent
 			sac.creatures[1].first = !h->getArt(ArtifactPosition::MACH3); //first aid tent
 			sac.creatures[2].first = !h->getArt(ArtifactPosition::MACH2); //ammo cart
 			sac.creatures[2].first = !h->getArt(ArtifactPosition::MACH2); //ammo cart
-			cb->sendAndApply(&sac);
+			cb->sendAndApply(sac);
 		}
 		}
 
 
 		auto windowMode = (ID == Obj::CREATURE_GENERATOR1 || ID == Obj::REFUGEE_CAMP) ? EOpenWindowMode::RECRUITMENT_FIRST : EOpenWindowMode::RECRUITMENT_ALL;
 		auto windowMode = (ID == Obj::CREATURE_GENERATOR1 || ID == Obj::REFUGEE_CAMP) ? EOpenWindowMode::RECRUITMENT_FIRST : EOpenWindowMode::RECRUITMENT_ALL;

+ 1 - 1
lib/mapObjects/CGHeroInstance.cpp

@@ -827,7 +827,7 @@ void CGHeroInstance::spendMana(ServerCallback * server, const int spellCost) con
 		sm.hid = id;
 		sm.hid = id;
 		sm.val = -spellCost;
 		sm.val = -spellCost;
 
 
-		server->apply(&sm);
+		server->apply(sm);
 	}
 	}
 }
 }
 
 

+ 1 - 1
lib/mapObjects/CGMarket.cpp

@@ -93,7 +93,7 @@ void CGBlackMarket::newTurn(vstd::RNG & rand) const
 	SetAvailableArtifacts saa;
 	SetAvailableArtifacts saa;
 	saa.id = id;
 	saa.id = id;
 	cb->pickAllowedArtsSet(saa.arts, rand);
 	cb->pickAllowedArtsSet(saa.arts, rand);
-	cb->sendAndApply(&saa);
+	cb->sendAndApply(saa);
 }
 }
 
 
 std::vector<TradeItemBuy> CGUniversity::availableItemsIds(EMarketMode mode) const
 std::vector<TradeItemBuy> CGUniversity::availableItemsIds(EMarketMode mode) const

+ 6 - 3
lib/mapObjects/CGTownInstance.cpp

@@ -268,7 +268,10 @@ CGTownInstance::CGTownInstance(IGameCallback *cb):
 	built(0),
 	built(0),
 	destroyed(0),
 	destroyed(0),
 	identifier(0),
 	identifier(0),
-	alignmentToPlayer(PlayerColor::NEUTRAL)
+	alignmentToPlayer(PlayerColor::NEUTRAL),
+	spellResearchCounterDay(0),
+	spellResearchAcceptedCounter(0),
+	spellResearchAllowed(true)
 {
 {
 	this->setNodeType(CBonusSystemNode::TOWN);
 	this->setNodeType(CBonusSystemNode::TOWN);
 }
 }
@@ -347,7 +350,7 @@ void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const
 			scp.heroid = h->id;
 			scp.heroid = h->id;
 			scp.which = SetCommanderProperty::ALIVE;
 			scp.which = SetCommanderProperty::ALIVE;
 			scp.amount = 1;
 			scp.amount = 1;
-			cb->sendAndApply(&scp);
+			cb->sendAndApply(scp);
 		}
 		}
 		cb->heroVisitCastle(this, h);
 		cb->heroVisitCastle(this, h);
 		// TODO(vmarkovtsev): implement payment for rising the commander
 		// TODO(vmarkovtsev): implement payment for rising the commander
@@ -628,7 +631,7 @@ void CGTownInstance::removeCapitols(const PlayerColor & owner) const
 				rs.tid = id;
 				rs.tid = id;
 				rs.bid.insert(BuildingID::CAPITOL);
 				rs.bid.insert(BuildingID::CAPITOL);
 				rs.destroyed = destroyed;
 				rs.destroyed = destroyed;
-				cb->sendAndApply(&rs);
+				cb->sendAndApply(rs);
 				return;
 				return;
 			}
 			}
 		}
 		}

+ 10 - 0
lib/mapObjects/CGTownInstance.h

@@ -73,6 +73,9 @@ public:
 	std::vector<std::vector<SpellID> > spells; //spells[level] -> vector of spells, first will be available in guild
 	std::vector<std::vector<SpellID> > spells; //spells[level] -> vector of spells, first will be available in guild
 	std::vector<CCastleEvent> events;
 	std::vector<CCastleEvent> events;
 	std::pair<si32, si32> bonusValue;//var to store town bonuses (rampart = resources from mystic pond, factory = save debts);
 	std::pair<si32, si32> bonusValue;//var to store town bonuses (rampart = resources from mystic pond, factory = save debts);
+	int spellResearchCounterDay;
+	int spellResearchAcceptedCounter;
+	bool spellResearchAllowed;
 
 
 	//////////////////////////////////////////////////////////////////////////
 	//////////////////////////////////////////////////////////////////////////
 	template <typename Handler> void serialize(Handler &h)
 	template <typename Handler> void serialize(Handler &h)
@@ -93,6 +96,13 @@ public:
 		h & spells;
 		h & spells;
 		h & events;
 		h & events;
 
 
+		if (h.version >= Handler::Version::SPELL_RESEARCH)
+		{
+			h & spellResearchCounterDay;
+			h & spellResearchAcceptedCounter;
+			h & spellResearchAllowed;
+		}
+
 		if (h.version >= Handler::Version::NEW_TOWN_BUILDINGS)
 		if (h.version >= Handler::Version::NEW_TOWN_BUILDINGS)
 		{
 		{
 			h & rewardableBuildings;
 			h & rewardableBuildings;

+ 4 - 4
lib/mapObjects/CQuest.cpp

@@ -588,7 +588,7 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const
 			AddQuest aq;
 			AddQuest aq;
 			aq.quest = QuestInfo (quest, this, visitablePos());
 			aq.quest = QuestInfo (quest, this, visitablePos());
 			aq.player = h->tempOwner;
 			aq.player = h->tempOwner;
-			cb->sendAndApply(&aq); //TODO: merge with setObjProperty?
+			cb->sendAndApply(aq); //TODO: merge with setObjProperty?
 		}
 		}
 
 
 		if(firstVisit || failRequirements)
 		if(firstVisit || failRequirements)
@@ -811,7 +811,7 @@ void CGKeymasterTent::onHeroVisit( const CGHeroInstance * h ) const
 		cow.mode = ChangeObjectVisitors::VISITOR_GLOBAL;
 		cow.mode = ChangeObjectVisitors::VISITOR_GLOBAL;
 		cow.hero = h->id;
 		cow.hero = h->id;
 		cow.object = id;
 		cow.object = id;
-		cb->sendAndApply(&cow);
+		cb->sendAndApply(cow);
 		txt_id=19;
 		txt_id=19;
 	}
 	}
 	else
 	else
@@ -860,7 +860,7 @@ void CGBorderGuard::onHeroVisit(const CGHeroInstance * h) const
 		AddQuest aq;
 		AddQuest aq;
 		aq.quest = QuestInfo (quest, this, visitablePos());
 		aq.quest = QuestInfo (quest, this, visitablePos());
 		aq.player = h->tempOwner;
 		aq.player = h->tempOwner;
-		cb->sendAndApply (&aq);
+		cb->sendAndApply(aq);
 		//TODO: add this quest only once OR check for multiple instances later
 		//TODO: add this quest only once OR check for multiple instances later
 	}
 	}
 }
 }
@@ -885,7 +885,7 @@ void CGBorderGate::onHeroVisit(const CGHeroInstance * h) const //TODO: passabili
 		AddQuest aq;
 		AddQuest aq;
 		aq.quest = QuestInfo (quest, this, visitablePos());
 		aq.quest = QuestInfo (quest, this, visitablePos());
 		aq.player = h->tempOwner;
 		aq.player = h->tempOwner;
-		cb->sendAndApply (&aq);
+		cb->sendAndApply(aq);
 	}
 	}
 }
 }
 
 

+ 5 - 5
lib/mapObjects/CRewardableObject.cpp

@@ -35,7 +35,7 @@ const IObjectInterface * CRewardableObject::getObject() const
 void CRewardableObject::markAsScouted(const CGHeroInstance * hero) const
 void CRewardableObject::markAsScouted(const CGHeroInstance * hero) const
 {
 {
 	ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, hero->id);
 	ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, hero->id);
-	cb->sendAndApply(&cov);
+	cb->sendAndApply(cov);
 }
 }
 
 
 bool CRewardableObject::isGuarded() const
 bool CRewardableObject::isGuarded() const
@@ -48,7 +48,7 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *hero) const
 	if(!wasScouted(hero->getOwner()))
 	if(!wasScouted(hero->getOwner()))
 	{
 	{
 		ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_SCOUTED, id, hero->id);
 		ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_SCOUTED, id, hero->id);
-		cb->sendAndApply(&cov);
+		cb->sendAndApply(cov);
 	}
 	}
 
 
 	if (isGuarded())
 	if (isGuarded())
@@ -116,7 +116,7 @@ void CRewardableObject::markAsVisited(const CGHeroInstance * hero) const
 	cb->setObjPropertyValue(id, ObjProperty::REWARD_CLEARED, true);
 	cb->setObjPropertyValue(id, ObjProperty::REWARD_CLEARED, true);
 
 
 	ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_HERO, id, hero->id);
 	ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_HERO, id, hero->id);
-	cb->sendAndApply(&cov);
+	cb->sendAndApply(cov);
 }
 }
 
 
 void CRewardableObject::grantReward(ui32 rewardID, const CGHeroInstance * hero) const
 void CRewardableObject::grantReward(ui32 rewardID, const CGHeroInstance * hero) const
@@ -329,14 +329,14 @@ void CRewardableObject::newTurn(vstd::RNG & rand) const
 		if (configuration.resetParameters.rewards)
 		if (configuration.resetParameters.rewards)
 		{
 		{
 			auto handler = std::dynamic_pointer_cast<const CRewardableConstructor>(getObjectHandler());
 			auto handler = std::dynamic_pointer_cast<const CRewardableConstructor>(getObjectHandler());
-			auto newConfiguration = handler->generateConfiguration(cb, rand, ID);
+			auto newConfiguration = handler->generateConfiguration(cb, rand, ID, configuration.variables.preset);
 			cb->setRewardableObjectConfiguration(id, newConfiguration);
 			cb->setRewardableObjectConfiguration(id, newConfiguration);
 		}
 		}
 		if (configuration.resetParameters.visitors)
 		if (configuration.resetParameters.visitors)
 		{
 		{
 			cb->setObjPropertyValue(id, ObjProperty::REWARD_CLEARED, false);
 			cb->setObjPropertyValue(id, ObjProperty::REWARD_CLEARED, false);
 			ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_CLEAR, id);
 			ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_CLEAR, id);
-			cb->sendAndApply(&cov);
+			cb->sendAndApply(cov);
 		}
 		}
 	}
 	}
 }
 }

+ 1 - 1
lib/mapObjects/IObjectInterface.cpp

@@ -28,7 +28,7 @@ void IObjectInterface::showInfoDialog(const ui32 txtID, const ui16 soundID, EInf
 	iw.player = getOwner();
 	iw.player = getOwner();
 	iw.type = mode;
 	iw.type = mode;
 	iw.text.appendLocalString(EMetaText::ADVOB_TXT,txtID);
 	iw.text.appendLocalString(EMetaText::ADVOB_TXT,txtID);
-	cb->sendAndApply(&iw);
+	cb->sendAndApply(iw);
 }
 }
 
 
 ///IObjectInterface
 ///IObjectInterface

+ 7 - 7
lib/mapObjects/MiscObjects.cpp

@@ -1087,14 +1087,14 @@ void CGMagi::onHeroVisit(const CGHeroInstance * h) const
 			for(const auto & eye : eyes)
 			for(const auto & eye : eyes)
 			{
 			{
 				cb->getTilesInRange (fw.tiles, eye->pos, 10, ETileVisibility::HIDDEN, h->tempOwner);
 				cb->getTilesInRange (fw.tiles, eye->pos, 10, ETileVisibility::HIDDEN, h->tempOwner);
-				cb->sendAndApply(&fw);
+				cb->sendAndApply(fw);
 				cv.pos = eye->pos;
 				cv.pos = eye->pos;
 
 
-				cb->sendAndApply(&cv);
+				cb->sendAndApply(cv);
 			}
 			}
 			cv.pos = h->visitablePos();
 			cv.pos = h->visitablePos();
 			cv.focusTime = 0;
 			cv.focusTime = 0;
-			cb->sendAndApply(&cv);
+			cb->sendAndApply(cv);
 		}
 		}
 	}
 	}
 	else if (ID == Obj::EYE_OF_MAGI)
 	else if (ID == Obj::EYE_OF_MAGI)
@@ -1258,7 +1258,7 @@ void CGObelisk::onHeroVisit( const CGHeroInstance * h ) const
 	if(!wasVisited(team))
 	if(!wasVisited(team))
 	{
 	{
 		iw.text.appendLocalString(EMetaText::ADVOB_TXT, 96);
 		iw.text.appendLocalString(EMetaText::ADVOB_TXT, 96);
-		cb->sendAndApply(&iw);
+		cb->sendAndApply(iw);
 
 
 		// increment general visited obelisks counter
 		// increment general visited obelisks counter
 		cb->setObjPropertyID(id, ObjProperty::OBELISK_VISITED, team);
 		cb->setObjPropertyID(id, ObjProperty::OBELISK_VISITED, team);
@@ -1273,7 +1273,7 @@ void CGObelisk::onHeroVisit( const CGHeroInstance * h ) const
 	else
 	else
 	{
 	{
 		iw.text.appendLocalString(EMetaText::ADVOB_TXT, 97);
 		iw.text.appendLocalString(EMetaText::ADVOB_TXT, 97);
-		cb->sendAndApply(&iw);
+		cb->sendAndApply(iw);
 	}
 	}
 
 
 }
 }
@@ -1341,7 +1341,7 @@ void CGLighthouse::onHeroVisit( const CGHeroInstance * h ) const
 			rb.whoID = oldOwner;
 			rb.whoID = oldOwner;
 			rb.source = BonusSource::OBJECT_INSTANCE;
 			rb.source = BonusSource::OBJECT_INSTANCE;
 			rb.id = BonusSourceID(id);
 			rb.id = BonusSourceID(id);
-			cb->sendAndApply(&rb);
+			cb->sendAndApply(rb);
 		}
 		}
 	}
 	}
 }
 }
@@ -1372,7 +1372,7 @@ void CGLighthouse::giveBonusTo(const PlayerColor & player, bool onInit) const
 	if(onInit)
 	if(onInit)
 		gb.applyGs(cb->gameState());
 		gb.applyGs(cb->gameState());
 	else
 	else
-		cb->sendAndApply(&gb);
+		cb->sendAndApply(gb);
 }
 }
 
 
 void CGLighthouse::serializeJsonOptions(JsonSerializeFormat& handler)
 void CGLighthouse::serializeJsonOptions(JsonSerializeFormat& handler)

+ 2 - 2
lib/mapping/CMapHeader.cpp

@@ -189,7 +189,7 @@ void CMapHeader::registerMapStrings()
 		JsonUtils::mergeCopy(data, translations[language]);
 		JsonUtils::mergeCopy(data, translations[language]);
 	
 	
 	for(auto & s : data.Struct())
 	for(auto & s : data.Struct())
-		texts.registerString("map", TextIdentifier(s.first), s.second.String(), language);
+		texts.registerString("map", TextIdentifier(s.first), s.second.String());
 }
 }
 
 
 std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized)
 std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized)
@@ -199,7 +199,7 @@ std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeade
 
 
 std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized, const std::string & language)
 std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized, const std::string & language)
 {
 {
-	mapHeader.texts.registerString(modContext, UID, localized, language);
+	mapHeader.texts.registerString(modContext, UID, localized);
 	mapHeader.translations.Struct()[language].Struct()[UID.get()].String() = localized;
 	mapHeader.translations.Struct()[language].Struct()[UID.get()].String() = localized;
 	return UID.get();
 	return UID.get();
 }
 }

+ 1 - 4
lib/mapping/MapFormatH3M.cpp

@@ -2235,10 +2235,7 @@ CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_pt
 	}
 	}
 
 
 	if(features.levelHOTA1)
 	if(features.levelHOTA1)
-	{
-		// TODO: HOTA support
-		[[maybe_unused]] bool spellResearchAvailable = reader->readBool();
-	}
+		object->spellResearchAllowed = reader->readBool();
 
 
 	// Read castle events
 	// Read castle events
 	uint32_t eventsCount = reader->readUInt32();
 	uint32_t eventsCount = reader->readUInt32();

+ 42 - 43
lib/modding/CModHandler.cpp

@@ -17,6 +17,7 @@
 #include "ModIncompatibility.h"
 #include "ModIncompatibility.h"
 
 
 #include "../CCreatureHandler.h"
 #include "../CCreatureHandler.h"
+#include "../CConfigHandler.h"
 #include "../CStopWatch.h"
 #include "../CStopWatch.h"
 #include "../GameSettings.h"
 #include "../GameSettings.h"
 #include "../ScriptHandler.h"
 #include "../ScriptHandler.h"
@@ -331,10 +332,38 @@ void CModHandler::loadModFilesystems()
 
 
 	coreMod->updateChecksum(calculateModChecksum(ModScope::scopeBuiltin(), CResourceHandler::get(ModScope::scopeBuiltin())));
 	coreMod->updateChecksum(calculateModChecksum(ModScope::scopeBuiltin(), CResourceHandler::get(ModScope::scopeBuiltin())));
 
 
+	std::map<std::string, ISimpleResourceLoader *> modFilesystems;
+
+	for(std::string & modName : activeMods)
+		modFilesystems[modName] = genModFilesystem(modName, allMods[modName].config);
+
 	for(std::string & modName : activeMods)
 	for(std::string & modName : activeMods)
+		CResourceHandler::addFilesystem("data", modName, modFilesystems[modName]);
+
+	if (settings["mods"]["validation"].String() == "full")
 	{
 	{
-		CModInfo & mod = allMods[modName];
-		CResourceHandler::addFilesystem("data", modName, genModFilesystem(modName, mod.config));
+		for(std::string & leftModName : activeMods)
+		{
+			for(std::string & rightModName : activeMods)
+			{
+				if (leftModName == rightModName)
+					continue;
+
+				if (getModDependencies(leftModName).count(rightModName) || getModDependencies(rightModName).count(leftModName))
+					continue;
+
+				const auto & filter = [](const ResourcePath &path){return path.getType() != EResType::DIRECTORY;};
+
+				std::unordered_set<ResourcePath> leftResources = modFilesystems[leftModName]->getFilteredFiles(filter);
+				std::unordered_set<ResourcePath> rightResources = modFilesystems[rightModName]->getFilteredFiles(filter);
+
+				for (auto const & leftFile : leftResources)
+				{
+					if (rightResources.count(leftFile))
+						logMod->warn("Potential confict detected between '%s' and '%s': both mods add file '%s'", leftModName, rightModName, leftFile.getOriginalName());
+				}
+			}
+		}
 	}
 	}
 }
 }
 
 
@@ -370,6 +399,12 @@ std::string CModHandler::getModLanguage(const TModID& modId) const
 	return allMods.at(modId).baseLanguage;
 	return allMods.at(modId).baseLanguage;
 }
 }
 
 
+std::set<TModID> CModHandler::getModDependencies(const TModID & modId) const
+{
+	bool isModFound;
+	return getModDependencies(modId, isModFound);
+}
+
 std::set<TModID> CModHandler::getModDependencies(const TModID & modId, bool & isModFound) const
 std::set<TModID> CModHandler::getModDependencies(const TModID & modId, bool & isModFound) const
 {
 {
 	auto it = allMods.find(modId);
 	auto it = allMods.find(modId);
@@ -384,7 +419,7 @@ std::set<TModID> CModHandler::getModDependencies(const TModID & modId, bool & is
 
 
 void CModHandler::initializeConfig()
 void CModHandler::initializeConfig()
 {
 {
-	VLC->settingsHandler->loadBase(coreMod->config["settings"]);
+	VLC->settingsHandler->loadBase(JsonUtils::assembleFromFiles(coreMod->config["settings"]));
 
 
 	for(const TModID & modName : activeMods)
 	for(const TModID & modName : activeMods)
 	{
 	{
@@ -401,33 +436,6 @@ CModVersion CModHandler::getModVersion(TModID modName) const
 	return {};
 	return {};
 }
 }
 
 
-bool CModHandler::validateTranslations(TModID modName) const
-{
-	bool result = true;
-	const auto & mod = allMods.at(modName);
-
-	{
-		auto fileList = mod.config["translations"].convertTo<std::vector<std::string> >();
-		JsonNode json = JsonUtils::assembleFromFiles(fileList);
-		result |= VLC->generaltexth->validateTranslation(mod.baseLanguage, modName, json);
-	}
-
-	for(const auto & language : Languages::getLanguageList())
-	{
-		if (mod.config[language.identifier].isNull())
-			continue;
-
-		if (mod.config[language.identifier]["skipValidation"].Bool())
-			continue;
-
-		auto fileList = mod.config[language.identifier]["translations"].convertTo<std::vector<std::string> >();
-		JsonNode json = JsonUtils::assembleFromFiles(fileList);
-		result |= VLC->generaltexth->validateTranslation(language.identifier, modName, json);
-	}
-
-	return result;
-}
-
 void CModHandler::loadTranslation(const TModID & modName)
 void CModHandler::loadTranslation(const TModID & modName)
 {
 {
 	const auto & mod = allMods[modName];
 	const auto & mod = allMods[modName];
@@ -435,14 +443,11 @@ void CModHandler::loadTranslation(const TModID & modName)
 	std::string preferredLanguage = VLC->generaltexth->getPreferredLanguage();
 	std::string preferredLanguage = VLC->generaltexth->getPreferredLanguage();
 	std::string modBaseLanguage = allMods[modName].baseLanguage;
 	std::string modBaseLanguage = allMods[modName].baseLanguage;
 
 
-	auto baseTranslationList = mod.config["translations"].convertTo<std::vector<std::string> >();
-	auto extraTranslationList = mod.config[preferredLanguage]["translations"].convertTo<std::vector<std::string> >();
+	JsonNode baseTranslation = JsonUtils::assembleFromFiles(mod.config["translations"]);
+	JsonNode extraTranslation = JsonUtils::assembleFromFiles(mod.config[preferredLanguage]["translations"]);
 
 
-	JsonNode baseTranslation = JsonUtils::assembleFromFiles(baseTranslationList);
-	JsonNode extraTranslation = JsonUtils::assembleFromFiles(extraTranslationList);
-
-	VLC->generaltexth->loadTranslationOverrides(modBaseLanguage, modName, baseTranslation);
-	VLC->generaltexth->loadTranslationOverrides(preferredLanguage, modName, extraTranslation);
+	VLC->generaltexth->loadTranslationOverrides(modName, baseTranslation);
+	VLC->generaltexth->loadTranslationOverrides(modName, extraTranslation);
 }
 }
 
 
 void CModHandler::load()
 void CModHandler::load()
@@ -480,12 +485,6 @@ void CModHandler::load()
 	for(const TModID & modName : activeMods)
 	for(const TModID & modName : activeMods)
 		loadTranslation(modName);
 		loadTranslation(modName);
 
 
-#if 0
-	for(const TModID & modName : activeMods)
-		if (!validateTranslations(modName))
-			allMods[modName].validation = CModInfo::FAILED;
-#endif
-
 	logMod->info("\tLoading mod data: %d ms", timer.getDiff());
 	logMod->info("\tLoading mod data: %d ms", timer.getDiff());
 	VLC->creh->loadCrExpMod();
 	VLC->creh->loadCrExpMod();
 	VLC->identifiersHandler->finalize();
 	VLC->identifiersHandler->finalize();

+ 1 - 2
lib/modding/CModHandler.h

@@ -49,8 +49,6 @@ class DLL_LINKAGE CModHandler final : boost::noncopyable
 	void loadOneMod(std::string modName, const std::string & parent, const JsonNode & modSettings, bool enableMods);
 	void loadOneMod(std::string modName, const std::string & parent, const JsonNode & modSettings, bool enableMods);
 	void loadTranslation(const TModID & modName);
 	void loadTranslation(const TModID & modName);
 
 
-	bool validateTranslations(TModID modName) const;
-
 	CModVersion getModVersion(TModID modName) const;
 	CModVersion getModVersion(TModID modName) const;
 
 
 public:
 public:
@@ -66,6 +64,7 @@ public:
 
 
 	std::string getModLanguage(const TModID & modId) const;
 	std::string getModLanguage(const TModID & modId) const;
 
 
+	std::set<TModID> getModDependencies(const TModID & modId) const;
 	std::set<TModID> getModDependencies(const TModID & modId, bool & isModFound) const;
 	std::set<TModID> getModDependencies(const TModID & modId, bool & isModFound) const;
 
 
 	/// returns list of all (active) mods
 	/// returns list of all (active) mods

+ 78 - 21
lib/modding/ContentTypeHandler.cpp

@@ -17,6 +17,7 @@
 #include "../BattleFieldHandler.h"
 #include "../BattleFieldHandler.h"
 #include "../CArtHandler.h"
 #include "../CArtHandler.h"
 #include "../CCreatureHandler.h"
 #include "../CCreatureHandler.h"
+#include "../CConfigHandler.h"
 #include "../entities/faction/CTownHandler.h"
 #include "../entities/faction/CTownHandler.h"
 #include "../texts/CGeneralTextHandler.h"
 #include "../texts/CGeneralTextHandler.h"
 #include "../CHeroHandler.h"
 #include "../CHeroHandler.h"
@@ -39,9 +40,9 @@
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
-ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, const std::string & objectName):
+ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, const std::string & entityName):
 	handler(handler),
 	handler(handler),
-	objectName(objectName),
+	entityName(entityName),
 	originalData(handler->loadLegacyData())
 	originalData(handler->loadLegacyData())
 {
 {
 	for(auto & node : originalData)
 	for(auto & node : originalData)
@@ -50,7 +51,7 @@ ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, const std::string
 	}
 	}
 }
 }
 
 
-bool ContentTypeHandler::preloadModData(const std::string & modName, const std::vector<std::string> & fileList, bool validate)
+bool ContentTypeHandler::preloadModData(const std::string & modName, const JsonNode & fileList, bool validate)
 {
 {
 	bool result = false;
 	bool result = false;
 	JsonNode data = JsonUtils::assembleFromFiles(fileList, result);
 	JsonNode data = JsonUtils::assembleFromFiles(fileList, result);
@@ -79,6 +80,9 @@ bool ContentTypeHandler::preloadModData(const std::string & modName, const std::
 			logMod->trace("Patching object %s (%s) from %s", objectName, remoteName, modName);
 			logMod->trace("Patching object %s (%s) from %s", objectName, remoteName, modName);
 			JsonNode & remoteConf = modData[remoteName].patches[objectName];
 			JsonNode & remoteConf = modData[remoteName].patches[objectName];
 
 
+			if (!remoteConf.isNull() && settings["mods"]["validation"].String() != "off")
+				JsonUtils::detectConflicts(conflictList, remoteConf, entry.second, objectName);
+
 			JsonUtils::merge(remoteConf, entry.second);
 			JsonUtils::merge(remoteConf, entry.second);
 		}
 		}
 	}
 	}
@@ -93,7 +97,7 @@ bool ContentTypeHandler::loadMod(const std::string & modName, bool validate)
 	auto performValidate = [&,this](JsonNode & data, const std::string & name){
 	auto performValidate = [&,this](JsonNode & data, const std::string & name){
 		handler->beforeValidate(data);
 		handler->beforeValidate(data);
 		if (validate)
 		if (validate)
-			result &= JsonUtils::validate(data, "vcmi:" + objectName, name);
+			result &= JsonUtils::validate(data, "vcmi:" + entityName, name);
 	};
 	};
 
 
 	// apply patches
 	// apply patches
@@ -113,7 +117,7 @@ bool ContentTypeHandler::loadMod(const std::string & modName, bool validate)
 			// - another mod attempts to add object into this mod (technically can be supported, but might lead to weird edge cases)
 			// - another mod attempts to add object into this mod (technically can be supported, but might lead to weird edge cases)
 			// - another mod attempts to edit object from this mod that no longer exist - DANGER since such patch likely has very incomplete data
 			// - another mod attempts to edit object from this mod that no longer exist - DANGER since such patch likely has very incomplete data
 			// so emit warning and skip such case
 			// so emit warning and skip such case
-			logMod->warn("Mod '%s' attempts to edit object '%s' of type '%s' from mod '%s' but no such object exist!", data.getModScope(), name, objectName, modName);
+			logMod->warn("Mod '%s' attempts to edit object '%s' of type '%s' from mod '%s' but no such object exist!", data.getModScope(), name, entityName, modName);
 			continue;
 			continue;
 		}
 		}
 
 
@@ -159,30 +163,69 @@ void ContentTypeHandler::loadCustom()
 
 
 void ContentTypeHandler::afterLoadFinalization()
 void ContentTypeHandler::afterLoadFinalization()
 {
 {
-	for (auto const & data : modData)
+	if (settings["mods"]["validation"].String() != "off")
 	{
 	{
-		if (data.second.modData.isNull())
+		for (auto const & data : modData)
 		{
 		{
-			for (auto node : data.second.patches.Struct())
-				logMod->warn("Mod '%s' have added patch for object '%s' from mod '%s', but this mod was not loaded or has no new objects.", node.second.getModScope(), node.first, data.first);
+			if (data.second.modData.isNull())
+			{
+				for (auto node : data.second.patches.Struct())
+					logMod->warn("Mod '%s' have added patch for object '%s' from mod '%s', but this mod was not loaded or has no new objects.", node.second.getModScope(), node.first, data.first);
+			}
+
+			for(auto & otherMod : modData)
+			{
+				if (otherMod.first == data.first)
+					continue;
+
+				if (otherMod.second.modData.isNull())
+					continue;
+
+				for(auto & otherObject : otherMod.second.modData.Struct())
+				{
+					if (data.second.modData.Struct().count(otherObject.first))
+					{
+						logMod->warn("Mod '%s' have added object with name '%s' that is also available in mod '%s'", data.first, otherObject.first, otherMod.first);
+						logMod->warn("Two objects with same name were loaded. Please use form '%s:%s' if mod '%s' needs to modify this object instead", otherMod.first, otherObject.first, data.first);
+					}
+				}
+			}
 		}
 		}
 
 
-		for(auto & otherMod : modData)
+		for (const auto& [conflictPath, conflictModData] : conflictList.Struct())
 		{
 		{
-			if (otherMod.first == data.first)
-				continue;
+			std::set<std::string> conflictingMods;
+			std::set<std::string> resolvedConflicts;
+
+			for (auto const & conflictModData : conflictModData.Struct())
+				conflictingMods.insert(conflictModData.first);
+
+			for (auto const & modID : conflictingMods)
+				resolvedConflicts.merge(VLC->modh->getModDependencies(modID));
 
 
-			if (otherMod.second.modData.isNull())
-				continue;
+			vstd::erase_if(conflictingMods, [&resolvedConflicts](const std::string & entry){ return resolvedConflicts.count(entry);});
 
 
-			for(auto & otherObject : otherMod.second.modData.Struct())
+			if (conflictingMods.size() < 2)
+				continue; // all conflicts were resolved - either via compatibility patch (mod that depends on 2 conflicting mods) or simple mod that depends on another one
+
+			bool allEqual = true;
+
+			for (auto const & modID : conflictingMods)
 			{
 			{
-				if (data.second.modData.Struct().count(otherObject.first))
+				if (conflictModData[modID] != conflictModData[*conflictingMods.begin()])
 				{
 				{
-					logMod->warn("Mod '%s' have added object with name '%s' that is also available in mod '%s'", data.first, otherObject.first, otherMod.first);
-					logMod->warn("Two objects with same name were loaded. Please use form '%s:%s' if mod '%s' needs to modify this object instead", otherMod.first, otherObject.first, data.first);
+					allEqual = false;
+					break;
 				}
 				}
 			}
 			}
+
+			if (allEqual)
+				continue; // conflict still present, but all mods use the same value for conflicting entry - permit it
+
+			logMod->warn("Potential confict in '%s'", conflictPath);
+
+			for (auto const & modID : conflictingMods)
+				logMod->warn("Mod '%s' - value set to %s", modID, conflictModData[modID].toCompactString());
 		}
 		}
 	}
 	}
 
 
@@ -216,7 +259,7 @@ bool CContentHandler::preloadModData(const std::string & modName, JsonNode modCo
 	bool result = true;
 	bool result = true;
 	for(auto & handler : handlers)
 	for(auto & handler : handlers)
 	{
 	{
-		result &= handler.second.preloadModData(modName, modConfig[handler.first].convertTo<std::vector<std::string> >(), validate);
+		result &= handler.second.preloadModData(modName, modConfig[handler.first], validate);
 	}
 	}
 	return result;
 	return result;
 }
 }
@@ -249,7 +292,7 @@ void CContentHandler::afterLoadFinalization()
 
 
 void CContentHandler::preloadData(CModInfo & mod)
 void CContentHandler::preloadData(CModInfo & mod)
 {
 {
-	bool validate = (mod.validation != CModInfo::PASSED);
+	bool validate = validateMod(mod);
 
 
 	// print message in format [<8-symbols checksum>] <modname>
 	// print message in format [<8-symbols checksum>] <modname>
 	auto & info = mod.getVerificationInfo();
 	auto & info = mod.getVerificationInfo();
@@ -266,7 +309,7 @@ void CContentHandler::preloadData(CModInfo & mod)
 
 
 void CContentHandler::load(CModInfo & mod)
 void CContentHandler::load(CModInfo & mod)
 {
 {
-	bool validate = (mod.validation != CModInfo::PASSED);
+	bool validate = validateMod(mod);
 
 
 	if (!loadMod(mod.identifier, validate))
 	if (!loadMod(mod.identifier, validate))
 		mod.validation = CModInfo::FAILED;
 		mod.validation = CModInfo::FAILED;
@@ -287,4 +330,18 @@ const ContentTypeHandler & CContentHandler::operator[](const std::string & name)
 	return handlers.at(name);
 	return handlers.at(name);
 }
 }
 
 
+bool CContentHandler::validateMod(const CModInfo & mod) const
+{
+	if (settings["mods"]["validation"].String() == "full")
+		return true;
+
+	if (mod.validation == CModInfo::PASSED)
+		return false;
+
+	if (settings["mods"]["validation"].String() == "off")
+		return false;
+
+	return true;
+}
+
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 5 - 2
lib/modding/ContentTypeHandler.h

@@ -19,6 +19,8 @@ class CModInfo;
 /// internal type to handle loading of one data type (e.g. artifacts, creatures)
 /// internal type to handle loading of one data type (e.g. artifacts, creatures)
 class DLL_LINKAGE ContentTypeHandler
 class DLL_LINKAGE ContentTypeHandler
 {
 {
+	JsonNode conflictList;
+
 public:
 public:
 	struct ModInfo
 	struct ModInfo
 	{
 	{
@@ -29,7 +31,7 @@ public:
 	};
 	};
 	/// handler to which all data will be loaded
 	/// handler to which all data will be loaded
 	IHandlerBase * handler;
 	IHandlerBase * handler;
-	std::string objectName;
+	std::string entityName;
 
 
 	/// contains all loaded H3 data
 	/// contains all loaded H3 data
 	std::vector<JsonNode> originalData;
 	std::vector<JsonNode> originalData;
@@ -39,7 +41,7 @@ public:
 
 
 	/// local version of methods in ContentHandler
 	/// local version of methods in ContentHandler
 	/// returns true if loading was successful
 	/// returns true if loading was successful
-	bool preloadModData(const std::string & modName, const std::vector<std::string> & fileList, bool validate);
+	bool preloadModData(const std::string & modName, const JsonNode & fileList, bool validate);
 	bool loadMod(const std::string & modName, bool validate);
 	bool loadMod(const std::string & modName, bool validate);
 	void loadCustom();
 	void loadCustom();
 	void afterLoadFinalization();
 	void afterLoadFinalization();
@@ -56,6 +58,7 @@ class DLL_LINKAGE CContentHandler
 
 
 	std::map<std::string, ContentTypeHandler> handlers;
 	std::map<std::string, ContentTypeHandler> handlers;
 
 
+	bool validateMod(const CModInfo & mod) const;
 public:
 public:
 	void init();
 	void init();
 
 

+ 2 - 0
lib/networkPacks/NetPackVisitor.h

@@ -42,6 +42,7 @@ public:
 	virtual void visitSetSecSkill(SetSecSkill & pack) {}
 	virtual void visitSetSecSkill(SetSecSkill & pack) {}
 	virtual void visitHeroVisitCastle(HeroVisitCastle & pack) {}
 	virtual void visitHeroVisitCastle(HeroVisitCastle & pack) {}
 	virtual void visitChangeSpells(ChangeSpells & pack) {}
 	virtual void visitChangeSpells(ChangeSpells & pack) {}
+	virtual void visitSetResearchedSpells(SetResearchedSpells & pack) {}
 	virtual void visitSetMana(SetMana & pack) {}
 	virtual void visitSetMana(SetMana & pack) {}
 	virtual void visitSetMovePoints(SetMovePoints & pack) {}
 	virtual void visitSetMovePoints(SetMovePoints & pack) {}
 	virtual void visitFoWChange(FoWChange & pack) {}
 	virtual void visitFoWChange(FoWChange & pack) {}
@@ -128,6 +129,7 @@ public:
 	virtual void visitBuildStructure(BuildStructure & pack) {}
 	virtual void visitBuildStructure(BuildStructure & pack) {}
 	virtual void visitVisitTownBuilding(VisitTownBuilding & pack) {}
 	virtual void visitVisitTownBuilding(VisitTownBuilding & pack) {}
 	virtual void visitRazeStructure(RazeStructure & pack) {}
 	virtual void visitRazeStructure(RazeStructure & pack) {}
+	virtual void visitSpellResearch(SpellResearch & pack) {}
 	virtual void visitRecruitCreatures(RecruitCreatures & pack) {}
 	virtual void visitRecruitCreatures(RecruitCreatures & pack) {}
 	virtual void visitUpgradeCreature(UpgradeCreature & pack) {}
 	virtual void visitUpgradeCreature(UpgradeCreature & pack) {}
 	virtual void visitGarrisonHeroSwap(GarrisonHeroSwap & pack) {}
 	virtual void visitGarrisonHeroSwap(GarrisonHeroSwap & pack) {}

+ 23 - 1
lib/networkPacks/NetPacksLib.cpp

@@ -162,6 +162,10 @@ void ChangeSpells::visitTyped(ICPackVisitor & visitor)
 	visitor.visitChangeSpells(*this);
 	visitor.visitChangeSpells(*this);
 }
 }
 
 
+void SetResearchedSpells::visitTyped(ICPackVisitor & visitor)
+{
+	visitor.visitSetResearchedSpells(*this);
+}
 void SetMana::visitTyped(ICPackVisitor & visitor)
 void SetMana::visitTyped(ICPackVisitor & visitor)
 {
 {
 	visitor.visitSetMana(*this);
 	visitor.visitSetMana(*this);
@@ -592,6 +596,11 @@ void RazeStructure::visitTyped(ICPackVisitor & visitor)
 	visitor.visitRazeStructure(*this);
 	visitor.visitRazeStructure(*this);
 }
 }
 
 
+void SpellResearch::visitTyped(ICPackVisitor & visitor)
+{
+	visitor.visitSpellResearch(*this);
+}
+
 void RecruitCreatures::visitTyped(ICPackVisitor & visitor)
 void RecruitCreatures::visitTyped(ICPackVisitor & visitor)
 {
 {
 	visitor.visitRecruitCreatures(*this);
 	visitor.visitRecruitCreatures(*this);
@@ -930,6 +939,16 @@ void ChangeSpells::applyGs(CGameState *gs)
 			hero->removeSpellFromSpellbook(sid);
 			hero->removeSpellFromSpellbook(sid);
 }
 }
 
 
+void SetResearchedSpells::applyGs(CGameState *gs)
+{
+	CGTownInstance *town = gs->getTown(tid);
+
+	town->spells[level] = spells;
+	town->spellResearchCounterDay++;
+	if(accepted)
+		town->spellResearchAcceptedCounter++;
+}
+
 void SetMana::applyGs(CGameState *gs)
 void SetMana::applyGs(CGameState *gs)
 {
 {
 	CGHeroInstance * hero = gs->getHero(hid);
 	CGHeroInstance * hero = gs->getHero(hid);
@@ -1715,7 +1734,7 @@ void BulkEraseArtifacts::applyGs(CGameState *gs)
 			for(auto & slotInfoWorn : artSet->artifactsWorn)
 			for(auto & slotInfoWorn : artSet->artifactsWorn)
 			{
 			{
 				auto art = slotInfoWorn.second.artifact;
 				auto art = slotInfoWorn.second.artifact;
-				if(art->isCombined() && art->isPart(slotInfo->getArt()))
+				if(art->isCombined() && art->isPart(slotInfo->artifact))
 				{
 				{
 					dis.al.slot = artSet->getArtPos(art);
 					dis.al.slot = artSet->getArtPos(art);
 					break;
 					break;
@@ -1914,7 +1933,10 @@ void NewTurn::applyGs(CGameState *gs)
 		creatureSet.applyGs(gs);
 		creatureSet.applyGs(gs);
 
 
 	for(CGTownInstance* t : gs->map->towns)
 	for(CGTownInstance* t : gs->map->towns)
+	{
 		t->built = 0;
 		t->built = 0;
+		t->spellResearchCounterDay = 0;
+	}
 
 
 	if(newRumor)
 	if(newRumor)
 		gs->currentRumor = *newRumor;
 		gs->currentRumor = *newRumor;

+ 20 - 0
lib/networkPacks/PacksForClient.h

@@ -288,6 +288,26 @@ struct DLL_LINKAGE ChangeSpells : public CPackForClient
 	}
 	}
 };
 };
 
 
+struct DLL_LINKAGE SetResearchedSpells : public CPackForClient
+{
+	void applyGs(CGameState * gs) override;
+
+	void visitTyped(ICPackVisitor & visitor) override;
+
+	ui8 level = 0;
+	ObjectInstanceID tid;
+	std::vector<SpellID> spells;
+	bool accepted;
+
+	template <typename Handler> void serialize(Handler & h)
+	{
+		h & level;
+		h & tid;
+		h & spells;
+		h & accepted;
+	}
+};
+
 struct DLL_LINKAGE SetMana : public CPackForClient
 struct DLL_LINKAGE SetMana : public CPackForClient
 {
 {
 	void applyGs(CGameState * gs) override;
 	void applyGs(CGameState * gs) override;

+ 22 - 0
lib/networkPacks/PacksForServer.h

@@ -306,6 +306,28 @@ struct DLL_LINKAGE RazeStructure : public BuildStructure
 	void visitTyped(ICPackVisitor & visitor) override;
 	void visitTyped(ICPackVisitor & visitor) override;
 };
 };
 
 
+struct DLL_LINKAGE SpellResearch : public CPackForServer
+{
+	SpellResearch() = default;
+	SpellResearch(const ObjectInstanceID & TID, SpellID spellAtSlot, bool accepted)
+		: tid(TID), spellAtSlot(spellAtSlot), accepted(accepted)
+	{
+	}
+	ObjectInstanceID tid;
+	SpellID spellAtSlot;
+	bool accepted;
+
+	void visitTyped(ICPackVisitor & visitor) override;
+
+	template <typename Handler> void serialize(Handler & h)
+	{
+		h & static_cast<CPackForServer &>(*this);
+		h & tid;
+		h & spellAtSlot;
+		h & accepted;
+	}
+};
+
 struct DLL_LINKAGE RecruitCreatures : public CPackForServer
 struct DLL_LINKAGE RecruitCreatures : public CPackForServer
 {
 {
 	RecruitCreatures() = default;
 	RecruitCreatures() = default;

+ 1 - 1
lib/rewardable/Info.cpp

@@ -76,7 +76,7 @@ void Rewardable::Info::init(const JsonNode & objectConfig, const std::string & o
 
 
 	auto loadString = [&](const JsonNode & entry, const TextIdentifier & textID){
 	auto loadString = [&](const JsonNode & entry, const TextIdentifier & textID){
 		if (entry.isString() && !entry.String().empty() && entry.String()[0] != '@')
 		if (entry.isString() && !entry.String().empty() && entry.String()[0] != '@')
-			VLC->generaltexth->registerString(entry.getModScope(), textID, entry.String());
+			VLC->generaltexth->registerString(entry.getModScope(), textID, entry);
 	};
 	};
 
 
 	parameters = objectConfig;
 	parameters = objectConfig;

+ 6 - 6
lib/serializer/Connection.cpp

@@ -68,7 +68,7 @@ CConnection::CConnection(std::weak_ptr<INetworkConnection> networkConnection)
 
 
 CConnection::~CConnection() = default;
 CConnection::~CConnection() = default;
 
 
-void CConnection::sendPack(const CPack * pack)
+void CConnection::sendPack(const CPack & pack)
 {
 {
 	boost::mutex::scoped_lock lock(writeMutex);
 	boost::mutex::scoped_lock lock(writeMutex);
 
 
@@ -78,18 +78,18 @@ void CConnection::sendPack(const CPack * pack)
 		throw std::runtime_error("Attempt to send packet on a closed connection!");
 		throw std::runtime_error("Attempt to send packet on a closed connection!");
 
 
 	packWriter->buffer.clear();
 	packWriter->buffer.clear();
-	*serializer & pack;
+	(*serializer) & (&pack);
 
 
-	logNetwork->trace("Sending a pack of type %s", typeid(*pack).name());
+	logNetwork->trace("Sending a pack of type %s", typeid(pack).name());
 
 
 	connectionPtr->sendPacket(packWriter->buffer);
 	connectionPtr->sendPacket(packWriter->buffer);
 	packWriter->buffer.clear();
 	packWriter->buffer.clear();
 	serializer->savedPointers.clear();
 	serializer->savedPointers.clear();
 }
 }
 
 
-CPack * CConnection::retrievePack(const std::vector<std::byte> & data)
+std::unique_ptr<CPack> CConnection::retrievePack(const std::vector<std::byte> & data)
 {
 {
-	CPack * result;
+	std::unique_ptr<CPack> result;
 
 
 	packReader->buffer = &data;
 	packReader->buffer = &data;
 	packReader->position = 0;
 	packReader->position = 0;
@@ -102,7 +102,7 @@ CPack * CConnection::retrievePack(const std::vector<std::byte> & data)
 	if (packReader->position != data.size())
 	if (packReader->position != data.size())
 		throw std::runtime_error("Failed to retrieve pack! Not all data has been read!");
 		throw std::runtime_error("Failed to retrieve pack! Not all data has been read!");
 
 
-	logNetwork->trace("Received CPack of type %s", typeid(*result).name());
+	logNetwork->trace("Received CPack of type %s", typeid(result.get()).name());
 	deserializer->loadedPointers.clear();
 	deserializer->loadedPointers.clear();
 	deserializer->loadedSharedPointers.clear();
 	deserializer->loadedSharedPointers.clear();
 	return result;
 	return result;

+ 2 - 2
lib/serializer/Connection.h

@@ -51,8 +51,8 @@ public:
 	explicit CConnection(std::weak_ptr<INetworkConnection> networkConnection);
 	explicit CConnection(std::weak_ptr<INetworkConnection> networkConnection);
 	~CConnection();
 	~CConnection();
 
 
-	void sendPack(const CPack * pack);
-	CPack * retrievePack(const std::vector<std::byte> & data);
+	void sendPack(const CPack & pack);
+	std::unique_ptr<CPack> retrievePack(const std::vector<std::byte> & data);
 
 
 	void enterLobbyConnectionMode();
 	void enterLobbyConnectionMode();
 	void setCallback(IGameCallback * cb);
 	void setCallback(IGameCallback * cb);

+ 2 - 1
lib/serializer/ESerializationVersion.h

@@ -61,6 +61,7 @@ enum class ESerializationVersion : int32_t
 	CAMPAIGN_OUTRO_SUPPORT, // 862 - support for campaign outro video
 	CAMPAIGN_OUTRO_SUPPORT, // 862 - support for campaign outro video
 	REWARDABLE_BANKS, // 863 - team state contains list of scouted objects, coast visitable rewardable objects
 	REWARDABLE_BANKS, // 863 - team state contains list of scouted objects, coast visitable rewardable objects
 	REGION_LABEL, // 864 - labels for campaign regions
 	REGION_LABEL, // 864 - labels for campaign regions
+	SPELL_RESEARCH, // 865 - spell research
 
 
-	CURRENT = REGION_LABEL
+	CURRENT = SPELL_RESEARCH
 };
 };

+ 2 - 0
lib/serializer/RegisterTypes.h

@@ -288,6 +288,8 @@ void registerTypes(Serializer &s)
 	s.template registerType<LobbySetDifficulty>(238);
 	s.template registerType<LobbySetDifficulty>(238);
 	s.template registerType<LobbyForceSetPlayer>(239);
 	s.template registerType<LobbyForceSetPlayer>(239);
 	s.template registerType<LobbySetExtraOptions>(240);
 	s.template registerType<LobbySetExtraOptions>(240);
+	s.template registerType<SpellResearch>(241);
+	s.template registerType<SetResearchedSpells>(242);
 }
 }
 
 
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 18 - 18
lib/spells/AdventureSpellMechanics.cpp

@@ -105,7 +105,7 @@ ESpellCastResult AdventureSpellMechanics::applyAdventureEffects(SpellCastEnviron
 			GiveBonus gb;
 			GiveBonus gb;
 			gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId());
 			gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId());
 			gb.bonus = b;
 			gb.bonus = b;
-			env->apply(&gb);
+			env->apply(gb);
 		}
 		}
 
 
 		return ESpellCastResult::OK;
 		return ESpellCastResult::OK;
@@ -136,7 +136,7 @@ void AdventureSpellMechanics::performCast(SpellCastEnvironment * env, const Adve
 	AdvmapSpellCast asc;
 	AdvmapSpellCast asc;
 	asc.casterID = ObjectInstanceID(parameters.caster->getCasterUnitId());
 	asc.casterID = ObjectInstanceID(parameters.caster->getCasterUnitId());
 	asc.spellID = owner->id;
 	asc.spellID = owner->id;
-	env->apply(&asc);
+	env->apply(asc);
 
 
 	ESpellCastResult result = applyAdventureEffects(env, parameters);
 	ESpellCastResult result = applyAdventureEffects(env, parameters);
 
 
@@ -194,7 +194,7 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment
 		iw.player = parameters.caster->getCasterOwner();
 		iw.player = parameters.caster->getCasterOwner();
 		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 336); //%s tried to summon a boat, but failed.
 		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 336); //%s tried to summon a boat, but failed.
 		parameters.caster->getCasterName(iw.text);
 		parameters.caster->getCasterName(iw.text);
-		env->apply(&iw);
+		env->apply(iw);
 		return ESpellCastResult::OK;
 		return ESpellCastResult::OK;
 	}
 	}
 
 
@@ -226,14 +226,14 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment
 		cop.objid = nearest->id;
 		cop.objid = nearest->id;
 		cop.nPos = summonPos;
 		cop.nPos = summonPos;
 		cop.initiator = parameters.caster->getCasterOwner();
 		cop.initiator = parameters.caster->getCasterOwner();
-		env->apply(&cop);
+		env->apply(cop);
 	}
 	}
 	else if(schoolLevel < 2) //none or basic level -> cannot create boat :(
 	else if(schoolLevel < 2) //none or basic level -> cannot create boat :(
 	{
 	{
 		InfoWindow iw;
 		InfoWindow iw;
 		iw.player = parameters.caster->getCasterOwner();
 		iw.player = parameters.caster->getCasterOwner();
 		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 335); //There are no boats to summon.
 		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 335); //There are no boats to summon.
-		env->apply(&iw);
+		env->apply(iw);
 		return ESpellCastResult::ERROR;
 		return ESpellCastResult::ERROR;
 	}
 	}
 	else //create boat
 	else //create boat
@@ -282,7 +282,7 @@ ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(SpellCastEnvironmen
 		iw.player = parameters.caster->getCasterOwner();
 		iw.player = parameters.caster->getCasterOwner();
 		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed
 		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed
 		parameters.caster->getCasterName(iw.text);
 		parameters.caster->getCasterName(iw.text);
-		env->apply(&iw);
+		env->apply(iw);
 		return ESpellCastResult::OK;
 		return ESpellCastResult::OK;
 	}
 	}
 
 
@@ -291,7 +291,7 @@ ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(SpellCastEnvironmen
 	RemoveObject ro;
 	RemoveObject ro;
 	ro.initiator = parameters.caster->getCasterOwner();
 	ro.initiator = parameters.caster->getCasterOwner();
 	ro.objectID = t.visitableObjects.back()->id;
 	ro.objectID = t.visitableObjects.back()->id;
-	env->apply(&ro);
+	env->apply(ro);
 	return ESpellCastResult::OK;
 	return ESpellCastResult::OK;
 }
 }
 
 
@@ -400,14 +400,14 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm
 		{
 		{
 			// SOD: DD to such "wrong" terrain results in mana and move points spending, but fails to move hero
 			// SOD: DD to such "wrong" terrain results in mana and move points spending, but fails to move hero
 			iw.text = MetaString::createFromTextID("core.genrltxt.70"); // Dimension Door failed!
 			iw.text = MetaString::createFromTextID("core.genrltxt.70"); // Dimension Door failed!
-			env->apply(&iw);
+			env->apply(iw);
 			// no return - resources will be spent
 			// no return - resources will be spent
 		}
 		}
 		else
 		else
 		{
 		{
 			// HotA: game will show error message without taking mana or move points, even when DD into terra incognita
 			// HotA: game will show error message without taking mana or move points, even when DD into terra incognita
 			iw.text = MetaString::createFromTextID("vcmi.dimensionDoor.seaToLandError");
 			iw.text = MetaString::createFromTextID("vcmi.dimensionDoor.seaToLandError");
-			env->apply(&iw);
+			env->apply(iw);
 			return ESpellCastResult::CANCEL;
 			return ESpellCastResult::CANCEL;
 		}
 		}
 	}
 	}
@@ -415,7 +415,7 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm
 	GiveBonus gb;
 	GiveBonus gb;
 	gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId());
 	gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId());
 	gb.bonus = Bonus(BonusDuration::ONE_DAY, BonusType::NONE, BonusSource::SPELL_EFFECT, 0, BonusSourceID(owner->id));
 	gb.bonus = Bonus(BonusDuration::ONE_DAY, BonusType::NONE, BonusSource::SPELL_EFFECT, 0, BonusSourceID(owner->id));
-	env->apply(&gb);
+	env->apply(gb);
 
 
 	SetMovePoints smp;
 	SetMovePoints smp;
 	smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());
 	smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());
@@ -423,7 +423,7 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm
 		smp.val = parameters.caster->getHeroCaster()->movementPointsRemaining() - movementCost;
 		smp.val = parameters.caster->getHeroCaster()->movementPointsRemaining() - movementCost;
 	else
 	else
 		smp.val = 0;
 		smp.val = 0;
-	env->apply(&smp);
+	env->apply(smp);
 
 
 	return ESpellCastResult::OK;
 	return ESpellCastResult::OK;
 }
 }
@@ -471,7 +471,7 @@ ESpellCastResult TownPortalMechanics::applyAdventureEffects(SpellCastEnvironment
 			InfoWindow iw;
 			InfoWindow iw;
 			iw.player = parameters.caster->getCasterOwner();
 			iw.player = parameters.caster->getCasterOwner();
 			iw.text.appendLocalString(EMetaText::GENERAL_TXT, 123);
 			iw.text.appendLocalString(EMetaText::GENERAL_TXT, 123);
-			env->apply(&iw);
+			env->apply(iw);
 			return ESpellCastResult::CANCEL;
 			return ESpellCastResult::CANCEL;
 		}
 		}
 	}
 	}
@@ -539,7 +539,7 @@ ESpellCastResult TownPortalMechanics::applyAdventureEffects(SpellCastEnvironment
 		InfoWindow iw;
 		InfoWindow iw;
 		iw.player = parameters.caster->getCasterOwner();
 		iw.player = parameters.caster->getCasterOwner();
 		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 135);
 		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 135);
-		env->apply(&iw);
+		env->apply(iw);
 		return ESpellCastResult::ERROR;
 		return ESpellCastResult::ERROR;
 	}
 	}
 
 
@@ -568,7 +568,7 @@ void TownPortalMechanics::endCast(SpellCastEnvironment * env, const AdventureSpe
 		SetMovePoints smp;
 		SetMovePoints smp;
 		smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());
 		smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());
 		smp.val = std::max<ui32>(0, parameters.caster->getHeroCaster()->movementPointsRemaining() - moveCost);
 		smp.val = std::max<ui32>(0, parameters.caster->getHeroCaster()->movementPointsRemaining() - moveCost);
-		env->apply(&smp);
+		env->apply(smp);
 	}
 	}
 }
 }
 
 
@@ -587,7 +587,7 @@ ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, cons
 		InfoWindow iw;
 		InfoWindow iw;
 		iw.player = parameters.caster->getCasterOwner();
 		iw.player = parameters.caster->getCasterOwner();
 		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 124);
 		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 124);
-		env->apply(&iw);
+		env->apply(iw);
 		return ESpellCastResult::CANCEL;
 		return ESpellCastResult::CANCEL;
 	}
 	}
 
 
@@ -598,7 +598,7 @@ ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, cons
 		InfoWindow iw;
 		InfoWindow iw;
 		iw.player = parameters.caster->getCasterOwner();
 		iw.player = parameters.caster->getCasterOwner();
 		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 125);
 		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 125);
-		env->apply(&iw);
+		env->apply(iw);
 		return ESpellCastResult::CANCEL;
 		return ESpellCastResult::CANCEL;
 	}
 	}
 
 
@@ -643,7 +643,7 @@ ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, cons
 			InfoWindow iw;
 			InfoWindow iw;
 			iw.player = parameters.caster->getCasterOwner();
 			iw.player = parameters.caster->getCasterOwner();
 			iw.text.appendLocalString(EMetaText::GENERAL_TXT, 124);
 			iw.text.appendLocalString(EMetaText::GENERAL_TXT, 124);
-			env->apply(&iw);
+			env->apply(iw);
 			return ESpellCastResult::CANCEL;
 			return ESpellCastResult::CANCEL;
 		}
 		}
 
 
@@ -737,7 +737,7 @@ ESpellCastResult ViewMechanics::applyAdventureEffects(SpellCastEnvironment * env
 	}
 	}
 	pack.showTerrain = showTerrain(spellLevel);
 	pack.showTerrain = showTerrain(spellLevel);
 
 
-	env->apply(&pack);
+	env->apply(pack);
 
 
 	return ESpellCastResult::OK;
 	return ESpellCastResult::OK;
 }
 }

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov