Selaa lähdekoodia

Added GameSettings to gamestate, potentially allowing to define game
settings per map (or in random map template)

Ivan Savenko 1 vuosi sitten
vanhempi
sitoutus
8225eb454e
68 muutettua tiedostoa jossa 429 lisäystä ja 290 poistoa
  1. 1 1
      AI/Nullkiller/AIGateway.cpp
  2. 1 1
      AI/Nullkiller/AIUtility.cpp
  3. 3 3
      AI/Nullkiller/Analyzers/HeroManager.cpp
  4. 1 1
      AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp
  5. 1 1
      AI/VCAI/Pathfinding/AIPathfinderConfig.cpp
  6. 4 4
      AI/VCAI/VCAI.cpp
  7. 2 2
      client/CGameInfo.cpp
  8. 1 1
      client/CGameInfo.h
  9. 2 2
      client/adventureMap/AdventureMapInterface.cpp
  10. 2 2
      client/adventureMap/CList.cpp
  11. 8 8
      client/lobby/SelectionTab.cpp
  12. 3 3
      client/widgets/CArtifactsOfHeroBackpack.cpp
  13. 3 3
      client/widgets/MiscWidgets.cpp
  14. 3 3
      client/windows/CCastleInterface.cpp
  15. 3 3
      client/windows/CCreatureWindow.cpp
  16. 4 2
      client/windows/CHeroOverview.cpp
  17. 2 2
      client/windows/CKingdomInterface.cpp
  18. 5 5
      client/windows/GUIClasses.cpp
  19. 1 0
      config/gameConfig.json
  20. 1 1
      include/vcmi/Services.h
  21. 2 2
      lib/ArtifactUtils.cpp
  22. 6 6
      lib/BasicTypes.cpp
  23. 4 4
      lib/CArtHandler.cpp
  24. 5 5
      lib/CCreatureHandler.cpp
  25. 4 4
      lib/CCreatureSet.cpp
  26. 7 2
      lib/CGameInfoCallback.cpp
  27. 2 0
      lib/CGameInfoCallback.h
  28. 4 4
      lib/CHeroHandler.cpp
  29. 1 0
      lib/CMakeLists.txt
  30. 51 19
      lib/GameSettings.cpp
  31. 42 82
      lib/GameSettings.h
  32. 98 0
      lib/IGameSettings.h
  33. 2 2
      lib/RiverHandler.cpp
  34. 2 2
      lib/RoadHandler.cpp
  35. 2 2
      lib/TerrainHandler.cpp
  36. 1 1
      lib/VCMI_Lib.cpp
  37. 1 1
      lib/VCMI_Lib.h
  38. 7 5
      lib/battle/DamageCalculator.cpp
  39. 2 2
      lib/entities/faction/CTownHandler.cpp
  40. 8 3
      lib/gameState/CGameState.cpp
  41. 2 1
      lib/gameState/CGameState.h
  42. 2 2
      lib/mapObjectConstructors/CObjectClassesHandler.cpp
  43. 2 2
      lib/mapObjects/CBank.cpp
  44. 6 6
      lib/mapObjects/CGCreature.cpp
  45. 4 4
      lib/mapObjects/CGDwelling.cpp
  46. 5 5
      lib/mapObjects/CGHeroInstance.cpp
  47. 2 2
      lib/mapObjects/CGMarket.cpp
  48. 15 0
      lib/mapping/CMap.cpp
  49. 10 0
      lib/mapping/CMap.h
  50. 8 8
      lib/mapping/CMapInfo.cpp
  51. 8 9
      lib/mapping/MapFormatH3M.cpp
  52. 2 2
      lib/modding/CModHandler.cpp
  53. 1 1
      lib/modding/ContentTypeHandler.cpp
  54. 3 3
      lib/networkPacks/NetPacksLib.cpp
  55. 14 12
      lib/pathfinder/PathfinderOptions.cpp
  56. 3 2
      lib/pathfinder/PathfinderOptions.h
  57. 2 1
      lib/serializer/ESerializationVersion.h
  58. 0 6
      lib/serializer/RegisterTypes.h
  59. 8 0
      lib/serializer/SerializerReflection.cpp
  60. 5 5
      lib/spells/AdventureSpellMechanics.cpp
  61. 2 2
      lib/texts/CGeneralTextHandler.cpp
  62. 5 5
      server/CGameHandler.cpp
  63. 4 4
      server/battles/BattleActionProcessor.cpp
  64. 3 3
      server/battles/BattleFlowProcessor.cpp
  65. 2 2
      server/battles/BattleResultProcessor.cpp
  66. 5 5
      server/processors/HeroPoolProcessor.cpp
  67. 3 3
      server/processors/NewTurnProcessor.cpp
  68. 1 1
      test/mock/mock_Services.h

+ 1 - 1
AI/Nullkiller/AIGateway.cpp

@@ -18,7 +18,7 @@
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CHeroHandler.h"
-#include "../../lib/GameSettings.h"
+#include "../../lib/IGameSettings.h"
 #include "../../lib/gameState/CGameState.h"
 #include "../../lib/serializer/CTypeList.h"
 #include "../../lib/networkPacks/PacksForClient.h"

+ 1 - 1
AI/Nullkiller/AIUtility.cpp

@@ -18,7 +18,7 @@
 #include "../../lib/mapObjects/MapObjects.h"
 #include "../../lib/mapping/CMapDefines.h"
 #include "../../lib/gameState/QuestInfo.h"
-#include "../../lib/GameSettings.h"
+#include "../../lib/IGameSettings.h"
 
 #include <vcmi/CreatureService.h>
 

+ 3 - 3
AI/Nullkiller/Analyzers/HeroManager.cpp

@@ -12,7 +12,7 @@
 #include "../Engine/Nullkiller.h"
 #include "../../../lib/mapObjects/MapObjects.h"
 #include "../../../lib/CHeroHandler.h"
-#include "../../../lib/GameSettings.h"
+#include "../../../lib/IGameSettings.h"
 
 namespace NKAI
 {
@@ -196,8 +196,8 @@ bool HeroManager::heroCapReached() const
 
 	return heroCount >= ALLOWED_ROAMING_HEROES
 		|| heroCount >= ai->settings->getMaxRoamingHeroes()
-		|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)
-		|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP);
+		|| heroCount >= cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)
+		|| heroCount >= cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP);
 }
 
 float HeroManager::getFightingStrengthCached(const CGHeroInstance * hero) const

+ 1 - 1
AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp

@@ -44,7 +44,7 @@ namespace AIPathfinding
 		Nullkiller * ai,
 		std::shared_ptr<AINodeStorage> nodeStorage,
 		bool allowBypassObjects)
-		:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage, allowBypassObjects)), aiNodeStorage(nodeStorage)
+		:PathfinderConfig(nodeStorage, cb, makeRuleset(cb, ai, nodeStorage, allowBypassObjects)), aiNodeStorage(nodeStorage)
 	{
 		options.canUseCast = true;
 		options.allowLayerTransitioningAfterBattle = true;

+ 1 - 1
AI/VCAI/Pathfinding/AIPathfinderConfig.cpp

@@ -39,7 +39,7 @@ namespace AIPathfinding
 		CPlayerSpecificInfoCallback * cb,
 		VCAI * ai,
 		std::shared_ptr<AINodeStorage> nodeStorage)
-		:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), hero(nodeStorage->getHero())
+		:PathfinderConfig(nodeStorage, cb, makeRuleset(cb, ai, nodeStorage)), hero(nodeStorage->getHero())
 	{
 		options.ignoreGuards = false;
 		options.useEmbarkAndDisembark = true;

+ 4 - 4
AI/VCAI/VCAI.cpp

@@ -21,7 +21,7 @@
 #include "../../lib/mapObjects/ObjectTemplate.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CHeroHandler.h"
-#include "../../lib/GameSettings.h"
+#include "../../lib/IGameSettings.h"
 #include "../../lib/gameState/CGameState.h"
 #include "../../lib/bonuses/Limiters.h"
 #include "../../lib/bonuses/Updaters.h"
@@ -1317,7 +1317,7 @@ bool VCAI::canRecruitAnyHero(const CGTownInstance * t) const
 		return false;
 	if(cb->getHeroesInfo().size() >= ALLOWED_ROAMING_HEROES)
 		return false;
-	if(cb->getHeroesInfo().size() >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
+	if(cb->getHeroesInfo().size() >= cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
 		return false;
 	if(!cb->getAvailableHeroes(t).size())
 		return false;
@@ -2852,12 +2852,12 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
 	case Obj::MAGIC_WELL:
 		return h->mana < h->manaLimit();
 	case Obj::PRISON:
-		return ai->myCb->getHeroesInfo().size() < VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP);
+		return ai->myCb->getHeroesInfo().size() < cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP);
 	case Obj::TAVERN:
 	{
 		//TODO: make AI actually recruit heroes
 		//TODO: only on request
-		if(ai->myCb->getHeroesInfo().size() >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
+		if(ai->myCb->getHeroesInfo().size() >= cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
 			return false;
 		else if(ai->ah->freeGold() < GameConstants::HERO_GOLD_COST)
 			return false;

+ 2 - 2
client/CGameInfo.cpp

@@ -92,9 +92,9 @@ const ObstacleService * CGameInfo::obstacles() const
 	return globalServices->obstacles();
 }
 
-const IGameSettings * CGameInfo::settings() const
+const IGameSettings * CGameInfo::engineSettings() const
 {
-	return globalServices->settings();
+	return globalServices->engineSettings();
 }
 
 spells::effects::Registry * CGameInfo::spellEffects()

+ 1 - 1
client/CGameInfo.h

@@ -70,7 +70,7 @@ public:
 	const SkillService * skills() const override;
 	const BattleFieldService * battlefields() const override;
 	const ObstacleService * obstacles() const override;
-	const IGameSettings * settings() const override;
+	const IGameSettings * engineSettings() const override;
 
 	const spells::effects::Registry * spellEffects() const override;
 	spells::effects::Registry * spellEffects() override;

+ 2 - 2
client/adventureMap/AdventureMapInterface.cpp

@@ -39,7 +39,7 @@
 #include "../CPlayerInterface.h"
 
 #include "../../CCallback.h"
-#include "../../lib/GameSettings.h"
+#include "../../lib/IGameSettings.h"
 #include "../../lib/StartInfo.h"
 #include "../../lib/texts/CGeneralTextHandler.h"
 #include "../../lib/spells/CSpellHandler.h"
@@ -617,7 +617,7 @@ void AdventureMapInterface::onTileHovered(const int3 &targetPosition)
 		case SpellID::DIMENSION_DOOR:
 			if(isValidAdventureSpellTarget(targetPosition))
 			{
-				if(VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS) && LOCPLINT->cb->isTileGuardedUnchecked(targetPosition))
+				if(LOCPLINT->cb->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS) && LOCPLINT->cb->isTileGuardedUnchecked(targetPosition))
 					CCS->curh->set(Cursor::Map::T1_ATTACK);
 				else
 					CCS->curh->set(Cursor::Map::TELEPORT);

+ 2 - 2
client/adventureMap/CList.cpp

@@ -30,7 +30,7 @@
 
 #include "../../lib/texts/CGeneralTextHandler.h"
 #include "../../lib/CHeroHandler.h"
-#include "../../lib/GameSettings.h"
+#include "../../lib/IGameSettings.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 
@@ -432,7 +432,7 @@ std::shared_ptr<CIntObject> CTownList::CTownItem::genSelection()
 
 void CTownList::CTownItem::update()
 {
-	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= CGI->settings()->getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
+	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
 
 	picture->setFrame(iconIndex + 2);
 	redraw();

+ 8 - 8
client/lobby/SelectionTab.cpp

@@ -36,7 +36,7 @@
 #include "../../CCallback.h"
 
 #include "../../lib/CConfigHandler.h"
-#include "../../lib/GameSettings.h"
+#include "../../lib/IGameSettings.h"
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/campaign/CampaignState.h"
 #include "../../lib/mapping/CMapInfo.h"
@@ -782,19 +782,19 @@ bool SelectionTab::isMapSupported(const CMapInfo & info)
 	switch (info.mapHeader->version)
 	{
 		case EMapFormat::ROE:
-			return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA)["supported"].Bool();
+			return CGI->engineSettings()->getValue(EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA)["supported"].Bool();
 		case EMapFormat::AB:
-			return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE)["supported"].Bool();
+			return CGI->engineSettings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE)["supported"].Bool();
 		case EMapFormat::SOD:
-			return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH)["supported"].Bool();
+			return CGI->engineSettings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH)["supported"].Bool();
 		case EMapFormat::CHR:
-			return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_CHRONICLES)["supported"].Bool();
+			return CGI->engineSettings()->getValue(EGameSettings::MAP_FORMAT_CHRONICLES)["supported"].Bool();
 		case EMapFormat::WOG:
-			return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS)["supported"].Bool();
+			return CGI->engineSettings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS)["supported"].Bool();
 		case EMapFormat::HOTA:
-			return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS)["supported"].Bool();
+			return CGI->engineSettings()->getValue(EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS)["supported"].Bool();
 		case EMapFormat::VCMI:
-			return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_JSON_VCMI)["supported"].Bool();
+			return CGI->engineSettings()->getValue(EGameSettings::MAP_FORMAT_JSON_VCMI)["supported"].Bool();
 	}
 	return false;
 }

+ 3 - 3
client/widgets/CArtifactsOfHeroBackpack.cpp

@@ -13,7 +13,7 @@
 #include "../gui/CGuiHandler.h"
 
 #include "Images.h"
-#include "GameSettings.h"
+#include "IGameSettings.h"
 #include "ObjectLists.h"
 
 #include "../CPlayerInterface.h"
@@ -34,7 +34,7 @@ CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack(size_t slotsColumnsMax, size_
 CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack()
 	: CArtifactsOfHeroBackpack(8, 8)
 {
-	const auto backpackCap = VLC->settings()->getInteger(EGameSettings::HEROES_BACKPACK_CAP);
+	const auto backpackCap = LOCPLINT->cb->getSettings().getInteger(EGameSettings::HEROES_BACKPACK_CAP);
 	auto visibleCapacityMax = slotsRowsMax * slotsColumnsMax;
 	if(backpackCap >= 0)
 		visibleCapacityMax = visibleCapacityMax > backpackCap ? backpackCap : visibleCapacityMax;
@@ -204,4 +204,4 @@ void CArtifactsOfHeroQuickBackpack::swapSelected()
 		}
 	if(backpackLoc.slot != ArtifactPosition::PRE_FIRST && filterBySlot != ArtifactPosition::PRE_FIRST && curHero)
 		LOCPLINT->cb->swapArtifacts(backpackLoc, ArtifactLocation(curHero->id, filterBySlot));
-}
+}

+ 3 - 3
client/widgets/MiscWidgets.cpp

@@ -32,7 +32,7 @@
 #include "../../CCallback.h"
 
 #include "../../lib/CConfigHandler.h"
-#include "../../lib/GameSettings.h"
+#include "../../lib/IGameSettings.h"
 #include "../../lib/entities/faction/CTownHandler.h"
 #include "../../lib/gameState/InfoAboutArmy.h"
 #include "../../lib/mapObjects/CGCreature.h"
@@ -388,7 +388,7 @@ void CTownTooltip::init(const InfoAboutTown & town)
 
 	assert(town.tType);
 
-	size_t iconIndex = town.tType->clientInfo.icons[town.fortLevel > 0][town.built >= CGI->settings()->getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
+	size_t iconIndex = town.tType->clientInfo.icons[town.fortLevel > 0][town.built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
 
 	build = std::make_shared<CAnimImage>(AnimationPath::builtin("itpt"), iconIndex, 0, 3, 2);
 
@@ -487,7 +487,7 @@ void CInteractableTownTooltip::init(const CGTownInstance * town)
 
 	assert(townInfo.tType);
 
-	size_t iconIndex = townInfo.tType->clientInfo.icons[townInfo.fortLevel > 0][townInfo.built >= CGI->settings()->getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
+	size_t iconIndex = townInfo.tType->clientInfo.icons[townInfo.fortLevel > 0][townInfo.built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
 
 	build = std::make_shared<CAnimImage>(AnimationPath::builtin("itpt"), iconIndex, 0, 3, 2);
 	title = std::make_shared<CLabel>(66, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, townInfo.name);

+ 3 - 3
client/windows/CCastleInterface.cpp

@@ -46,7 +46,7 @@
 #include "../../lib/CSoundBase.h"
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/texts/CGeneralTextHandler.h"
-#include "../../lib/GameSettings.h"
+#include "../../lib/IGameSettings.h"
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/GameConstants.h"
 #include "../../lib/StartInfo.h"
@@ -527,7 +527,7 @@ void HeroSlots::swapArmies()
 	//moving hero out of town - check if it is allowed
 	if (town->garrisonHero)
 	{
-		if (!town->visitingHero && LOCPLINT->cb->howManyHeroes(false) >= CGI->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
+		if (!town->visitingHero && LOCPLINT->cb->howManyHeroes(false) >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
 		{
 			std::string text = CGI->generaltexth->translate("core.genrltxt.18"); //You already have %d adventuring heroes under your command.
 			boost::algorithm::replace_first(text,"%d",std::to_string(LOCPLINT->cb->howManyHeroes(false)));
@@ -1388,7 +1388,7 @@ void CCastleInterface::removeBuilding(BuildingID bid)
 void CCastleInterface::recreateIcons()
 {
 	OBJECT_CONSTRUCTION;
-	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= CGI->settings()->getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
+	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
 
 	icon->setFrame(iconIndex);
 	TResources townIncome = town->dailyIncome();

+ 3 - 3
client/windows/CCreatureWindow.cpp

@@ -30,7 +30,7 @@
 #include "../../lib/ArtifactUtils.h"
 #include "../../lib/CStack.h"
 #include "../../lib/CBonusTypeHandler.h"
-#include "../../lib/GameSettings.h"
+#include "../../lib/IGameSettings.h"
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/gameState/CGameState.h"
 #include "../../lib/networkPacks/ArtifactLocation.h"
@@ -801,8 +801,8 @@ void CStackWindow::initSections()
 {
 	OBJECT_CONSTRUCTION;
 
-	bool showArt = CGI->settings()->getBoolean(EGameSettings::MODULE_STACK_ARTIFACT) && info->commander == nullptr && info->stackNode;
-	bool showExp = (CGI->settings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE) || info->commander != nullptr) && info->stackNode;
+	bool showArt = LOCPLINT->cb->getSettings().getBoolean(EGameSettings::MODULE_STACK_ARTIFACT) && info->commander == nullptr && info->stackNode;
+	bool showExp = (LOCPLINT->cb->getSettings().getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE) || info->commander != nullptr) && info->stackNode;
 
 	mainSection = std::make_shared<MainSection>(this, pos.h, showExp, showArt);
 

+ 4 - 2
client/windows/CHeroOverview.cpp

@@ -10,7 +10,9 @@
 #include "StdInc.h"
 #include "CHeroOverview.h"
 
+#include "../CCallback.h"
 #include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
 #include "../gui/CGuiHandler.h"
 #include "../render/Canvas.h"
 #include "../render/Colors.h"
@@ -21,7 +23,7 @@
 #include "../widgets/TextControls.h"
 #include "../widgets/GraphicalPrimitiveCanvas.h"
 
-#include "../../lib/GameSettings.h"
+#include "../../lib/IGameSettings.h"
 #include "../../lib/texts/CGeneralTextHandler.h"
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/CHeroHandler.h"
@@ -123,7 +125,7 @@ void CHeroOverview::genControls()
     r = Rect(302, 3 * borderOffset + yOffset + 62, 292, 32);
     backgroundRectangles.push_back(std::make_shared<TransparentFilledRectangle>(r.resize(1), rectangleColor, borderColor));
 
-    auto stacksCountChances = CGI->settings()->getVector(EGameSettings::HEROES_STARTING_STACKS_CHANCES);
+    auto stacksCountChances = LOCPLINT->cb->getSettings().getVector(EGameSettings::HEROES_STARTING_STACKS_CHANCES);
 
     // army
     int space = (260 - 7 * 32) / 6;

+ 2 - 2
client/windows/CKingdomInterface.cpp

@@ -35,7 +35,7 @@
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/texts/CGeneralTextHandler.h"
 #include "../../lib/CHeroHandler.h"
-#include "../../lib/GameSettings.h"
+#include "../../lib/IGameSettings.h"
 #include "../../lib/CSkillHandler.h"
 #include "../../lib/StartInfo.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
@@ -800,7 +800,7 @@ CTownItem::CTownItem(const CGTownInstance * Town)
 	garr = std::make_shared<CGarrisonInt>(Point(313, 3), 4, Point(232,0), town->getUpperArmy(), town->visitingHero, true, true, CGarrisonInt::ESlotsLayout::TWO_ROWS);
 	heroes = std::make_shared<HeroSlots>(town, Point(244,6), Point(475,6), garr, false);
 
-	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= CGI->settings()->getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
+	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
 
 	picture = std::make_shared<CAnimImage>(AnimationPath::builtin("ITPT"), iconIndex, 0, 5, 6);
 	openTown = std::make_shared<LRClickableAreaOpenTown>(Rect(5, 6, 58, 64), town);

+ 5 - 5
client/windows/GUIClasses.cpp

@@ -53,7 +53,7 @@
 #include "../lib/gameState/TavernHeroesPool.h"
 #include "../lib/texts/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
-#include "../lib/GameSettings.h"
+#include "../lib/IGameSettings.h"
 #include "ConditionalWait.h"
 #include "../lib/CRandomGenerator.h"
 #include "../lib/CSkillHandler.h"
@@ -153,7 +153,7 @@ void CRecruitmentWindow::buy()
 	if(!dstslot.validSlot() && (selected->creature->warMachine == ArtifactID::NONE)) //no available slot
 	{
 		std::pair<SlotID, SlotID> toMerge;
-		bool allowMerge = CGI->settings()->getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED);
+		bool allowMerge = LOCPLINT->cb->getSettings().getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED);
 
 		if (allowMerge && dst->mergeableStacks(toMerge))
 		{
@@ -491,7 +491,7 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func
 		recruit->addHoverText(EButtonState::NORMAL, CGI->generaltexth->tavernInfo[0]); //Cannot afford a Hero
 		recruit->block(true);
 	}
-	else if(LOCPLINT->cb->howManyHeroes(true) >= CGI->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP))
+	else if(LOCPLINT->cb->howManyHeroes(true) >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP))
 	{
 		MetaString message;
 		message.appendTextID("core.tvrninfo.1");
@@ -501,7 +501,7 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func
 		recruit->addHoverText(EButtonState::NORMAL, message.toString());
 		recruit->block(true);
 	}
-	else if(LOCPLINT->cb->howManyHeroes(false) >= CGI->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
+	else if(LOCPLINT->cb->howManyHeroes(false) >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
 	{
 		MetaString message;
 		message.appendTextID("core.tvrninfo.1");
@@ -534,7 +534,7 @@ void CTavernWindow::addInvite()
 {
 	OBJECT_CONSTRUCTION;
 
-	if(!VLC->settings()->getBoolean(EGameSettings::HEROES_TAVERN_INVITE))
+	if(!LOCPLINT->cb->getSettings().getBoolean(EGameSettings::HEROES_TAVERN_INVITE))
 		return;
 
 	const auto & heroesPool = CSH->client->gameState()->heroesPool;

+ 1 - 0
config/gameConfig.json

@@ -354,6 +354,7 @@
 			// if stack experience is on, creatures on map will get specified amount of experience daily 
 			"dailyStackExperience" : 100,
 			// if enabled, double growth, plague and creature weeks can happen randomly. Has no effect on weeks by "Deity of Fire" 
+			// NOTE: on HotA maps, this setting has no effect. Value provided in map will be used instead.
 			"allowRandomSpecialWeeks" : true,
 			// if enabled, every creature can get double growth month, ignoring predefined list
 			"allowAllForDoubleMonth" : false

+ 1 - 1
include/vcmi/Services.h

@@ -59,7 +59,7 @@ public:
 	virtual const SkillService * skills() const = 0;
 	virtual const BattleFieldService * battlefields() const = 0;
 	virtual const ObstacleService * obstacles() const = 0;
-	virtual const IGameSettings * settings() const = 0;
+	virtual const IGameSettings * engineSettings() const = 0;
 
 	virtual const spells::effects::Registry * spellEffects() const = 0;
 	virtual spells::effects::Registry * spellEffects() = 0;

+ 2 - 2
lib/ArtifactUtils.cpp

@@ -11,7 +11,7 @@
 #include "ArtifactUtils.h"
 
 #include "CArtHandler.h"
-#include "GameSettings.h"
+#include "IGameSettings.h"
 #include "spells/CSpellHandler.h"
 
 #include "mapObjects/CGHeroInstance.h"
@@ -184,7 +184,7 @@ DLL_LINKAGE bool ArtifactUtils::isBackpackFreeSlots(const CArtifactSet * target,
 {
 	if(target->bearerType() == ArtBearer::HERO)
 	{
-		const auto backpackCap = VLC->settings()->getInteger(EGameSettings::HEROES_BACKPACK_CAP);
+		const auto backpackCap = VLC->engineSettings()->getInteger(EGameSettings::HEROES_BACKPACK_CAP);
 		if(backpackCap < 0)
 			return true;
 		else

+ 6 - 6
lib/BasicTypes.cpp

@@ -12,7 +12,7 @@
 
 #include "VCMI_Lib.h"
 #include "GameConstants.h"
-#include "GameSettings.h"
+#include "IGameSettings.h"
 #include "bonuses/BonusList.h"
 #include "bonuses/Bonus.h"
 #include "bonuses/IBonusBearer.h"
@@ -86,14 +86,14 @@ int AFactionMember::getPrimSkillLevel(PrimarySkill id) const
 	static const std::string keyAllSkills = "type_PRIMARY_SKILL";
 	auto allSkills = getBonusBearer()->getBonuses(selectorAllSkills, keyAllSkills);
 	auto ret = allSkills->valOfBonuses(Selector::subtype()(BonusSubtypeID(id)));
-	auto minSkillValue = VLC->settings()->getVector(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS)[id.getNum()];
+	auto minSkillValue = VLC->engineSettings()->getVector(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS)[id.getNum()];
 	return std::max(ret, minSkillValue); //otherwise, some artifacts may cause negative skill value effect, sp=0 works in old saves
 }
 
 int AFactionMember::moraleValAndBonusList(TConstBonusListPtr & bonusList) const
 {
-	int32_t maxGoodMorale = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE).size();
-	int32_t maxBadMorale = - (int32_t) VLC->settings()->getVector(EGameSettings::COMBAT_BAD_MORALE_DICE).size();
+	int32_t maxGoodMorale = VLC->engineSettings()->getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE).size();
+	int32_t maxBadMorale = - (int32_t) VLC->engineSettings()->getVector(EGameSettings::COMBAT_BAD_MORALE_DICE).size();
 
 	if(getBonusBearer()->hasBonusOfType(BonusType::MAX_MORALE))
 	{
@@ -123,8 +123,8 @@ int AFactionMember::moraleValAndBonusList(TConstBonusListPtr & bonusList) const
 
 int AFactionMember::luckValAndBonusList(TConstBonusListPtr & bonusList) const
 {
-	int32_t maxGoodLuck = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_LUCK_DICE).size();
-	int32_t maxBadLuck = - (int32_t) VLC->settings()->getVector(EGameSettings::COMBAT_BAD_LUCK_DICE).size();
+	int32_t maxGoodLuck = VLC->engineSettings()->getVector(EGameSettings::COMBAT_GOOD_LUCK_DICE).size();
+	int32_t maxBadLuck = - (int32_t) VLC->engineSettings()->getVector(EGameSettings::COMBAT_BAD_LUCK_DICE).size();
 
 	if(getBonusBearer()->hasBonusOfType(BonusType::MAX_LUCK))
 	{

+ 4 - 4
lib/CArtHandler.cpp

@@ -12,7 +12,7 @@
 
 #include "ArtifactUtils.h"
 #include "ExceptionsCommon.h"
-#include "GameSettings.h"
+#include "IGameSettings.h"
 #include "mapObjects/MapObjects.h"
 #include "constants/StringConstants.h"
 #include "json/JsonBonus.h"
@@ -327,7 +327,7 @@ CArtHandler::~CArtHandler() = default;
 
 std::vector<JsonNode> CArtHandler::loadLegacyData()
 {
-	size_t dataSize = VLC->settings()->getInteger(EGameSettings::TEXTS_ARTIFACT);
+	size_t dataSize = VLC->engineSettings()->getInteger(EGameSettings::TEXTS_ARTIFACT);
 
 	objects.resize(dataSize);
 	std::vector<JsonNode> h3Data;
@@ -656,10 +656,10 @@ bool CArtHandler::legalArtifact(const ArtifactID & id) const
 	if(art->possibleSlots.count(ArtBearer::HERO) && !art->possibleSlots.at(ArtBearer::HERO).empty())
 		return true;
 
-	if(art->possibleSlots.count(ArtBearer::CREATURE) && !art->possibleSlots.at(ArtBearer::CREATURE).empty() && VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_ARTIFACT))
+	if(art->possibleSlots.count(ArtBearer::CREATURE) && !art->possibleSlots.at(ArtBearer::CREATURE).empty() && VLC->engineSettings()->getBoolean(EGameSettings::MODULE_STACK_ARTIFACT))
 		return true;
 
-	if(art->possibleSlots.count(ArtBearer::COMMANDER) && !art->possibleSlots.at(ArtBearer::COMMANDER).empty() && VLC->settings()->getBoolean(EGameSettings::MODULE_COMMANDERS))
+	if(art->possibleSlots.count(ArtBearer::COMMANDER) && !art->possibleSlots.at(ArtBearer::COMMANDER).empty() && VLC->engineSettings()->getBoolean(EGameSettings::MODULE_COMMANDERS))
 		return true;
 
 	return false;

+ 5 - 5
lib/CCreatureHandler.cpp

@@ -15,7 +15,7 @@
 #include "entities/faction/CTownHandler.h"
 #include "filesystem/Filesystem.h"
 #include "VCMI_Lib.h"
-#include "GameSettings.h"
+#include "IGameSettings.h"
 #include "constants/StringConstants.h"
 #include "bonuses/Limiters.h"
 #include "bonuses/Updaters.h"
@@ -519,7 +519,7 @@ void CCreatureHandler::loadBonuses(JsonNode & creature, std::string bonuses) con
 
 std::vector<JsonNode> CCreatureHandler::loadLegacyData()
 {
-	size_t dataSize = VLC->settings()->getInteger(EGameSettings::TEXTS_CREATURE);
+	size_t dataSize = VLC->engineSettings()->getInteger(EGameSettings::TEXTS_CREATURE);
 
 	objects.resize(dataSize);
 	std::vector<JsonNode> h3Data;
@@ -704,7 +704,7 @@ const std::vector<std::string> & CCreatureHandler::getTypeNames() const
 
 void CCreatureHandler::loadCrExpMod()
 {
-	if (VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE)) 	//reading default stack experience values
+	if (VLC->engineSettings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE)) 	//reading default stack experience values
 	{
 		//Calculate rank exp values, formula appears complicated bu no parsing needed
 		expRanks.resize(8);
@@ -754,7 +754,7 @@ void CCreatureHandler::loadCrExpMod()
 
 void CCreatureHandler::loadCrExpBon(CBonusSystemNode & globalEffects)
 {
-	if (VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE)) 	//reading default stack experience bonuses
+	if (VLC->engineSettings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE)) 	//reading default stack experience bonuses
 	{
 		logGlobal->debug("\tLoading stack experience bonuses");
 		auto addBonusForAllCreatures = [&](std::shared_ptr<Bonus> b) {
@@ -833,7 +833,7 @@ void CCreatureHandler::loadAnimationInfo(std::vector<JsonNode> &h3Data) const
 	parser.endLine(); // header
 	parser.endLine();
 
-	for(int dd = 0; dd < VLC->settings()->getInteger(EGameSettings::TEXTS_CREATURE); ++dd)
+	for(int dd = 0; dd < VLC->engineSettings()->getInteger(EGameSettings::TEXTS_CREATURE); ++dd)
 	{
 		while (parser.isNextEntryEmpty() && parser.endLine()) // skip empty lines
 			;

+ 4 - 4
lib/CCreatureSet.cpp

@@ -14,7 +14,7 @@
 #include "CConfigHandler.h"
 #include "CCreatureHandler.h"
 #include "VCMI_Lib.h"
-#include "GameSettings.h"
+#include "IGameSettings.h"
 #include "mapObjects/CGHeroInstance.h"
 #include "modding/ModScope.h"
 #include "IGameCallback.h"
@@ -424,7 +424,7 @@ void CCreatureSet::setStackCount(const SlotID & slot, TQuantity count)
 {
 	assert(hasStackAtSlot(slot));
 	assert(stacks[slot]->count + count > 0);
-	if (VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE) && count > stacks[slot]->count)
+	if (count > stacks[slot]->count)
 		stacks[slot]->experience = static_cast<TExpType>(stacks[slot]->experience * (count / static_cast<double>(stacks[slot]->count)));
 	stacks[slot]->count = count;
 	armyChanged();
@@ -705,7 +705,7 @@ CCreature::CreatureQuantityId CStackInstance::getQuantityID() const
 
 int CStackInstance::getExpRank() const
 {
-	if (!VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
+	if (!VLC->engineSettings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
 		return 0;
 	int tier = type->getLevel();
 	if (vstd::iswithin(tier, 1, 7))
@@ -759,7 +759,7 @@ void CStackInstance::setType(const CCreature *c)
 	if(type)
 	{
 		detachFromSource(*type);
-		if (type->isMyUpgrade(c) && VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
+		if (type->isMyUpgrade(c) && VLC->engineSettings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
 			experience = static_cast<TExpType>(experience * VLC->creh->expAfterUpgrade / 100.0);
 	}
 

+ 7 - 2
lib/CGameInfoCallback.cpp

@@ -23,7 +23,7 @@
 #include "texts/CGeneralTextHandler.h"
 #include "StartInfo.h" // for StartInfo
 #include "battle/BattleInfo.h" // for BattleInfo
-#include "GameSettings.h"
+#include "IGameSettings.h"
 #include "TerrainHandler.h"
 #include "spells/CSpellHandler.h"
 #include "mapping/CMap.h"
@@ -274,6 +274,11 @@ bool CGameInfoCallback::getTownInfo(const CGObjectInstance * town, InfoAboutTown
 	return true;
 }
 
+const IGameSettings & CGameInfoCallback::getSettings() const
+{
+	return gs->getSettings();
+}
+
 int3 CGameInfoCallback::guardingCreaturePosition (int3 pos) const //FIXME: redundant?
 {
 	ERROR_RET_VAL_IF(!isVisible(pos), "Tile is not visible!", int3(-1,-1,-1));
@@ -626,7 +631,7 @@ EBuildingState CGameInfoCallback::canBuildStructure( const CGTownInstance *t, Bu
 	if (!t->genBuildingRequirements(ID).test(buildTest))
 		return EBuildingState::PREREQUIRES;
 
-	if(t->built >= VLC->settings()->getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP))
+	if(t->built >= getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP))
 		return EBuildingState::CANT_BUILD_TODAY; //building limit
 
 	//checking resources

+ 2 - 0
lib/CGameInfoCallback.h

@@ -18,6 +18,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 class Player;
 class Team;
+class IGameSettings;
 
 struct InfoWindow;
 struct PlayerSettings;
@@ -148,6 +149,7 @@ public:
 	bool isAllowed(SpellID id) const override;
 	bool isAllowed(ArtifactID id) const override;
 	bool isAllowed(SecondarySkill id) const override;
+	const IGameSettings & getSettings() const;
 
 	//player
 	std::optional<PlayerColor> getPlayerID() const override;

+ 4 - 4
lib/CHeroHandler.cpp

@@ -15,7 +15,7 @@
 #include "constants/StringConstants.h"
 #include "battle/BattleHex.h"
 #include "CCreatureHandler.h"
-#include "GameSettings.h"
+#include "IGameSettings.h"
 #include "CSkillHandler.h"
 #include "BattleFieldHandler.h"
 #include "bonuses/Limiters.h"
@@ -240,7 +240,7 @@ void CHeroClassHandler::fillPrimarySkillData(const JsonNode & node, CHeroClass *
 {
 	const auto & skillName = NPrimarySkill::names[pSkill.getNum()];
 	auto currentPrimarySkillValue = static_cast<int>(node["primarySkills"][skillName].Integer());
-	int primarySkillLegalMinimum = VLC->settings()->getVector(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS)[pSkill.getNum()];
+	int primarySkillLegalMinimum = VLC->engineSettings()->getVector(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS)[pSkill.getNum()];
 
 	if(currentPrimarySkillValue < primarySkillLegalMinimum)
 	{
@@ -350,7 +350,7 @@ std::shared_ptr<CHeroClass> CHeroClassHandler::loadFromJson(const std::string &
 
 std::vector<JsonNode> CHeroClassHandler::loadLegacyData()
 {
-	size_t dataSize = VLC->settings()->getInteger(EGameSettings::TEXTS_HERO_CLASS);
+	size_t dataSize = VLC->engineSettings()->getInteger(EGameSettings::TEXTS_HERO_CLASS);
 
 	objects.resize(dataSize);
 	std::vector<JsonNode> h3Data;
@@ -717,7 +717,7 @@ static std::string genRefName(std::string input)
 
 std::vector<JsonNode> CHeroHandler::loadLegacyData()
 {
-	size_t dataSize = VLC->settings()->getInteger(EGameSettings::TEXTS_HERO);
+	size_t dataSize = VLC->engineSettings()->getInteger(EGameSettings::TEXTS_HERO);
 
 	objects.resize(dataSize);
 	std::vector<JsonNode> h3Data;

+ 1 - 0
lib/CMakeLists.txt

@@ -694,6 +694,7 @@ set(lib_MAIN_HEADERS
 	IBonusTypeHandler.h
 	IGameCallback.h
 	IGameEventsReceiver.h
+	IGameSettings.h
 	IHandlerBase.h
 	int3.h
 	LoadProgress.h

+ 51 - 19
lib/GameSettings.cpp

@@ -33,23 +33,10 @@ std::vector<int> IGameSettings::getVector(EGameSettings option) const
 	return getValue(option).convertTo<std::vector<int>>();
 }
 
+GameSettings::GameSettings() = default;
 GameSettings::~GameSettings() = default;
 
-GameSettings::GameSettings()
-	: gameSettings(static_cast<size_t>(EGameSettings::OPTIONS_COUNT))
-{
-}
-
-void GameSettings::load(const JsonNode & input)
-{
-	struct SettingOption
-	{
-		EGameSettings setting;
-		std::string group;
-		std::string key;
-	};
-
-	static const std::vector<SettingOption> optionPath = {
+const std::vector<GameSettings::SettingOption> GameSettings::settingProperties = {
 		{EGameSettings::BONUSES_GLOBAL,                         "bonuses",   "global"                           },
 		{EGameSettings::BONUSES_PER_HERO,                       "bonuses",   "perHero"                          },
 		{EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR,      "combat",    "attackPointDamageFactor"          },
@@ -114,7 +101,9 @@ void GameSettings::load(const JsonNode & input)
 		{EGameSettings::TOWNS_STARTING_DWELLING_CHANCES,        "towns",     "startingDwellingChances"          },
 	};
 
-	for(const auto & option : optionPath)
+void GameSettings::loadBase(const JsonNode & input)
+{
+	for(const auto & option : settingProperties)
 	{
 		const JsonNode & optionValue = input[option.group][option.key];
 		size_t index = static_cast<size_t>(option.setting);
@@ -122,16 +111,59 @@ void GameSettings::load(const JsonNode & input)
 		if(optionValue.isNull())
 			continue;
 
-		JsonUtils::mergeCopy(gameSettings[index], optionValue);
+		JsonUtils::mergeCopy(baseSettings[index], optionValue);
+	}
+	actualSettings = baseSettings;
+}
+
+void GameSettings::loadOverrides(const JsonNode & input)
+{
+	for(const auto & option : settingProperties)
+	{
+		const JsonNode & optionValue = input[option.group][option.key];
+		if (!optionValue.isNull())
+			addOverride(option.setting, optionValue);
 	}
 }
 
+void GameSettings::addOverride(EGameSettings option, const JsonNode & input)
+{
+	size_t index = static_cast<size_t>(option);
+
+	overridenSettings[index] = input;
+	JsonNode newValue = baseSettings[index];
+	JsonUtils::mergeCopy(newValue, input);
+	actualSettings[index] = newValue;
+}
+
 const JsonNode & GameSettings::getValue(EGameSettings option) const
 {
 	auto index = static_cast<size_t>(option);
 
-	assert(!gameSettings.at(index).isNull());
-	return gameSettings.at(index);
+	assert(!actualSettings.at(index).isNull());
+	return actualSettings.at(index);
+}
+
+JsonNode GameSettings::getFullConfig() const
+{
+	JsonNode result;
+	for(const auto & option : settingProperties)
+		result[option.group][option.key] = getValue(option.setting);
+
+	return result;
+}
+
+JsonNode GameSettings::getAllOverrides() const
+{
+	JsonNode result;
+	for(const auto & option : settingProperties)
+	{
+		const JsonNode & value = overridenSettings[static_cast<int32_t>(option.setting)];
+		if (!value.isNull())
+			result[option.group][option.key] = value;
+	}
+
+	return result;
 }
 
 VCMI_LIB_NAMESPACE_END

+ 42 - 82
lib/GameSettings.h

@@ -9,105 +9,65 @@
  */
 #pragma once
 
-VCMI_LIB_NAMESPACE_BEGIN
+#include "IGameSettings.h"
+#include "json/JsonNode.h"
 
-class JsonNode;
+VCMI_LIB_NAMESPACE_BEGIN
 
-enum class EGameSettings
+class DLL_LINKAGE GameSettings final : public IGameSettings, boost::noncopyable
 {
-	BONUSES_GLOBAL,
-	BONUSES_PER_HERO,
-	COMBAT_ATTACK_POINT_DAMAGE_FACTOR,
-	COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP,
-	COMBAT_BAD_LUCK_DICE,
-	COMBAT_BAD_MORALE_DICE,
-	COMBAT_DEFENSE_POINT_DAMAGE_FACTOR,
-	COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP,
-	COMBAT_GOOD_LUCK_DICE,
-	COMBAT_GOOD_MORALE_DICE,
-	CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH,
-	CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS,
-	CREATURES_DAILY_STACK_EXPERIENCE,
-	CREATURES_WEEKLY_GROWTH_CAP,
-	CREATURES_WEEKLY_GROWTH_PERCENT,
-	DWELLINGS_ACCUMULATE_WHEN_NEUTRAL,
-	DWELLINGS_ACCUMULATE_WHEN_OWNED,
-	DWELLINGS_MERGE_ON_RECRUIT,
-	HEROES_PER_PLAYER_ON_MAP_CAP,
-	HEROES_PER_PLAYER_TOTAL_CAP,
-	HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS,
-	HEROES_STARTING_STACKS_CHANCES,
-	HEROES_BACKPACK_CAP,
-	HEROES_TAVERN_INVITE,
-	HEROES_MINIMAL_PRIMARY_SKILLS,
-	MARKETS_BLACK_MARKET_RESTOCK_PERIOD,
-	BANKS_SHOW_GUARDS_COMPOSITION,
-	MODULE_COMMANDERS,
-	MODULE_STACK_ARTIFACT,
-	MODULE_STACK_EXPERIENCE,
-	TEXTS_ARTIFACT,
-	TEXTS_CREATURE,
-	TEXTS_FACTION,
-	TEXTS_HERO,
-	TEXTS_HERO_CLASS,
-	TEXTS_OBJECT,
-	TEXTS_RIVER,
-	TEXTS_ROAD,
-	TEXTS_SPELL,
-	TEXTS_TERRAIN,
-	MAP_FORMAT_RESTORATION_OF_ERATHIA,
-	MAP_FORMAT_ARMAGEDDONS_BLADE,
-	MAP_FORMAT_SHADOW_OF_DEATH,
-	MAP_FORMAT_CHRONICLES,
-	MAP_FORMAT_HORN_OF_THE_ABYSS,
-	MAP_FORMAT_JSON_VCMI,
-	MAP_FORMAT_IN_THE_WAKE_OF_GODS,
-	PATHFINDER_USE_BOAT,
-	PATHFINDER_IGNORE_GUARDS,
-	PATHFINDER_USE_MONOLITH_TWO_WAY,
-	PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE,
-	PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM,
-	PATHFINDER_USE_WHIRLPOOL,
-	PATHFINDER_ORIGINAL_FLY_RULES,
-	TOWNS_BUILDINGS_PER_TURN_CAP,
-	TOWNS_STARTING_DWELLING_CHANCES,
-	COMBAT_ONE_HEX_TRIGGERS_OBSTACLES,
-	DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES,
-	DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE,
-	DIMENSION_DOOR_FAILURE_SPENDS_POINTS,
-	DIMENSION_DOOR_TRIGGERS_GUARDS,
-	DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT,
+	struct SettingOption
+	{
+		EGameSettings setting;
+		std::string group;
+		std::string key;
+	};
 
-	OPTIONS_COUNT
-};
+	static constexpr int32_t OPTIONS_COUNT = static_cast<int32_t>(EGameSettings::OPTIONS_COUNT);
+	static const std::vector<SettingOption> settingProperties;
 
-class DLL_LINKAGE IGameSettings
-{
-public:
-	virtual const JsonNode & getValue(EGameSettings option) const = 0;
-	virtual ~IGameSettings() = default;
+	// contains base settings, like those defined in base game or mods
+	std::array<JsonNode, OPTIONS_COUNT> baseSettings;
+	// contains settings that were overriden, in map or in random map template
+	std::array<JsonNode, OPTIONS_COUNT> overridenSettings;
+	// for convenience / performance, contains actual settings - combined version of base and override settings
+	std::array<JsonNode, OPTIONS_COUNT> actualSettings;
 
-	bool getBoolean(EGameSettings option) const;
-	int64_t getInteger(EGameSettings option) const;
-	double getDouble(EGameSettings option) const;
-	std::vector<int> getVector(EGameSettings option) const;
-};
+	// converts all existing overrides into a single json node for serialization
+	JsonNode getAllOverrides() const;
 
-class DLL_LINKAGE GameSettings final : public IGameSettings, boost::noncopyable
-{
-	std::vector<JsonNode> gameSettings;
+	// loads all overrides from provided json node, for deserialization
+	void loadOverrides(const JsonNode &);
 
 public:
 	GameSettings();
 	~GameSettings();
 
-	void load(const JsonNode & input);
+	/// Loads settings as 'base settings' that can be overriden
+	/// For settings defined in vcmi or in mods
+	void loadBase(const JsonNode & input);
+
+	/// Loads setting as an override, for use in maps or rmg templates
+	/// undefined behavior if setting was already overriden (TODO: decide which approach is better - replace or append)
+	void addOverride(EGameSettings option, const JsonNode & input);
+
+	JsonNode getFullConfig() const override;
 	const JsonNode & getValue(EGameSettings option) const override;
 
 	template<typename Handler>
 	void serialize(Handler & h)
 	{
-		h & gameSettings;
+		if (h.saving)
+		{
+			JsonNode overrides = getAllOverrides();
+			h & overrides;
+		}
+		else
+		{
+			JsonNode overrides;
+			h & overrides;
+			loadOverrides(overrides);
+		}
 	}
 };
 

+ 98 - 0
lib/IGameSettings.h

@@ -0,0 +1,98 @@
+/*
+ * IIGameSettings.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class JsonNode;
+
+enum class EGameSettings
+{
+	BONUSES_GLOBAL,
+	BONUSES_PER_HERO,
+	COMBAT_ATTACK_POINT_DAMAGE_FACTOR,
+	COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP,
+	COMBAT_BAD_LUCK_DICE,
+	COMBAT_BAD_MORALE_DICE,
+	COMBAT_DEFENSE_POINT_DAMAGE_FACTOR,
+	COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP,
+	COMBAT_GOOD_LUCK_DICE,
+	COMBAT_GOOD_MORALE_DICE,
+	CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH,
+	CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS,
+	CREATURES_DAILY_STACK_EXPERIENCE,
+	CREATURES_WEEKLY_GROWTH_CAP,
+	CREATURES_WEEKLY_GROWTH_PERCENT,
+	DWELLINGS_ACCUMULATE_WHEN_NEUTRAL,
+	DWELLINGS_ACCUMULATE_WHEN_OWNED,
+	DWELLINGS_MERGE_ON_RECRUIT,
+	HEROES_PER_PLAYER_ON_MAP_CAP,
+	HEROES_PER_PLAYER_TOTAL_CAP,
+	HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS,
+	HEROES_STARTING_STACKS_CHANCES,
+	HEROES_BACKPACK_CAP,
+	HEROES_TAVERN_INVITE,
+	HEROES_MINIMAL_PRIMARY_SKILLS,
+	MARKETS_BLACK_MARKET_RESTOCK_PERIOD,
+	BANKS_SHOW_GUARDS_COMPOSITION,
+	MODULE_COMMANDERS,
+	MODULE_STACK_ARTIFACT,
+	MODULE_STACK_EXPERIENCE,
+	TEXTS_ARTIFACT,
+	TEXTS_CREATURE,
+	TEXTS_FACTION,
+	TEXTS_HERO,
+	TEXTS_HERO_CLASS,
+	TEXTS_OBJECT,
+	TEXTS_RIVER,
+	TEXTS_ROAD,
+	TEXTS_SPELL,
+	TEXTS_TERRAIN,
+	MAP_FORMAT_RESTORATION_OF_ERATHIA,
+	MAP_FORMAT_ARMAGEDDONS_BLADE,
+	MAP_FORMAT_SHADOW_OF_DEATH,
+	MAP_FORMAT_CHRONICLES,
+	MAP_FORMAT_HORN_OF_THE_ABYSS,
+	MAP_FORMAT_JSON_VCMI,
+	MAP_FORMAT_IN_THE_WAKE_OF_GODS,
+	PATHFINDER_USE_BOAT,
+	PATHFINDER_IGNORE_GUARDS,
+	PATHFINDER_USE_MONOLITH_TWO_WAY,
+	PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE,
+	PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM,
+	PATHFINDER_USE_WHIRLPOOL,
+	PATHFINDER_ORIGINAL_FLY_RULES,
+	TOWNS_BUILDINGS_PER_TURN_CAP,
+	TOWNS_STARTING_DWELLING_CHANCES,
+	COMBAT_ONE_HEX_TRIGGERS_OBSTACLES,
+	DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES,
+	DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE,
+	DIMENSION_DOOR_FAILURE_SPENDS_POINTS,
+	DIMENSION_DOOR_TRIGGERS_GUARDS,
+	DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT,
+
+	OPTIONS_COUNT,
+	OPTIONS_BEGIN = BONUSES_GLOBAL
+};
+
+class DLL_LINKAGE IGameSettings
+{
+public:
+	virtual JsonNode getFullConfig() const = 0;
+	virtual const JsonNode & getValue(EGameSettings option) const = 0;
+	virtual ~IGameSettings() = default;
+
+	bool getBoolean(EGameSettings option) const;
+	int64_t getInteger(EGameSettings option) const;
+	double getDouble(EGameSettings option) const;
+	std::vector<int> getVector(EGameSettings option) const;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 2 - 2
lib/RiverHandler.cpp

@@ -11,7 +11,7 @@
 #include "StdInc.h"
 #include "RiverHandler.h"
 #include "texts/CGeneralTextHandler.h"
-#include "GameSettings.h"
+#include "IGameSettings.h"
 #include "json/JsonNode.h"
 #include "VCMI_Lib.h"
 
@@ -63,7 +63,7 @@ const std::vector<std::string> & RiverTypeHandler::getTypeNames() const
 
 std::vector<JsonNode> RiverTypeHandler::loadLegacyData()
 {
-	size_t dataSize = VLC->settings()->getInteger(EGameSettings::TEXTS_RIVER);
+	size_t dataSize = VLC->engineSettings()->getInteger(EGameSettings::TEXTS_RIVER);
 
 	objects.resize(dataSize);
 	return {};

+ 2 - 2
lib/RoadHandler.cpp

@@ -11,7 +11,7 @@
 #include "StdInc.h"
 #include "RoadHandler.h"
 #include "texts/CGeneralTextHandler.h"
-#include "GameSettings.h"
+#include "IGameSettings.h"
 #include "json/JsonNode.h"
 #include "VCMI_Lib.h"
 
@@ -54,7 +54,7 @@ const std::vector<std::string> & RoadTypeHandler::getTypeNames() const
 
 std::vector<JsonNode> RoadTypeHandler::loadLegacyData()
 {
-	size_t dataSize = VLC->settings()->getInteger(EGameSettings::TEXTS_ROAD);
+	size_t dataSize = VLC->engineSettings()->getInteger(EGameSettings::TEXTS_ROAD);
 
 	objects.resize(dataSize);
 	return {};

+ 2 - 2
lib/TerrainHandler.cpp

@@ -10,7 +10,7 @@
 
 #include "StdInc.h"
 #include "TerrainHandler.h"
-#include "GameSettings.h"
+#include "IGameSettings.h"
 #include "json/JsonNode.h"
 #include "modding/IdentifierStorage.h"
 #include "texts/CGeneralTextHandler.h"
@@ -133,7 +133,7 @@ const std::vector<std::string> & TerrainTypeHandler::getTypeNames() const
 
 std::vector<JsonNode> TerrainTypeHandler::loadLegacyData()
 {
-	size_t dataSize = VLC->settings()->getInteger(EGameSettings::TEXTS_TERRAIN);
+	size_t dataSize = VLC->engineSettings()->getInteger(EGameSettings::TEXTS_TERRAIN);
 
 	objects.resize(dataSize);
 

+ 1 - 1
lib/VCMI_Lib.cpp

@@ -135,7 +135,7 @@ const ObstacleService * LibClasses::obstacles() const
 	return obstacleHandler.get();
 }
 
-const IGameSettings * LibClasses::settings() const
+const IGameSettings * LibClasses::engineSettings() const
 {
 	return settingsHandler.get();
 }

+ 1 - 1
lib/VCMI_Lib.h

@@ -69,7 +69,7 @@ public:
 	const SkillService * skills() const override;
 	const BattleFieldService * battlefields() const override;
 	const ObstacleService * obstacles() const override;
-	const IGameSettings * settings() const override;
+	const IGameSettings * engineSettings() const override;
 
 	const spells::effects::Registry * spellEffects() const override;
 	spells::effects::Registry * spellEffects() override;

+ 7 - 5
lib/battle/DamageCalculator.cpp

@@ -17,7 +17,7 @@
 #include "../bonuses/Bonus.h"
 #include "../mapObjects/CGTownInstance.h"
 #include "../spells/CSpellHandler.h"
-#include "../GameSettings.h"
+#include "../IGameSettings.h"
 #include "../VCMI_Lib.h"
 
 
@@ -212,8 +212,9 @@ double DamageCalculator::getAttackSkillFactor() const
 
 	if(attackAdvantage > 0)
 	{
-		const double attackMultiplier = VLC->settings()->getDouble(EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR);
-		const double attackMultiplierCap = VLC->settings()->getDouble(EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP);
+		// FIXME: use cb to acquire these settings
+		const double attackMultiplier = VLC->engineSettings()->getDouble(EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR);
+		const double attackMultiplierCap = VLC->engineSettings()->getDouble(EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP);
 		const double attackFactor = std::min(attackMultiplier * attackAdvantage, attackMultiplierCap);
 
 		return attackFactor;
@@ -312,8 +313,9 @@ double DamageCalculator::getDefenseSkillFactor() const
 	//bonus from attack/defense skills
 	if(defenseAdvantage > 0) //decreasing dmg
 	{
-		const double defenseMultiplier = VLC->settings()->getDouble(EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR);
-		const double defenseMultiplierCap = VLC->settings()->getDouble(EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP);
+		// FIXME: use cb to acquire these settings
+		const double defenseMultiplier = VLC->engineSettings()->getDouble(EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR);
+		const double defenseMultiplierCap = VLC->engineSettings()->getDouble(EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP);
 
 		const double dec = std::min(defenseMultiplier * defenseAdvantage, defenseMultiplierCap);
 		return dec;

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

@@ -16,7 +16,7 @@
 
 #include "../../CCreatureHandler.h"
 #include "../../CHeroHandler.h"
-#include "../../GameSettings.h"
+#include "../../IGameSettings.h"
 #include "../../TerrainHandler.h"
 #include "../../VCMI_Lib.h"
 
@@ -76,7 +76,7 @@ const TPropagatorPtr & CTownHandler::emptyPropagator()
 
 std::vector<JsonNode> CTownHandler::loadLegacyData()
 {
-	size_t dataSize = VLC->settings()->getInteger(EGameSettings::TEXTS_FACTION);
+	size_t dataSize = VLC->engineSettings()->getInteger(EGameSettings::TEXTS_FACTION);
 
 	std::vector<JsonNode> dest(dataSize);
 	objects.resize(dataSize);

+ 8 - 3
lib/gameState/CGameState.cpp

@@ -21,7 +21,7 @@
 #include "../CHeroHandler.h"
 #include "../CPlayerState.h"
 #include "../CStopWatch.h"
-#include "../GameSettings.h"
+#include "../IGameSettings.h"
 #include "../StartInfo.h"
 #include "../TerrainHandler.h"
 #include "../VCMIDirs.h"
@@ -153,6 +153,11 @@ CGameState::~CGameState()
 	initialOpts.dellNull();
 }
 
+const IGameSettings & CGameState::getSettings() const
+{
+	return map->getSettings();
+}
+
 void CGameState::preInit(Services * newServices, IGameCallback * newCallback)
 {
 	services = newServices;
@@ -356,7 +361,7 @@ void CGameState::generateOwnedObjectsAfterDeserialize()
 
 void CGameState::initGlobalBonuses()
 {
-	const JsonNode & baseBonuses = VLC->settings()->getValue(EGameSettings::BONUSES_GLOBAL);
+	const JsonNode & baseBonuses = getSettings().getValue(EGameSettings::BONUSES_GLOBAL);
 	logGlobal->debug("\tLoading global bonuses");
 	for(const auto & b : baseBonuses.Struct())
 	{
@@ -807,7 +812,7 @@ void CGameState::initTowns()
 			if(vti->tempOwner != PlayerColor::NEUTRAL)
 				vti->addBuilding(BuildingID::TAVERN);
 
-			auto definesBuildingsChances = VLC->settings()->getVector(EGameSettings::TOWNS_STARTING_DWELLING_CHANCES);
+			auto definesBuildingsChances = getSettings().getVector(EGameSettings::TOWNS_STARTING_DWELLING_CHANCES);
 
 			for(int i = 0; i < definesBuildingsChances.size(); i++)
 			{

+ 2 - 1
lib/gameState/CGameState.h

@@ -37,6 +37,7 @@ class CGameStateCampaign;
 class TavernHeroesPool;
 struct SThievesGuildInfo;
 class CRandomGenerator;
+class GameSettings;
 
 struct UpgradeInfo
 {
@@ -53,7 +54,6 @@ DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EVictoryLossCheck
 class DLL_LINKAGE CGameState : public CNonConstInfoCallback, public Serializeable
 {
 	friend class CGameStateCampaign;
-
 public:
 	/// Stores number of times each artifact was placed on map via randomization
 	std::map<ArtifactID, int> allocatedArtifacts;
@@ -130,6 +130,7 @@ public:
 	bool checkForStandardLoss(const PlayerColor & player) const; //checks if given player lost the game
 
 	void obtainPlayersStats(SThievesGuildInfo & tgi, int level); //fills tgi with info about other players that is available at given level of thieves' guild
+	const IGameSettings & getSettings() const;
 
 	bool isVisible(int3 pos, const std::optional<PlayerColor> & player) const override;
 	bool isVisible(const CGObjectInstance * obj, const std::optional<PlayerColor> & player) const override;

+ 2 - 2
lib/mapObjectConstructors/CObjectClassesHandler.cpp

@@ -16,7 +16,7 @@
 #include "../VCMI_Lib.h"
 #include "../GameConstants.h"
 #include "../constants/StringConstants.h"
-#include "../GameSettings.h"
+#include "../IGameSettings.h"
 #include "../CSoundBase.h"
 
 #include "../mapObjectConstructors/CBankInstanceConstructor.h"
@@ -103,7 +103,7 @@ CObjectClassesHandler::~CObjectClassesHandler() = default;
 
 std::vector<JsonNode> CObjectClassesHandler::loadLegacyData()
 {
-	size_t dataSize = VLC->settings()->getInteger(EGameSettings::TEXTS_OBJECT);
+	size_t dataSize = VLC->engineSettings()->getInteger(EGameSettings::TEXTS_OBJECT);
 
 	CLegacyConfigParser parser(TextPath::builtin("Data/Objects.txt"));
 	auto totalNumber = static_cast<size_t>(parser.readNumber()); // first line contains number of objects to read and nothing else

+ 2 - 2
lib/mapObjects/CBank.cpp

@@ -16,7 +16,7 @@
 
 #include "../texts/CGeneralTextHandler.h"
 #include "../CSoundBase.h"
-#include "../GameSettings.h"
+#include "../IGameSettings.h"
 #include "../CPlayerState.h"
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
 #include "../mapObjectConstructors/CBankInstanceConstructor.h"
@@ -68,7 +68,7 @@ std::vector<Component> CBank::getPopupComponents(PlayerColor player) const
 	if (!wasVisited(player))
 		return {};
 
-	if (!VLC->settings()->getBoolean(EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION))
+	if (!cb->getSettings().getBoolean(EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION))
 		return {};
 
 	if (bankConfig == nullptr)

+ 6 - 6
lib/mapObjects/CGCreature.cpp

@@ -14,7 +14,7 @@
 
 #include "../texts/CGeneralTextHandler.h"
 #include "../CConfigHandler.h"
-#include "../GameSettings.h"
+#include "../IGameSettings.h"
 #include "../IGameCallback.h"
 #include "../gameState/CGameState.h"
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
@@ -280,15 +280,15 @@ void CGCreature::newTurn(vstd::RNG & rand) const
 {//Works only for stacks of single type of size up to 2 millions
 	if (!notGrowingTeam)
 	{
-		if (stacks.begin()->second->count < VLC->settings()->getInteger(EGameSettings::CREATURES_WEEKLY_GROWTH_CAP) && cb->getDate(Date::DAY_OF_WEEK) == 1 && cb->getDate(Date::DAY) > 1)
+		if (stacks.begin()->second->count < cb->getSettings().getInteger(EGameSettings::CREATURES_WEEKLY_GROWTH_CAP) && cb->getDate(Date::DAY_OF_WEEK) == 1 && cb->getDate(Date::DAY) > 1)
 		{
-			ui32 power = static_cast<ui32>(temppower * (100 + VLC->settings()->getInteger(EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT)) / 100);
-			cb->setObjPropertyValue(id, ObjProperty::MONSTER_COUNT, std::min<uint32_t>(power / 1000, VLC->settings()->getInteger(EGameSettings::CREATURES_WEEKLY_GROWTH_CAP))); //set new amount
+			ui32 power = static_cast<ui32>(temppower * (100 + cb->getSettings().getInteger(EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT)) / 100);
+			cb->setObjPropertyValue(id, ObjProperty::MONSTER_COUNT, std::min<uint32_t>(power / 1000, cb->getSettings().getInteger(EGameSettings::CREATURES_WEEKLY_GROWTH_CAP))); //set new amount
 			cb->setObjPropertyValue(id, ObjProperty::MONSTER_POWER, power); //increase temppower
 		}
 	}
-	if (VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
-		cb->setObjPropertyValue(id, ObjProperty::MONSTER_EXP, VLC->settings()->getInteger(EGameSettings::CREATURES_DAILY_STACK_EXPERIENCE)); //for testing purpose
+	if (cb->getSettings().getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
+		cb->setObjPropertyValue(id, ObjProperty::MONSTER_EXP, cb->getSettings().getInteger(EGameSettings::CREATURES_DAILY_STACK_EXPERIENCE)); //for testing purpose
 }
 void CGCreature::setPropertyDer(ObjProperty what, ObjPropertyID identifier)
 {

+ 4 - 4
lib/mapObjects/CGDwelling.cpp

@@ -24,7 +24,7 @@
 #include "../IGameCallback.h"
 #include "../gameState/CGameState.h"
 #include "../CPlayerState.h"
-#include "../GameSettings.h"
+#include "../IGameSettings.h"
 #include "../CConfigHandler.h"
 
 #include <vstd/RNG.h>
@@ -309,9 +309,9 @@ void CGDwelling::newTurn(vstd::RNG & rand) const
 			bool creaturesAccumulate = false;
 
 			if (tempOwner.isValidPlayer())
-				creaturesAccumulate = VLC->settings()->getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED);
+				creaturesAccumulate = cb->getSettings().getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED);
 			else
-				creaturesAccumulate = VLC->settings()->getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL);
+				creaturesAccumulate = cb->getSettings().getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL);
 
 			const CCreature * cre =creatures[i].second[0].toCreature();
 			TQuantity amount = cre->getGrowth() * (1 + cre->valOfBonuses(BonusType::CREATURE_GROWTH_PERCENT)/100) + cre->valOfBonuses(BonusType::CREATURE_GROWTH, BonusCustomSubtype::creatureLevel(cre->getLevel()));
@@ -418,7 +418,7 @@ void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const
 		if(count) //there are available creatures
 		{
 
-			if (VLC->settings()->getBoolean(EGameSettings::DWELLINGS_MERGE_ON_RECRUIT))
+			if (cb->getSettings().getBoolean(EGameSettings::DWELLINGS_MERGE_ON_RECRUIT))
 			{
 				SlotID testSlot = h->getSlotFor(crid);
 				if(!testSlot.validSlot()) //no available slot - try merging army of visiting hero

+ 5 - 5
lib/mapObjects/CGHeroInstance.cpp

@@ -20,7 +20,7 @@
 #include "../CHeroHandler.h"
 #include "../TerrainHandler.h"
 #include "../RoadHandler.h"
-#include "../GameSettings.h"
+#include "../IGameSettings.h"
 #include "../CSoundBase.h"
 #include "../spells/CSpellHandler.h"
 #include "../CSkillHandler.h"
@@ -393,7 +393,7 @@ void CGHeroInstance::initHero(vstd::RNG & rand)
 	// are not attached to global bonus node but need access to some global bonuses
 	// e.g. MANA_PER_KNOWLEDGE_PERCENTAGE for correct preview and initial state after recruit	for(const auto & ob : VLC->modh->heroBaseBonuses)
 	// or MOVEMENT to compute initial movement before recruiting is finished
-	const JsonNode & baseBonuses = VLC->settings()->getValue(EGameSettings::BONUSES_PER_HERO);
+	const JsonNode & baseBonuses = cb->getSettings().getValue(EGameSettings::BONUSES_PER_HERO);
 	for(const auto & b : baseBonuses.Struct())
 	{
 		auto bonus = JsonUtils::parseBonus(b.second);
@@ -403,7 +403,7 @@ void CGHeroInstance::initHero(vstd::RNG & rand)
 		addNewBonus(bonus);
 	}
 
-	if (VLC->settings()->getBoolean(EGameSettings::MODULE_COMMANDERS) && !commander && type->heroClass->commander.hasValue())
+	if (cb->getSettings().getBoolean(EGameSettings::MODULE_COMMANDERS) && !commander && type->heroClass->commander.hasValue())
 	{
 		commander = new CCommanderInstance(type->heroClass->commander);
 		commander->setArmyObj (castToArmyObj()); //TODO: separate function for setting commanders
@@ -430,7 +430,7 @@ void CGHeroInstance::initArmy(vstd::RNG & rand, IArmyDescriptor * dst)
 
 	int warMachinesGiven = 0;
 
-	auto stacksCountChances = VLC->settings()->getVector(EGameSettings::HEROES_STARTING_STACKS_CHANCES);
+	auto stacksCountChances = cb->getSettings().getVector(EGameSettings::HEROES_STARTING_STACKS_CHANCES);
 	int stacksCountInitRandomNumber = rand.nextInt(1, 100);
 
 	size_t maxStacksCount = std::min(stacksCountChances.size(), type->initialArmy.size());
@@ -517,7 +517,7 @@ void CGHeroInstance::onHeroVisit(const CGHeroInstance * h) const
 	}
 	else if(ID == Obj::PRISON)
 	{
-		if (cb->getHeroCount(h->tempOwner, false) < VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))//free hero slot
+		if (cb->getHeroCount(h->tempOwner, false) < cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))//free hero slot
 		{
 			//update hero parameters
 			SetMovePoints smp;

+ 2 - 2
lib/mapObjects/CGMarket.cpp

@@ -15,7 +15,7 @@
 #include "../IGameCallback.h"
 #include "../CCreatureHandler.h"
 #include "CGTownInstance.h"
-#include "../GameSettings.h"
+#include "../IGameSettings.h"
 #include "../CSkillHandler.h"
 #include "../mapObjectConstructors/AObjectTypeHandler.h"
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
@@ -82,7 +82,7 @@ std::vector<TradeItemBuy> CGBlackMarket::availableItemsIds(EMarketMode mode) con
 
 void CGBlackMarket::newTurn(vstd::RNG & rand) const
 {
-	int resetPeriod = VLC->settings()->getInteger(EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD);
+	int resetPeriod = cb->getSettings().getInteger(EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD);
 
 	bool isFirstDay = cb->getDate(Date::DAY) == 1;
 	bool regularResetTriggered = resetPeriod != 0 && ((cb->getDate(Date::DAY)-1) % resetPeriod) == 0;

+ 15 - 0
lib/mapping/CMap.cpp

@@ -14,6 +14,7 @@
 #include "../VCMI_Lib.h"
 #include "../CCreatureHandler.h"
 #include "../CHeroHandler.h"
+#include "../GameSettings.h"
 #include "../RiverHandler.h"
 #include "../RoadHandler.h"
 #include "../TerrainHandler.h"
@@ -208,6 +209,9 @@ CMap::CMap(IGameCallback * cb)
 	allowedAbilities = VLC->skillh->getDefaultAllowed();
 	allowedArtifact = VLC->arth->getDefaultAllowed();
 	allowedSpells = VLC->spellh->getDefaultAllowed();
+
+	gameSettings = std::make_unique<GameSettings>();
+	gameSettings->loadBase(VLC->settingsHandler->getFullConfig());
 }
 
 CMap::~CMap()
@@ -781,4 +785,15 @@ void CMap::reindexObjects()
 	}
 }
 
+const IGameSettings & CMap::getSettings() const
+{
+	return *gameSettings;
+}
+
+void CMap::overrideGameSetting(EGameSettings option, const JsonNode & input)
+{
+	return gameSettings->addOverride(option, input);
+}
+
+
 VCMI_LIB_NAMESPACE_END

+ 10 - 0
lib/mapping/CMap.h

@@ -32,7 +32,10 @@ class IQuestObject;
 class CInputStream;
 class CMapEditManager;
 class JsonSerializeFormat;
+class IGameSettings;
+class GameSettings;
 struct TeleportChannel;
+enum class EGameSettings;
 
 /// The rumor struct consists of a rumor name and text.
 struct DLL_LINKAGE Rumor
@@ -76,6 +79,7 @@ struct DLL_LINKAGE DisposedHero
 /// The map contains the map header, the tiles of the terrain, objects, heroes, towns, rumors...
 class DLL_LINKAGE CMap : public CMapHeader, public GameCallbackHolder
 {
+	std::unique_ptr<GameSettings> gameSettings;
 public:
 	explicit CMap(IGameCallback *cb);
 	~CMap();
@@ -176,6 +180,9 @@ public:
 	std::vector<const CArtifact *> townMerchantArtifacts;
 	std::vector<TradeItemBuy> townUniversitySkills;
 
+	void overrideGameSetting(EGameSettings option, const JsonNode & input);
+	const IGameSettings & getSettings() const;
+
 private:
 	/// a 3-dimensional array of terrain tiles, access is as follows: x, y, level. where level=1 is underground
 	boost::multi_array<TerrainTile, 3> terrain;
@@ -215,6 +222,9 @@ public:
 		h & townUniversitySkills;
 
 		h & instanceNames;
+
+		if (h.version >= Handler::Version::PER_MAP_GAME_SETTINGS)
+			h & *gameSettings;
 	}
 };
 

+ 8 - 8
lib/mapping/CMapInfo.cpp

@@ -24,7 +24,7 @@
 #include "../texts/CGeneralTextHandler.h"
 #include "../texts/TextOperations.h"
 #include "../CCreatureHandler.h"
-#include "../GameSettings.h"
+#include "../IGameSettings.h"
 #include "../CHeroHandler.h"
 #include "../CConfigHandler.h"
 
@@ -167,19 +167,19 @@ int CMapInfo::getMapSizeFormatIconId() const
 	switch(mapHeader->version)
 	{
 		case EMapFormat::ROE:
-			return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA)["iconIndex"].Integer();
+			return VLC->engineSettings()->getValue(EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA)["iconIndex"].Integer();
 		case EMapFormat::AB:
-			return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE)["iconIndex"].Integer();
+			return VLC->engineSettings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE)["iconIndex"].Integer();
 		case EMapFormat::SOD:
-			return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH)["iconIndex"].Integer();
+			return VLC->engineSettings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH)["iconIndex"].Integer();
 		case EMapFormat::CHR:
-			return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_CHRONICLES)["iconIndex"].Integer();
+			return VLC->engineSettings()->getValue(EGameSettings::MAP_FORMAT_CHRONICLES)["iconIndex"].Integer();
 		case EMapFormat::WOG:
-			return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS)["iconIndex"].Integer();
+			return VLC->engineSettings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS)["iconIndex"].Integer();
 		case EMapFormat::HOTA:
-			return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS)["iconIndex"].Integer();
+			return VLC->engineSettings()->getValue(EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS)["iconIndex"].Integer();
 		case EMapFormat::VCMI:
-			return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_JSON_VCMI)["iconIndex"].Integer();
+			return VLC->engineSettings()->getValue(EGameSettings::MAP_FORMAT_JSON_VCMI)["iconIndex"].Integer();
 	}
 	return 0;
 }

+ 8 - 9
lib/mapping/MapFormatH3M.cpp

@@ -21,7 +21,7 @@
 #include "../CHeroHandler.h"
 #include "../CSkillHandler.h"
 #include "../CStopWatch.h"
-#include "../GameSettings.h"
+#include "../IGameSettings.h"
 #include "../RiverHandler.h"
 #include "../RoadHandler.h"
 #include "../TerrainHandler.h"
@@ -130,17 +130,17 @@ static MapIdentifiersH3M generateMapping(EMapFormat format)
 	MapIdentifiersH3M identifierMapper;
 
 	if(features.levelROE)
-		identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA));
+		identifierMapper.loadMapping(VLC->engineSettings()->getValue(EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA));
 	if(features.levelAB)
-		identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE));
+		identifierMapper.loadMapping(VLC->engineSettings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE));
 	if(features.levelSOD)
-		identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH));
+		identifierMapper.loadMapping(VLC->engineSettings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH));
 	if(features.levelCHR)
-		identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_CHRONICLES));
+		identifierMapper.loadMapping(VLC->engineSettings()->getValue(EGameSettings::MAP_FORMAT_CHRONICLES));
 	if(features.levelWOG)
-		identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS));
+		identifierMapper.loadMapping(VLC->engineSettings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS));
 	if(features.levelHOTA0)
-		identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS));
+		identifierMapper.loadMapping(VLC->engineSettings()->getValue(EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS));
 
 	return identifierMapper;
 }
@@ -737,8 +737,7 @@ void CMapLoaderH3M::readMapOptions()
 	{
 		//TODO: HotA
 		bool allowSpecialMonths = reader->readBool();
-		if(!allowSpecialMonths)
-			logGlobal->warn("Map '%s': Option 'allow special months' is not implemented!", mapName);
+		map->overrideGameSetting(EGameSettings::CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS, JsonNode(allowSpecialMonths));
 		reader->skipZero(3);
 	}
 

+ 2 - 2
lib/modding/CModHandler.cpp

@@ -384,13 +384,13 @@ std::set<TModID> CModHandler::getModDependencies(const TModID & modId, bool & is
 
 void CModHandler::initializeConfig()
 {
-	VLC->settingsHandler->load(coreMod->config["settings"]);
+	VLC->settingsHandler->loadBase(coreMod->config["settings"]);
 
 	for(const TModID & modName : activeMods)
 	{
 		const auto & mod = allMods[modName];
 		if (!mod.config["settings"].isNull())
-			VLC->settingsHandler->load(mod.config["settings"]);
+			VLC->settingsHandler->loadBase(mod.config["settings"]);
 	}
 }
 

+ 1 - 1
lib/modding/ContentTypeHandler.cpp

@@ -22,7 +22,7 @@
 #include "../CHeroHandler.h"
 #include "../CSkillHandler.h"
 #include "../CStopWatch.h"
-#include "../GameSettings.h"
+#include "../IGameSettings.h"
 #include "../IHandlerBase.h"
 #include "../ObstacleHandler.h"
 #include "../mapObjects/ObstacleSetHandler.h"

+ 3 - 3
lib/networkPacks/NetPacksLib.cpp

@@ -44,7 +44,7 @@
 #include "mapObjectConstructors/AObjectTypeHandler.h"
 #include "mapObjectConstructors/CObjectClassesHandler.h"
 #include "campaign/CampaignState.h"
-#include "GameSettings.h"
+#include "IGameSettings.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -1580,7 +1580,7 @@ void RebalanceStacks::applyGs(CGameState *gs)
 
 	const CCreature * srcType = src.army->getCreature(src.slot);
 	TQuantity srcCount = src.army->getStackCount(src.slot);
-	bool stackExp = VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE);
+	bool stackExp = gs->getSettings().getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE);
 
 	if(srcCount == count) //moving whole stack
 	{
@@ -2109,7 +2109,7 @@ void BattleResultAccepted::applyGs(CGameState *gs)
 		}
 	}
 
-	if(VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
+	if(gs->getSettings().getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
 	{
 		if(heroResult[BattleSide::ATTACKER].army)
 			heroResult[BattleSide::ATTACKER].army->giveStackExp(heroResult[BattleSide::ATTACKER].exp);

+ 14 - 12
lib/pathfinder/PathfinderOptions.cpp

@@ -10,7 +10,8 @@
 #include "StdInc.h"
 #include "PathfinderOptions.h"
 
-#include "../GameSettings.h"
+#include "../gameState/CGameState.h"
+#include "../IGameSettings.h"
 #include "../VCMI_Lib.h"
 #include "NodeStorage.h"
 #include "PathfindingRules.h"
@@ -18,16 +19,16 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-PathfinderOptions::PathfinderOptions()
+PathfinderOptions::PathfinderOptions(const CGameInfoCallback * cb)
 	: useFlying(true)
 	, useWaterWalking(true)
-	, ignoreGuards(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_IGNORE_GUARDS))
-	, useEmbarkAndDisembark(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_BOAT))
-	, useTeleportTwoWay(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY))
-	, useTeleportOneWay(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE))
-	, useTeleportOneWayRandom(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM))
-	, useTeleportWhirlpool(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_WHIRLPOOL))
-	, originalFlyRules(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES))
+	, ignoreGuards(cb->getSettings().getBoolean(EGameSettings::PATHFINDER_IGNORE_GUARDS))
+	, useEmbarkAndDisembark(cb->getSettings().getBoolean(EGameSettings::PATHFINDER_USE_BOAT))
+	, useTeleportTwoWay(cb->getSettings().getBoolean(EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY))
+	, useTeleportOneWay(cb->getSettings().getBoolean(EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE))
+	, useTeleportOneWayRandom(cb->getSettings().getBoolean(EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM))
+	, useTeleportWhirlpool(cb->getSettings().getBoolean(EGameSettings::PATHFINDER_USE_WHIRLPOOL))
+	, originalFlyRules(cb->getSettings().getBoolean(EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES))
 	, useCastleGate(false)
 	, lightweightFlyingMode(false)
 	, oneTurnSpecialLayersLimit(true)
@@ -38,9 +39,10 @@ PathfinderOptions::PathfinderOptions()
 {
 }
 
-PathfinderConfig::PathfinderConfig(std::shared_ptr<INodeStorage> nodeStorage, std::vector<std::shared_ptr<IPathfindingRule>> rules):
+PathfinderConfig::PathfinderConfig(std::shared_ptr<INodeStorage> nodeStorage, const CGameInfoCallback * callback, std::vector<std::shared_ptr<IPathfindingRule>> rules):
 	nodeStorage(std::move(nodeStorage)),
-	rules(std::move(rules))
+	rules(std::move(rules)),
+	options(callback)
 {
 }
 
@@ -58,7 +60,7 @@ std::vector<std::shared_ptr<IPathfindingRule>> SingleHeroPathfinderConfig::build
 SingleHeroPathfinderConfig::~SingleHeroPathfinderConfig() = default;
 
 SingleHeroPathfinderConfig::SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero)
-	: PathfinderConfig(std::make_shared<NodeStorage>(out, hero), buildRuleSet())
+	: PathfinderConfig(std::make_shared<NodeStorage>(out, hero), gs, buildRuleSet())
 {
 	pathfinderHelper = std::make_unique<CPathfinderHelper>(gs, hero, options);
 }

+ 3 - 2
lib/pathfinder/PathfinderOptions.h

@@ -16,7 +16,7 @@ class IPathfindingRule;
 class CPathfinderHelper;
 class CGameState;
 class CGHeroInstance;
-
+class CGameInfoCallback;
 struct PathNodeInfo;
 struct CPathsInfo;
 
@@ -85,7 +85,7 @@ struct DLL_LINKAGE PathfinderOptions
 	/// </summary>
 	bool allowLayerTransitioningAfterBattle;
 
-	PathfinderOptions();
+	PathfinderOptions(const CGameInfoCallback * callback);
 };
 
 class DLL_LINKAGE PathfinderConfig
@@ -97,6 +97,7 @@ public:
 
 	PathfinderConfig(
 		std::shared_ptr<INodeStorage> nodeStorage,
+		const CGameInfoCallback * callback,
 		std::vector<std::shared_ptr<IPathfindingRule>> rules);
 	virtual ~PathfinderConfig() = default;
 

+ 2 - 1
lib/serializer/ESerializationVersion.h

@@ -57,6 +57,7 @@ enum class ESerializationVersion : int32_t
 	PLAYER_STATE_OWNED_OBJECTS, // 858 - player state stores all owned objects in a single list
 	SAVE_COMPATIBILITY_FIXES, // 859 - implementation of previoulsy postponed changes to serialization
 	CHRONICLES_SUPPORT, // 860 - support for heroes chronicles
+	PER_MAP_GAME_SETTINGS, // 861 - game settings are now stored per-map
 
-	CURRENT = CHRONICLES_SUPPORT
+	CURRENT = PER_MAP_GAME_SETTINGS
 };

+ 0 - 6
lib/serializer/RegisterTypes.h

@@ -12,9 +12,6 @@
 #include "../CHeroHandler.h"
 #include "../CPlayerState.h"
 #include "../CStack.h"
-#include "../RiverHandler.h"
-#include "../RoadHandler.h"
-#include "../TerrainHandler.h"
 #include "../battle/BattleInfo.h"
 #include "../battle/CObstacleInstance.h"
 #include "../bonuses/Limiters.h"
@@ -31,17 +28,14 @@
 #include "../mapObjects/CGTownInstance.h"
 #include "../mapObjects/CQuest.h"
 #include "../mapObjects/MiscObjects.h"
-#include "../mapObjects/ObjectTemplate.h"
 #include "../mapObjects/TownBuildingInstance.h"
 #include "../mapping/CMap.h"
-#include "../mapping/CMapInfo.h"
 #include "../networkPacks/PacksForClient.h"
 #include "../networkPacks/PacksForClientBattle.h"
 #include "../networkPacks/PacksForLobby.h"
 #include "../networkPacks/PacksForServer.h"
 #include "../networkPacks/SetRewardableConfiguration.h"
 #include "../networkPacks/SetStackEffect.h"
-#include "../rmg/CMapGenOptions.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 8 - 0
lib/serializer/SerializerReflection.cpp

@@ -15,6 +15,14 @@
 
 #include "RegisterTypes.h"
 
+#include "../GameSettings.h"
+#include "../RiverHandler.h"
+#include "../RoadHandler.h"
+#include "../TerrainHandler.h"
+#include "../mapObjects/ObjectTemplate.h"
+#include "../mapping/CMapInfo.h"
+#include "../rmg/CMapGenOptions.h"
+
 VCMI_LIB_NAMESPACE_BEGIN
 
 template<typename Type>

+ 5 - 5
lib/spells/AdventureSpellMechanics.cpp

@@ -17,7 +17,7 @@
 
 #include "../CGameInfoCallback.h"
 #include "../CPlayerState.h"
-#include "../GameSettings.h"
+#include "../IGameSettings.h"
 #include "../mapObjects/CGHeroInstance.h"
 #include "../mapObjects/CGTownInstance.h"
 #include "../mapObjects/MiscObjects.h"
@@ -320,7 +320,7 @@ bool DimensionDoorMechanics::canBeCastImpl(spells::Problem & problem, const CGam
 	int castsAlreadyPerformedThisTurn = caster->getHeroCaster()->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(owner->id)), Selector::all, cachingStr.str())->size();
 	int castsLimit = owner->getLevelPower(schoolLevel);
 
-	bool isTournamentRulesLimitEnabled = VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT);
+	bool isTournamentRulesLimitEnabled = cb->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT);
 	if(isTournamentRulesLimitEnabled)
 	{
 		int3 mapSize = cb->getMapSize();
@@ -347,7 +347,7 @@ bool DimensionDoorMechanics::canBeCastAtImpl(spells::Problem & problem, const CG
 	if(!cb->isInTheMap(pos))
 		return false;
 
-	if(VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES))
+	if(cb->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES))
 	{
 		if(!cb->isVisible(pos, caster->getCasterOwner()))
 			return false;
@@ -367,7 +367,7 @@ bool DimensionDoorMechanics::canBeCastAtImpl(spells::Problem & problem, const CG
 	if(!isInScreenRange(casterPosition, pos))
 		return false;
 
-	if(VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE))
+	if(cb->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE))
 	{
 		if(!dest->isClear(curr))
 			return false;
@@ -396,7 +396,7 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm
 		iw.player = parameters.caster->getCasterOwner();
 
 		// tile is either blocked or not possible to move (e.g. water <-> land)
-		if(VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS))
+		if(env->getCb()->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS))
 		{
 			// 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!

+ 2 - 2
lib/texts/CGeneralTextHandler.cpp

@@ -12,7 +12,7 @@
 
 #include "CLegacyConfigParser.h"
 #include "CConfigHandler.h"
-#include "GameSettings.h"
+#include "IGameSettings.h"
 #include "Languages.h"
 #include "../filesystem/Filesystem.h"
 #include "../mapObjects/CQuest.h"
@@ -303,7 +303,7 @@ CGeneralTextHandler::CGeneralTextHandler():
 			scenariosCountPerCampaign.push_back(region);
 		}
 	}
-	if (VLC->settings()->getBoolean(EGameSettings::MODULE_COMMANDERS))
+	if (VLC->engineSettings()->getBoolean(EGameSettings::MODULE_COMMANDERS))
 	{
 		if(CResourceHandler::get()->existsResource(TextPath::builtin("DATA/ZNPC00.TXT")))
 			readToVector("vcmi.znpc00", "DATA/ZNPC00.TXT" );

+ 5 - 5
server/CGameHandler.cpp

@@ -36,7 +36,7 @@
 #include "../lib/CThreadHelper.h"
 #include "../lib/GameConstants.h"
 #include "../lib/UnlockGuard.h"
-#include "../lib/GameSettings.h"
+#include "../lib/IGameSettings.h"
 #include "../lib/ScriptHandler.h"
 #include "../lib/StartInfo.h"
 #include "../lib/TerrainHandler.h"
@@ -823,7 +823,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme
 	tmh.movePoints = h->movementPointsRemaining();
 
 	//check if destination tile is available
-	auto pathfinderHelper = std::make_unique<CPathfinderHelper>(gs, h, PathfinderOptions());
+	auto pathfinderHelper = std::make_unique<CPathfinderHelper>(gs, h, PathfinderOptions(this));
 	auto ti = pathfinderHelper->getTurnInfo();
 
 	const bool canFly = pathfinderHelper->hasBonusOfType(BonusType::FLYING_MOVEMENT) || (h->boat && h->boat->layer == EPathfindingLayer::AIR);
@@ -966,7 +966,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme
 		if (blockingVisit()) // e.g. hero on the other side of teleporter
 			return true;
 
-		EGuardLook guardsCheck = (VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS) && movementMode == EMovementMode::DIMENSION_DOOR)
+		EGuardLook guardsCheck = (getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS) && movementMode == EMovementMode::DIMENSION_DOOR)
 			? CHECK_FOR_GUARDS
 			: IGNORE_GUARDS;
 
@@ -2427,7 +2427,7 @@ bool CGameHandler::garrisonSwap(ObjectInstanceID tid)
 	}
 	else if (town->garrisonHero && !town->visitingHero) //move hero out of the garrison
 	{
-		int mapCap = VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP);
+		int mapCap = getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP);
 		//check if moving hero out of town will break wandering heroes limit
 		if (getHeroCount(town->garrisonHero->tempOwner,false) >= mapCap)
 		{
@@ -2718,7 +2718,7 @@ bool CGameHandler::switchArtifactsCostume(const PlayerColor & player, const Obje
 				estimateBackpackSize++;
 			}
 		
-		const auto backpackCap = VLC->settings()->getInteger(EGameSettings::HEROES_BACKPACK_CAP);
+		const auto backpackCap = getSettings().getInteger(EGameSettings::HEROES_BACKPACK_CAP);
 		if((backpackCap < 0 || estimateBackpackSize <= backpackCap) && !bma.artsPack0.empty())
 			sendAndApply(&bma);
 	}

+ 4 - 4
server/battles/BattleActionProcessor.cpp

@@ -16,7 +16,7 @@
 
 #include "../../lib/texts/CGeneralTextHandler.h"
 #include "../../lib/CStack.h"
-#include "../../lib/GameSettings.h"
+#include "../../lib/IGameSettings.h"
 #include "../../lib/battle/CBattleInfoCallback.h"
 #include "../../lib/battle/CObstacleInstance.h"
 #include "../../lib/battle/IBattleState.h"
@@ -891,7 +891,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta
 		}
 	}
 	//handle last hex separately for deviation
-	if (VLC->settings()->getBoolean(EGameSettings::COMBAT_ONE_HEX_TRIGGERS_OBSTACLES))
+	if (gameHandler->getSettings().getBoolean(EGameSettings::COMBAT_ONE_HEX_TRIGGERS_OBSTACLES))
 	{
 		if (dest == battle::Unit::occupiedHex(start, curStack->doubleWide(), curStack->unitSide())
 			|| start == battle::Unit::occupiedHex(dest, curStack->doubleWide(), curStack->unitSide()))
@@ -930,7 +930,7 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
 
 	if(attackerLuck > 0)
 	{
-		auto diceSize = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_LUCK_DICE);
+		auto diceSize = gameHandler->getSettings().getVector(EGameSettings::COMBAT_GOOD_LUCK_DICE);
 		size_t diceIndex = std::min<size_t>(diceSize.size(), attackerLuck) - 1; // array index, so 0-indexed
 
 		if(diceSize.size() > 0 && gameHandler->getRandomGenerator().nextInt(1, diceSize[diceIndex]) == 1)
@@ -939,7 +939,7 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
 
 	if(attackerLuck < 0)
 	{
-		auto diceSize = VLC->settings()->getVector(EGameSettings::COMBAT_BAD_LUCK_DICE);
+		auto diceSize = gameHandler->getSettings().getVector(EGameSettings::COMBAT_BAD_LUCK_DICE);
 		size_t diceIndex = std::min<size_t>(diceSize.size(), -attackerLuck) - 1; // array index, so 0-indexed
 
 		if(diceSize.size() > 0 && gameHandler->getRandomGenerator().nextInt(1, diceSize[diceIndex]) == 1)

+ 3 - 3
server/battles/BattleFlowProcessor.cpp

@@ -16,7 +16,7 @@
 #include "../TurnTimerHandler.h"
 
 #include "../../lib/CStack.h"
-#include "../../lib/GameSettings.h"
+#include "../../lib/IGameSettings.h"
 #include "../../lib/battle/CBattleInfoCallback.h"
 #include "../../lib/battle/IBattleState.h"
 #include "../../lib/entities/building/TownFortifications.h"
@@ -347,7 +347,7 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CBattleInfoCallback & bat
 	int nextStackMorale = next->moraleVal();
 	if(!next->hadMorale && !next->waited() && nextStackMorale < 0)
 	{
-		auto diceSize = VLC->settings()->getVector(EGameSettings::COMBAT_BAD_MORALE_DICE);
+		auto diceSize = gameHandler->getSettings().getVector(EGameSettings::COMBAT_BAD_MORALE_DICE);
 		size_t diceIndex = std::min<size_t>(diceSize.size(), -nextStackMorale) - 1; // array index, so 0-indexed
 
 		if(diceSize.size() > 0 && gameHandler->getRandomGenerator().nextInt(1, diceSize[diceIndex]) == 1)
@@ -526,7 +526,7 @@ bool BattleFlowProcessor::rollGoodMorale(const CBattleInfoCallback & battle, con
 		&& next->canMove()
 		&& nextStackMorale > 0)
 	{
-		auto diceSize = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE);
+		auto diceSize = gameHandler->getSettings().getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE);
 		size_t diceIndex = std::min<size_t>(diceSize.size(), nextStackMorale) - 1; // array index, so 0-indexed
 
 		if(diceSize.size() > 0 && gameHandler->getRandomGenerator().nextInt(1, diceSize[diceIndex]) == 1)

+ 2 - 2
server/battles/BattleResultProcessor.cpp

@@ -19,7 +19,7 @@
 #include "../../lib/ArtifactUtils.h"
 #include "../../lib/CStack.h"
 #include "../../lib/CPlayerState.h"
-#include "../../lib/GameSettings.h"
+#include "../../lib/IGameSettings.h"
 #include "../../lib/battle/CBattleInfoCallback.h"
 #include "../../lib/battle/IBattleState.h"
 #include "../../lib/battle/SideInBattle.h"
@@ -592,7 +592,7 @@ void BattleResultProcessor::battleAfterLevelUp(const BattleID & battleID, const
 		RemoveObject ro(finishingBattle->winnerHero->id, finishingBattle->winnerHero->getOwner());
 		gameHandler->sendAndApply(&ro);
 
-		if (VLC->settings()->getBoolean(EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS))
+		if (gameHandler->getSettings().getBoolean(EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS))
 			gameHandler->heroPool->onHeroEscaped(finishingBattle->victor, finishingBattle->winnerHero);
 	}
 

+ 5 - 5
server/processors/HeroPoolProcessor.cpp

@@ -16,7 +16,7 @@
 #include "../../lib/CRandomGenerator.h"
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/CPlayerState.h"
-#include "../../lib/GameSettings.h"
+#include "../../lib/IGameSettings.h"
 #include "../../lib/StartInfo.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
@@ -24,7 +24,7 @@
 #include "../../lib/gameState/CGameState.h"
 #include "../../lib/gameState/TavernHeroesPool.h"
 #include "../../lib/gameState/TavernSlot.h"
-#include "../../lib/GameSettings.h"
+#include "../../lib/IGameSettings.h"
 
 HeroPoolProcessor::HeroPoolProcessor(CGameHandler * gameHandler)
 	: gameHandler(gameHandler)
@@ -158,15 +158,15 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy
 	if (playerState->resources[EGameResID::GOLD] < GameConstants::HERO_GOLD_COST && gameHandler->complain("Not enough gold for buying hero!"))
 		return false;
 
-	if (gameHandler->getHeroCount(player, false) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP) && gameHandler->complain("Cannot hire hero, too many wandering heroes already!"))
+	if (gameHandler->getHeroCount(player, false) >= gameHandler->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP) && gameHandler->complain("Cannot hire hero, too many wandering heroes already!"))
 		return false;
 
-	if (gameHandler->getHeroCount(player, true) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP) && gameHandler->complain("Cannot hire hero, too many heroes garrizoned and wandering already!"))
+	if (gameHandler->getHeroCount(player, true) >= gameHandler->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP) && gameHandler->complain("Cannot hire hero, too many heroes garrizoned and wandering already!"))
 		return false;
 
 	if (nextHero != HeroTypeID::NONE) // player attempts to invite next hero
 	{
-		if(!VLC->settings()->getBoolean(EGameSettings::HEROES_TAVERN_INVITE) && gameHandler->complain("Inviting heroes not allowed!"))
+		if(!gameHandler->getSettings().getBoolean(EGameSettings::HEROES_TAVERN_INVITE) && gameHandler->complain("Inviting heroes not allowed!"))
 			return false;
 
 		if(!heroesPool->unusedHeroesFromPool().count(nextHero) && gameHandler->complain("Cannot invite specified hero!"))

+ 3 - 3
server/processors/NewTurnProcessor.cpp

@@ -15,7 +15,7 @@
 #include "../CGameHandler.h"
 
 #include "../../lib/CPlayerState.h"
-#include "../../lib/GameSettings.h"
+#include "../../lib/IGameSettings.h"
 #include "../../lib/StartInfo.h"
 #include "../../lib/TerrainHandler.h"
 #include "../../lib/entities/building/CBuilding.h"
@@ -479,7 +479,7 @@ std::tuple<EWeekType, CreatureID> NewTurnProcessor::pickWeekType(bool newMonth)
 			return { EWeekType::DEITYOFFIRE, CreatureID::IMP };
 	}
 
-	if(!VLC->settings()->getBoolean(EGameSettings::CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS))
+	if(!gameHandler->getSettings().getBoolean(EGameSettings::CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS))
 		return { EWeekType::NORMAL, CreatureID::NONE};
 
 	int monthType = gameHandler->getRandomGenerator().nextInt(99);
@@ -487,7 +487,7 @@ std::tuple<EWeekType, CreatureID> NewTurnProcessor::pickWeekType(bool newMonth)
 	{
 		if (monthType < 40) //double growth
 		{
-			if (VLC->settings()->getBoolean(EGameSettings::CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH))
+			if (gameHandler->getSettings().getBoolean(EGameSettings::CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH))
 			{
 				CreatureID creatureID = VLC->creh->pickRandomMonster(gameHandler->getRandomGenerator());
 				return { EWeekType::DOUBLE_GROWTH, creatureID};

+ 1 - 1
test/mock/mock_Services.h

@@ -28,7 +28,7 @@ public:
 	MOCK_CONST_METHOD0(skills, const SkillService * ());
 	MOCK_CONST_METHOD0(battlefields, const BattleFieldService *());
 	MOCK_CONST_METHOD0(obstacles, const ObstacleService *());
-	MOCK_CONST_METHOD0(settings, const IGameSettings *());
+	MOCK_CONST_METHOD0(engineSettings, const IGameSettings *());
 
 	MOCK_METHOD3(updateEntity, void(Metatype, int32_t, const JsonNode &));