Pārlūkot izejas kodu

Renamed & reorganized all game mechanics settings names

Ivan Savenko 2 gadi atpakaļ
vecāks
revīzija
a0e9e01b48

+ 1 - 1
AI/Nullkiller/AIGateway.cpp

@@ -1072,7 +1072,7 @@ bool AIGateway::canRecruitAnyHero(const CGTownInstance * t) const
 		return false;
 		return false;
 	if(cb->getHeroesInfo().size() >= ALLOWED_ROAMING_HEROES)
 	if(cb->getHeroesInfo().size() >= ALLOWED_ROAMING_HEROES)
 		return false;
 		return false;
-	if(cb->getHeroesInfo().size() >= VLC->settings()->getInteger(EGameSettings::INT_MAX_HEROES_ON_MAP_PER_PLAYER))
+	if(cb->getHeroesInfo().size() >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
 		return false;
 		return false;
 	if(!cb->getAvailableHeroes(t).size())
 	if(!cb->getAvailableHeroes(t).size())
 		return false;
 		return false;

+ 1 - 1
AI/Nullkiller/AIUtility.cpp

@@ -445,7 +445,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
 	case Obj::MAGIC_WELL:
 	case Obj::MAGIC_WELL:
 		return h->mana < h->manaLimit();
 		return h->mana < h->manaLimit();
 	case Obj::PRISON:
 	case Obj::PRISON:
-		return ai->cb->getHeroesInfo().size() < VLC->settings()->getInteger(EGameSettings::INT_MAX_HEROES_ON_MAP_PER_PLAYER);
+		return ai->cb->getHeroesInfo().size() < VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP);
 	case Obj::TAVERN:
 	case Obj::TAVERN:
 	case Obj::EYE_OF_MAGI:
 	case Obj::EYE_OF_MAGI:
 	case Obj::BOAT:
 	case Obj::BOAT:

+ 3 - 3
AI/VCAI/VCAI.cpp

@@ -1318,7 +1318,7 @@ bool VCAI::canRecruitAnyHero(const CGTownInstance * t) const
 		return false;
 		return false;
 	if(cb->getHeroesInfo().size() >= ALLOWED_ROAMING_HEROES)
 	if(cb->getHeroesInfo().size() >= ALLOWED_ROAMING_HEROES)
 		return false;
 		return false;
-	if(cb->getHeroesInfo().size() >= VLC->settings()->getInteger(EGameSettings::INT_MAX_HEROES_ON_MAP_PER_PLAYER))
+	if(cb->getHeroesInfo().size() >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
 		return false;
 		return false;
 	if(!cb->getAvailableHeroes(t).size())
 	if(!cb->getAvailableHeroes(t).size())
 		return false;
 		return false;
@@ -2851,12 +2851,12 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
 	case Obj::MAGIC_WELL:
 	case Obj::MAGIC_WELL:
 		return h->mana < h->manaLimit();
 		return h->mana < h->manaLimit();
 	case Obj::PRISON:
 	case Obj::PRISON:
-		return ai->myCb->getHeroesInfo().size() < VLC->settings()->getInteger(EGameSettings::INT_MAX_HEROES_ON_MAP_PER_PLAYER);
+		return ai->myCb->getHeroesInfo().size() < VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP);
 	case Obj::TAVERN:
 	case Obj::TAVERN:
 	{
 	{
 		//TODO: make AI actually recruit heroes
 		//TODO: make AI actually recruit heroes
 		//TODO: only on request
 		//TODO: only on request
-		if(ai->myCb->getHeroesInfo().size() >= VLC->settings()->getInteger(EGameSettings::INT_MAX_HEROES_ON_MAP_PER_PLAYER))
+		if(ai->myCb->getHeroesInfo().size() >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
 			return false;
 			return false;
 		else if(ai->ah->freeGold() < GameConstants::HERO_GOLD_COST)
 		else if(ai->ah->freeGold() < GameConstants::HERO_GOLD_COST)
 			return false;
 			return false;

+ 1 - 1
client/adventureMap/CList.cpp

@@ -284,7 +284,7 @@ std::shared_ptr<CIntObject> CTownList::CTownItem::genSelection()
 
 
 void CTownList::CTownItem::update()
 void CTownList::CTownItem::update()
 {
 {
-	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->builded >= CGI->settings()->getInteger(EGameSettings::INT_MAX_BUILDING_PER_TURN)];
+	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->builded >= CGI->settings()->getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
 
 
 	picture->setFrame(iconIndex + 2);
 	picture->setFrame(iconIndex + 2);
 	redraw();
 	redraw();

+ 1 - 1
client/widgets/MiscWidgets.cpp

@@ -329,7 +329,7 @@ void CTownTooltip::init(const InfoAboutTown & town)
 
 
 	assert(town.tType);
 	assert(town.tType);
 
 
-	size_t iconIndex = town.tType->clientInfo.icons[town.fortLevel > 0][town.built >= CGI->settings()->getInteger(EGameSettings::INT_MAX_BUILDING_PER_TURN)];
+	size_t iconIndex = town.tType->clientInfo.icons[town.fortLevel > 0][town.built >= CGI->settings()->getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
 
 
 	build = std::make_shared<CAnimImage>("itpt", iconIndex, 0, 3, 2);
 	build = std::make_shared<CAnimImage>("itpt", iconIndex, 0, 3, 2);
 
 

+ 2 - 2
client/windows/CCastleInterface.cpp

@@ -408,7 +408,7 @@ void CHeroGSlot::clickLeft(tribool down, bool previousState)
 			bool allow = true;
 			bool allow = true;
 			if(upg) //moving hero out of town - check if it is allowed
 			if(upg) //moving hero out of town - check if it is allowed
 			{
 			{
-				if(!hero && LOCPLINT->cb->howManyHeroes(false) >= CGI->settings()->getInteger(EGameSettings::INT_MAX_HEROES_ON_MAP_PER_PLAYER))
+				if(!hero && LOCPLINT->cb->howManyHeroes(false) >= CGI->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
 				{
 				{
 					std::string tmp = CGI->generaltexth->allTexts[18]; //You already have %d adventuring heroes under your command.
 					std::string tmp = CGI->generaltexth->allTexts[18]; //You already have %d adventuring heroes under your command.
 					boost::algorithm::replace_first(tmp,"%d",std::to_string(LOCPLINT->cb->howManyHeroes(false)));
 					boost::algorithm::replace_first(tmp,"%d",std::to_string(LOCPLINT->cb->howManyHeroes(false)));
@@ -1270,7 +1270,7 @@ void CCastleInterface::removeBuilding(BuildingID bid)
 void CCastleInterface::recreateIcons()
 void CCastleInterface::recreateIcons()
 {
 {
 	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
 	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
-	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->builded >= CGI->settings()->getInteger(EGameSettings::INT_MAX_BUILDING_PER_TURN)];
+	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->builded >= CGI->settings()->getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
 
 
 	icon->setFrame(iconIndex);
 	icon->setFrame(iconIndex);
 	TResources townIncome = town->dailyIncome();
 	TResources townIncome = town->dailyIncome();

+ 1 - 1
client/windows/CKingdomInterface.cpp

@@ -779,7 +779,7 @@ CTownItem::CTownItem(const CGTownInstance * Town)
 	garr = std::make_shared<CGarrisonInt>(313, 3, 4, Point(232,0), town->getUpperArmy(), town->visitingHero, true, true, true);
 	garr = std::make_shared<CGarrisonInt>(313, 3, 4, Point(232,0), town->getUpperArmy(), town->visitingHero, true, true, true);
 	heroes = std::make_shared<HeroSlots>(town, Point(244,6), Point(475,6), garr, false);
 	heroes = std::make_shared<HeroSlots>(town, Point(244,6), Point(475,6), garr, false);
 
 
-	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->builded >= CGI->settings()->getInteger(EGameSettings::INT_MAX_BUILDING_PER_TURN)];
+	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->builded >= CGI->settings()->getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
 
 
 	picture = std::make_shared<CAnimImage>("ITPT", iconIndex, 0, 5, 6);
 	picture = std::make_shared<CAnimImage>("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);

+ 2 - 2
client/windows/GUIClasses.cpp

@@ -468,13 +468,13 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj)
 		recruit->addHoverText(CButton::NORMAL, CGI->generaltexth->tavernInfo[0]); //Cannot afford a Hero
 		recruit->addHoverText(CButton::NORMAL, CGI->generaltexth->tavernInfo[0]); //Cannot afford a Hero
 		recruit->block(true);
 		recruit->block(true);
 	}
 	}
-	else if(LOCPLINT->cb->howManyHeroes(true) >= CGI->settings()->getInteger(EGameSettings::INT_MAX_HEROES_AVAILABLE_PER_PLAYER))
+	else if(LOCPLINT->cb->howManyHeroes(true) >= CGI->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP))
 	{
 	{
 		//Cannot recruit. You already have %d Heroes.
 		//Cannot recruit. You already have %d Heroes.
 		recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(true)));
 		recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(true)));
 		recruit->block(true);
 		recruit->block(true);
 	}
 	}
-	else if(LOCPLINT->cb->howManyHeroes(false) >= CGI->settings()->getInteger(EGameSettings::INT_MAX_HEROES_ON_MAP_PER_PLAYER))
+	else if(LOCPLINT->cb->howManyHeroes(false) >= CGI->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
 	{
 	{
 		//Cannot recruit. You already have %d Heroes.
 		//Cannot recruit. You already have %d Heroes.
 		recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(false)));
 		recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(false)));

+ 74 - 27
config/gameConfig.json

@@ -108,6 +108,7 @@
 	
 	
 	"settings":
 	"settings":
 	{
 	{
+		// Number of entries of each type to load from Heroes III text files
 		"textData" :
 		"textData" :
 		{
 		{
 			"heroClass"  : 18,
 			"heroClass"  : 18,
@@ -123,33 +124,79 @@
 			"mapVersion" : 28 // max supported version, SoD
 			"mapVersion" : 28 // max supported version, SoD
 		},
 		},
 
 
-		"hardcodedFeatures" :
+		"heroes" :
 		{
 		{
-			"CREEP_SIZE": 4000,
-			"WEEKLY_GROWTH_PERCENT" : 10,
-			"NEUTRAL_STACK_EXP_DAILY" : 500,
-			"MAX_BUILDING_PER_TURN" : 1,
-			"DWELLINGS_ACCUMULATE_CREATURES" : false,
-			"ALL_CREATURES_GET_DOUBLE_MONTHS" : false,
-			"NEGATIVE_LUCK" : false,
-			"MAX_HEROES_AVAILABLE_PER_PLAYER" : 16,
-			"MAX_HEROES_ON_MAP_PER_PLAYER" : 8,
-			"WINNING_HERO_WITH_NO_TROOPS_RETREATS": true,
-			"BLACK_MARKET_MONTHLY_ARTIFACTS_CHANGE": true,
-			"NO_RANDOM_SPECIAL_WEEKS_AND_MONTHS": false,
-			"ATTACK_POINT_DMG_MULTIPLIER": 0.05, //every 1 attack point damage influence in battle when attack points > defense points during creature attack
-			"ATTACK_POINTS_DMG_MULTIPLIER_CAP": 4.0, //limit of damage increase that can be achieved by overpowering attack points
-			"DEFENSE_POINT_DMG_MULTIPLIER": 0.025, //every 1 defense point damage influence in battle when defense points > attack points during creature attack
-			"DEFENSE_POINTS_DMG_MULTIPLIER_CAP": 0.7, //limit of damage reduction that can be achieved by overpowering defense points
-			//chances for new hero units count - technically random number 1-100, first element in list below generated number sets count, if none then result is number of elements + 1
-			"HERO_STARTING_ARMY_STACKS_COUNT_CHANCES": [10, 80], //example: [10,80] gives 10% chance for 1 stack, 70% for 2, 20% for 3. Additionally you can add -1 as last special value to start counting from 0 and allowing empty armies
-			"DEFAULT_BUILDING_SET_DWELLING_CHANCES": [100, 50] //percent chance for dwellings to appear - for example [30,10,0,100] means 30% chance for 1st level dwelling, 10% for 2nd, 0% for 3rd, and guaranteed 4th level, no 5th, no 6th, no 7th
+			// number of heroes that player can have active on map at the same time
+			"perPlayerOnMapCap" : 8,
+			// number of heroes that player can have in total, including garrisoned
+			"perPlayerTotalCap" : 16,
+			// if hero wins a battle without any non-summoned troops left, he will retreat and become available in tavern instead of being lost
+			"retreatOnWinWithoutTroops" : true,
+			// Chances for a hero with default army to receive corresponding stack out of his predefined starting troops
+			"startingStackChances": [ 100, 88, 25]
 		},
 		},
+
+		"towns":
+		{
+			// How many new building can be built in a town per day
+			"buildingsPerTurnCap" : 1,
+			// Chances for a town with default buildings to receive corresponding dwelling level built in start
+			"startingDwellingChances": [100, 50] 
+		},
+
+		"combat":
+		{
+			//defines dice size of a morale roll, based on creature's morale
+			"goodMoraleDice" : [ 24, 12, 8 ],
+			"badMoraleDice" : [ 12, 6, 4],
+
+			//defines dice size of a luck roll, based on creature's luck
+			"goodLuckDice" : [ 24, 12, 8 ],
+			"badLuckDice" : [],
+			
+			//every 1 attack point damage influence in battle when attack points > defense points during creature attack
+			"attackPointDamageFactor": 0.05, 
+			//limit of damage increase that can be achieved by overpowering attack points
+			"attackPointDamageFactorCap": 4.0, 
+			//every 1 defense point damage influence in battle when defense points > attack points during creature attack
+			"defensePointDamageFactor": 0.025, 
+			//limit of damage reduction that can be achieved by overpowering defense points
+			"defensePointDamageFactorCap": 0.7
+		},	
+
+		"creatures":
+		{
+			// creatures on map will grow by specific percentage each week
+			"weeklyGrowthPercent" : 10,
+			// creatures on map will not grow if their quantity is greater than this value
+			"weeklyGrowthCap" : 4000,
+			// if stack experience is on, creatures on map will get specified amount of experience daily 
+			"dailyStackExperience" : 100,
+			// if set to true, double growth, plague and creature weeks can happen randomly. Has no effect on "Deity of Fire"
+			"allowRandomSpecialWeeks" : true,
+			// if set to true, every creature can get double growth month, ignoring predefined list
+			"allowAllForDoubleMonth" : false,
+		},
+		
+		"dwellings" :
+		{
+			// If true, neutral dwellings will accumulate creatures 
+			"accumulateWhenNeutral" : false,
+			// If true, dwellings owned by players will accumulate creatures 
+			"accumulateWhenOwned" : false
+		},
+		
+		"markets" : 
+		{
+			// period between restocking of "Black Market" object found on adventure map
+			"blackMarketRestockPeriod" : 0,
+		},
+
 		"modules":
 		"modules":
 		{
 		{
-			"STACK_EXPERIENCE": false,
-			"STACK_ARTIFACTS": false,
-			"COMMANDERS": false
+			"stackExperience": false,
+			"stackArtifact": false,
+			"commanders": false
 		},
 		},
 		
 		
 		"bonuses" : 
 		"bonuses" : 
@@ -158,18 +205,18 @@
 			{
 			{
 				"spellDamage" : 
 				"spellDamage" : 
 				{
 				{
-					"type" : "SPELL_DAMAGE", //Default spell damage
+					"type" : "SPELL_DAMAGE",
 					"val" : 100,
 					"val" : 100,
 					"valueType" : "BASE_NUMBER"
 					"valueType" : "BASE_NUMBER"
 				},
 				},
-				"wisdomBase" : 
+				"wisdom" : 
 				{
 				{
 					"type" : "MAX_LEARNABLE_SPELL_LEVEL", //Hero can always learn level 1 and 2 spells
 					"type" : "MAX_LEARNABLE_SPELL_LEVEL", //Hero can always learn level 1 and 2 spells
 					"val" : 2,
 					"val" : 2,
 					"valueType" : "BASE_NUMBER"
 					"valueType" : "BASE_NUMBER"
 				}
 				}
 			},
 			},
-			"heroes":
+			"perHero":
 			{
 			{
 				"manaRegeneration" :
 				"manaRegeneration" :
 				{
 				{
@@ -191,7 +238,7 @@
 				},
 				},
 				"manaPerKnowledge" :
 				"manaPerKnowledge" :
 				{
 				{
-					"type" : "MANA_PER_KNOWLEDGE", //10 knowledge to 100 mana is default
+					"type" : "MANA_PER_KNOWLEDGE", //10 mana per knowledge
 					"val" : 10,
 					"val" : 10,
 					"valueType" : "BASE_NUMBER"
 					"valueType" : "BASE_NUMBER"
 				},
 				},

+ 1 - 1
lib/CGameInfoCallback.cpp

@@ -604,7 +604,7 @@ EBuildingState::EBuildingState CGameInfoCallback::canBuildStructure( const CGTow
 	if (!t->genBuildingRequirements(ID).test(buildTest))
 	if (!t->genBuildingRequirements(ID).test(buildTest))
 		return EBuildingState::PREREQUIRES;
 		return EBuildingState::PREREQUIRES;
 
 
-	if(t->builded >= VLC->settings()->getInteger(EGameSettings::INT_MAX_BUILDING_PER_TURN))
+	if(t->builded >= VLC->settings()->getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP))
 		return EBuildingState::CANT_BUILD_TODAY; //building limit
 		return EBuildingState::CANT_BUILD_TODAY; //building limit
 
 
 	//checking resources
 	//checking resources

+ 2 - 2
lib/CGameState.cpp

@@ -949,7 +949,7 @@ void CGameState::checkMapChecksum()
 
 
 void CGameState::initGlobalBonuses()
 void CGameState::initGlobalBonuses()
 {
 {
-	const JsonNode & baseBonuses = VLC->settings()->getValue(EGameSettings::BONUSES_LIST_GLOBAL);
+	const JsonNode & baseBonuses = VLC->settings()->getValue(EGameSettings::BONUSES_GLOBAL);
 	logGlobal->debug("\tLoading global bonuses");
 	logGlobal->debug("\tLoading global bonuses");
 	for(const auto & b : baseBonuses.Struct())
 	for(const auto & b : baseBonuses.Struct())
 	{
 	{
@@ -1748,7 +1748,7 @@ void CGameState::initTowns()
 			if(vti->tempOwner != PlayerColor::NEUTRAL)
 			if(vti->tempOwner != PlayerColor::NEUTRAL)
 				vti->builtBuildings.insert(BuildingID::TAVERN);
 				vti->builtBuildings.insert(BuildingID::TAVERN);
 
 
-			auto definesBuildingsChances = VLC->settings()->getValue(EGameSettings::VECTOR_DEFAULT_BUILDING_SET_DWELLING_CHANCES).convertTo<std::vector<int32_t>>();
+			auto definesBuildingsChances = VLC->settings()->getVector(EGameSettings::TOWNS_STARTING_DWELLING_CHANCES);
 
 
 			BuildingID basicDwellings[] = { BuildingID::DWELL_FIRST, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3, BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7 };
 			BuildingID basicDwellings[] = { BuildingID::DWELL_FIRST, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3, BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7 };
 
 

+ 59 - 49
lib/GameSettings.cpp

@@ -9,7 +9,6 @@
  */
  */
 #include "StdInc.h"
 #include "StdInc.h"
 #include "GameSettings.h"
 #include "GameSettings.h"
-
 #include "JsonNode.h"
 #include "JsonNode.h"
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
@@ -29,61 +28,72 @@ double IGameSettings::getDouble(EGameSettings option) const
 	return getValue(option).Float();
 	return getValue(option).Float();
 }
 }
 
 
-GameSettings::GameSettings():
-	gameSettings(static_cast<size_t>(EGameSettings::OPTIONS_COUNT))
+std::vector<int> IGameSettings::getVector(EGameSettings option) const
 {
 {
+	return getValue(option).convertTo<std::vector<int>>();
 }
 }
 
 
-void GameSettings::load(const JsonNode & input)
+GameSettings::GameSettings()
+	: gameSettings(static_cast<size_t>(EGameSettings::OPTIONS_COUNT))
 {
 {
-	static std::array<std::array<std::string, 2>, 34> optionPath =
-	{ {
-		{"textData", "heroClass"},
-		{"textData", "artifact"},
-		{"textData", "creature"},
-		{"textData", "faction"},
-		{"textData", "hero"},
-		{"textData", "spell"},
-		{"textData", "object"},
-		{"textData", "terrain"},
-		{"textData", "river"},
-		{"textData", "road"},
-		{"textData", "mapVersion"},
-		{"modules", "STACK_EXPERIENCE"},
-		{"modules", "STACK_ARTIFACTS"},
-		{"modules", "COMMANDERS"},
-		{"hardcodedFeatures", "DWELLINGS_ACCUMULATE_CREATURES"},
-		{"hardcodedFeatures", "NO_RANDOM_SPECIAL_WEEKS_AND_MONTHS"},
-		{"hardcodedFeatures", "BLACK_MARKET_MONTHLY_ARTIFACTS_CHANGE"},
-		{"hardcodedFeatures", "WINNING_HERO_WITH_NO_TROOPS_RETREATS"},
-		{"hardcodedFeatures", "ALL_CREATURES_GET_DOUBLE_MONTHS"},
-		{"hardcodedFeatures", "NEGATIVE_LUCK"},
-		{"hardcodedFeatures", "CREEP_SIZE"},
-		{"hardcodedFeatures", "WEEKLY_GROWTH"},
-		{"hardcodedFeatures", "NEUTRAL_STACK_EXP"},
-		{"hardcodedFeatures", "MAX_BUILDING_PER_TURN"},
-		{"hardcodedFeatures", "MAX_HEROES_AVAILABLE_PER_PLAYER"},
-		{"hardcodedFeatures", "MAX_HEROES_ON_MAP_PER_PLAYER"},
-		{"hardcodedFeatures", "ATTACK_POINT_DMG_MULTIPLIER"},
-		{"hardcodedFeatures", "ATTACK_POINTS_DMG_MULTIPLIER_CAP"},
-		{"hardcodedFeatures", "DEFENSE_POINT_DMG_MULTIPLIER"},
-		{"hardcodedFeatures", "DEFENSE_POINTS_DMG_MULTIPLIER_CAP"},
-		{"hardcodedFeatures", "HERO_STARTING_ARMY_STACKS_COUNT_CHANCES"},
-		{"hardcodedFeatures", "DEFAULT_BUILDING_SET_DWELLING_CHANCES"},
-		{"bonuses", "global"},
-		{"bonuses", "heroes"},
-	} };
-	static_assert(static_cast<size_t>(EGameSettings::OPTIONS_COUNT) == optionPath.size(), "Missing option path");
+}
 
 
-	for (size_t i = 0; i < optionPath.size(); ++i)
+void GameSettings::load(const JsonNode & input)
+{
+	struct SettingOption
 	{
 	{
-		const std::string & group = optionPath[i][0];
-		const std::string & key = optionPath[i][1];
+		EGameSettings setting;
+		std::string group;
+		std::string key;
+	};
+
+	static const std::vector<SettingOption> optionPath = {
+		{EGameSettings::BONUSES_GLOBAL,                         "bonuses",   "global"                     },
+		{EGameSettings::BONUSES_PER_HERO,                       "bonuses",   "perHero"                    },
+		{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::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::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL,      "dwellings", "accumulateWhenNeutral"      },
+		{EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED,        "dwellings", "accumulateWhenOwned"        },
+		{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::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::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_MAP_VERSION,                      "textData",  "mapVersion"                 },
+		{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"    },
+	};
 
 
-		const JsonNode & optionValue = input[group][key];
+	for(const auto & option : optionPath)
+	{
+		const JsonNode & optionValue = input[option.group][option.key];
 
 
-		if (!optionValue.isNull())
-			gameSettings[i] = optionValue;
+		if(!optionValue.isNull())
+			gameSettings[static_cast<size_t>(option.setting)] = optionValue;
 	}
 	}
 }
 }
 
 
@@ -92,8 +102,8 @@ const JsonNode & GameSettings::getValue(EGameSettings option) const
 	assert(option < EGameSettings::OPTIONS_COUNT);
 	assert(option < EGameSettings::OPTIONS_COUNT);
 	auto index = static_cast<size_t>(option);
 	auto index = static_cast<size_t>(option);
 
 
+	assert(!gameSettings[index].isNull());
 	return gameSettings[index];
 	return gameSettings[index];
 }
 }
 
 
-
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 32 - 33
lib/GameSettings.h

@@ -15,44 +15,44 @@ class JsonNode;
 
 
 enum class EGameSettings
 enum class EGameSettings
 {
 {
-	TEXTS_HERO_CLASS,
+	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,
+	HEROES_PER_PLAYER_ON_MAP_CAP,
+	HEROES_PER_PLAYER_TOTAL_CAP,
+	HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS,
+	HEROES_STARTING_STACKS_CHANCES,
+	MARKETS_BLACK_MARKET_RESTOCK_PERIOD,
+	MODULE_COMMANDERS,
+	MODULE_STACK_ARTIFACT,
+	MODULE_STACK_EXPERIENCE,
 	TEXTS_ARTIFACT,
 	TEXTS_ARTIFACT,
 	TEXTS_CREATURE,
 	TEXTS_CREATURE,
 	TEXTS_FACTION,
 	TEXTS_FACTION,
 	TEXTS_HERO,
 	TEXTS_HERO,
-	TEXTS_SPELL,
+	TEXTS_HERO_CLASS,
+	TEXTS_MAP_VERSION,
 	TEXTS_OBJECT,
 	TEXTS_OBJECT,
-	TEXTS_TERRAIN,
 	TEXTS_RIVER,
 	TEXTS_RIVER,
 	TEXTS_ROAD,
 	TEXTS_ROAD,
-	TEXTS_MAP_VERSION,
-
-	MODULE_STACK_EXPERIENCE,
-	MODULE_STACK_ARTIFACT,
-	MODULE_COMMANDERS,
-
-	BOOL_WINNING_HERO_WITH_NO_TROOPS_RETREATS,
-	BOOL_BLACK_MARKET_MONTHLY_ARTIFACTS_CHANGE,
-	BOOL_NO_RANDOM_SPECIAL_WEEKS_AND_MONTHS,
-	BOOL_DWELLINGS_ACCUMULATE_CREATURES,
-	BOOL_ALL_CREATURES_GET_DOUBLE_MONTHS,
-	BOOL_NEGATIVE_LUCK,
-
-	INT_CREEP_SIZE,
-	INT_WEEKLY_GROWTH,
-	INT_NEUTRAL_STACK_EXP,
-	INT_MAX_BUILDING_PER_TURN,
-	INT_MAX_HEROES_AVAILABLE_PER_PLAYER,
-	INT_MAX_HEROES_ON_MAP_PER_PLAYER,
-	DOUBLE_ATTACK_POINT_DMG_MULTIPLIER,
-	DOUBLE_ATTACK_POINTS_DMG_MULTIPLIER_CAP,
-	DOUBLE_DEFENSE_POINT_DMG_MULTIPLIER,
-	DOUBLE_DEFENSE_POINTS_DMG_MULTIPLIER_CAP,
-	VECTOR_HERO_STARTING_ARMY_STACKS_COUNT_CHANCES,
-	VECTOR_DEFAULT_BUILDING_SET_DWELLING_CHANCES,
-
-	BONUSES_LIST_GLOBAL,
-	BONUSES_LIST_HERO,
+	TEXTS_SPELL,
+	TEXTS_TERRAIN,
+	TOWNS_BUILDINGS_PER_TURN_CAP,
+	TOWNS_STARTING_DWELLING_CHANCES,
 
 
 	OPTIONS_COUNT
 	OPTIONS_COUNT
 };
 };
@@ -65,6 +65,7 @@ public:
 	bool getBoolean(EGameSettings option) const;
 	bool getBoolean(EGameSettings option) const;
 	int64_t getInteger(EGameSettings option) const;
 	int64_t getInteger(EGameSettings option) const;
 	double getDouble(EGameSettings option) const;
 	double getDouble(EGameSettings option) const;
+	std::vector<int> getVector(EGameSettings option) const;
 };
 };
 
 
 class DLL_LINKAGE GameSettings final : public IGameSettings
 class DLL_LINKAGE GameSettings final : public IGameSettings
@@ -73,9 +74,7 @@ class DLL_LINKAGE GameSettings final : public IGameSettings
 
 
 public:
 public:
 	GameSettings();
 	GameSettings();
-
 	void load(const JsonNode & input);
 	void load(const JsonNode & input);
-
 	const JsonNode & getValue(EGameSettings option) const override;
 	const JsonNode & getValue(EGameSettings option) const override;
 
 
 	template<typename Handler>
 	template<typename Handler>

+ 1 - 0
lib/IGameCallback.cpp

@@ -30,6 +30,7 @@
 #include "CGameState.h"
 #include "CGameState.h"
 #include "mapping/CMap.h"
 #include "mapping/CMap.h"
 #include "CPlayerState.h"
 #include "CPlayerState.h"
+#include "GameSettings.h"
 #include "ScriptHandler.h"
 #include "ScriptHandler.h"
 #include "RoadHandler.h"
 #include "RoadHandler.h"
 #include "RiverHandler.h"
 #include "RiverHandler.h"

+ 1 - 0
lib/VCMI_Lib.h

@@ -128,6 +128,7 @@ public:
 		}
 		}
 #endif
 #endif
 
 
+		h & settingsHandler;
 		h & heroh;
 		h & heroh;
 		h & arth;
 		h & arth;
 		h & creh;
 		h & creh;

+ 4 - 4
lib/battle/DamageCalculator.cpp

@@ -183,8 +183,8 @@ double DamageCalculator::getAttackSkillFactor() const
 
 
 	if(attackAdvantage > 0)
 	if(attackAdvantage > 0)
 	{
 	{
-		const double attackMultiplier = VLC->settings()->getDouble(EGameSettings::DOUBLE_ATTACK_POINT_DMG_MULTIPLIER);
-		const double attackMultiplierCap = VLC->settings()->getDouble(EGameSettings::DOUBLE_ATTACK_POINTS_DMG_MULTIPLIER_CAP);
+		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);
 		const double attackFactor = std::min(attackMultiplier * attackAdvantage, attackMultiplierCap);
 		const double attackFactor = std::min(attackMultiplier * attackAdvantage, attackMultiplierCap);
 
 
 		return attackFactor;
 		return attackFactor;
@@ -269,8 +269,8 @@ double DamageCalculator::getDefenseSkillFactor() const
 	//bonus from attack/defense skills
 	//bonus from attack/defense skills
 	if(defenseAdvantage > 0) //decreasing dmg
 	if(defenseAdvantage > 0) //decreasing dmg
 	{
 	{
-		const double defenseMultiplier = VLC->settings()->getDouble(EGameSettings::DOUBLE_DEFENSE_POINT_DMG_MULTIPLIER);
-		const double defenseMultiplierCap = VLC->settings()->getDouble(EGameSettings::DOUBLE_DEFENSE_POINTS_DMG_MULTIPLIER_CAP);
+		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);
 
 
 		const double dec = std::min(defenseMultiplier * defenseAdvantage, defenseMultiplierCap);
 		const double dec = std::min(defenseMultiplier * defenseAdvantage, defenseMultiplierCap);
 		return dec;
 		return dec;

+ 8 - 19
lib/mapObjects/CGHeroInstance.cpp

@@ -313,7 +313,7 @@ void CGHeroInstance::initHero(CRandomGenerator & rand)
 	// are not attached to global bonus node but need access to some global bonuses
 	// are not attached to global bonus node but need access to some global bonuses
 	// e.g. MANA_PER_KNOWLEDGE for correct preview and initial state after recruit	for(const auto & ob : VLC->modh->heroBaseBonuses)
 	// e.g. MANA_PER_KNOWLEDGE 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
 	// or MOVEMENT to compute initial movement before recruiting is finished
-	const JsonNode & baseBonuses = VLC->settings()->getValue(EGameSettings::BONUSES_LIST_HERO);
+	const JsonNode & baseBonuses = VLC->settings()->getValue(EGameSettings::BONUSES_PER_HERO);
 	for(const auto & b : baseBonuses.Struct())
 	for(const auto & b : baseBonuses.Struct())
 	{
 	{
 		auto bonus = JsonUtils::parseBonus(b.second);
 		auto bonus = JsonUtils::parseBonus(b.second);
@@ -341,27 +341,16 @@ void CGHeroInstance::initArmy(CRandomGenerator & rand, IArmyDescriptor * dst)
 
 
 	int warMachinesGiven = 0;
 	int warMachinesGiven = 0;
 
 
-	auto stacksCountChances = VLC->settings()->getValue(EGameSettings::VECTOR_HERO_STARTING_ARMY_STACKS_COUNT_CHANCES).convertTo<std::vector<int32_t>>();
-
-	const int zeroStacksAllowingValue = -1;
-	bool allowZeroStacksArmy = !stacksCountChances.empty() && stacksCountChances.back() == zeroStacksAllowingValue;
-	if(allowZeroStacksArmy)
-		stacksCountChances.pop_back();
-
+	auto stacksCountChances = VLC->settings()->getVector(EGameSettings::HEROES_STARTING_STACKS_CHANCES);
 	int stacksCountInitRandomNumber = rand.nextInt(1, 100);
 	int stacksCountInitRandomNumber = rand.nextInt(1, 100);
 
 
-	auto stacksCountElementIndex = vstd::find_pos_if(stacksCountChances, [stacksCountInitRandomNumber](int element){ return stacksCountInitRandomNumber < element; });
-	if(stacksCountElementIndex == -1)
-		stacksCountElementIndex = stacksCountChances.size();
-
-	int howManyStacks = stacksCountElementIndex;
-	if(!allowZeroStacksArmy)
-		howManyStacks++;
+	size_t maxStacksCount = std::min(stacksCountChances.size(), type->initialArmy.size());
 
 
-	vstd::amin(howManyStacks, type->initialArmy.size());
-
-	for(int stackNo=0; stackNo < howManyStacks; stackNo++)
+	for(int stackNo=0; stackNo < maxStacksCount; stackNo++)
 	{
 	{
+		if (stacksCountInitRandomNumber > stacksCountChances[stackNo])
+			continue;
+
 		auto & stack = type->initialArmy[stackNo];
 		auto & stack = type->initialArmy[stackNo];
 
 
 		int count = rand.nextInt(stack.minAmount, stack.maxAmount);
 		int count = rand.nextInt(stack.minAmount, stack.maxAmount);
@@ -438,7 +427,7 @@ void CGHeroInstance::onHeroVisit(const CGHeroInstance * h) const
 	{
 	{
 		int txt_id;
 		int txt_id;
 
 
-		if (cb->getHeroCount(h->tempOwner, false) < VLC->settings()->getInteger(EGameSettings::INT_MAX_HEROES_ON_MAP_PER_PLAYER))//free hero slot
+		if (cb->getHeroCount(h->tempOwner, false) < VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))//free hero slot
 		{
 		{
 			//update hero parameters
 			//update hero parameters
 			SetMovePoints smp;
 			SetMovePoints smp;

+ 4 - 2
lib/mapObjects/CGMarket.cpp

@@ -277,10 +277,12 @@ std::vector<int> CGBlackMarket::availableItemsIds(EMarketMode::EMarketMode mode)
 
 
 void CGBlackMarket::newTurn(CRandomGenerator & rand) const
 void CGBlackMarket::newTurn(CRandomGenerator & rand) const
 {
 {
-	if(!VLC->settings()->getBoolean(EGameSettings::BOOL_BLACK_MARKET_MONTHLY_ARTIFACTS_CHANGE)) //check if feature changing OH3 behavior is enabled
+	int resetPeriod = VLC->settings()->getInteger(EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD);
+
+	if(resetPeriod == 0) //check if feature changing OH3 behavior is enabled
 		return;
 		return;
 
 
-	if(cb->getDate(Date::DAY_OF_MONTH) != 1) //new month
+	if (((cb->getDate(Date::DAY)-1) % resetPeriod) != 0)
 		return;
 		return;
 
 
 	SetAvailableArtifacts saa;
 	SetAvailableArtifacts saa;

+ 8 - 1
lib/mapObjects/CGTownInstance.cpp

@@ -255,9 +255,16 @@ void CGDwelling::newTurn(CRandomGenerator & rand) const
 	{
 	{
 		if(!creatures[i].second.empty())
 		if(!creatures[i].second.empty())
 		{
 		{
+			bool creaturesAccumulate = false;
+
+			if (tempOwner.isValidPlayer())
+				creaturesAccumulate = VLC->settings()->getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED);
+			else
+				creaturesAccumulate = VLC->settings()->getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL);
+
 			CCreature *cre = VLC->creh->objects[creatures[i].second[0]];
 			CCreature *cre = VLC->creh->objects[creatures[i].second[0]];
 			TQuantity amount = cre->growth * (1 + cre->valOfBonuses(Bonus::CREATURE_GROWTH_PERCENT)/100) + cre->valOfBonuses(Bonus::CREATURE_GROWTH);
 			TQuantity amount = cre->growth * (1 + cre->valOfBonuses(Bonus::CREATURE_GROWTH_PERCENT)/100) + cre->valOfBonuses(Bonus::CREATURE_GROWTH);
-			if (VLC->settings()->getBoolean(EGameSettings::BOOL_DWELLINGS_ACCUMULATE_CREATURES) && ID != Obj::REFUGEE_CAMP) //camp should not try to accumulate different kinds of creatures
+			if (creaturesAccumulate && ID != Obj::REFUGEE_CAMP) //camp should not try to accumulate different kinds of creatures
 				sac.creatures[i].first += amount;
 				sac.creatures[i].first += amount;
 			else
 			else
 				sac.creatures[i].first = amount;
 				sac.creatures[i].first = amount;

+ 4 - 4
lib/mapObjects/MiscObjects.cpp

@@ -233,15 +233,15 @@ void CGCreature::newTurn(CRandomGenerator & rand) const
 {//Works only for stacks of single type of size up to 2 millions
 {//Works only for stacks of single type of size up to 2 millions
 	if (!notGrowingTeam)
 	if (!notGrowingTeam)
 	{
 	{
-		if (stacks.begin()->second->count < VLC->settings()->getInteger(EGameSettings::INT_CREEP_SIZE) && cb->getDate(Date::DAY_OF_WEEK) == 1 && cb->getDate(Date::DAY) > 1)
+		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)
 		{
 		{
-			ui32 power = static_cast<ui32>(temppower * (100 + VLC->settings()->getInteger(EGameSettings::INT_WEEKLY_GROWTH)) / 100);
-			cb->setObjProperty(id, ObjProperty::MONSTER_COUNT, std::min<uint32_t>(power / 1000, VLC->settings()->getInteger(EGameSettings::INT_CREEP_SIZE))); //set new amount
+			ui32 power = static_cast<ui32>(temppower * (100 + VLC->settings()->getInteger(EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT)) / 100);
+			cb->setObjProperty(id, ObjProperty::MONSTER_COUNT, std::min<uint32_t>(power / 1000, VLC->settings()->getInteger(EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT))); //set new amount
 			cb->setObjProperty(id, ObjProperty::MONSTER_POWER, power); //increase temppower
 			cb->setObjProperty(id, ObjProperty::MONSTER_POWER, power); //increase temppower
 		}
 		}
 	}
 	}
 	if (VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
 	if (VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
-		cb->setObjProperty(id, ObjProperty::MONSTER_EXP, VLC->settings()->getInteger(EGameSettings::INT_NEUTRAL_STACK_EXP)); //for testing purpose
+		cb->setObjProperty(id, ObjProperty::MONSTER_EXP, VLC->settings()->getInteger(EGameSettings::CREATURES_DAILY_STACK_EXPERIENCE)); //for testing purpose
 }
 }
 void CGCreature::setPropertyDer(ui8 what, ui32 val)
 void CGCreature::setPropertyDer(ui8 what, ui32 val)
 {
 {

+ 2 - 2
lib/serializer/CSerializer.h

@@ -14,8 +14,8 @@
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
-const ui32 SERIALIZATION_VERSION = 819;
-const ui32 MINIMAL_SERIALIZATION_VERSION = 819;
+const ui32 SERIALIZATION_VERSION = 820;
+const ui32 MINIMAL_SERIALIZATION_VERSION = 820;
 const std::string SAVEGAME_MAGIC = "VCMISVG";
 const std::string SAVEGAME_MAGIC = "VCMISVG";
 
 
 class CHero;
 class CHero;

+ 27 - 18
server/CGameHandler.cpp

@@ -988,7 +988,7 @@ void CGameHandler::battleAfterLevelUp(const BattleResult &result)
 		RemoveObject ro(finishingBattle->winnerHero->id);
 		RemoveObject ro(finishingBattle->winnerHero->id);
 		sendAndApply(&ro);
 		sendAndApply(&ro);
 
 
-		if (VLC->settings()->getBoolean(EGameSettings::BOOL_WINNING_HERO_WITH_NO_TROOPS_RETREATS))
+		if (VLC->settings()->getBoolean(EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS))
 		{
 		{
 			SetAvailableHeroes sah;
 			SetAvailableHeroes sah;
 			sah.player = finishingBattle->victor;
 			sah.player = finishingBattle->victor;
@@ -1026,17 +1026,22 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender,
 
 
 	const int attackerLuck = attacker->LuckVal();
 	const int attackerLuck = attacker->LuckVal();
 
 
-	if (attackerLuck > 0  && getRandomGenerator().nextInt(23) < attackerLuck)
+	if(attackerLuck > 0)
 	{
 	{
-		bat.flags |= BattleAttack::LUCKY;
+		auto diceSize = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_LUCK_DICE);
+		size_t diceIndex = std::min<size_t>(diceSize.size() - 1, attackerLuck);
+
+		if(diceSize.size() > 0 && getRandomGenerator().nextInt(1, diceSize[diceIndex]) == 1)
+			bat.flags |= BattleAttack::LUCKY;
 	}
 	}
 
 
-	if (VLC->settings()->getBoolean(EGameSettings::BOOL_NEGATIVE_LUCK)) // negative luck enabled
+	if(attackerLuck < 0)
 	{
 	{
-		if (attackerLuck < 0 && getRandomGenerator().nextInt(23) < abs(attackerLuck))
-		{
+		auto diceSize = VLC->settings()->getVector(EGameSettings::COMBAT_BAD_LUCK_DICE);
+		size_t diceIndex = std::min<size_t>(diceSize.size() - 1, -attackerLuck);
+
+		if(diceSize.size() > 0 && getRandomGenerator().nextInt(1, diceSize[diceIndex]) == 1)
 			bat.flags |= BattleAttack::UNLUCKY;
 			bat.flags |= BattleAttack::UNLUCKY;
-		}
 	}
 	}
 
 
 	if (getRandomGenerator().nextInt(99) < attacker->valOfBonuses(Bonus::DOUBLE_DAMAGE_CHANCE))
 	if (getRandomGenerator().nextInt(99) < attacker->valOfBonuses(Bonus::DOUBLE_DAMAGE_CHANCE))
@@ -1779,7 +1784,7 @@ void CGameHandler::newTurn()
 			n.specialWeek = NewTurn::DEITYOFFIRE;
 			n.specialWeek = NewTurn::DEITYOFFIRE;
 			n.creatureid = CreatureID::IMP;
 			n.creatureid = CreatureID::IMP;
 		}
 		}
-		else if(!VLC->settings()->getBoolean(EGameSettings::BOOL_NO_RANDOM_SPECIAL_WEEKS_AND_MONTHS))
+		else if(!VLC->settings()->getBoolean(EGameSettings::CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS))
 		{
 		{
 			int monthType = getRandomGenerator().nextInt(99);
 			int monthType = getRandomGenerator().nextInt(99);
 			if (newMonth) //new month
 			if (newMonth) //new month
@@ -1787,15 +1792,13 @@ void CGameHandler::newTurn()
 				if (monthType < 40) //double growth
 				if (monthType < 40) //double growth
 				{
 				{
 					n.specialWeek = NewTurn::DOUBLE_GROWTH;
 					n.specialWeek = NewTurn::DOUBLE_GROWTH;
-					if (VLC->settings()->getBoolean(EGameSettings::BOOL_ALL_CREATURES_GET_DOUBLE_MONTHS))
+					if (VLC->settings()->getBoolean(EGameSettings::CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH))
 					{
 					{
-						std::pair<int, CreatureID> newMonster(54, VLC->creh->pickRandomMonster(getRandomGenerator()));
-						n.creatureid = newMonster.second;
+						n.creatureid = VLC->creh->pickRandomMonster(getRandomGenerator());
 					}
 					}
 					else if (VLC->creh->doubledCreatures.size())
 					else if (VLC->creh->doubledCreatures.size())
 					{
 					{
-						const std::vector<CreatureID> doubledCreatures (VLC->creh->doubledCreatures.begin(), VLC->creh->doubledCreatures.end());
-						n.creatureid = *RandomGeneratorUtil::nextItem(doubledCreatures, getRandomGenerator());
+						n.creatureid = *RandomGeneratorUtil::nextItem(VLC->creh->doubledCreatures, getRandomGenerator());
 					}
 					}
 					else
 					else
 					{
 					{
@@ -3962,7 +3965,7 @@ bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocat
 			if(ArtifactUtils::checkSpellbookIsNeeded(hero, srcArtifact->artType->getId(), dst.slot))
 			if(ArtifactUtils::checkSpellbookIsNeeded(hero, srcArtifact->artType->getId(), dst.slot))
 				giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK);
 				giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK);
 		}
 		}
-		catch (boost::bad_get const &)
+		catch(const boost::bad_get &)
 		{
 		{
 			// object other than hero received an art - ignore
 			// object other than hero received an art - ignore
 		}
 		}
@@ -4366,8 +4369,8 @@ bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, PlayerColor pl
 //	if ((p->resources.at(Res::GOLD)<GOLD_NEEDED  && complain("Not enough gold for buying hero!"))
 //	if ((p->resources.at(Res::GOLD)<GOLD_NEEDED  && complain("Not enough gold for buying hero!"))
 //		|| (getHeroCount(player, false) >= GameConstants::MAX_HEROES_PER_PLAYER && complain("Cannot hire hero, only 8 wandering heroes are allowed!")))
 //		|| (getHeroCount(player, false) >= GameConstants::MAX_HEROES_PER_PLAYER && complain("Cannot hire hero, only 8 wandering heroes are allowed!")))
 	if ((p->resources.at(Res::GOLD) < GameConstants::HERO_GOLD_COST && complain("Not enough gold for buying hero!"))
 	if ((p->resources.at(Res::GOLD) < GameConstants::HERO_GOLD_COST && complain("Not enough gold for buying hero!"))
-		|| ((getHeroCount(player, false) >= VLC->settings()->getInteger(EGameSettings::INT_MAX_HEROES_ON_MAP_PER_PLAYER) && complain("Cannot hire hero, too many wandering heroes already!")))
-		|| ((getHeroCount(player, true) >= VLC->settings()->getInteger(EGameSettings::INT_MAX_HEROES_AVAILABLE_PER_PLAYER) && complain("Cannot hire hero, too many heroes garrizoned and wandering already!"))))
+		|| ((getHeroCount(player, false) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP) && complain("Cannot hire hero, too many wandering heroes already!")))
+		|| ((getHeroCount(player, true) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP) && complain("Cannot hire hero, too many heroes garrizoned and wandering already!"))))
 	{
 	{
 		return false;
 		return false;
 	}
 	}
@@ -6570,7 +6573,10 @@ void CGameHandler::runBattle()
 			int nextStackMorale = next->MoraleVal();
 			int nextStackMorale = next->MoraleVal();
 			if (nextStackMorale < 0)
 			if (nextStackMorale < 0)
 			{
 			{
-				if (getRandomGenerator().nextInt(23) < -2 * nextStackMorale)
+				auto diceSize = VLC->settings()->getVector(EGameSettings::COMBAT_BAD_MORALE_DICE);
+				size_t diceIndex = std::min<size_t>(diceSize.size()-1, -nextStackMorale);
+
+				if(diceSize.size() > 0 && getRandomGenerator().nextInt(1, diceSize[diceIndex]) == 1)
 				{
 				{
 					//unit loses its turn - empty freeze action
 					//unit loses its turn - empty freeze action
 					BattleAction ba;
 					BattleAction ba;
@@ -6758,7 +6764,10 @@ void CGameHandler::runBattle()
 						&&  next->alive()
 						&&  next->alive()
 						&&  nextStackMorale > 0)
 						&&  nextStackMorale > 0)
 					{
 					{
-						if(getRandomGenerator().nextInt(23) < nextStackMorale) //this stack hasn't got morale this turn
+						auto diceSize = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE);
+						size_t diceIndex = std::min<size_t>(diceSize.size()-1, nextStackMorale);
+
+						if(diceSize.size() > 0 && getRandomGenerator().nextInt(1, diceSize[diceIndex]) == 1)
 						{
 						{
 							BattleTriggerEffect bte;
 							BattleTriggerEffect bte;
 							bte.stackID = next->ID;
 							bte.stackID = next->ID;