浏览代码

Merge pull request #5724 from IvanSavenko/randomization

Better randomization logic
Ivan Savenko 5 月之前
父节点
当前提交
07662a070f
共有 75 个文件被更改,包括 1035 次插入778 次删除
  1. 4 4
      AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp
  2. 7 7
      AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp
  3. 18 2
      config/gameConfig.json
  4. 8 22
      lib/CCreatureHandler.cpp
  5. 2 1
      lib/CCreatureHandler.h
  6. 4 0
      lib/CMakeLists.txt
  7. 3 0
      lib/GameSettings.cpp
  8. 3 0
      lib/IGameSettings.h
  9. 0 10
      lib/callback/CGameInfoCallback.cpp
  10. 0 1
      lib/callback/CGameInfoCallback.h
  11. 333 0
      lib/callback/GameRandomizer.cpp
  12. 145 0
      lib/callback/GameRandomizer.h
  13. 1 0
      lib/callback/IGameEventCallback.h
  14. 0 4
      lib/callback/IGameInfoCallback.h
  15. 47 0
      lib/callback/IGameRandomizer.h
  16. 0 23
      lib/entities/hero/CHeroClass.cpp
  17. 0 1
      lib/entities/hero/CHeroClass.h
  18. 21 84
      lib/gameState/CGameState.cpp
  19. 11 15
      lib/gameState/CGameState.h
  20. 1 6
      lib/gameState/GameStatePackVisitor.cpp
  21. 0 1
      lib/gameState/GameStatePackVisitor.h
  22. 40 33
      lib/json/JsonRandom.cpp
  23. 21 18
      lib/json/JsonRandom.h
  24. 2 1
      lib/mapObjectConstructors/AObjectTypeHandler.h
  25. 3 3
      lib/mapObjectConstructors/CDefaultObjectTypeHandler.h
  26. 4 4
      lib/mapObjectConstructors/CRewardableConstructor.cpp
  27. 2 2
      lib/mapObjectConstructors/CRewardableConstructor.h
  28. 8 8
      lib/mapObjectConstructors/CommonConstructors.cpp
  29. 4 4
      lib/mapObjectConstructors/CommonConstructors.h
  30. 3 3
      lib/mapObjectConstructors/DwellingInstanceConstructor.cpp
  31. 1 1
      lib/mapObjectConstructors/DwellingInstanceConstructor.h
  32. 16 15
      lib/mapObjects/CGCreature.cpp
  33. 3 3
      lib/mapObjects/CGCreature.h
  34. 13 12
      lib/mapObjects/CGDwelling.cpp
  35. 4 4
      lib/mapObjects/CGDwelling.h
  36. 42 154
      lib/mapObjects/CGHeroInstance.cpp
  37. 16 32
      lib/mapObjects/CGHeroInstance.h
  38. 5 4
      lib/mapObjects/CGMarket.cpp
  39. 2 2
      lib/mapObjects/CGMarket.h
  40. 2 2
      lib/mapObjects/CGObjectInstance.cpp
  41. 2 2
      lib/mapObjects/CGObjectInstance.h
  42. 2 2
      lib/mapObjects/CGPandoraBox.cpp
  43. 1 1
      lib/mapObjects/CGPandoraBox.h
  44. 5 4
      lib/mapObjects/CGResource.cpp
  45. 2 2
      lib/mapObjects/CGResource.h
  46. 10 9
      lib/mapObjects/CGTownInstance.cpp
  47. 4 4
      lib/mapObjects/CGTownInstance.h
  48. 7 6
      lib/mapObjects/CQuest.cpp
  49. 3 3
      lib/mapObjects/CQuest.h
  50. 4 4
      lib/mapObjects/CRewardableObject.cpp
  51. 2 2
      lib/mapObjects/CRewardableObject.h
  52. 1 1
      lib/mapObjects/FlaggableMapObject.cpp
  53. 1 1
      lib/mapObjects/FlaggableMapObject.h
  54. 3 3
      lib/mapObjects/IObjectInterface.cpp
  55. 4 3
      lib/mapObjects/IObjectInterface.h
  56. 19 18
      lib/mapObjects/MiscObjects.cpp
  57. 10 10
      lib/mapObjects/MiscObjects.h
  58. 6 6
      lib/mapObjects/TownBuildingInstance.cpp
  59. 3 3
      lib/mapObjects/TownBuildingInstance.h
  60. 0 1
      lib/networkPacks/NetPackVisitor.h
  61. 0 5
      lib/networkPacks/NetPacksLib.cpp
  62. 0 12
      lib/networkPacks/PacksForClient.h
  63. 73 72
      lib/rewardable/Info.cpp
  64. 8 12
      lib/rewardable/Info.h
  65. 2 1
      lib/serializer/ESerializationVersion.h
  66. 0 1
      lib/serializer/RegisterTypes.h
  67. 13 26
      server/CGameHandler.cpp
  68. 3 3
      server/CGameHandler.h
  69. 20 34
      server/battles/BattleActionProcessor.cpp
  70. 12 19
      server/battles/BattleFlowProcessor.cpp
  71. 0 11
      server/processors/HeroPoolProcessor.cpp
  72. 6 6
      server/processors/HeroPoolProcessor.h
  73. 3 2
      server/processors/NewTurnProcessor.cpp
  74. 3 0
      server/queries/MapQueries.cpp
  75. 4 3
      test/game/CGameStateTest.cpp

+ 4 - 4
AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp

@@ -12,7 +12,7 @@
 
 #include "../Engine/Nullkiller.h"
 #include "../pforeach.h"
-#include "../../../lib/CRandomGenerator.h"
+#include "../../../lib/callback/GameRandomizer.h"
 #include "../../../lib/logging/VisualLogger.h"
 
 namespace NKAI
@@ -211,14 +211,14 @@ void DangerHitMapAnalyzer::calculateTileOwners()
 	auto addTownHero = [&](const CGTownInstance * town)
 	{
 			auto townHero = temporaryHeroes.emplace_back(std::make_unique<CGHeroInstance>(town->cb)).get();
-			CRandomGenerator rng;
+			GameRandomizer randomizer(*town->cb);
 			auto visitablePos = town->visitablePos();
 			
 			townHero->id = town->id;
 			townHero->setOwner(ai->playerID); // lets avoid having multiple colors
-			townHero->initHero(rng, static_cast<HeroTypeID>(0));
+			townHero->initHero(randomizer, static_cast<HeroTypeID>(0));
 			townHero->pos = townHero->convertFromVisitablePos(visitablePos);
-			townHero->initObj(rng);
+			townHero->initObj(randomizer);
 			
 			heroTownMap[townHero] = town;
 			townHeroes[townHero] = HeroRole::MAIN;

+ 7 - 7
AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp

@@ -10,7 +10,7 @@
 #include "StdInc.h"
 #include "ObjectGraphCalculator.h"
 #include "AIPathfinderConfig.h"
-#include "../../../lib/CRandomGenerator.h"
+#include "../../../lib/callback/GameRandomizer.h"
 #include "../../../lib/mapping/CMap.h"
 #include "../Engine/Nullkiller.h"
 #include "../../../lib/logging/VisualLogger.h"
@@ -287,13 +287,13 @@ void ObjectGraphCalculator::addObjectActor(const CGObjectInstance * obj)
 {
 	auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(obj->cb)).get();
 
-	CRandomGenerator rng;
+	GameRandomizer randomizer(*obj->cb);
 	auto visitablePos = obj->visitablePos();
 
 	objectActor->setOwner(ai->playerID); // lets avoid having multiple colors
-	objectActor->initHero(rng, static_cast<HeroTypeID>(0));
+	objectActor->initHero(randomizer, static_cast<HeroTypeID>(0));
 	objectActor->pos = objectActor->convertFromVisitablePos(visitablePos);
-	objectActor->initObj(rng);
+	objectActor->initObj(randomizer);
 
 	if(cb->getTile(visitablePos)->isWater())
 	{
@@ -325,12 +325,12 @@ void ObjectGraphCalculator::addJunctionActor(const int3 & visitablePos, bool isV
 	auto internalCb = temporaryActorHeroes.front()->cb;
 	auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(internalCb)).get();
 
-	CRandomGenerator rng;
+	GameRandomizer randomizer(*internalCb);
 
 	objectActor->setOwner(ai->playerID); // lets avoid having multiple colors
-	objectActor->initHero(rng, static_cast<HeroTypeID>(0));
+	objectActor->initHero(randomizer, static_cast<HeroTypeID>(0));
 	objectActor->pos = objectActor->convertFromVisitablePos(visitablePos);
-	objectActor->initObj(rng);
+	objectActor->initObj(randomizer);
 
 	if(isVirtualBoat || ai->cb->getTile(visitablePos)->isWater())
 	{

+ 18 - 2
config/gameConfig.json

@@ -340,16 +340,32 @@
 
 		"combat":
 		{
+			// defines bias used for percentage-based ability rolls, such Death Blow of Dread Knight
+			// If bias is set to 0, then all rolls will be completely independent - it is possible to get lucky and roll ability with 10% chance
+			// multiple times in a row, or be unlucky and not roll 50% ability multiple times in a row
+			// If bias is non-zero, game will adjust probability based on previous rolls while keeping average chance at desired value
+			// So matter what value is used for bias, actual probability for large (1000+) number of rolls is same as stated in description
+			// However, non-zero bias allows to prevent long streaks of "bad" rolls, and enforce actual probabilities even for small (10-20) number of rolls
+			// Recommended value is ~10-25. Excessively large values, like 100 can make rolls very predictable, for example rolling 20% ability every 5th roll
+			"abilityBias" : 25,
+
 			// defines dice chance and dice size of a morale roll, based on creature's morale.
 			// Resulting chance is chanceValue / diceSize. If list contains 0 values, option will be disabled
 			"goodMoraleChance" : [ 1, 2, 3 ],
 			"badMoraleChance" : [ 2, 4, 6],
-            "moraleDiceSize" : 24,
+			"moraleDiceSize" : 24,
+			// Bias for morale rolls. See abilityBias for detailed description
+			// Recommended value is around moraleDiceSize / 4
+			"moraleBias" : 8,
 
 			// defines dice chance and dice size of a luck roll, based on creature's luck
 			"goodLuckChance" : [ 1, 2, 3 ],
 			"badLuckChance" : [],
-            "luckDiceSize" : 24,
+			"luckDiceSize" : 24,
+			// Bias for luck rolls. See abilityBias for detailed description
+			// Recommended value is around luckDiceSize / 4
+			"luckBias" : 8,
+			
 
 			// every 1 attack point damage influence in battle when attack points > defense points during creature attack
 			"attackPointDamageFactor": 0.05, 

+ 8 - 22
lib/CCreatureHandler.cpp

@@ -1357,34 +1357,20 @@ CCreatureHandler::~CCreatureHandler()
 		p.first.clear();
 }
 
-CreatureID CCreatureHandler::pickRandomMonster(vstd::RNG & rand, int tier) const
+void CCreatureHandler::afterLoadFinalization()
 {
-	std::vector<CreatureID> allowed;
-	for(const auto & creature : objects)
-	{
-		if(creature->special)
-			continue;
-
-		if(creature->excludeFromRandomization)
-			continue;
 
-		if (creature->level == tier || tier == -1)
-			allowed.push_back(creature->getId());
-	}
-
-	if(allowed.empty())
-	{
-		logGlobal->warn("Cannot pick a random creature of tier %d!", tier);
-		return CreatureID::NONE;
-	}
-
-	return *RandomGeneratorUtil::nextItem(allowed, rand);
 }
 
-
-void CCreatureHandler::afterLoadFinalization()
+std::set<CreatureID> CCreatureHandler::getDefaultAllowed() const
 {
+	std::set<CreatureID> result;
+
+	for(auto & creature : objects)
+		if (creature && !creature->special)
+			result.insert(creature->getId());
 
+	return result;
 }
 
 VCMI_LIB_NAMESPACE_END

+ 2 - 1
lib/CCreatureHandler.h

@@ -223,7 +223,6 @@ public:
 	std::vector< std::vector <ui8> > skillLevels; //how much of a bonus will be given to commander with every level. SPELL_POWER also gives CASTS and RESISTANCE
 	std::vector <std::pair <std::vector<std::shared_ptr<Bonus> >, std::pair <ui8, ui8> > > skillRequirements; // first - Bonus, second - which two skills are needed to use it
 
-	CreatureID pickRandomMonster(vstd::RNG & rand, int tier = -1) const; //tier <1 - CREATURES_PER_TOWN> or -1 for any
 
 	CCreatureHandler();
 	~CCreatureHandler();
@@ -238,6 +237,8 @@ public:
 
 	std::vector<JsonNode> loadLegacyData() override;
 
+	std::set<CreatureID> getDefaultAllowed() const;
+
 };
 
 VCMI_LIB_NAMESPACE_END

+ 4 - 0
lib/CMakeLists.txt

@@ -85,6 +85,7 @@ set(lib_MAIN_SRCS
 	callback/CGameInfoCallback.cpp
 	callback/CNonConstInfoCallback.cpp
 	callback/CPlayerSpecificInfoCallback.cpp
+	callback/GameRandomizer.cpp
 
 	campaign/CampaignHandler.cpp
 	campaign/CampaignState.cpp
@@ -477,6 +478,9 @@ set(lib_MAIN_HEADERS
 	callback/IGameEventCallback.h
 	callback/IGameEventsReceiver.h
 	callback/IGameInfoCallback.h
+	callback/IGameRandomizer.h
+	callback/GameRandomizer.h
+
 
 	campaign/CampaignConstants.h
 	campaign/CampaignHandler.h

+ 3 - 0
lib/GameSettings.cpp

@@ -45,6 +45,7 @@ const std::vector<GameSettings::SettingOption> GameSettings::settingProperties =
 		{EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION,                    "banks",     "showGuardsComposition"                },
 		{EGameSettings::BONUSES_GLOBAL,                                   "bonuses",   "global"                               },
 		{EGameSettings::BONUSES_PER_HERO,                                 "bonuses",   "perHero"                              },
+		{EGameSettings::COMBAT_ABILITY_BIAS,                              "combat",    "abilityBias"                          },
 		{EGameSettings::COMBAT_AREA_SHOT_CAN_TARGET_EMPTY_HEX,            "combat",    "areaShotCanTargetEmptyHex"            },
 		{EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR,                "combat",    "attackPointDamageFactor"              },
 		{EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP,            "combat",    "attackPointDamageFactorCap"           },
@@ -53,9 +54,11 @@ const std::vector<GameSettings::SettingOption> GameSettings::settingProperties =
 		{EGameSettings::COMBAT_GOOD_MORALE_CHANCE,                        "combat",    "goodMoraleChance"                     },
 		{EGameSettings::COMBAT_BAD_MORALE_CHANCE,                         "combat",    "badMoraleChance"                      },
 		{EGameSettings::COMBAT_MORALE_DICE_SIZE,                          "combat",    "moraleDiceSize"                       },
+		{EGameSettings::COMBAT_MORALE_BIAS,                               "combat",    "moraleBias"                           },
 		{EGameSettings::COMBAT_GOOD_LUCK_CHANCE,                          "combat",    "goodLuckChance"                       },
 		{EGameSettings::COMBAT_BAD_LUCK_CHANCE,                           "combat",    "badLuckChance"                        },
 		{EGameSettings::COMBAT_LUCK_DICE_SIZE,                            "combat",    "luckDiceSize"                         },
+		{EGameSettings::COMBAT_LUCK_BIAS,                                 "combat",    "luckBias"                             },
 		{EGameSettings::COMBAT_LAYOUTS,                                   "combat",    "layouts"                              },
 		{EGameSettings::COMBAT_ONE_HEX_TRIGGERS_OBSTACLES,                "combat",    "oneHexTriggersObstacles"              },
 		{EGameSettings::CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH,             "creatures", "allowAllForDoubleMonth"               },

+ 3 - 0
lib/IGameSettings.h

@@ -18,6 +18,7 @@ enum class EGameSettings
 	BANKS_SHOW_GUARDS_COMPOSITION,
 	BONUSES_GLOBAL,
 	BONUSES_PER_HERO,
+	COMBAT_ABILITY_BIAS,
 	COMBAT_AREA_SHOT_CAN_TARGET_EMPTY_HEX,
 	COMBAT_ATTACK_POINT_DAMAGE_FACTOR,
 	COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP,
@@ -26,9 +27,11 @@ enum class EGameSettings
 	COMBAT_GOOD_MORALE_CHANCE, 
 	COMBAT_BAD_MORALE_CHANCE, 
 	COMBAT_MORALE_DICE_SIZE,
+	COMBAT_MORALE_BIAS,
 	COMBAT_GOOD_LUCK_CHANCE,
 	COMBAT_BAD_LUCK_CHANCE,
 	COMBAT_LUCK_DICE_SIZE,
+	COMBAT_LUCK_BIAS,
 	COMBAT_LAYOUTS,
 	COMBAT_ONE_HEX_TRIGGERS_OBSTACLES,
 	CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH,

+ 0 - 10
lib/callback/CGameInfoCallback.cpp

@@ -947,16 +947,6 @@ void CGameInfoCallback::getAllTiles(std::unordered_set<int3> & tiles, std::optio
 	}
 }
 
-void CGameInfoCallback::pickAllowedArtsSet(std::vector<ArtifactID> & out, vstd::RNG & rand)
-{
-	for (int j = 0; j < 3 ; j++)
-		out.push_back(gameState().pickRandomArtifact(rand, EArtifactClass::ART_TREASURE));
-	for (int j = 0; j < 3 ; j++)
-		out.push_back(gameState().pickRandomArtifact(rand, EArtifactClass::ART_MINOR));
-
-	out.push_back(gameState().pickRandomArtifact(rand, EArtifactClass::ART_MAJOR));
-}
-
 void CGameInfoCallback::getAllowedSpells(std::vector<SpellID> & out, std::optional<ui16> level)
 {
 	for (auto const & spellID : gameState().getMap().allowedSpells)

+ 0 - 1
lib/callback/CGameInfoCallback.h

@@ -115,7 +115,6 @@ public:
 	void getTilesInRange(std::unordered_set<int3> & tiles, const int3 & pos, int radius, ETileVisibility mode, std::optional<PlayerColor> player = std::optional<PlayerColor>(), int3::EDistanceFormula formula = int3::DIST_2D) const override;
 	void getAllTiles(std::unordered_set<int3> &tiles, std::optional<PlayerColor> player, int level, std::function<bool(const TerrainTile *)> filter) const override;
 
-	void pickAllowedArtsSet(std::vector<ArtifactID> & out, vstd::RNG & rand) override;
 	void getAllowedSpells(std::vector<SpellID> &out, std::optional<ui16> level = std::nullopt);
 
 #if SCRIPTING_ENABLED

+ 333 - 0
lib/callback/GameRandomizer.cpp

@@ -0,0 +1,333 @@
+/*
+ * GameRandomizer.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "GameRandomizer.h"
+
+#include "IGameInfoCallback.h"
+
+#include "../../lib/CRandomGenerator.h"
+#include "../../lib/GameLibrary.h"
+#include "../../lib/CCreatureHandler.h"
+#include "../../lib/CSkillHandler.h"
+#include "../../lib/IGameSettings.h"
+#include "../../lib/entities/artifact/CArtHandler.h"
+#include "../../lib/entities/artifact/EArtifactClass.h"
+#include "../../lib/entities/hero/CHeroClass.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+bool RandomizationBias::roll(vstd::RNG & generator, int successChance, int totalWeight, int biasValue)
+{
+	assert(successChance > 0);
+	assert(totalWeight >= successChance);
+
+	int failChance = totalWeight - successChance;
+	int newRoll = generator.nextInt(1, totalWeight);
+	// accumulated bias is stored as premultiplied to avoid precision loss on division
+	// so multiply everything else in equation to compensate
+	// precision loss is small, and generally insignificant, but better to play it safe
+	bool success = newRoll * totalWeight - accumulatedBias <= successChance * totalWeight;
+	if(success)
+		accumulatedBias -= failChance * biasValue;
+	else
+		accumulatedBias += successChance * biasValue;
+
+	return success;
+}
+
+RandomGeneratorWithBias::RandomGeneratorWithBias(int seed)
+	: generator(seed)
+{
+}
+
+bool RandomGeneratorWithBias::roll(int successChance, int totalWeight, int biasValue)
+{
+	return bias.roll(generator, successChance, totalWeight, biasValue);
+}
+
+GameRandomizer::GameRandomizer(const IGameInfoCallback & gameInfo)
+	: gameInfo(gameInfo)
+{
+}
+
+GameRandomizer::~GameRandomizer() = default;
+
+
+bool GameRandomizer::rollMoraleLuck(std::map<ObjectInstanceID, RandomGeneratorWithBias> & seeds, ObjectInstanceID actor, int moraleLuckValue, EGameSettings biasValueSetting, EGameSettings diceSizeSetting, EGameSettings chanceVectorSetting)
+{
+	assert(moraleLuckValue > 0);
+	auto chanceVector = gameInfo.getSettings().getVector(chanceVectorSetting);
+	int diceSize = gameInfo.getSettings().getInteger(diceSizeSetting);
+	int biasValue = gameInfo.getSettings().getInteger(biasValueSetting);
+	size_t chanceIndex = std::min<size_t>(chanceVector.size(), moraleLuckValue) - 1; // array index, so 0-indexed
+
+	if(!seeds.count(actor))
+		seeds.try_emplace(actor, getDefault().nextInt());
+
+	if(chanceVector.empty())
+		return false;
+
+	return seeds.at(actor).roll(chanceVector[chanceIndex], diceSize, biasValue);
+}
+
+bool GameRandomizer::rollGoodMorale(ObjectInstanceID actor, int moraleValue)
+{
+	return rollMoraleLuck(goodMoraleSeed, actor, moraleValue, EGameSettings::COMBAT_MORALE_BIAS, EGameSettings::COMBAT_MORALE_DICE_SIZE, EGameSettings::COMBAT_GOOD_MORALE_CHANCE);
+}
+
+bool GameRandomizer::rollBadMorale(ObjectInstanceID actor, int moraleValue)
+{
+	return rollMoraleLuck(badMoraleSeed, actor, moraleValue, EGameSettings::COMBAT_MORALE_BIAS, EGameSettings::COMBAT_MORALE_DICE_SIZE, EGameSettings::COMBAT_BAD_MORALE_CHANCE);
+}
+
+bool GameRandomizer::rollGoodLuck(ObjectInstanceID actor, int luckValue)
+{
+	return rollMoraleLuck(goodLuckSeed, actor, luckValue, EGameSettings::COMBAT_LUCK_BIAS, EGameSettings::COMBAT_LUCK_DICE_SIZE, EGameSettings::COMBAT_GOOD_LUCK_CHANCE);
+}
+
+bool GameRandomizer::rollBadLuck(ObjectInstanceID actor, int luckValue)
+{
+	return rollMoraleLuck(badLuckSeed, actor, luckValue, EGameSettings::COMBAT_LUCK_BIAS, EGameSettings::COMBAT_LUCK_DICE_SIZE, EGameSettings::COMBAT_BAD_LUCK_CHANCE);
+}
+
+bool GameRandomizer::rollCombatAbility(ObjectInstanceID actor, int percentageChance)
+{
+	if(!combatAbilitySeed.count(actor))
+		combatAbilitySeed.try_emplace(actor, getDefault().nextInt());
+
+	if(percentageChance <= 0)
+		return false;
+
+	if(percentageChance >= 100)
+		return true;
+
+	int biasValue = gameInfo.getSettings().getInteger(EGameSettings::COMBAT_ABILITY_BIAS);
+
+	return combatAbilitySeed.at(actor).roll(percentageChance, 100, biasValue);
+}
+
+CreatureID GameRandomizer::rollCreature()
+{
+	std::vector<CreatureID> allowed;
+	for(const auto & creatureID : LIBRARY->creh->getDefaultAllowed())
+	{
+		const auto * creaturePtr = creatureID.toCreature();
+		if(!creaturePtr->excludeFromRandomization)
+			allowed.push_back(creaturePtr->getId());
+	}
+
+	if(allowed.empty())
+		throw std::runtime_error("Cannot pick a random creature!");
+
+	return *RandomGeneratorUtil::nextItem(allowed, getDefault());
+}
+
+CreatureID GameRandomizer::rollCreature(int tier)
+{
+	std::vector<CreatureID> allowed;
+	for(const auto & creatureID : LIBRARY->creh->getDefaultAllowed())
+	{
+		const auto * creaturePtr = creatureID.toCreature();
+		if(creaturePtr->excludeFromRandomization)
+			continue;
+
+		if(creaturePtr->getLevel() == tier)
+			allowed.push_back(creaturePtr->getId());
+	}
+
+	if(allowed.empty())
+		throw std::runtime_error("Cannot pick a random creature!");
+
+	return *RandomGeneratorUtil::nextItem(allowed, getDefault());
+}
+
+ArtifactID GameRandomizer::rollArtifact()
+{
+	std::set<ArtifactID> potentialPicks;
+
+	for(const auto & artifactID : LIBRARY->arth->getDefaultAllowed())
+	{
+		if(!LIBRARY->arth->legalArtifact(artifactID))
+			continue;
+
+		potentialPicks.insert(artifactID);
+	}
+
+	return rollArtifact(potentialPicks);
+}
+
+ArtifactID GameRandomizer::rollArtifact(EArtifactClass type)
+{
+	std::set<ArtifactID> potentialPicks;
+
+	for(const auto & artifactID : LIBRARY->arth->getDefaultAllowed())
+	{
+		if(!LIBRARY->arth->legalArtifact(artifactID))
+			continue;
+
+		if(!gameInfo.isAllowed(artifactID))
+			continue;
+
+		const auto * artifact = artifactID.toArtifact();
+
+		if(type != artifact->aClass)
+			continue;
+
+		potentialPicks.insert(artifactID);
+	}
+
+	return rollArtifact(potentialPicks);
+}
+
+ArtifactID GameRandomizer::rollArtifact(std::set<ArtifactID> potentialPicks)
+{
+	// No allowed artifacts at all - give Grail - this can't be banned (hopefully)
+	// FIXME: investigate how such cases are handled by H3 - some heavily customized user-made maps likely rely on H3 behavior
+	if(potentialPicks.empty())
+	{
+		logGlobal->warn("Failed to find artifact that matches requested parameters!");
+		return ArtifactID::GRAIL;
+	}
+
+	// Find how many times least used artifacts were picked by randomizer
+	int leastUsedTimes = std::numeric_limits<int>::max();
+	for(const auto & artifact : potentialPicks)
+		if(allocatedArtifacts[artifact] < leastUsedTimes)
+			leastUsedTimes = allocatedArtifacts[artifact];
+
+	// Pick all artifacts that were used least number of times
+	std::set<ArtifactID> preferredPicks;
+	for(const auto & artifact : potentialPicks)
+		if(allocatedArtifacts[artifact] == leastUsedTimes)
+			preferredPicks.insert(artifact);
+
+	assert(!preferredPicks.empty());
+
+	ArtifactID artID = *RandomGeneratorUtil::nextItem(preferredPicks, getDefault());
+	allocatedArtifacts[artID] += 1; // record +1 more usage
+	return artID;
+}
+
+std::vector<ArtifactID> GameRandomizer::rollMarketArtifactSet()
+{
+	return {
+		rollArtifact(EArtifactClass::ART_TREASURE),
+		rollArtifact(EArtifactClass::ART_TREASURE),
+		rollArtifact(EArtifactClass::ART_TREASURE),
+		rollArtifact(EArtifactClass::ART_MINOR),
+		rollArtifact(EArtifactClass::ART_MINOR),
+		rollArtifact(EArtifactClass::ART_MINOR),
+		rollArtifact(EArtifactClass::ART_MAJOR)
+	};
+}
+
+vstd::RNG & GameRandomizer::getDefault()
+{
+	return globalRandomNumberGenerator;
+}
+
+void GameRandomizer::setSeed(int newSeed)
+{
+	globalRandomNumberGenerator.setSeed(newSeed);
+}
+
+PrimarySkill GameRandomizer::rollPrimarySkillForLevelup(const CGHeroInstance * hero)
+{
+	if(!heroSkillSeed.count(hero->getHeroTypeID()))
+		heroSkillSeed.try_emplace(hero->getHeroTypeID(), getDefault().nextInt());
+
+	const bool isLowLevelHero = hero->level < GameConstants::HERO_HIGH_LEVEL;
+	const auto & skillChances = isLowLevelHero ? hero->getHeroClass()->primarySkillLowLevel : hero->getHeroClass()->primarySkillHighLevel;
+	auto & heroRng = heroSkillSeed.at(hero->getHeroTypeID());
+
+	if(hero->isCampaignYog())
+	{
+		// Yog can only receive Attack or Defence on level-up
+		std::vector<int> yogChances = {skillChances[0], skillChances[1]};
+		return static_cast<PrimarySkill>(RandomGeneratorUtil::nextItemWeighted(yogChances, heroRng.seed));
+	}
+	return static_cast<PrimarySkill>(RandomGeneratorUtil::nextItemWeighted(skillChances, heroRng.seed));
+}
+
+SecondarySkill GameRandomizer::rollSecondarySkillForLevelup(const CGHeroInstance * hero, const std::set<SecondarySkill> & options)
+{
+	if(!heroSkillSeed.count(hero->getHeroTypeID()))
+		heroSkillSeed.try_emplace(hero->getHeroTypeID(), getDefault().nextInt());
+
+	auto & heroRng = heroSkillSeed.at(hero->getHeroTypeID());
+
+	auto getObligatorySkills = [](CSkill::Obligatory obl)
+	{
+		std::set<SecondarySkill> obligatory;
+		for(auto i = 0; i < LIBRARY->skillh->size(); i++)
+			if((*LIBRARY->skillh)[SecondarySkill(i)]->obligatory(obl))
+				obligatory.insert(i); //Always return all obligatory skills
+
+		return obligatory;
+	};
+
+	auto intersect = [](const std::set<SecondarySkill> & left, const std::set<SecondarySkill> & right)
+	{
+		std::set<SecondarySkill> intersection;
+		std::set_intersection(left.begin(), left.end(), right.begin(), right.end(), std::inserter(intersection, intersection.begin()));
+		return intersection;
+	};
+
+	std::set<SecondarySkill> wisdomList = getObligatorySkills(CSkill::Obligatory::MAJOR);
+	std::set<SecondarySkill> schoolList = getObligatorySkills(CSkill::Obligatory::MINOR);
+
+	bool wantsWisdom = heroRng.wisdomCounter + 1 >= hero->maxlevelsToWisdom();
+	bool wantsSchool = heroRng.magicSchoolCounter + 1 >= hero->maxlevelsToMagicSchool();
+	bool selectWisdom = wantsWisdom && !intersect(options, wisdomList).empty();
+	bool selectSchool = wantsSchool && !intersect(options, schoolList).empty();
+
+	std::set<SecondarySkill> actualCandidates;
+
+	if(selectWisdom)
+		actualCandidates = intersect(options, wisdomList);
+	else if(selectSchool)
+		actualCandidates = intersect(options, schoolList);
+	else
+		actualCandidates = options;
+
+	assert(!actualCandidates.empty());
+
+	std::vector<int> weights;
+	std::vector<SecondarySkill> skills;
+
+	for(const auto & possible : actualCandidates)
+	{
+		skills.push_back(possible);
+		if(hero->getHeroClass()->secSkillProbability.count(possible) != 0)
+		{
+			int weight = hero->getHeroClass()->secSkillProbability.at(possible);
+			weights.push_back(std::max(1, weight));
+		}
+		else
+			weights.push_back(1); // H3 behavior - banned skills have minimal (1) chance to be picked
+	}
+
+	int selectedIndex = RandomGeneratorUtil::nextItemWeighted(weights, heroRng.seed);
+	SecondarySkill selectedSkill = skills.at(selectedIndex);
+
+	//deterministic secondary skills
+	++heroRng.magicSchoolCounter;
+	++heroRng.wisdomCounter;
+
+	if((*LIBRARY->skillh)[selectedSkill]->obligatory(CSkill::Obligatory::MAJOR))
+		heroRng.wisdomCounter = 0;
+	if((*LIBRARY->skillh)[selectedSkill]->obligatory(CSkill::Obligatory::MINOR))
+		heroRng.magicSchoolCounter = 0;
+
+	return selectedSkill;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 145 - 0
lib/callback/GameRandomizer.h

@@ -0,0 +1,145 @@
+/*
+ * GameRandomizer.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "callback/IGameRandomizer.h"
+#include "CRandomGenerator.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+enum class EGameSettings;
+
+class CGHeroInstance;
+
+class DLL_LINKAGE RandomizationBias
+{
+	int32_t accumulatedBias = 0;
+
+public:
+	/// Performs coin flip with specified success chance
+	/// Returns true with probability successChance percents, and false with probability totalWeight-successChance percents
+	bool roll(vstd::RNG & generator, int successChance, int totalWeight, int biasValue);
+
+	template<typename Handler>
+	void serialize(Handler & h)
+	{
+		h & accumulatedBias;
+	}
+};
+
+/// Biased randomizer that has following properties:
+/// - at bias value of 0 it acts as statistical random generator, just like vstd::RNG
+/// - at bias value of 100 it guarantees that it will take at most 100/chance rolls till succesfull roll
+/// - at bias value between 1..99 similar guarantee is also provided, but with larger number of rolls
+/// No matter what bias is, statistical probability on large number of rolls remains the same
+/// Its goal is to simulate human expectations of random distributions and reduce frustration from "bad" rolls
+class DLL_LINKAGE RandomGeneratorWithBias
+{
+	CRandomGenerator generator;
+	RandomizationBias bias;
+
+public:
+	explicit RandomGeneratorWithBias(int seed = 0);
+	/// Performs coin flip with specified success chance
+	/// Returns true with probability successChance percents, and false with probability 100-successChance percents
+	bool roll(int successChance, int totalWeight, int biasValue);
+
+	template<typename Handler>
+	void serialize(Handler & h)
+	{
+		h & generator;
+		h & bias;
+	}
+};
+
+class DLL_LINKAGE GameRandomizer final : public IGameRandomizer
+{
+	struct HeroSkillRandomizer
+	{
+		explicit HeroSkillRandomizer(int seed = 0)
+			: seed(seed)
+		{}
+
+		CRandomGenerator seed;
+		int8_t magicSchoolCounter = 1;
+		int8_t wisdomCounter = 1;
+
+		template<typename Handler>
+		void serialize(Handler & h)
+		{
+			h & seed;
+			h & magicSchoolCounter;
+			h & wisdomCounter;
+		}
+	};
+
+	const IGameInfoCallback & gameInfo;
+
+	/// Global RNG, for use when there is no specialized instance
+	CRandomGenerator globalRandomNumberGenerator;
+
+	/// Stores number of times each artifact was placed on map via randomization
+	std::map<ArtifactID, int> allocatedArtifacts;
+
+	std::map<HeroTypeID, HeroSkillRandomizer> heroSkillSeed;
+
+	std::map<ObjectInstanceID, RandomGeneratorWithBias> goodMoraleSeed;
+	std::map<ObjectInstanceID, RandomGeneratorWithBias> badMoraleSeed;
+	std::map<ObjectInstanceID, RandomGeneratorWithBias> goodLuckSeed;
+	std::map<ObjectInstanceID, RandomGeneratorWithBias> badLuckSeed;
+	std::map<ObjectInstanceID, RandomGeneratorWithBias> combatAbilitySeed;
+
+	bool rollMoraleLuck(std::map<ObjectInstanceID, RandomGeneratorWithBias> & seeds, ObjectInstanceID actor, int moraleLuckValue, EGameSettings biasValue, EGameSettings diceSize, EGameSettings diceWeights);
+
+public:
+	explicit GameRandomizer(const IGameInfoCallback & gameInfo);
+	~GameRandomizer();
+
+	PrimarySkill rollPrimarySkillForLevelup(const CGHeroInstance * hero) override;
+	SecondarySkill rollSecondarySkillForLevelup(const CGHeroInstance * hero, const std::set<SecondarySkill> & candidates) override;
+
+	bool rollGoodMorale(ObjectInstanceID actor, int moraleValue);
+	bool rollBadMorale(ObjectInstanceID actor, int moraleValue);
+	bool rollGoodLuck(ObjectInstanceID actor, int luckValue);
+	bool rollBadLuck(ObjectInstanceID actor, int luckValue);
+
+	bool rollCombatAbility(ObjectInstanceID actor, int percentageChance);
+
+	CreatureID rollCreature() override;
+	CreatureID rollCreature(int tier) override;
+
+	ArtifactID rollArtifact() override;
+	ArtifactID rollArtifact(EArtifactClass type) override;
+	ArtifactID rollArtifact(std::set<ArtifactID> filtered) override;
+	std::vector<ArtifactID> rollMarketArtifactSet() override;
+
+	vstd::RNG & getDefault() override;
+
+	void setSeed(int newSeed);
+
+	template<typename Handler>
+	void serialize(Handler & h)
+	{
+		h & globalRandomNumberGenerator;
+
+		if (h.hasFeature(Handler::Version::RANDOMIZATION_REWORK))
+		{
+			h & allocatedArtifacts;
+			h & heroSkillSeed;
+			h & goodMoraleSeed;
+			h & badMoraleSeed;
+			h & goodLuckSeed;
+			h & badLuckSeed;
+			h & combatAbilitySeed;
+		}
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 1 - 0
lib/callback/IGameEventCallback.h

@@ -117,6 +117,7 @@ public:
 
 	virtual bool isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero) = 0;
 
+	/// Returns global random generator. TODO: remove, replace with IGameRanndomizer as separate parameter to such methods
 	virtual vstd::RNG & getRandomGenerator() = 0;
 };
 

+ 0 - 4
lib/callback/IGameInfoCallback.h

@@ -162,10 +162,6 @@ public:
 	virtual bool isTeleportChannelUnidirectional(TeleportChannelID id, PlayerColor player = PlayerColor::UNFLAGGABLE) const  = 0;
 	virtual bool isTeleportEntrancePassable(const CGTeleport * obj, PlayerColor player) const  = 0;
 
-	/// gives 3 treasures, 3 minors, 1 major -> used by Black Market and Artifact Merchant
-	/// TODO: remove non-const method from this interface
-	virtual void pickAllowedArtsSet(std::vector<ArtifactID> & out, vstd::RNG & rand) = 0;
-
 #if SCRIPTING_ENABLED
 	virtual scripting::Pool * getGlobalContextPool() const = 0;
 #endif

+ 47 - 0
lib/callback/IGameRandomizer.h

@@ -0,0 +1,47 @@
+/*
+ * IGameRandomizer.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../constants/EntityIdentifiers.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CGHeroInstance;
+
+enum class EArtifactClass;
+
+namespace vstd
+{
+class RNG;
+}
+
+/// Provides source of random rolls for game entities
+/// Instance of this interface only exists on server
+class DLL_LINKAGE IGameRandomizer : boost::noncopyable
+{
+public:
+	virtual ~IGameRandomizer() = default;
+
+	virtual ArtifactID rollArtifact() = 0;
+	virtual ArtifactID rollArtifact(EArtifactClass type) = 0;
+	virtual ArtifactID rollArtifact(std::set<ArtifactID> filtered) = 0;
+
+	virtual std::vector<ArtifactID> rollMarketArtifactSet() = 0;
+
+	virtual CreatureID rollCreature() = 0;
+	virtual CreatureID rollCreature(int tier) = 0;
+
+	virtual PrimarySkill rollPrimarySkillForLevelup(const CGHeroInstance * hero) = 0;
+	virtual SecondarySkill rollSecondarySkillForLevelup(const CGHeroInstance * hero, const std::set<SecondarySkill> & candidates) = 0;
+
+	virtual vstd::RNG & getDefault() = 0;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 0 - 23
lib/entities/hero/CHeroClass.cpp

@@ -19,29 +19,6 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-SecondarySkill CHeroClass::chooseSecSkill(const std::set<SecondarySkill> & possibles, vstd::RNG & rand) const //picks secondary skill out from given possibilities
-{
-	assert(!possibles.empty());
-
-	std::vector<int> weights;
-	std::vector<SecondarySkill> skills;
-
-	for(const auto & possible : possibles)
-	{
-		skills.push_back(possible);
-		if (secSkillProbability.count(possible) != 0)
-		{
-			int weight = secSkillProbability.at(possible);
-			weights.push_back(std::max(1, weight));
-		}
-		else
-			weights.push_back(1); // H3 behavior - banned skills have minimal (1) chance to be picked
-	}
-
-	int selectedIndex = RandomGeneratorUtil::nextItemWeighted(weights, rand);
-	return skills.at(selectedIndex);
-}
-
 bool CHeroClass::isMagicHero() const
 {
 	return affinity == MAGIC;

+ 0 - 1
lib/entities/hero/CHeroClass.h

@@ -72,7 +72,6 @@ public:
 	std::string getNameTextID() const override;
 
 	bool isMagicHero() const;
-	SecondarySkill chooseSecSkill(const std::set<SecondarySkill> & possibles, vstd::RNG & rand) const; //picks secondary skill out from given possibilities
 
 	void updateFrom(const JsonNode & data);
 	void serializeJson(JsonSerializeFormat & handler);

+ 21 - 84
lib/gameState/CGameState.cpp

@@ -32,6 +32,7 @@
 #include "../bonuses/Updaters.h"
 #include "../battle/BattleInfo.h"
 #include "../callback/IGameInfoCallback.h"
+#include "../callback/IGameRandomizer.h"
 #include "../campaign/CampaignState.h"
 #include "../constants/StringConstants.h"
 #include "../entities/artifact/ArtifactUtils.h"
@@ -173,13 +174,14 @@ void CGameState::preInit(Services * newServices)
 	services = newServices;
 }
 
-void CGameState::init(const IMapService * mapService, StartInfo * si, vstd::RNG & randomGenerator, Load::ProgressAccumulator & progressTracking, bool allowSavingRandomMap)
+void CGameState::init(const IMapService * mapService, StartInfo * si, IGameRandomizer & gameRandomizer, Load::ProgressAccumulator & progressTracking, bool allowSavingRandomMap)
 {
 	assert(services);
 	assert(cb);
 	scenarioOps = CMemorySerializer::deepCopy(*si);
 	initialOpts = CMemorySerializer::deepCopy(*si);
 	si = nullptr;
+	auto & randomGenerator = gameRandomizer.getDefault();
 
 	switch(scenarioOps->mode)
 	{
@@ -206,16 +208,16 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, vstd::RNG
 	removeHeroPlaceholders();
 	initGrailPosition(randomGenerator);
 	initRandomFactionsForPlayers(randomGenerator);
-	randomizeMapObjects(randomGenerator);
+	randomizeMapObjects(gameRandomizer);
 	placeStartingHeroes(randomGenerator);
 	initOwnedObjects();
 	initDifficulty();
-	initHeroes(randomGenerator);
-	initStartingBonus(randomGenerator);
+	initHeroes(gameRandomizer);
+	initStartingBonus(gameRandomizer);
 	initTowns(randomGenerator);
 	initTownNames(randomGenerator);
 	placeHeroesInTowns();
-	initMapObjects(randomGenerator);
+	initMapObjects(gameRandomizer);
 	buildBonusSystemTree();
 	initVisitingAndGarrisonedHeroes();
 	initFogOfWar();
@@ -488,12 +490,12 @@ void CGameState::initRandomFactionsForPlayers(vstd::RNG & randomGenerator)
 	}
 }
 
-void CGameState::randomizeMapObjects(vstd::RNG & randomGenerator)
+void CGameState::randomizeMapObjects(IGameRandomizer & gameRandomizer)
 {
 	logGlobal->debug("\tRandomizing objects");
 	for(const auto & object : map->getObjects())
 	{
-		object->pickRandomObject(randomGenerator);
+		object->pickRandomObject(gameRandomizer);
 
 		//handle Favouring Winds - mark tiles under it
 		if(object->ID == Obj::FAVORABLE_WINDS)
@@ -594,7 +596,7 @@ void CGameState::removeHeroPlaceholders()
 	}
 }
 
-void CGameState::initHeroes(vstd::RNG & randomGenerator)
+void CGameState::initHeroes(IGameRandomizer & gameRandomizer)
 {
 	//heroes instances initialization
 	for (auto heroID : map->getHeroesOnMap())
@@ -605,7 +607,7 @@ void CGameState::initHeroes(vstd::RNG & randomGenerator)
 			logGlobal->warn("Hero with uninitialized owner!");
 			continue;
 		}
-		hero->initHero(randomGenerator);
+		hero->initHero(gameRandomizer);
 	}
 
 	// generate boats for all heroes on water
@@ -622,7 +624,7 @@ void CGameState::initHeroes(vstd::RNG & randomGenerator)
 		{
 			auto handler = LIBRARY->objtypeh->getHandlerFor(Obj::BOAT, hero->getBoatType().getNum());
 			auto boat = std::dynamic_pointer_cast<CGBoat>(handler->create(cb, nullptr));
-			handler->configureObject(boat.get(), randomGenerator);
+			handler->configureObject(boat.get(), gameRandomizer);
 
 			boat->setAnchorPos(hero->anchorPos());
 			boat->appearance = handler->getTemplates().front();
@@ -648,7 +650,7 @@ void CGameState::initHeroes(vstd::RNG & randomGenerator)
 			heroInPool = newHeroPtr.get();
 		}
 		map->generateUniqueInstanceName(heroInPool);
-		heroInPool->initHero(randomGenerator);
+		heroInPool->initHero(gameRandomizer);
 		heroesPool->addHeroToPool(htype);
 	}
 
@@ -685,7 +687,7 @@ void CGameState::initFogOfWar()
 	}
 }
 
-void CGameState::initStartingBonus(vstd::RNG & randomGenerator)
+void CGameState::initStartingBonus(IGameRandomizer & gameRandomizer)
 {
 	if (scenarioOps->mode == EStartMode::CAMPAIGN)
 		return;
@@ -697,25 +699,25 @@ void CGameState::initStartingBonus(vstd::RNG & randomGenerator)
 	{
 		//starting bonus
 		if(scenarioOps->playerInfos[elem.first].bonus == PlayerStartingBonus::RANDOM)
-			scenarioOps->playerInfos[elem.first].bonus = static_cast<PlayerStartingBonus>(randomGenerator.nextInt(2));
+			scenarioOps->playerInfos[elem.first].bonus = static_cast<PlayerStartingBonus>(gameRandomizer.getDefault().nextInt(2));
 
 		switch(scenarioOps->playerInfos[elem.first].bonus)
 		{
 		case PlayerStartingBonus::GOLD:
-			elem.second.resources[EGameResID::GOLD] += randomGenerator.nextInt(5, 10) * 100;
+			elem.second.resources[EGameResID::GOLD] += gameRandomizer.getDefault().nextInt(5, 10) * 100;
 			break;
 		case PlayerStartingBonus::RESOURCE:
 			{
 				auto res = (*LIBRARY->townh)[scenarioOps->playerInfos[elem.first].castle]->town->primaryRes;
 				if(res == EGameResID::WOOD_AND_ORE)
 				{
-					int amount = randomGenerator.nextInt(5, 10);
+					int amount = gameRandomizer.getDefault().nextInt(5, 10);
 					elem.second.resources[EGameResID::WOOD] += amount;
 					elem.second.resources[EGameResID::ORE] += amount;
 				}
 				else
 				{
-					elem.second.resources[res] += randomGenerator.nextInt(3, 6);
+					elem.second.resources[res] += gameRandomizer.getDefault().nextInt(3, 6);
 				}
 				break;
 			}
@@ -726,7 +728,7 @@ void CGameState::initStartingBonus(vstd::RNG & randomGenerator)
 					logGlobal->error("Cannot give starting artifact - no heroes!");
 					break;
 				}
-				const Artifact * toGive = pickRandomArtifact(randomGenerator, EArtifactClass::ART_TREASURE).toEntity(LIBRARY);
+				const Artifact * toGive = gameRandomizer.rollArtifact(EArtifactClass::ART_TREASURE).toEntity(LIBRARY);
 
 				CGHeroInstance *hero = elem.second.getHeroes()[0];
 				if(!giveHeroArtifact(hero, toGive->getId()))
@@ -923,12 +925,12 @@ void CGameState::initTowns(vstd::RNG & randomGenerator)
 	}
 }
 
-void CGameState::initMapObjects(vstd::RNG & randomGenerator)
+void CGameState::initMapObjects(IGameRandomizer & gameRandomizer)
 {
 	logGlobal->debug("\tObject initialization");
 
 	for(auto & obj : map->getObjects())
-		obj->initObj(randomGenerator);
+		obj->initObj(gameRandomizer);
 
 	logGlobal->debug("\tObject initialization done");
 	for(auto & q : map->getObjects<CGSeerHut>())
@@ -1611,71 +1613,6 @@ TeamState::TeamState()
 	setNodeType(TEAM);
 }
 
-ArtifactID CGameState::pickRandomArtifact(vstd::RNG & randomGenerator, std::optional<EArtifactClass> type, std::function<bool(ArtifactID)> accepts)
-{
-	std::set<ArtifactID> potentialPicks;
-
-	// Select artifacts that satisfy provided criteria
-	for (auto const & artifactID : map->allowedArtifact)
-	{
-		if (!LIBRARY->arth->legalArtifact(artifactID))
-			continue;
-
-		auto const * artifact = artifactID.toArtifact();
-
-		assert(artifact->aClass != EArtifactClass::ART_SPECIAL); // should be filtered out when allowedArtifacts is initialized
-
-		if (type.has_value() && *type != artifact->aClass)
-			continue;
-
-		if (!accepts(artifact->getId()))
-			continue;
-
-		potentialPicks.insert(artifact->getId());
-	}
-
-	return pickRandomArtifact(randomGenerator, potentialPicks);
-}
-
-ArtifactID CGameState::pickRandomArtifact(vstd::RNG & randomGenerator, std::set<ArtifactID> potentialPicks)
-{
-	// No allowed artifacts at all - give Grail - this can't be banned (hopefully)
-	// FIXME: investigate how such cases are handled by H3 - some heavily customized user-made maps likely rely on H3 behavior
-	if (potentialPicks.empty())
-	{
-		logGlobal->warn("Failed to find artifact that matches requested parameters!");
-		return ArtifactID::GRAIL;
-	}
-
-	// Find how many times least used artifacts were picked by randomizer
-	int leastUsedTimes = std::numeric_limits<int>::max();
-	for (auto const & artifact : potentialPicks)
-		if (allocatedArtifacts[artifact] < leastUsedTimes)
-			leastUsedTimes = allocatedArtifacts[artifact];
-
-	// Pick all artifacts that were used least number of times
-	std::set<ArtifactID> preferredPicks;
-	for (auto const & artifact : potentialPicks)
-		if (allocatedArtifacts[artifact] == leastUsedTimes)
-			preferredPicks.insert(artifact);
-
-	assert(!preferredPicks.empty());
-
-	ArtifactID artID = *RandomGeneratorUtil::nextItem(preferredPicks, randomGenerator);
-	allocatedArtifacts[artID] += 1; // record +1 more usage
-	return artID;
-}
-
-ArtifactID CGameState::pickRandomArtifact(vstd::RNG & randomGenerator, std::function<bool(ArtifactID)> accepts)
-{
-	return pickRandomArtifact(randomGenerator, std::nullopt, std::move(accepts));
-}
-
-ArtifactID CGameState::pickRandomArtifact(vstd::RNG & randomGenerator, std::optional<EArtifactClass> type)
-{
-	return pickRandomArtifact(randomGenerator, type, [](const ArtifactID &) { return true; });
-}
-
 CArtifactInstance * CGameState::createScroll(const SpellID & spellId)
 {
 	return map->createScroll(spellId);

+ 11 - 15
lib/gameState/CGameState.h

@@ -22,6 +22,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 class EVictoryLossCheckResult;
 class Services;
+class IGameRandomizer;
 class IMapService;
 class CMap;
 class CSaveFile;
@@ -54,9 +55,6 @@ public:
 	ArtifactInstanceID saveCompatibilityLastAllocatedArtifactID;
 	std::vector<std::shared_ptr<CArtifactInstance>> saveCompatibilityUnregisteredArtifacts;
 
-	/// Stores number of times each artifact was placed on map via randomization
-	std::map<ArtifactID, int> allocatedArtifacts;
-
 	/// List of currently ongoing battles
 	std::vector<std::unique_ptr<BattleInfo>> currentBattles;
 	/// ID that can be allocated to next battle
@@ -76,7 +74,7 @@ public:
 
 	void preInit(Services * services);
 
-	void init(const IMapService * mapService, StartInfo * si, vstd::RNG & randomGenerator, Load::ProgressAccumulator &, bool allowSavingRandomMap = true);
+	void init(const IMapService * mapService, StartInfo * si, IGameRandomizer & gameRandomizer, Load::ProgressAccumulator &, bool allowSavingRandomMap = true);
 	void updateOnLoad(StartInfo * si);
 
 	ui32 day; //total number of days in game
@@ -103,12 +101,6 @@ public:
 	void calculatePaths(const std::shared_ptr<PathfinderConfig> & config) const override;
 	std::vector<const CGObjectInstance*> guardingCreatures (int3 pos) const;
 
-	/// Gets a artifact ID randomly and removes the selected artifact from this handler.
-	ArtifactID pickRandomArtifact(vstd::RNG & randomGenerator, std::optional<EArtifactClass> type);
-	ArtifactID pickRandomArtifact(vstd::RNG & randomGenerator, std::function<bool(ArtifactID)> accepts);
-	ArtifactID pickRandomArtifact(vstd::RNG & randomGenerator, std::optional<EArtifactClass> type, std::function<bool(ArtifactID)> accepts);
-	ArtifactID pickRandomArtifact(vstd::RNG & randomGenerator, std::set<ArtifactID> filtered);
-
 	/// Creates instance of spell scroll artifact with provided spell
 	CArtifactInstance * createScroll(const SpellID & spellId);
 
@@ -187,7 +179,11 @@ public:
 		h & globalEffects;
 		h & currentRumor;
 		h & campaign;
-		h & allocatedArtifacts;
+		if (!h.hasFeature(Handler::Version::RANDOMIZATION_REWORK))
+		{
+			std::map<ArtifactID, int> allocatedArtifactsUnused;
+			h & allocatedArtifactsUnused;
+		}
 		h & statistic;
 
 		if(!h.saving && h.loadingGamestate)
@@ -201,19 +197,19 @@ private:
 	void initGrailPosition(vstd::RNG & randomGenerator);
 	void initRandomFactionsForPlayers(vstd::RNG & randomGenerator);
 	void initOwnedObjects();
-	void randomizeMapObjects(vstd::RNG & randomGenerator);
+	void randomizeMapObjects(IGameRandomizer & gameRandomizer);
 	void initPlayerStates();
 	void placeStartingHeroes(vstd::RNG & randomGenerator);
 	void placeStartingHero(const PlayerColor & playerColor, const HeroTypeID & heroTypeId, int3 townPos);
 	void removeHeroPlaceholders();
 	void initDifficulty();
-	void initHeroes(vstd::RNG & randomGenerator);
+	void initHeroes(IGameRandomizer & gameRandomizer);
 	void placeHeroesInTowns();
 	void initFogOfWar();
-	void initStartingBonus(vstd::RNG & randomGenerator);
+	void initStartingBonus(IGameRandomizer & gameRandomizer);
 	void initTowns(vstd::RNG & randomGenerator);
 	void initTownNames(vstd::RNG & randomGenerator);
-	void initMapObjects(vstd::RNG & randomGenerator);
+	void initMapObjects(IGameRandomizer & gameRandomizer);
 	void initVisitingAndGarrisonedHeroes();
 	void initCampaign();
 

+ 1 - 6
lib/gameState/GameStatePackVisitor.cpp

@@ -101,11 +101,6 @@ void GameStatePackVisitor::visitAddQuest(AddQuest & pack)
 		logNetwork->warn("Warning! Attempt to add duplicated quest");
 }
 
-void GameStatePackVisitor::visitUpdateArtHandlerLists(UpdateArtHandlerLists & pack)
-{
-	gs.allocatedArtifacts = pack.allocatedArtifacts;
-}
-
 void GameStatePackVisitor::visitChangeFormation(ChangeFormation & pack)
 {
 	gs.getHero(pack.hid)->setFormation(pack.formation);
@@ -1142,7 +1137,7 @@ void GameStatePackVisitor::visitHeroLevelUp(HeroLevelUp & pack)
 {
 	auto * hero = gs.getHero(pack.heroId);
 	assert(hero);
-	hero->levelUp(pack.skills);
+	hero->levelUp();
 }
 
 void GameStatePackVisitor::visitCommanderLevelUp(CommanderLevelUp & pack)

+ 0 - 1
lib/gameState/GameStatePackVisitor.h

@@ -84,7 +84,6 @@ public:
 	void visitEntitiesChanged(EntitiesChanged & pack) override;
 	void visitSetCommanderProperty(SetCommanderProperty & pack) override;
 	void visitAddQuest(AddQuest & pack) override;
-	void visitUpdateArtHandlerLists(UpdateArtHandlerLists & pack) override;
 	void visitChangeFormation(ChangeFormation & pack) override;
 	void visitChangeSpells(ChangeSpells & pack) override;
 	void visitSetAvailableHero(SetAvailableHero & pack) override;

+ 40 - 33
lib/json/JsonRandom.cpp

@@ -19,6 +19,7 @@
 #include "JsonBonus.h"
 
 #include "../callback/IGameInfoCallback.h"
+#include "../callback/IGameRandomizer.h"
 #include "../constants/StringConstants.h"
 #include "../GameLibrary.h"
 #include "../CCreatureHandler.h"
@@ -49,6 +50,12 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 	: std::runtime_error(message + " Input was: " + cleanupJson(input))
 {}
 
+JsonRandom::JsonRandom(IGameInfoCallback * cb, IGameRandomizer & gameRandomizer)
+	: GameCallbackHolder(cb)
+	, gameRandomizer(gameRandomizer)
+	, rng(gameRandomizer.getDefault())
+{
+}
 
 	si32 JsonRandom::loadVariable(const std::string & variableGroup, const std::string & value, const Variables & variables, si32 defaultValue)
 	{
@@ -68,7 +75,7 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 		return variables.at(variableID);
 	}
 
-	si32 JsonRandom::loadValue(const JsonNode & value, vstd::RNG & rng, const Variables & variables, si32 defaultValue)
+	si32 JsonRandom::loadValue(const JsonNode & value, const Variables & variables, si32 defaultValue)
 	{
 		if(value.isNull())
 			return defaultValue;
@@ -82,14 +89,14 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 			const auto & vector = value.Vector();
 
 			size_t index= rng.nextInt64(0, vector.size()-1);
-			return loadValue(vector[index], rng, variables, 0);
+			return loadValue(vector[index], variables, 0);
 		}
 		if(value.isStruct())
 		{
 			if (!value["amount"].isNull())
-				return loadValue(value["amount"], rng, variables, defaultValue);
-			si32 min = loadValue(value["min"], rng, variables, 0);
-			si32 max = loadValue(value["max"], rng, variables, 0);
+				return loadValue(value["amount"], variables, defaultValue);
+			si32 min = loadValue(value["min"], variables, 0);
+			si32 max = loadValue(value["max"], variables, 0);
 			return rng.nextInt64(min, max);
 		}
 		return defaultValue;
@@ -280,25 +287,25 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 		return valuesSet;
 	}
 
-	TResources JsonRandom::loadResources(const JsonNode & value, vstd::RNG & rng, const Variables & variables)
+	TResources JsonRandom::loadResources(const JsonNode & value, const Variables & variables)
 	{
 		TResources ret;
 
 		if (value.isVector())
 		{
 			for (const auto & entry : value.Vector())
-				ret += loadResource(entry, rng, variables);
+				ret += loadResource(entry, variables);
 			return ret;
 		}
 
 		for (size_t i=0; i<GameConstants::RESOURCE_QUANTITY; i++)
 		{
-			ret[i] = loadValue(value[GameConstants::RESOURCE_NAMES[i]], rng, variables);
+			ret[i] = loadValue(value[GameConstants::RESOURCE_NAMES[i]], variables);
 		}
 		return ret;
 	}
 
-	TResources JsonRandom::loadResource(const JsonNode & value, vstd::RNG & rng, const Variables & variables)
+	TResources JsonRandom::loadResource(const JsonNode & value, const Variables & variables)
 	{
 		std::set<GameResID> defaultResources{
 			GameResID::WOOD,
@@ -312,14 +319,14 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 
 		std::set<GameResID> potentialPicks = filterKeys(value, defaultResources, variables);
 		GameResID resourceID = *RandomGeneratorUtil::nextItem(potentialPicks, rng);
-		si32 resourceAmount = loadValue(value, rng, variables, 0);
+		si32 resourceAmount = loadValue(value, variables, 0);
 
 		TResources ret;
 		ret[resourceID] = resourceAmount;
 		return ret;
 	}
 
-	PrimarySkill JsonRandom::loadPrimary(const JsonNode & value, vstd::RNG & rng, const Variables & variables)
+	PrimarySkill JsonRandom::loadPrimary(const JsonNode & value, const Variables & variables)
 	{
 		std::set<PrimarySkill> defaultSkills{
 			PrimarySkill::ATTACK,
@@ -331,7 +338,7 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 		return *RandomGeneratorUtil::nextItem(potentialPicks, rng);
 	}
 
-	std::vector<si32> JsonRandom::loadPrimaries(const JsonNode & value, vstd::RNG & rng, const Variables & variables)
+	std::vector<si32> JsonRandom::loadPrimaries(const JsonNode & value, const Variables & variables)
 	{
 		std::vector<si32> ret(GameConstants::PRIMARY_SKILLS, 0);
 		std::set<PrimarySkill> defaultSkills{
@@ -346,7 +353,7 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 			for(const auto & pair : value.Struct())
 			{
 				PrimarySkill id = decodeKey<PrimarySkill>(pair.second.getModScope(), pair.first, variables);
-				ret[id.getNum()] += loadValue(pair.second, rng, variables);
+				ret[id.getNum()] += loadValue(pair.second, variables);
 			}
 		}
 		if(value.isVector())
@@ -357,13 +364,13 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 				PrimarySkill skillID = *RandomGeneratorUtil::nextItem(potentialPicks, rng);
 
 				defaultSkills.erase(skillID);
-				ret[skillID.getNum()] += loadValue(element, rng, variables);
+				ret[skillID.getNum()] += loadValue(element, variables);
 			}
 		}
 		return ret;
 	}
 
-	SecondarySkill JsonRandom::loadSecondary(const JsonNode & value, vstd::RNG & rng, const Variables & variables)
+	SecondarySkill JsonRandom::loadSecondary(const JsonNode & value, const Variables & variables)
 	{
 		std::set<SecondarySkill> defaultSkills;
 		for(const auto & skill : LIBRARY->skillh->objects)
@@ -374,7 +381,7 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 		return *RandomGeneratorUtil::nextItem(potentialPicks, rng);
 	}
 
-	std::map<SecondarySkill, si32> JsonRandom::loadSecondaries(const JsonNode & value, vstd::RNG & rng, const Variables & variables)
+	std::map<SecondarySkill, si32> JsonRandom::loadSecondaries(const JsonNode & value, const Variables & variables)
 	{
 		std::map<SecondarySkill, si32> ret;
 		if(value.isStruct())
@@ -382,7 +389,7 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 			for(const auto & pair : value.Struct())
 			{
 				SecondarySkill id = decodeKey<SecondarySkill>(pair.second.getModScope(), pair.first, variables);
-				ret[id] = loadValue(pair.second, rng, variables);
+				ret[id] = loadValue(pair.second, variables);
 			}
 		}
 		if(value.isVector())
@@ -398,13 +405,13 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 				SecondarySkill skillID = *RandomGeneratorUtil::nextItem(potentialPicks, rng);
 
 				defaultSkills.erase(skillID); //avoid dupicates
-				ret[skillID] = loadValue(element, rng, variables);
+				ret[skillID] = loadValue(element, variables);
 			}
 		}
 		return ret;
 	}
 
-	ArtifactID JsonRandom::loadArtifact(const JsonNode & value, vstd::RNG & rng, const Variables & variables)
+	ArtifactID JsonRandom::loadArtifact(const JsonNode & value, const Variables & variables)
 	{
 		std::set<ArtifactID> allowedArts;
 		for(const auto & artifact : LIBRARY->arth->objects)
@@ -413,20 +420,20 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 
 		std::set<ArtifactID> potentialPicks = filterKeys(value, allowedArts, variables);
 
-		return cb->gameState().pickRandomArtifact(rng, potentialPicks);
+		return gameRandomizer.rollArtifact(potentialPicks);
 	}
 
-	std::vector<ArtifactID> JsonRandom::loadArtifacts(const JsonNode & value, vstd::RNG & rng, const Variables & variables)
+	std::vector<ArtifactID> JsonRandom::loadArtifacts(const JsonNode & value, const Variables & variables)
 	{
 		std::vector<ArtifactID> ret;
 		for (const JsonNode & entry : value.Vector())
 		{
-			ret.push_back(loadArtifact(entry, rng, variables));
+			ret.push_back(loadArtifact(entry, variables));
 		}
 		return ret;
 	}
 
-	std::vector<ArtifactPosition> JsonRandom::loadArtifactSlots(const JsonNode & value, vstd::RNG & rng, const Variables & variables)
+	std::vector<ArtifactPosition> JsonRandom::loadArtifactSlots(const JsonNode & value, const Variables & variables)
 	{
 		std::set<ArtifactPosition> allowedSlots;
 		for(ArtifactPosition pos(0); pos < ArtifactPosition::BACKPACK_START; ++pos)
@@ -441,7 +448,7 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 		return ret;
 	}
 
-	SpellID JsonRandom::loadSpell(const JsonNode & value, vstd::RNG & rng, const Variables & variables)
+	SpellID JsonRandom::loadSpell(const JsonNode & value, const Variables & variables)
 	{
 		std::set<SpellID> defaultSpells;
 		for(const auto & spell : LIBRARY->spellh->objects)
@@ -458,17 +465,17 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 		return *RandomGeneratorUtil::nextItem(potentialPicks, rng);
 	}
 
-	std::vector<SpellID> JsonRandom::loadSpells(const JsonNode & value, vstd::RNG & rng, const Variables & variables)
+	std::vector<SpellID> JsonRandom::loadSpells(const JsonNode & value, const Variables & variables)
 	{
 		std::vector<SpellID> ret;
 		for (const JsonNode & entry : value.Vector())
 		{
-			ret.push_back(loadSpell(entry, rng, variables));
+			ret.push_back(loadSpell(entry, variables));
 		}
 		return ret;
 	}
 
-	std::vector<PlayerColor> JsonRandom::loadColors(const JsonNode & value, vstd::RNG & rng, const Variables & variables)
+	std::vector<PlayerColor> JsonRandom::loadColors(const JsonNode & value, const Variables & variables)
 	{
 		std::vector<PlayerColor> ret;
 		std::set<PlayerColor> defaultPlayers;
@@ -484,7 +491,7 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 		return ret;
 	}
 
-	std::vector<HeroTypeID> JsonRandom::loadHeroes(const JsonNode & value, vstd::RNG & rng)
+	std::vector<HeroTypeID> JsonRandom::loadHeroes(const JsonNode & value)
 	{
 		std::vector<HeroTypeID> ret;
 		for(auto & entry : value.Vector())
@@ -494,7 +501,7 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 		return ret;
 	}
 
-	std::vector<HeroClassID> JsonRandom::loadHeroClasses(const JsonNode & value, vstd::RNG & rng)
+	std::vector<HeroClassID> JsonRandom::loadHeroClasses(const JsonNode & value)
 	{
 		std::vector<HeroClassID> ret;
 		for(auto & entry : value.Vector())
@@ -504,7 +511,7 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 		return ret;
 	}
 
-	CStackBasicDescriptor JsonRandom::loadCreature(const JsonNode & value, vstd::RNG & rng, const Variables & variables)
+	CStackBasicDescriptor JsonRandom::loadCreature(const JsonNode & value, const Variables & variables)
 	{
 		CStackBasicDescriptor stack;
 
@@ -525,7 +532,7 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 			throw JsonRandomizationException("Invalid creature picked!", value);
 
 		stack.setType(pickedCreature.toCreature());
-		stack.setCount(loadValue(value, rng, variables));
+		stack.setCount(loadValue(value, variables));
 		if (!value["upgradeChance"].isNull() && !stack.getCreature()->upgrades.empty())
 		{
 			if (int(value["upgradeChance"].Float()) > rng.nextInt(99)) // select random upgrade
@@ -536,12 +543,12 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 		return stack;
 	}
 
-	std::vector<CStackBasicDescriptor> JsonRandom::loadCreatures(const JsonNode & value, vstd::RNG & rng, const Variables & variables)
+	std::vector<CStackBasicDescriptor> JsonRandom::loadCreatures(const JsonNode & value, const Variables & variables)
 	{
 		std::vector<CStackBasicDescriptor> ret;
 		for (const JsonNode & node : value.Vector())
 		{
-			ret.push_back(loadCreature(node, rng, variables));
+			ret.push_back(loadCreature(node, variables));
 		}
 		return ret;
 	}

+ 21 - 18
lib/json/JsonRandom.h

@@ -26,6 +26,7 @@ using JsonVector = std::vector<JsonNode>;
 struct Bonus;
 struct Component;
 class CStackBasicDescriptor;
+class IGameRandomizer;
 
 class JsonRandomizationException : public std::runtime_error
 {
@@ -36,6 +37,8 @@ public:
 
 class JsonRandom : public GameCallbackHolder
 {
+	IGameRandomizer & gameRandomizer;
+	vstd::RNG & rng;
 public:
 	using Variables = std::map<std::string, int>;
 
@@ -55,7 +58,7 @@ private:
 	si32 loadVariable(const std::string & variableGroup, const std::string & value, const Variables & variables, si32 defaultValue);
 
 public:
-	using GameCallbackHolder::GameCallbackHolder;
+	JsonRandom(IGameInfoCallback *cb, IGameRandomizer & gameRandomizer);
 
 	struct RandomStackInfo
 	{
@@ -64,29 +67,29 @@ public:
 		si32 maxAmount;
 	};
 
-	si32 loadValue(const JsonNode & value, vstd::RNG & rng, const Variables & variables, si32 defaultValue = 0);
+	si32 loadValue(const JsonNode & value, const Variables & variables, si32 defaultValue = 0);
 
-	TResources loadResources(const JsonNode & value, vstd::RNG & rng, const Variables & variables);
-	TResources loadResource(const JsonNode & value, vstd::RNG & rng, const Variables & variables);
-	PrimarySkill loadPrimary(const JsonNode & value, vstd::RNG & rng, const Variables & variables);
-	std::vector<si32> loadPrimaries(const JsonNode & value, vstd::RNG & rng, const Variables & variables);
-	SecondarySkill loadSecondary(const JsonNode & value, vstd::RNG & rng, const Variables & variables);
-	std::map<SecondarySkill, si32> loadSecondaries(const JsonNode & value, vstd::RNG & rng, const Variables & variables);
+	TResources loadResources(const JsonNode & value, const Variables & variables);
+	TResources loadResource(const JsonNode & value, const Variables & variables);
+	PrimarySkill loadPrimary(const JsonNode & value, const Variables & variables);
+	std::vector<si32> loadPrimaries(const JsonNode & value, const Variables & variables);
+	SecondarySkill loadSecondary(const JsonNode & value, const Variables & variables);
+	std::map<SecondarySkill, si32> loadSecondaries(const JsonNode & value, const Variables & variables);
 
-	ArtifactID loadArtifact(const JsonNode & value, vstd::RNG & rng, const Variables & variables);
-	std::vector<ArtifactID> loadArtifacts(const JsonNode & value, vstd::RNG & rng, const Variables & variables);
-	std::vector<ArtifactPosition> loadArtifactSlots(const JsonNode & value, vstd::RNG & rng, const Variables & variables);
+	ArtifactID loadArtifact(const JsonNode & value, const Variables & variables);
+	std::vector<ArtifactID> loadArtifacts(const JsonNode & value, const Variables & variables);
+	std::vector<ArtifactPosition> loadArtifactSlots(const JsonNode & value, const Variables & variables);
 
-	SpellID loadSpell(const JsonNode & value, vstd::RNG & rng, const Variables & variables);
-	std::vector<SpellID> loadSpells(const JsonNode & value, vstd::RNG & rng, const Variables & variables);
+	SpellID loadSpell(const JsonNode & value, const Variables & variables);
+	std::vector<SpellID> loadSpells(const JsonNode & value, const Variables & variables);
 
-	CStackBasicDescriptor loadCreature(const JsonNode & value, vstd::RNG & rng, const Variables & variables);
-	std::vector<CStackBasicDescriptor> loadCreatures(const JsonNode & value, vstd::RNG & rng, const Variables & variables);
+	CStackBasicDescriptor loadCreature(const JsonNode & value, const Variables & variables);
+	std::vector<CStackBasicDescriptor> loadCreatures(const JsonNode & value, const Variables & variables);
 	std::vector<RandomStackInfo> evaluateCreatures(const JsonNode & value, const Variables & variables);
 
-	std::vector<PlayerColor> loadColors(const JsonNode & value, vstd::RNG & rng, const Variables & variables);
-	std::vector<HeroTypeID> loadHeroes(const JsonNode & value, vstd::RNG & rng);
-	std::vector<HeroClassID> loadHeroClasses(const JsonNode & value, vstd::RNG & rng);
+	std::vector<PlayerColor> loadColors(const JsonNode & value, const Variables & variables);
+	std::vector<HeroTypeID> loadHeroes(const JsonNode & value);
+	std::vector<HeroClassID> loadHeroClasses(const JsonNode & value);
 
 	static std::vector<Bonus> loadBonuses(const JsonNode & value);
 };

+ 2 - 1
lib/mapObjectConstructors/AObjectTypeHandler.h

@@ -24,6 +24,7 @@ class ObjectTemplate;
 class CGObjectInstance;
 class IObjectInfo;
 class IGameInfoCallback;
+class IGameRandomizer;
 
 /// Class responsible for creation of objects of specific type & subtype
 class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable
@@ -122,7 +123,7 @@ public:
 
 	/// Configures object properties. Should be re-entrable, resetting state of the object if necessarily
 	/// This should set remaining properties, including randomized or depending on map
-	virtual void configureObject(CGObjectInstance * object, vstd::RNG & rng) const = 0;
+	virtual void configureObject(CGObjectInstance * object, IGameRandomizer & gameRandomizer) const = 0;
 
 	/// Returns object configuration, if available. Otherwise returns NULL
 	virtual std::unique_ptr<IObjectInfo> getObjectInfo(std::shared_ptr<const ObjectTemplate> tmpl) const;

+ 3 - 3
lib/mapObjectConstructors/CDefaultObjectTypeHandler.h

@@ -17,14 +17,14 @@ VCMI_LIB_NAMESPACE_BEGIN
 template<class ObjectType>
 class CDefaultObjectTypeHandler : public AObjectTypeHandler
 {
-	void configureObject(CGObjectInstance * object, vstd::RNG & rng) const final
+	void configureObject(CGObjectInstance * object, IGameRandomizer & gameRandomizer) const final
 	{
 		ObjectType * castedObject = dynamic_cast<ObjectType*>(object);
 
 		if(castedObject == nullptr)
 			throw std::runtime_error("Unexpected object type!");
 
-		randomizeObject(castedObject, rng);
+		randomizeObject(castedObject, gameRandomizer);
 	}
 
 	std::shared_ptr<CGObjectInstance> create(IGameInfoCallback * cb, std::shared_ptr<const ObjectTemplate> tmpl) const final
@@ -43,7 +43,7 @@ class CDefaultObjectTypeHandler : public AObjectTypeHandler
 
 protected:
 	virtual void initializeObject(ObjectType * object) const {}
-	virtual void randomizeObject(ObjectType * object, vstd::RNG & rng) const {}
+	virtual void randomizeObject(ObjectType * object, IGameRandomizer & gameRandomizer) const {}
 	virtual std::shared_ptr<ObjectType> createObject(IGameInfoCallback * cb) const
 	{
 		return std::make_shared<ObjectType>(cb);

+ 4 - 4
lib/mapObjectConstructors/CRewardableConstructor.cpp

@@ -55,13 +55,13 @@ void CRewardableConstructor::assignBonuses(std::vector<Bonus> & bonuses, MapObje
 	}
 }
 
-Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameInfoCallback * cb, vstd::RNG & rand, MapObjectID objectID, const std::map<std::string, JsonNode> & presetVariables) const
+Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameInfoCallback * cb, IGameRandomizer & gameRandomizer, MapObjectID objectID, const std::map<std::string, JsonNode> & presetVariables) const
 {
 	Rewardable::Configuration result;
 	result.variables.preset = presetVariables;
 
 	try {
-		objectInfo.configureObject(result, rand, cb);
+		objectInfo.configureObject(result, gameRandomizer, cb);
 	}
 	catch (const JsonRandomizationException & e)
 	{
@@ -78,14 +78,14 @@ Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameInf
 	return result;
 }
 
-void CRewardableConstructor::configureObject(CGObjectInstance * object, vstd::RNG & rng) const
+void CRewardableConstructor::configureObject(CGObjectInstance * object, IGameRandomizer & gameRandomizer) const
 {
 	auto * rewardableObject = dynamic_cast<CRewardableObject*>(object);
 
 	if (!rewardableObject)
 		throw std::runtime_error("Object " + std::to_string(object->getObjGroupIndex()) + ", " + std::to_string(object->getObjTypeIndex()) + " is not a rewardable object!" );
 
-	rewardableObject->configuration = generateConfiguration(object->cb, rng, object->ID, rewardableObject->configuration.variables.preset);
+	rewardableObject->configuration = generateConfiguration(object->cb, gameRandomizer, object->ID, rewardableObject->configuration.variables.preset);
 	rewardableObject->initializeGuards();
 
 	if (rewardableObject->configuration.info.empty())

+ 2 - 2
lib/mapObjectConstructors/CRewardableConstructor.h

@@ -30,11 +30,11 @@ public:
 
 	std::shared_ptr<CGObjectInstance> create(IGameInfoCallback * cb, std::shared_ptr<const ObjectTemplate> tmpl = nullptr) const override;
 
-	void configureObject(CGObjectInstance * object, vstd::RNG & rng) const override;
+	void configureObject(CGObjectInstance * object, IGameRandomizer & gameRandomizer) const override;
 
 	std::unique_ptr<IObjectInfo> getObjectInfo(std::shared_ptr<const ObjectTemplate> tmpl) const override;
 
-	Rewardable::Configuration generateConfiguration(IGameInfoCallback * cb, vstd::RNG & rand, MapObjectID objectID, const std::map<std::string, JsonNode> & presetVariables) const;
+	Rewardable::Configuration generateConfiguration(IGameInfoCallback * cb, IGameRandomizer & gameRandomizer, MapObjectID objectID, const std::map<std::string, JsonNode> & presetVariables) const;
 };
 
 VCMI_LIB_NAMESPACE_END

+ 8 - 8
lib/mapObjectConstructors/CommonConstructors.cpp

@@ -79,16 +79,16 @@ int ResourceInstanceConstructor::getAmountMultiplier() const
 	return config["amountMultiplier"].Integer();
 }
 
-void ResourceInstanceConstructor::randomizeObject(CGResource * object, vstd::RNG & rng) const
+void ResourceInstanceConstructor::randomizeObject(CGResource * object, IGameRandomizer & gameRandomizer) const
 {
 	if (object->amount != CGResource::RANDOM_AMOUNT)
 		return;
 
-	JsonRandom randomizer(object->cb);
+	JsonRandom randomizer(object->cb, gameRandomizer);
 	JsonRandom::Variables dummy;
 
 	if (!config["amounts"].isNull())
-		object->amount = randomizer.loadValue(config["amounts"], rng, dummy, 0) * getAmountMultiplier();
+		object->amount = randomizer.loadValue(config["amounts"], dummy, 0) * getAmountMultiplier();
 	else
 		object->amount = 5 * getAmountMultiplier();
 }
@@ -136,7 +136,7 @@ void CTownInstanceConstructor::initializeObject(CGTownInstance * obj) const
 	obj->tempOwner = PlayerColor::NEUTRAL;
 }
 
-void CTownInstanceConstructor::randomizeObject(CGTownInstance * object, vstd::RNG & rng) const
+void CTownInstanceConstructor::randomizeObject(CGTownInstance * object, IGameRandomizer & gameRandomizer) const
 {
 	auto templ = getOverride(object->cb->getTile(object->pos)->getTerrainID(), object);
 	if(templ)
@@ -227,7 +227,7 @@ std::shared_ptr<const ObjectTemplate> CHeroInstanceConstructor::getOverride(Terr
 	return candidateBase;
 }
 
-void CHeroInstanceConstructor::randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const
+void CHeroInstanceConstructor::randomizeObject(CGHeroInstance * object, IGameRandomizer & gameRandomizer) const
 {
 
 }
@@ -340,14 +340,14 @@ const std::set<EMarketMode> & MarketInstanceConstructor::availableModes() const
 	return marketModes;
 }
 
-void MarketInstanceConstructor::randomizeObject(CGMarket * object, vstd::RNG & rng) const
+void MarketInstanceConstructor::randomizeObject(CGMarket * object, IGameRandomizer & gameRandomizer) const
 {
-	JsonRandom randomizer(object->cb);
+	JsonRandom randomizer(object->cb, gameRandomizer);
 	JsonRandom::Variables emptyVariables;
 
 	if(auto * university = dynamic_cast<CGUniversity *>(object))
 	{
-		for(auto skill : randomizer.loadSecondaries(predefinedOffer, rng, emptyVariables))
+		for(auto skill : randomizer.loadSecondaries(predefinedOffer, emptyVariables))
 			university->skills.push_back(skill.first);
 	}
 }

+ 4 - 4
lib/mapObjectConstructors/CommonConstructors.h

@@ -61,7 +61,7 @@ public:
 	int getAmountMultiplier() const;
 	int getBaseAmount(vstd::RNG & rng) const;
 
-	void randomizeObject(CGResource * object, vstd::RNG & rng) const override;
+	void randomizeObject(CGResource * object, IGameRandomizer & gameRandomizer) const override;
 };
 
 class CTownInstanceConstructor : public CDefaultObjectTypeHandler<CGTownInstance>
@@ -76,7 +76,7 @@ public:
 	std::map<std::string, LogicalExpression<BuildingID>> filters;
 
 	void initializeObject(CGTownInstance * object) const override;
-	void randomizeObject(CGTownInstance * object, vstd::RNG & rng) const override;
+	void randomizeObject(CGTownInstance * object, IGameRandomizer & gameRandomizer) const override;
 	void afterLoadFinalization() override;
 
 	bool hasNameTextID() const override;
@@ -99,7 +99,7 @@ class CHeroInstanceConstructor : public CDefaultObjectTypeHandler<CGHeroInstance
 	void initTypeData(const JsonNode & input) override;
 
 public:
-	void randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const override;
+	void randomizeObject(CGHeroInstance * object, IGameRandomizer & gameRandomizer) const override;
 
 	bool hasNameTextID() const override;
 	std::string getNameTextID() const override;
@@ -138,7 +138,7 @@ class MarketInstanceConstructor : public CDefaultObjectTypeHandler<CGMarket>
 	void initTypeData(const JsonNode & config) override;
 public:
 	std::shared_ptr<CGMarket> createObject(IGameInfoCallback * cb) const override;
-	void randomizeObject(CGMarket * object, vstd::RNG & rng) const override;
+	void randomizeObject(CGMarket * object, IGameRandomizer & gameRandomizer) const override;
 
 	const std::set<EMarketMode> & availableModes() const;
 	bool hasDescription() const;

+ 3 - 3
lib/mapObjectConstructors/DwellingInstanceConstructor.cpp

@@ -100,9 +100,9 @@ void DwellingInstanceConstructor::initializeObject(CGDwelling * obj) const
 	}
 }
 
-void DwellingInstanceConstructor::randomizeObject(CGDwelling * dwelling, vstd::RNG &rng) const
+void DwellingInstanceConstructor::randomizeObject(CGDwelling * dwelling, IGameRandomizer & gameRandomizer) const
 {
-	JsonRandom randomizer(dwelling->cb);
+	JsonRandom randomizer(dwelling->cb, gameRandomizer);
 
 	dwelling->creatures.clear();
 	dwelling->creatures.reserve(availableCreatures.size());
@@ -128,7 +128,7 @@ void DwellingInstanceConstructor::randomizeObject(CGDwelling * dwelling, vstd::R
 	{
 		//custom guards (eg. Elemental Conflux)
 		JsonRandom::Variables emptyVariables;
-		for(auto & stack : randomizer.loadCreatures(guards, rng, emptyVariables))
+		for(auto & stack : randomizer.loadCreatures(guards, emptyVariables))
 		{
 			dwelling->putStack(SlotID(dwelling->stacksCount()), std::make_unique<CStackInstance>(dwelling->cb, stack.getId(), stack.getCount()));
 		}

+ 1 - 1
lib/mapObjectConstructors/DwellingInstanceConstructor.h

@@ -34,7 +34,7 @@ public:
 	bool hasNameTextID() const override;
 
 	void initializeObject(CGDwelling * object) const override;
-	void randomizeObject(CGDwelling * object, vstd::RNG & rng) const override;
+	void randomizeObject(CGDwelling * object, IGameRandomizer & gameRandomizer) const override;
 
 	bool isBannedForRandomDwelling() const;
 	bool producesCreature(const CCreature * crea) const;

+ 16 - 15
lib/mapObjects/CGCreature.cpp

@@ -17,6 +17,7 @@
 #include "../IGameSettings.h"
 #include "../callback/IGameInfoCallback.h"
 #include "../callback/IGameEventCallback.h"
+#include "../callback/IGameRandomizer.h"
 #include "../gameState/CGameState.h"
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
 #include "../networkPacks/PacksForClient.h"
@@ -221,33 +222,33 @@ TQuantity CGCreature::getJoiningAmount() const
 	return std::max(static_cast<int64_t>(1), getStackCount(SlotID(0)) * cb->getSettings().getInteger(EGameSettings::CREATURES_JOINING_PERCENTAGE) / 100);
 }
 
-void CGCreature::pickRandomObject(vstd::RNG & rand)
+void CGCreature::pickRandomObject(IGameRandomizer & gameRandomizer)
 {
 	switch(ID.toEnum())
 	{
 		case MapObjectID::RANDOM_MONSTER:
-			subID = LIBRARY->creh->pickRandomMonster(rand);
+			subID = gameRandomizer.rollCreature();
 			break;
 		case MapObjectID::RANDOM_MONSTER_L1:
-			subID = LIBRARY->creh->pickRandomMonster(rand, 1);
+			subID = gameRandomizer.rollCreature(1);
 			break;
 		case MapObjectID::RANDOM_MONSTER_L2:
-			subID = LIBRARY->creh->pickRandomMonster(rand, 2);
+			subID = gameRandomizer.rollCreature(2);
 			break;
 		case MapObjectID::RANDOM_MONSTER_L3:
-			subID = LIBRARY->creh->pickRandomMonster(rand, 3);
+			subID = gameRandomizer.rollCreature(3);
 			break;
 		case MapObjectID::RANDOM_MONSTER_L4:
-			subID = LIBRARY->creh->pickRandomMonster(rand, 4);
+			subID = gameRandomizer.rollCreature(4);
 			break;
 		case MapObjectID::RANDOM_MONSTER_L5:
-			subID = LIBRARY->creh->pickRandomMonster(rand, 5);
+			subID = gameRandomizer.rollCreature(5);
 			break;
 		case MapObjectID::RANDOM_MONSTER_L6:
-			subID = LIBRARY->creh->pickRandomMonster(rand, 6);
+			subID = gameRandomizer.rollCreature(6);
 			break;
 		case MapObjectID::RANDOM_MONSTER_L7:
-			subID = LIBRARY->creh->pickRandomMonster(rand, 7);
+			subID = gameRandomizer.rollCreature(7);
 			break;
 	}
 
@@ -266,7 +267,7 @@ void CGCreature::pickRandomObject(vstd::RNG & rand)
 	setType(ID, subID);
 }
 
-void CGCreature::initObj(vstd::RNG & rand)
+void CGCreature::initObj(IGameRandomizer & gameRandomizer)
 {
 	blockVisit = true;
 	switch(character)
@@ -275,13 +276,13 @@ void CGCreature::initObj(vstd::RNG & rand)
 		character = -4;
 		break;
 	case 1:
-		character = rand.nextInt(1, 7);
+		character = gameRandomizer.getDefault().nextInt(1, 7);
 		break;
 	case 2:
-		character = rand.nextInt(1, 10);
+		character = gameRandomizer.getDefault().nextInt(1, 10);
 		break;
 	case 3:
-		character = rand.nextInt(4, 10);
+		character = gameRandomizer.getDefault().nextInt(4, 10);
 		break;
 	case 4:
 		character = 10;
@@ -292,7 +293,7 @@ void CGCreature::initObj(vstd::RNG & rand)
 	const Creature * c = getCreature();
 	if(stacks[SlotID(0)]->getCount() == 0)
 	{
-		stacks[SlotID(0)]->setCount(rand.nextInt(c->getAdvMapAmountMin(), c->getAdvMapAmountMax()));
+		stacks[SlotID(0)]->setCount(gameRandomizer.getDefault().nextInt(c->getAdvMapAmountMin(), c->getAdvMapAmountMax()));
 
 		if(stacks[SlotID(0)]->getCount() == 0) //armies with 0 creatures are illegal
 		{
@@ -305,7 +306,7 @@ void CGCreature::initObj(vstd::RNG & rand)
 	refusedJoining = false;
 }
 
-void CGCreature::newTurn(IGameEventCallback & gameEvents) const
+void CGCreature::newTurn(IGameEventCallback & gameEvents, IGameRandomizer & gameRandomizer) const
 {//Works only for stacks of single type of size up to 2 millions
 	if (!notGrowingTeam)
 	{

+ 3 - 3
lib/mapObjects/CGCreature.h

@@ -44,9 +44,9 @@ public:
 	std::string getPopupText(PlayerColor player) const override;
 	std::string getPopupText(const CGHeroInstance * hero) const override;
 	std::vector<Component> getPopupComponents(PlayerColor player) const override;
-	void initObj(vstd::RNG & rand) override;
-	void pickRandomObject(vstd::RNG & rand) override;
-	void newTurn(IGameEventCallback & gameEvents) const override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
+	void pickRandomObject(IGameRandomizer & gameRandomizer) override;
+	void newTurn(IGameEventCallback & gameEvents, IGameRandomizer & gameRandomizer) const override;
 	void battleFinished(IGameEventCallback & gameEvents, const CGHeroInstance *hero, const BattleResult &result) const override;
 	void blockingDialogAnswered(IGameEventCallback & gameEvents, const CGHeroInstance *hero, int32_t answer) const override;
 	CreatureID getCreatureID() const;

+ 13 - 12
lib/mapObjects/CGDwelling.cpp

@@ -12,6 +12,7 @@
 #include "CGDwelling.h"
 #include "../callback/IGameInfoCallback.h"
 #include "../callback/IGameEventCallback.h"
+#include "../callback/IGameRandomizer.h"
 #include "../serializer/JsonSerializeFormat.h"
 #include "../entities/faction/CTownHandler.h"
 #include "../mapping/CMap.h"
@@ -53,7 +54,7 @@ CGDwelling::CGDwelling(IGameInfoCallback *cb):
 
 CGDwelling::~CGDwelling() = default;
 
-FactionID CGDwelling::randomizeFaction(vstd::RNG & rand)
+FactionID CGDwelling::randomizeFaction(IGameRandomizer & gameRandomizer)
 {
 	if (ID == Obj::RANDOM_DWELLING_FACTION)
 		return FactionID(subID.getNum());
@@ -90,7 +91,7 @@ FactionID CGDwelling::randomizeFaction(vstd::RNG & rand)
 	if (linkedTown)
 	{
 		if(linkedTown->ID==Obj::RANDOM_TOWN)
-			linkedTown->pickRandomObject(rand); //we have to randomize the castle first
+			linkedTown->pickRandomObject(gameRandomizer); //we have to randomize the castle first
 
 		assert(linkedTown->ID == Obj::TOWN);
 		if(linkedTown->ID==Obj::TOWN)
@@ -98,7 +99,7 @@ FactionID CGDwelling::randomizeFaction(vstd::RNG & rand)
 	}
 
 	if(!randomizationInfo->allowedFactions.empty())
-		return *RandomGeneratorUtil::nextItem(randomizationInfo->allowedFactions, rand);
+		return *RandomGeneratorUtil::nextItem(randomizationInfo->allowedFactions, gameRandomizer.getDefault());
 
 
 	std::vector<FactionID> potentialPicks;
@@ -108,7 +109,7 @@ FactionID CGDwelling::randomizeFaction(vstd::RNG & rand)
 			potentialPicks.push_back(faction);
 
 	assert(!potentialPicks.empty());
-	return *RandomGeneratorUtil::nextItem(potentialPicks, rand);
+	return *RandomGeneratorUtil::nextItem(potentialPicks, gameRandomizer.getDefault());
 }
 
 int CGDwelling::randomizeLevel(vstd::RNG & rand)
@@ -128,12 +129,12 @@ int CGDwelling::randomizeLevel(vstd::RNG & rand)
 	return rand.nextInt(randomizationInfo->minLevel, randomizationInfo->maxLevel) - 1;
 }
 
-void CGDwelling::pickRandomObject(vstd::RNG & rand)
+void CGDwelling::pickRandomObject(IGameRandomizer & gameRandomizer)
 {
 	if (ID == Obj::RANDOM_DWELLING || ID == Obj::RANDOM_DWELLING_LVL || ID == Obj::RANDOM_DWELLING_FACTION)
 	{
-		FactionID faction = randomizeFaction(rand);
-		int level = randomizeLevel(rand);
+		FactionID faction = randomizeFaction(gameRandomizer);
+		int level = randomizeLevel(gameRandomizer.getDefault());
 		assert(faction != FactionID::NONE && faction != FactionID::NEUTRAL);
 		assert(level >= 0 && level <= 6);
 		randomizationInfo.reset();
@@ -168,14 +169,14 @@ void CGDwelling::pickRandomObject(vstd::RNG & rand)
 		{
 			logGlobal->error("Error: failed to find dwelling for %s of level %d", (*LIBRARY->townh)[faction]->getNameTranslated(), int(level));
 			ID = Obj::CREATURE_GENERATOR1;
-			subID = *RandomGeneratorUtil::nextItem(LIBRARY->objtypeh->knownSubObjects(Obj::CREATURE_GENERATOR1), rand);
+			subID = *RandomGeneratorUtil::nextItem(LIBRARY->objtypeh->knownSubObjects(Obj::CREATURE_GENERATOR1), gameRandomizer.getDefault());
 		}
 
 		setType(ID, subID);
 	}
 }
 
-void CGDwelling::initObj(vstd::RNG & rand)
+void CGDwelling::initObj(IGameRandomizer & gameRandomizer)
 {
 	switch(ID.toEnum())
 	{
@@ -183,7 +184,7 @@ void CGDwelling::initObj(vstd::RNG & rand)
 	case Obj::CREATURE_GENERATOR4:
 	case Obj::WAR_MACHINE_FACTORY:
 		{
-			getObjectHandler()->configureObject(this, rand);
+			getObjectHandler()->configureObject(this, gameRandomizer);
 			assert(!creatures.empty());
 			assert(!creatures[0].second.empty());
 			break;
@@ -278,7 +279,7 @@ void CGDwelling::onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstan
 	gameEvents.showBlockingDialog(this, &bd);
 }
 
-void CGDwelling::newTurn(IGameEventCallback & gameEvents) const
+void CGDwelling::newTurn(IGameEventCallback & gameEvents, IGameRandomizer & gameRandomizer) const
 {
 	if(cb->getDate(Date::DAY_OF_WEEK) != 1) //not first day of week
 		return;
@@ -289,7 +290,7 @@ void CGDwelling::newTurn(IGameEventCallback & gameEvents) const
 
 	if(ID == Obj::REFUGEE_CAMP) //if it's a refugee camp, we need to pick an available creature
 	{
-		gameEvents.setObjPropertyID(id, ObjProperty::AVAILABLE_CREATURE, LIBRARY->creh->pickRandomMonster(gameEvents.getRandomGenerator()));
+		gameEvents.setObjPropertyID(id, ObjProperty::AVAILABLE_CREATURE, gameRandomizer.rollCreature());
 	}
 
 	bool change = false;

+ 4 - 4
lib/mapObjects/CGDwelling.h

@@ -50,13 +50,13 @@ protected:
 	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 
 private:
-	FactionID randomizeFaction(vstd::RNG & rand);
+	FactionID randomizeFaction(IGameRandomizer & gameRandomizer);
 	int randomizeLevel(vstd::RNG & rand);
 
-	void pickRandomObject(vstd::RNG & rand) override;
-	void initObj(vstd::RNG & rand) override;
+	void pickRandomObject(IGameRandomizer & gameRandomizer) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
-	void newTurn(IGameEventCallback & gameEvents) const override;
+	void newTurn(IGameEventCallback & gameEvents, IGameRandomizer & gameRandomizer) const override;
 	void setPropertyDer(ObjProperty what, ObjPropertyID identifier) override;
 	void battleFinished(IGameEventCallback & gameEvents, const CGHeroInstance *hero, const BattleResult &result) const override;
 	void blockingDialogAnswered(IGameEventCallback & gameEvents, const CGHeroInstance *hero, int32_t answer) const override;

+ 42 - 154
lib/mapObjects/CGHeroInstance.cpp

@@ -17,6 +17,7 @@
 
 #include "../callback/IGameInfoCallback.h"
 #include "../callback/IGameEventCallback.h"
+#include "../callback/IGameRandomizer.h"
 #include "../texts/CGeneralTextHandler.h"
 #include "../TerrainHandler.h"
 #include "../RoadHandler.h"
@@ -347,16 +348,16 @@ CCommanderInstance * CGHeroInstance::getCommander()
 	return commander.get();
 }
 
-void CGHeroInstance::initObj(vstd::RNG & rand)
+void CGHeroInstance::initObj(IGameRandomizer & gameRandomizer)
 {
 	if (ID == Obj::HERO)
 		updateAppearance();
 }
 
-void CGHeroInstance::initHero(vstd::RNG & rand, const HeroTypeID & SUBID)
+void CGHeroInstance::initHero(IGameRandomizer & gameRandomizer, const HeroTypeID & SUBID)
 {
 	subID = SUBID.getNum();
-	initHero(rand);
+	initHero(gameRandomizer);
 }
 
 TObjectTypeHandler CGHeroInstance::getObjectHandler() const
@@ -376,7 +377,7 @@ void CGHeroInstance::updateAppearance()
 		appearance = app;
 }
 
-void CGHeroInstance::initHero(vstd::RNG & rand)
+void CGHeroInstance::initHero(IGameRandomizer & gameRandomizer)
 {
 	assert(validTypes(true));
 	
@@ -429,7 +430,7 @@ void CGHeroInstance::initHero(vstd::RNG & rand)
 	setFormation(EArmyFormation::LOOSE);
 	if (!stacksCount()) //standard army//initial army
 	{
-		initArmy(rand);
+		initArmy(gameRandomizer.getDefault());
 	}
 	assert(validTypes());
 
@@ -438,11 +439,11 @@ void CGHeroInstance::initHero(vstd::RNG & rand)
 
 	if(exp == UNINITIALIZED_EXPERIENCE)
 	{
-		initExp(rand);
+		initExp(gameRandomizer.getDefault());
 	}
 	else
 	{
-		levelUpAutomatically(rand);
+		levelUpAutomatically(gameRandomizer);
 	}
 
 	// load base hero bonuses, TODO: per-map loading of base hero bonuses
@@ -467,8 +468,6 @@ void CGHeroInstance::initHero(vstd::RNG & rand)
 		commander->giveTotalStackExperience(exp); //after our exp is set
 	}
 
-	skillsInfo = SecondarySkillsInfo();
-
 	//copy active (probably growing) bonuses from hero prototype to hero object
 	for(const std::shared_ptr<Bonus> & b : getHeroType()->specialty)
 		addNewBonus(b);
@@ -649,27 +648,13 @@ ui8 CGHeroInstance::maxlevelsToWisdom() const
 	return getHeroClass()->isMagicHero() ? 3 : 6;
 }
 
-CGHeroInstance::SecondarySkillsInfo::SecondarySkillsInfo():
-	magicSchoolCounter(1),
-	wisdomCounter(1)
-{}
-
-void CGHeroInstance::SecondarySkillsInfo::resetMagicSchoolCounter()
-{
-	magicSchoolCounter = 0;
-}
-void CGHeroInstance::SecondarySkillsInfo::resetWisdomCounter()
-{
-	wisdomCounter = 0;
-}
-
-void CGHeroInstance::pickRandomObject(vstd::RNG & randomGenerator)
+void CGHeroInstance::pickRandomObject(IGameRandomizer & gameRandomizer)
 {
 	assert(ID == Obj::HERO || ID == Obj::PRISON || ID == Obj::RANDOM_HERO);
 
 	if (ID == Obj::RANDOM_HERO)
 	{
-		auto selectedHero = cb->gameState().pickNextHeroType(randomGenerator, getOwner());
+		auto selectedHero = cb->gameState().pickNextHeroType(gameRandomizer.getDefault(), getOwner());
 
 		ID = Obj::HERO;
 		subID = selectedHero;
@@ -1425,34 +1410,18 @@ ArtBearer CGHeroInstance::bearerType() const
 	return ArtBearer::HERO;
 }
 
-std::vector<SecondarySkill> CGHeroInstance::getLevelUpProposedSecondarySkills(vstd::RNG & rand) const
+std::vector<SecondarySkill> CGHeroInstance::getLevelupSkillCandidates(IGameRandomizer & gameRandomizer) const
 {
-	auto getObligatorySkills = [](CSkill::Obligatory obl){
-		std::set<SecondarySkill> obligatory;
-		for(auto i = 0; i < LIBRARY->skillh->size(); i++)
-			if((*LIBRARY->skillh)[SecondarySkill(i)]->obligatory(obl))
-				obligatory.insert(i); //Always return all obligatory skills
-
-		return obligatory;
-	};
-
-	auto intersect = [](const std::set<SecondarySkill> & left, const std::set<SecondarySkill> & right)
-	{
-		std::set<SecondarySkill> intersect;
-		std::set_intersection(left.begin(), left.end(), right.begin(), right.end(),
-						 std::inserter(intersect, intersect.begin()));
-		return intersect;
-	};
-
-	std::set<SecondarySkill> wisdomList = getObligatorySkills(CSkill::Obligatory::MAJOR);
-	std::set<SecondarySkill> schoolList = getObligatorySkills(CSkill::Obligatory::MINOR);
-
 	std::set<SecondarySkill> basicAndAdv;
 	std::set<SecondarySkill> none;
+	std::vector<SecondarySkill>	skills;
 
-	for(int i = 0; i < LIBRARY->skillh->size(); i++)
-		if (canLearnSkill(SecondarySkill(i)))
-			none.insert(SecondarySkill(i));
+	if (canLearnSkill())
+	{
+		for(int i = 0; i < LIBRARY->skillh->size(); i++)
+			if (canLearnSkill(SecondarySkill(i)))
+				none.insert(SecondarySkill(i));
+	}
 
 	for(const auto & elem : secSkills)
 	{
@@ -1461,94 +1430,31 @@ std::vector<SecondarySkill> CGHeroInstance::getLevelUpProposedSecondarySkills(vs
 		none.erase(elem.first);
 	}
 
-	bool wantsWisdom = skillsInfo.wisdomCounter + 1 >= maxlevelsToWisdom();
-	bool wantsSchool = skillsInfo.magicSchoolCounter + 1 >= maxlevelsToMagicSchool();
-
-	std::vector<SecondarySkill> skills;
-
-	auto chooseSkill = [&](std::set<SecondarySkill> & options)
-	{
-		bool selectWisdom = wantsWisdom && !intersect(options, wisdomList).empty();
-		bool selectSchool = wantsSchool && !intersect(options, schoolList).empty();
-		SecondarySkill selection;
-
-		if (selectWisdom)
-			selection = getHeroClass()->chooseSecSkill(intersect(options, wisdomList), rand);
-		else if (selectSchool)
-			selection = getHeroClass()->chooseSecSkill(intersect(options, schoolList), rand);
-		else
-			selection = getHeroClass()->chooseSecSkill(options, rand);
-
-		skills.push_back(selection);
-		options.erase(selection);
-
-		if (wisdomList.count(selection))
-			wisdomList.clear();
-
-		if (schoolList.count(selection))
-			schoolList.clear();
-	};
-
 	if (!basicAndAdv.empty())
-		chooseSkill(basicAndAdv);
+	{
+		skills.push_back(gameRandomizer.rollSecondarySkillForLevelup(this, basicAndAdv));
+		basicAndAdv.erase(skills.back());
+	}
 
-	if (canLearnSkill() && !none.empty())
-		chooseSkill(none);
+	if (!none.empty())
+	{
+		skills.push_back(gameRandomizer.rollSecondarySkillForLevelup(this, none));
+		none.erase(skills.back());
+	}
 
 	if (!basicAndAdv.empty() && skills.size() < 2)
-		chooseSkill(basicAndAdv);
-
-	if (canLearnSkill() && !none.empty() && skills.size() < 2)
-		chooseSkill(none);
-
-	return skills;
-}
-
-PrimarySkill CGHeroInstance::nextPrimarySkill(vstd::RNG & rand) const
-{
-	assert(gainsLevel());
-	const auto isLowLevelHero = level < GameConstants::HERO_HIGH_LEVEL;
-	const auto & skillChances = isLowLevelHero ? getHeroClass()->primarySkillLowLevel : getHeroClass()->primarySkillHighLevel;
-
-	if (isCampaignYog())
 	{
-		// Yog can only receive Attack or Defence on level-up
-		std::vector<int> yogChances = { skillChances[0], skillChances[1]};
-		return static_cast<PrimarySkill>(RandomGeneratorUtil::nextItemWeighted(yogChances, rand));
+		skills.push_back(gameRandomizer.rollSecondarySkillForLevelup(this, basicAndAdv));
+		basicAndAdv.erase(skills.back());
 	}
 
-	return static_cast<PrimarySkill>(RandomGeneratorUtil::nextItemWeighted(skillChances, rand));
-}
-
-std::optional<SecondarySkill> CGHeroInstance::nextSecondarySkill(vstd::RNG & rand) const
-{
-	assert(gainsLevel());
-
-	std::optional<SecondarySkill> chosenSecondarySkill;
-	const auto proposedSecondarySkills = getLevelUpProposedSecondarySkills(rand);
-	if(!proposedSecondarySkills.empty())
+	if (!none.empty() && skills.size() < 2)
 	{
-		std::vector<SecondarySkill> learnedSecondarySkills;
-		for(const auto & secondarySkill : proposedSecondarySkills)
-		{
-			if(getSecSkillLevel(secondarySkill) > 0)
-			{
-				learnedSecondarySkills.push_back(secondarySkill);
-			}
-		}
-
-		if(learnedSecondarySkills.empty())
-		{
-			// there are only new skills to learn, so choose anyone of them
-			chosenSecondarySkill = std::make_optional(*RandomGeneratorUtil::nextItem(proposedSecondarySkills, rand));
-		}
-		else
-		{
-			// preferably upgrade a already learned secondary skill
-			chosenSecondarySkill = std::make_optional(*RandomGeneratorUtil::nextItem(learnedSecondarySkills, rand));
-		}
+		skills.push_back(gameRandomizer.rollSecondarySkillForLevelup(this, none));
+		none.erase(skills.back());
 	}
-	return chosenSecondarySkill;
+
+	return skills;
 }
 
 void CGHeroInstance::setPrimarySkill(PrimarySkill primarySkill, si64 value, ChangeValueMode mode)
@@ -1588,22 +1494,9 @@ bool CGHeroInstance::gainsLevel() const
 	return level < LIBRARY->heroh->maxSupportedLevel() && exp >= static_cast<TExpType>(LIBRARY->heroh->reqExp(level+1));
 }
 
-void CGHeroInstance::levelUp(const std::vector<SecondarySkill> & skills)
+void CGHeroInstance::levelUp()
 {
 	++level;
-
-	//deterministic secondary skills
-	++skillsInfo.magicSchoolCounter;
-	++skillsInfo.wisdomCounter;
-
-	for(const auto & skill : skills)
-	{
-		if((*LIBRARY->skillh)[skill]->obligatory(CSkill::Obligatory::MAJOR))
-			skillsInfo.resetWisdomCounter();
-		if((*LIBRARY->skillh)[skill]->obligatory(CSkill::Obligatory::MINOR))
-			skillsInfo.resetMagicSchoolCounter();
-	}
-
 	//update specialty and other bonuses that scale with level
 	nodeHasChanged();
 }
@@ -1614,23 +1507,18 @@ void CGHeroInstance::attachCommanderToArmy()
 		commander->setArmy(this);
 }
 
-void CGHeroInstance::levelUpAutomatically(vstd::RNG & rand)
+void CGHeroInstance::levelUpAutomatically(IGameRandomizer & gameRandomizer)
 {
 	while(gainsLevel())
 	{
-		const auto primarySkill = nextPrimarySkill(rand);
-		setPrimarySkill(primarySkill, 1, ChangeValueMode::RELATIVE);
-
-		auto proposedSecondarySkills = getLevelUpProposedSecondarySkills(rand);
+		const auto primarySkill = gameRandomizer.rollPrimarySkillForLevelup(this);
+		const auto proposedSecondarySkills = getLevelupSkillCandidates(gameRandomizer);
 
-		const auto secondarySkill = nextSecondarySkill(rand);
-		if(secondarySkill)
-		{
-			setSecSkillLevel(*secondarySkill, 1, ChangeValueMode::RELATIVE);
-		}
+		setPrimarySkill(primarySkill, 1, ChangeValueMode::RELATIVE);
+		if(!proposedSecondarySkills.empty())
+			setSecSkillLevel(proposedSecondarySkills.front(), 1, ChangeValueMode::RELATIVE);
 
-		//TODO why has the secondary skills to be passed to the method?
-		levelUp(proposedSecondarySkills);
+		levelUp();
 	}
 }
 

+ 16 - 32
lib/mapObjects/CGHeroInstance.h

@@ -116,23 +116,6 @@ public:
 		}
 	} patrol;
 
-	struct DLL_LINKAGE SecondarySkillsInfo
-	{
-		ui8 magicSchoolCounter;
-		ui8 wisdomCounter;
-
-		SecondarySkillsInfo();
-
-		void resetMagicSchoolCounter();
-		void resetWisdomCounter();
-
-		template <typename Handler> void serialize(Handler &h)
-		{
-			h & magicSchoolCounter;
-			h & wisdomCounter;
-		}
-	} skillsInfo;
-
 	inline bool isInitialized() const
 	{ // has this hero been on the map at least once?
 		return movement != UNINITIALIZED_MOVEMENT && mana != UNINITIALIZED_MANA;
@@ -200,14 +183,8 @@ public:
 	/// Returns true if hero has lower level than should upon his experience.
 	bool gainsLevel() const;
 
-	/// Returns the next primary skill on level up. Can only be called if hero can gain a level up.
-	PrimarySkill nextPrimarySkill(vstd::RNG & rand) const;
-
-	/// Returns the next secondary skill randomly on level up. Can only be called if hero can gain a level up.
-	std::optional<SecondarySkill> nextSecondarySkill(vstd::RNG & rand) const;
-
-	/// Gets 0, 1 or 2 secondary skills which are proposed on hero level up.
-	std::vector<SecondarySkill> getLevelUpProposedSecondarySkills(vstd::RNG & rand) const;
+	/// Selects 0-2 skills for player to select on levelup
+	std::vector<SecondarySkill> getLevelupSkillCandidates(IGameRandomizer & gameRandomizer) const;
 
 	ui8 getSecSkillLevel(const SecondarySkill & skill) const; //0 - no skill
 	int getPrimSkillLevel(PrimarySkill id) const;
@@ -218,7 +195,7 @@ public:
 
 	void setPrimarySkill(PrimarySkill primarySkill, si64 value, ChangeValueMode mode);
 	void setSecSkillLevel(const SecondarySkill & which, int val, ChangeValueMode mode); // abs == 0 - changes by value; 1 - sets to value
-	void levelUp(const std::vector<SecondarySkill> & skills);
+	void levelUp();
 
 	void setMovementPoints(int points);
 	int movementPointsRemaining() const;
@@ -261,9 +238,9 @@ public:
 	const CCommanderInstance * getCommander() const;
 	CCommanderInstance * getCommander();
 
-	void initObj(vstd::RNG & rand) override;
-	void initHero(vstd::RNG & rand);
-	void initHero(vstd::RNG & rand, const HeroTypeID & SUBID);
+	void initObj(IGameRandomizer & gameRandomizer) override;
+	void initHero(IGameRandomizer & gameRandomizer);
+	void initHero(IGameRandomizer & gameRandomizer, const HeroTypeID & SUBID);
 
 	ArtPlacementMap putArtifact(const ArtifactPosition & pos, const CArtifactInstance * art) override;
 	void removeArtifact(const ArtifactPosition & pos) override;
@@ -320,7 +297,7 @@ public:
 
 	void updateAppearance();
 
-	void pickRandomObject(vstd::RNG & rand) override;
+	void pickRandomObject(IGameRandomizer & gameRandomizer) override;
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
 	std::string getObjectName() const override;
 	std::string getHoverText(PlayerColor player) const override;
@@ -351,7 +328,7 @@ protected:
 	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 
 private:
-	void levelUpAutomatically(vstd::RNG & rand);
+	void levelUpAutomatically(IGameRandomizer & gameRandomizer);
 	void attachCommanderToArmy();
 
 public:
@@ -377,7 +354,14 @@ public:
 		h & spells;
 		h & patrol;
 		h & moveDir;
-		h & skillsInfo;
+		if (!h.hasFeature(Handler::Version::RANDOMIZATION_REWORK))
+		{
+			ui8 magicSchoolCounter = 0;
+			ui8 wisdomCounter = 0;
+
+			h & magicSchoolCounter;
+			h & wisdomCounter;
+		}
 
 		if (h.hasFeature(Handler::Version::NO_RAW_POINTERS_IN_SERIALIZER))
 		{

+ 5 - 4
lib/mapObjects/CGMarket.cpp

@@ -13,6 +13,7 @@
 
 #include "../callback/IGameInfoCallback.h"
 #include "../callback/IGameEventCallback.h"
+#include "../callback/IGameRandomizer.h"
 #include "../texts/CGeneralTextHandler.h"
 #include "../CCreatureHandler.h"
 #include "CGTownInstance.h"
@@ -30,9 +31,9 @@ ObjectInstanceID CGMarket::getObjInstanceID() const
 	return id;
 }
 
-void CGMarket::initObj(vstd::RNG & rand)
+void CGMarket::initObj(IGameRandomizer & gameRandomizer)
 {
-	getObjectHandler()->configureObject(this, rand);
+	getObjectHandler()->configureObject(this, gameRandomizer);
 }
 
 void CGMarket::onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const
@@ -99,7 +100,7 @@ std::vector<TradeItemBuy> CGBlackMarket::availableItemsIds(EMarketMode mode) con
 	}
 }
 
-void CGBlackMarket::newTurn(IGameEventCallback & gameEvents) const
+void CGBlackMarket::newTurn(IGameEventCallback & gameEvents, IGameRandomizer & gameRandomizer) const
 {
 	int resetPeriod = cb->getSettings().getInteger(EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD);
 
@@ -111,7 +112,7 @@ void CGBlackMarket::newTurn(IGameEventCallback & gameEvents) const
 
 	SetAvailableArtifacts saa;
 	saa.id = id;
-	cb->pickAllowedArtsSet(saa.arts, gameEvents.getRandomGenerator());
+	saa.arts = gameRandomizer.rollMarketArtifactSet();
 	gameEvents.sendAndApply(saa);
 }
 

+ 2 - 2
lib/mapObjects/CGMarket.h

@@ -25,7 +25,7 @@ public:
 	CGMarket(IGameInfoCallback *cb);
 	///IObjectInterface
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override; //open trading window
-	void initObj(vstd::RNG & rand) override;//set skills for trade
+	void initObj(IGameRandomizer & gameRandomizer) override;//set skills for trade
 
 	std::string getPopupText(PlayerColor player) const override;
 	std::string getPopupText(const CGHeroInstance * hero) const override;
@@ -44,7 +44,7 @@ public:
 
 	std::vector<ArtifactID> artifacts; //available artifacts
 
-	void newTurn(IGameEventCallback & gameEvents) const override; //reset artifacts for black market every month
+	void newTurn(IGameEventCallback & gameEvents, IGameRandomizer & gameRandomizer) const override; //reset artifacts for black market every month
 	std::vector<TradeItemBuy> availableItemsIds(EMarketMode mode) const override;
 
 	template <typename Handler> void serialize(Handler &h)

+ 2 - 2
lib/mapObjects/CGObjectInstance.cpp

@@ -160,12 +160,12 @@ void CGObjectInstance::setType(MapObjectID newID, MapObjectSubID newSubID)
 	cb->gameState().getMap().showObject(this);
 }
 
-void CGObjectInstance::pickRandomObject(vstd::RNG & rand)
+void CGObjectInstance::pickRandomObject(IGameRandomizer & gameRandomizer)
 {
 	// no-op
 }
 
-void CGObjectInstance::initObj(vstd::RNG & rand)
+void CGObjectInstance::initObj(IGameRandomizer & gameRandomizer)
 {
 	// no-op
 }

+ 2 - 2
lib/mapObjects/CGObjectInstance.h

@@ -131,8 +131,8 @@ public:
 
 	/** OVERRIDES OF IObjectInterface **/
 
-	void initObj(vstd::RNG & rand) override;
-	void pickRandomObject(vstd::RNG & rand) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
+	void pickRandomObject(IGameRandomizer & gameRandomizer) override;
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
 	/// method for synchronous update. Note: For new properties classes should override setPropertyDer instead
 	void setProperty(ObjProperty what, ObjPropertyID identifier) final;

+ 2 - 2
lib/mapObjects/CGPandoraBox.cpp

@@ -42,11 +42,11 @@ void CGPandoraBox::init()
 	}
 }
 
-void CGPandoraBox::initObj(vstd::RNG & rand)
+void CGPandoraBox::initObj(IGameRandomizer & gameRandomizer)
 {
 	init();
 	
-	CRewardableObject::initObj(rand);
+	CRewardableObject::initObj(gameRandomizer);
 }
 
 void CGPandoraBox::grantRewardWithMessage(IGameEventCallback & gameEvents, const CGHeroInstance * h, int index, bool markAsVisit) const

+ 1 - 1
lib/mapObjects/CGPandoraBox.h

@@ -23,7 +23,7 @@ public:
 
 	MetaString message;
 
-	void initObj(vstd::RNG & rand) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
 	void battleFinished(IGameEventCallback & gameEvents, const CGHeroInstance *hero, const BattleResult &result) const override;
 	void blockingDialogAnswered(IGameEventCallback & gameEvents, const CGHeroInstance *hero, int32_t answer) const override;

+ 5 - 4
lib/mapObjects/CGResource.cpp

@@ -13,6 +13,7 @@
 
 #include "../callback/IGameInfoCallback.h"
 #include "../callback/IGameEventCallback.h"
+#include "../callback/IGameRandomizer.h"
 #include "../mapObjectConstructors/CommonConstructors.h"
 #include "../texts/CGeneralTextHandler.h"
 #include "../networkPacks/PacksForClient.h"
@@ -52,24 +53,24 @@ std::string CGResource::getHoverText(PlayerColor player) const
 	return LIBRARY->generaltexth->restypes[resourceID().getNum()];
 }
 
-void CGResource::pickRandomObject(vstd::RNG & rand)
+void CGResource::pickRandomObject(IGameRandomizer & gameRandomizer)
 {
 	assert(ID == Obj::RESOURCE || ID == Obj::RANDOM_RESOURCE);
 
 	if (ID == Obj::RANDOM_RESOURCE)
 	{
 		ID = Obj::RESOURCE;
-		subID = rand.nextInt(EGameResID::WOOD, EGameResID::GOLD);
+		subID = gameRandomizer.getDefault().nextInt(EGameResID::WOOD, EGameResID::GOLD);
 		setType(ID, subID);
 
 		amount *= getAmountMultiplier();
 	}
 }
 
-void CGResource::initObj(vstd::RNG & rand)
+void CGResource::initObj(IGameRandomizer & gameRandomizer)
 {
 	blockVisit = true;
-	getResourceHandler()->randomizeObject(this, rand);
+	getResourceHandler()->randomizeObject(this, gameRandomizer);
 }
 
 void CGResource::onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const

+ 2 - 2
lib/mapObjects/CGResource.h

@@ -35,8 +35,8 @@ public:
 	using CArmedInstance::CArmedInstance;
 
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
-	void initObj(vstd::RNG & rand) override;
-	void pickRandomObject(vstd::RNG & rand) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
+	void pickRandomObject(IGameRandomizer & gameRandomizer) override;
 	void battleFinished(IGameEventCallback & gameEvents, const CGHeroInstance *hero, const BattleResult &result) const override;
 	void blockingDialogAnswered(IGameEventCallback & gameEvents, const CGHeroInstance *hero, int32_t answer) const override;
 	std::string getHoverText(PlayerColor player) const override;

+ 10 - 9
lib/mapObjects/CGTownInstance.cpp

@@ -26,6 +26,7 @@
 #include "../TerrainHandler.h"
 #include "../callback/IGameInfoCallback.h"
 #include "../callback/IGameEventCallback.h"
+#include "../callback/IGameRandomizer.h"
 #include "../entities/building/CBuilding.h"
 #include "../entities/faction/CTownHandler.h"
 #include "../mapObjectConstructors/AObjectTypeHandler.h"
@@ -389,7 +390,7 @@ bool CGTownInstance::townEnvisagesBuilding(BuildingSubID::EBuildingSubID subId)
 	return getTown()->getBuildingType(subId) != BuildingID::NONE;
 }
 
-void CGTownInstance::initializeConfigurableBuildings(vstd::RNG & rand)
+void CGTownInstance::initializeConfigurableBuildings(IGameRandomizer & gameRandomizer)
 {
 	for(const auto & kvp : getTown()->buildings)
 	{
@@ -397,7 +398,7 @@ void CGTownInstance::initializeConfigurableBuildings(vstd::RNG & rand)
 			continue;
 
 		try {
-			rewardableBuildings[kvp.first] = std::make_unique<TownRewardableBuildingInstance>(this, kvp.second->bid, rand);
+			rewardableBuildings[kvp.first] = std::make_unique<TownRewardableBuildingInstance>(this, kvp.second->bid, gameRandomizer);
 		}
 		catch (std::runtime_error & e)
 		{
@@ -458,13 +459,13 @@ FactionID CGTownInstance::randomizeFaction(vstd::RNG & rand)
 	return *RandomGeneratorUtil::nextItem(potentialPicks, rand);
 }
 
-void CGTownInstance::pickRandomObject(vstd::RNG & rand)
+void CGTownInstance::pickRandomObject(IGameRandomizer & gameRandomizer)
 {
 	assert(ID == MapObjectID::TOWN || ID == MapObjectID::RANDOM_TOWN);
 	if (ID == MapObjectID::RANDOM_TOWN)
 	{
 		ID = MapObjectID::TOWN;
-		subID = randomizeFaction(rand);
+		subID = randomizeFaction(gameRandomizer.getDefault());
 	}
 
 	assert(ID == Obj::TOWN); // just in case
@@ -473,7 +474,7 @@ void CGTownInstance::pickRandomObject(vstd::RNG & rand)
 	updateAppearance();
 }
 
-void CGTownInstance::initObj(vstd::RNG & rand) ///initialize town structures
+void CGTownInstance::initObj(IGameRandomizer & gameRandomizer) ///initialize town structures
 {
 	blockVisit = true;
 
@@ -493,8 +494,8 @@ void CGTownInstance::initObj(vstd::RNG & rand) ///initialize town structures
 				creatures[level].second.push_back(getTown()->creatures[level][upgradeNum]);
 		}
 	}
-	initializeConfigurableBuildings(rand);
-	initializeNeutralTownGarrison(rand);
+	initializeConfigurableBuildings(gameRandomizer);
+	initializeNeutralTownGarrison(gameRandomizer.getDefault());
 	recreateBuildingsBonuses();
 	updateAppearance();
 }
@@ -536,10 +537,10 @@ void CGTownInstance::initializeNeutralTownGarrison(vstd::RNG & rand)
 	}
 }
 
-void CGTownInstance::newTurn(IGameEventCallback & gameEvents) const
+void CGTownInstance::newTurn(IGameEventCallback & gameEvents, IGameRandomizer & gameRandomizer) const
 {
 	for(const auto & building : rewardableBuildings)
-		building.second->newTurn(gameEvents);
+		building.second->newTurn(gameEvents, gameRandomizer);
 		
 	if(hasBuilt(BuildingSubID::BANK) && bonusValue.second > 0)
 	{

+ 4 - 4
lib/mapObjects/CGTownInstance.h

@@ -211,11 +211,11 @@ public:
 	virtual ~CGTownInstance();
 
 	///IObjectInterface overrides
-	void newTurn(IGameEventCallback & gameEvents) const override;
+	void newTurn(IGameEventCallback & gameEvents, IGameRandomizer & gameRandomizer) const override;
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
 	void onHeroLeave(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
-	void initObj(vstd::RNG & rand) override;
-	void pickRandomObject(vstd::RNG & rand) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
+	void pickRandomObject(IGameRandomizer & gameRandomizer) override;
 	void battleFinished(IGameEventCallback & gameEvents, const CGHeroInstance * hero, const BattleResult & result) const override;
 	std::string getObjectName() const override;
 
@@ -238,7 +238,7 @@ private:
 	void onTownCaptured(IGameEventCallback & gameEvents, const PlayerColor & winner) const;
 	int getDwellingBonus(const std::vector<CreatureID>& creatureIds, const std::vector<const CGObjectInstance* >& dwellings) const;
 	bool townEnvisagesBuilding(BuildingSubID::EBuildingSubID bid) const;
-	void initializeConfigurableBuildings(vstd::RNG & rand);
+	void initializeConfigurableBuildings(IGameRandomizer & gameRandomizer);
 	void initializeNeutralTownGarrison(vstd::RNG & rand);
 };
 

+ 7 - 6
lib/mapObjects/CQuest.cpp

@@ -19,6 +19,7 @@
 #include "../IGameSettings.h"
 #include "../callback/IGameInfoCallback.h"
 #include "../callback/IGameEventCallback.h"
+#include "../callback/IGameRandomizer.h"
 #include "../entities/artifact/CArtifact.h"
 #include "../entities/hero/CHeroHandler.h"
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
@@ -444,11 +445,11 @@ void CGSeerHut::init(vstd::RNG & rand)
 	configuration.selectMode = Rewardable::ESelectMode::SELECT_PLAYER;
 }
 
-void CGSeerHut::initObj(vstd::RNG & rand)
+void CGSeerHut::initObj(IGameRandomizer & gameRandomizer)
 {
-	init(rand);
+	init(gameRandomizer.getDefault());
 	
-	CRewardableObject::initObj(rand);
+	CRewardableObject::initObj(gameRandomizer);
 	
 	setObjToKill();
 	getQuest().defineQuestName();
@@ -551,9 +552,9 @@ void CGSeerHut::setPropertyDer(ObjProperty what, ObjPropertyID identifier)
 	}
 }
 
-void CGSeerHut::newTurn(IGameEventCallback & gameEvents) const
+void CGSeerHut::newTurn(IGameEventCallback & gameEvents, IGameRandomizer & gameRandomizer) const
 {
-	CRewardableObject::newTurn(gameEvents);
+	CRewardableObject::newTurn(gameEvents, gameRandomizer);
 	if(getQuest().lastDay >= 0 && getQuest().lastDay <= cb->getDate() - 1) //time is up
 	{
 		gameEvents.setObjPropertyValue(id, ObjProperty::SEERHUT_COMPLETE, true);
@@ -815,7 +816,7 @@ void CGKeymasterTent::onHeroVisit(IGameEventCallback & gameEvents, const CGHeroI
 	h->showInfoDialog(gameEvents, txt_id);
 }
 
-void CGBorderGuard::initObj(vstd::RNG & rand)
+void CGBorderGuard::initObj(IGameRandomizer & gameRandomizer)
 {
 	blockVisit = true;
 }

+ 3 - 3
lib/mapObjects/CQuest.h

@@ -140,14 +140,14 @@ public:
 
 	std::string seerName;
 
-	void initObj(vstd::RNG & rand) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
 	std::string getHoverText(PlayerColor player) const override;
 	std::string getHoverText(const CGHeroInstance * hero) const override;
 	std::string getPopupText(PlayerColor player) const override;
 	std::string getPopupText(const CGHeroInstance * hero) const override;
 	std::vector<Component> getPopupComponents(PlayerColor player) const override;
 	std::vector<Component> getPopupComponents(const CGHeroInstance * hero) const override;
-	void newTurn(IGameEventCallback & gameEvents) const override;
+	void newTurn(IGameEventCallback & gameEvents, IGameRandomizer & gameRandomizer) const override;
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
 	void blockingDialogAnswered(IGameEventCallback & gameEvents, const CGHeroInstance *hero, int32_t answer) const override;
 	void getVisitText (MetaString &text, std::vector<Component> &components, bool FirstVisit, const CGHeroInstance * h = nullptr) const override;
@@ -227,7 +227,7 @@ class DLL_LINKAGE CGBorderGuard : public CGKeys, public IQuestObject
 public:
 	using CGKeys::CGKeys;
 
-	void initObj(vstd::RNG & rand) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
 	void blockingDialogAnswered(IGameEventCallback & gameEvents, const CGHeroInstance *hero, int32_t answer) const override;
 

+ 4 - 4
lib/mapObjects/CRewardableObject.cpp

@@ -331,14 +331,14 @@ void CRewardableObject::setPropertyDer(ObjProperty what, ObjPropertyID identifie
 	}
 }
 
-void CRewardableObject::newTurn(IGameEventCallback & gameEvents) const
+void CRewardableObject::newTurn(IGameEventCallback & gameEvents, IGameRandomizer & gameRandomizer) const
 {
 	if (configuration.resetParameters.period != 0 && cb->getDate(Date::DAY) > 1 && ((cb->getDate(Date::DAY)-1) % configuration.resetParameters.period) == 0)
 	{
 		if (configuration.resetParameters.rewards)
 		{
 			auto handler = std::dynamic_pointer_cast<const CRewardableConstructor>(getObjectHandler());
-			auto newConfiguration = handler->generateConfiguration(cb, gameEvents.getRandomGenerator(), ID, configuration.variables.preset);
+			auto newConfiguration = handler->generateConfiguration(cb, gameRandomizer, ID, configuration.variables.preset);
 			gameEvents.setRewardableObjectConfiguration(id, newConfiguration);
 		}
 		if (configuration.resetParameters.visitors)
@@ -350,9 +350,9 @@ void CRewardableObject::newTurn(IGameEventCallback & gameEvents) const
 	}
 }
 
-void CRewardableObject::initObj(vstd::RNG & rand)
+void CRewardableObject::initObj(IGameRandomizer & gameRandomizer)
 {
-	getObjectHandler()->configureObject(this, rand);
+	getObjectHandler()->configureObject(this, gameRandomizer);
 }
 
 CRewardableObject::CRewardableObject(IGameInfoCallback *cb)

+ 2 - 2
lib/mapObjects/CRewardableObject.h

@@ -61,7 +61,7 @@ public:
 	void garrisonDialogClosed(IGameEventCallback & gameEvents, const CGHeroInstance *hero) const override;
 
 	///possibly resets object state
-	void newTurn(IGameEventCallback & gameEvents) const override;
+	void newTurn(IGameEventCallback & gameEvents, IGameRandomizer & gameRandomizer) const override;
 
 	/// gives second part of reward after hero level-ups for proper granting of spells/mana
 	void heroLevelUpDone(IGameEventCallback & gameEvents, const CGHeroInstance *hero) const override;
@@ -69,7 +69,7 @@ public:
 	/// applies player selection of reward
 	void blockingDialogAnswered(IGameEventCallback & gameEvents, const CGHeroInstance *hero, int32_t answer) const override;
 
-	void initObj(vstd::RNG & rand) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
 
 	bool isCoastVisitable() const override;
 

+ 1 - 1
lib/mapObjects/FlaggableMapObject.cpp

@@ -50,7 +50,7 @@ void FlaggableMapObject::onHeroVisit(IGameEventCallback & gameEvents, const CGHe
 	gameEvents.showInfoDialog(&iw);
 }
 
-void FlaggableMapObject::initObj(vstd::RNG & rand)
+void FlaggableMapObject::initObj(IGameRandomizer & gameRandomizer)
 {
 	initBonuses();
 }

+ 1 - 1
lib/mapObjects/FlaggableMapObject.h

@@ -28,7 +28,7 @@ public:
 	using CGObjectInstance::CGObjectInstance;
 
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
-	void initObj(vstd::RNG & rand) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
 
 	const IOwnableObject * asOwnable() const final;
 	ResourceSet dailyIncome() const override;

+ 3 - 3
lib/mapObjects/IObjectInterface.cpp

@@ -39,13 +39,13 @@ void IObjectInterface::onHeroVisit(IGameEventCallback & gameEvents, const CGHero
 void IObjectInterface::onHeroLeave(IGameEventCallback & gameEvents, const CGHeroInstance * h) const
 {}
 
-void IObjectInterface::newTurn(IGameEventCallback & gameEvents) const
+void IObjectInterface::newTurn(IGameEventCallback & gameEvents, IGameRandomizer & gameRandomizer) const
 {}
 
-void IObjectInterface::initObj(vstd::RNG & rand)
+void IObjectInterface::initObj(IGameRandomizer & gameRandomizer)
 {}
 
-void IObjectInterface::pickRandomObject(vstd::RNG & rand)
+void IObjectInterface::pickRandomObject(IGameRandomizer & gameRandomizer)
 {}
 
 void IObjectInterface::setProperty(ObjProperty what, ObjPropertyID identifier)

+ 4 - 3
lib/mapObjects/IObjectInterface.h

@@ -30,6 +30,7 @@ class CStackInstance;
 class CGHeroInstance;
 class IGameInfoCallback;
 class IGameEventCallback;
+class IGameRandomizer;
 class ResourceSet;
 class int3;
 class MetaString;
@@ -55,9 +56,9 @@ public:
 
 	/// Called on new turn by server. This method can not modify object state on its own
 	/// Instead all changes must be propagated via netpacks
-	virtual void newTurn(IGameEventCallback & gameEvents) const;
-	virtual void initObj(vstd::RNG & rand); //synchr
-	virtual void pickRandomObject(vstd::RNG & rand);
+	virtual void newTurn(IGameEventCallback & gameEvents, IGameRandomizer & gameRandomizer) const;
+	virtual void initObj(IGameRandomizer & gameRandomizer); //synchr
+	virtual void pickRandomObject(IGameRandomizer & gameRandomizer);
 	virtual void setProperty(ObjProperty what, ObjPropertyID identifier);//synchr
 
 	//Called when queries created DURING HERO VISIT are resolved

+ 19 - 18
lib/mapObjects/MiscObjects.cpp

@@ -14,6 +14,7 @@
 #include "../bonuses/Propagators.h"
 #include "../callback/IGameInfoCallback.h"
 #include "../callback/IGameEventCallback.h"
+#include "../callback/IGameRandomizer.h"
 #include "../constants/StringConstants.h"
 #include "../entities/artifact/ArtifactUtils.h"
 #include "../entities/artifact/CArtifact.h"
@@ -96,19 +97,19 @@ void CGMine::onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance *
 	flagMine(gameEvents, h->tempOwner);
 }
 
-void CGMine::initObj(vstd::RNG & rand)
+void CGMine::initObj(IGameRandomizer & gameRandomizer)
 {
 	if(isAbandoned())
 	{
 		//set guardians
-		int howManyTroglodytes = rand.nextInt(100, 199);
+		int howManyTroglodytes = gameRandomizer.getDefault().nextInt(100, 199);
 		auto troglodytes = std::make_unique<CStackInstance>(cb, CreatureID::TROGLODYTES, howManyTroglodytes);
 		putStack(SlotID(0), std::move(troglodytes));
 
 		assert(!abandonedMineResources.empty());
 		if (!abandonedMineResources.empty())
 		{
-			producedResource = *RandomGeneratorUtil::nextItem(abandonedMineResources, rand);
+			producedResource = *RandomGeneratorUtil::nextItem(abandonedMineResources, gameRandomizer.getDefault());
 		}
 		else
 		{
@@ -431,7 +432,7 @@ void CGMonolith::teleportDialogAnswered(IGameEventCallback & gameEvents, const C
 	gameEvents.moveHero(hero->id, hero->convertFromVisitablePos(dPos), EMovementMode::MONOLITH);
 }
 
-void CGMonolith::initObj(vstd::RNG & rand)
+void CGMonolith::initObj(IGameRandomizer & gameRandomizer)
 {
 	std::vector<Obj> IDs;
 	IDs.push_back(ID);
@@ -476,7 +477,7 @@ void CGSubterraneanGate::onHeroVisit(IGameEventCallback & gameEvents, const CGHe
 	gameEvents.showTeleportDialog(&td);
 }
 
-void CGSubterraneanGate::initObj(vstd::RNG & rand)
+void CGSubterraneanGate::initObj(IGameRandomizer & gameRandomizer)
 {
 	type = BOTH;
 }
@@ -625,24 +626,24 @@ ArtifactID CGArtifact::getArtifactType() const
 		return getObjTypeIndex().getNum();
 }
 
-void CGArtifact::pickRandomObject(vstd::RNG & rand)
+void CGArtifact::pickRandomObject(IGameRandomizer & gameRandomizer)
 {
 	switch(ID.toEnum())
 	{
 		case MapObjectID::RANDOM_ART:
-			subID = cb->gameState().pickRandomArtifact(rand, std::nullopt);
+			subID = gameRandomizer.rollArtifact();
 			break;
 		case MapObjectID::RANDOM_TREASURE_ART:
-			subID = cb->gameState().pickRandomArtifact(rand, EArtifactClass::ART_TREASURE);
+			subID = gameRandomizer.rollArtifact(EArtifactClass::ART_TREASURE);
 			break;
 		case MapObjectID::RANDOM_MINOR_ART:
-			subID = cb->gameState().pickRandomArtifact(rand, EArtifactClass::ART_MINOR);
+			subID = gameRandomizer.rollArtifact(EArtifactClass::ART_MINOR);
 			break;
 		case MapObjectID::RANDOM_MAJOR_ART:
-			subID = cb->gameState().pickRandomArtifact(rand, EArtifactClass::ART_MAJOR);
+			subID = gameRandomizer.rollArtifact(EArtifactClass::ART_MAJOR);
 			break;
 		case MapObjectID::RANDOM_RELIC_ART:
-			subID = cb->gameState().pickRandomArtifact(rand, EArtifactClass::ART_RELIC);
+			subID = gameRandomizer.rollArtifact(EArtifactClass::ART_RELIC);
 			break;
 	}
 
@@ -660,7 +661,7 @@ void CGArtifact::setArtifactInstance(const CArtifactInstance * instance)
 	storedArtifact = instance->getId();
 }
 
-void CGArtifact::initObj(vstd::RNG & rand)
+void CGArtifact::initObj(IGameRandomizer & gameRandomizer)
 {
 	blockVisit = true;
 	if(ID == Obj::ARTIFACT)
@@ -827,13 +828,13 @@ void CGArtifact::serializeJsonOptions(JsonSerializeFormat& handler)
 	}
 }
 
-void CGSignBottle::initObj(vstd::RNG & rand)
+void CGSignBottle::initObj(IGameRandomizer & gameRandomizer)
 {
 	//if no text is set than we pick random from the predefined ones
 	if(message.empty())
 	{
 		auto vector = LIBRARY->generaltexth->findStringsWithPrefix("core.randsign");
-		std::string messageIdentifier = *RandomGeneratorUtil::nextItem(vector, rand);
+		std::string messageIdentifier = *RandomGeneratorUtil::nextItem(vector, gameRandomizer.getDefault());
 		message.appendTextID(messageIdentifier);
 	}
 
@@ -917,7 +918,7 @@ void CGGarrison::serializeJsonOptions(JsonSerializeFormat& handler)
 	CArmedInstance::serializeJsonOptions(handler);
 }
 
-void CGGarrison::initObj(vstd::RNG &rand)
+void CGGarrison::initObj(IGameRandomizer & gameRandomizer)
 {
 	if(this->subID == MapObjectSubID::decode(this->ID, "antiMagic"))
 		addAntimagicGarrisonBonus();
@@ -934,7 +935,7 @@ void CGGarrison::addAntimagicGarrisonBonus()
 	this->addNewBonus(bonus);
 }
 
-void CGMagi::initObj(vstd::RNG & rand)
+void CGMagi::initObj(IGameRandomizer & gameRandomizer)
 {
 	if (ID == Obj::EYE_OF_MAGI)
 		blockVisit = true;
@@ -1012,7 +1013,7 @@ const CGHeroInstance * CGBoat::getBoardedHero() const
 		return nullptr;
 }
 
-void CGSirens::initObj(vstd::RNG & rand)
+void CGSirens::initObj(IGameRandomizer & gameRandomizer)
 {
 	blockVisit = true;
 }
@@ -1174,7 +1175,7 @@ void CGObelisk::onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstanc
 
 }
 
-void CGObelisk::initObj(vstd::RNG & rand)
+void CGObelisk::initObj(IGameRandomizer & gameRandomizer)
 {
 	cb->gameState().getMap().obeliskCount++;
 }

+ 10 - 10
lib/mapObjects/MiscObjects.h

@@ -50,7 +50,7 @@ public:
 	MetaString message;
 
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
-	void initObj(vstd::RNG & rand) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
 
 	template <typename Handler> void serialize(Handler &h)
 	{
@@ -68,7 +68,7 @@ public:
 
 	bool removableUnits;
 
-	void initObj(vstd::RNG &rand) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
 	bool passableFor(PlayerColor color) const override;
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
 	void battleFinished(IGameEventCallback & gameEvents, const CGHeroInstance *hero, const BattleResult &result) const override;
@@ -106,8 +106,8 @@ public:
 	std::vector<Component> getPopupComponents(PlayerColor player) const override;
 
 	void pick(IGameEventCallback & gameEvents, const CGHeroInstance * h) const;
-	void initObj(vstd::RNG & rand) override;
-	void pickRandomObject(vstd::RNG & rand) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
+	void pickRandomObject(IGameRandomizer & gameRandomizer) override;
 
 	BattleField getBattlefield() const override;
 
@@ -151,7 +151,7 @@ private:
 	void blockingDialogAnswered(IGameEventCallback & gameEvents, const CGHeroInstance *hero, int32_t answer) const override;
 
 	void flagMine(IGameEventCallback & gameEvents, const PlayerColor & player) const;
-	void initObj(vstd::RNG & rand) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
 
 	std::string getObjectName() const override;
 	std::string getHoverText(PlayerColor player) const override;
@@ -238,7 +238,7 @@ class DLL_LINKAGE CGMonolith : public CGTeleport
 protected:
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
 	void teleportDialogAnswered(IGameEventCallback & gameEvents, const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const override;
-	void initObj(vstd::RNG & rand) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
 
 public:
 	using CGTeleport::CGTeleport;
@@ -252,7 +252,7 @@ public:
 class DLL_LINKAGE CGSubterraneanGate : public CGMonolith
 {
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
-	void initObj(vstd::RNG & rand) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
 
 public:
 	using CGMonolith::CGMonolith;
@@ -287,7 +287,7 @@ public:
 
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
 	std::string getHoverText(const CGHeroInstance * hero) const override;
-	void initObj(vstd::RNG & rand) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
 
 	template <typename Handler> void serialize(Handler &h)
 	{
@@ -377,7 +377,7 @@ class DLL_LINKAGE CGMagi : public CGObjectInstance
 public:
 	using CGObjectInstance::CGObjectInstance;
 
-	void initObj(vstd::RNG & rand) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
 
 	template <typename Handler> void serialize(Handler &h)
@@ -399,7 +399,7 @@ public:
 	using CTeamVisited::CTeamVisited;
 
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
-	void initObj(vstd::RNG & rand) override;
+	void initObj(IGameRandomizer & gameRandomizer) override;
 	std::string getHoverText(PlayerColor player) const override;
 	std::string getObjectDescription(PlayerColor player) const;
 

+ 6 - 6
lib/mapObjects/TownBuildingInstance.cpp

@@ -66,11 +66,11 @@ TownRewardableBuildingInstance::TownRewardableBuildingInstance(IGameInfoCallback
 	: TownBuildingInstance(cb)
 {}
 
-TownRewardableBuildingInstance::TownRewardableBuildingInstance(CGTownInstance * town, const BuildingID & index, vstd::RNG & rand)
+TownRewardableBuildingInstance::TownRewardableBuildingInstance(CGTownInstance * town, const BuildingID & index, IGameRandomizer & gameRandomizer)
 	: TownBuildingInstance(town, index)
 {
 	assert(town && town->getTown());
-	configuration = generateConfiguration(rand);
+	configuration = generateConfiguration(gameRandomizer);
 }
 
 void TownRewardableBuildingInstance::assignBonuses(std::vector<Bonus> & bonuses) const
@@ -92,14 +92,14 @@ void TownRewardableBuildingInstance::assignBonuses(std::vector<Bonus> & bonuses)
 	}
 }
 
-Rewardable::Configuration TownRewardableBuildingInstance::generateConfiguration(vstd::RNG & rand) const
+Rewardable::Configuration TownRewardableBuildingInstance::generateConfiguration(IGameRandomizer & gameRandomizer) const
 {
 	Rewardable::Configuration result;
 	const auto & building = town->getTown()->buildings.at(getBuildingType());
 
 	// force modal info window instead of displaying in inactive info box on adventure map
 	result.infoWindowType = EInfoWindowMode::MODAL;
-	building->rewardableObjectInfo.configureObject(result, rand, cb);
+	building->rewardableObjectInfo.configureObject(result, gameRandomizer, cb);
 	for(auto & rewardInfo : result.info)
 	{
 		assignBonuses(rewardInfo.reward.heroBonuses);
@@ -109,11 +109,11 @@ Rewardable::Configuration TownRewardableBuildingInstance::generateConfiguration(
 	return result;
 }
 
-void TownRewardableBuildingInstance::newTurn(IGameEventCallback & gameEvents) const
+void TownRewardableBuildingInstance::newTurn(IGameEventCallback & gameEvents, IGameRandomizer & gameRandomizer) const
 {
 	if (configuration.resetParameters.period != 0 && cb->getDate(Date::DAY) > 1 && ((cb->getDate(Date::DAY)-1) % configuration.resetParameters.period) == 0)
 	{
-		auto newConfiguration = generateConfiguration(gameEvents.getRandomGenerator());
+		auto newConfiguration = generateConfiguration(gameRandomizer);
 		gameEvents.setRewardableObjectConfiguration(town->id, getBuildingType(), newConfiguration);
 
 		if(configuration.resetParameters.visitors)

+ 3 - 3
lib/mapObjects/TownBuildingInstance.h

@@ -57,7 +57,7 @@ class DLL_LINKAGE TownRewardableBuildingInstance : public TownBuildingInstance,
 
 	bool wasVisitedBefore(const CGHeroInstance * contextHero) const override;
 	void grantReward(IGameEventCallback & gameEvents, ui32 rewardID, const CGHeroInstance * hero) const override;
-	Rewardable::Configuration generateConfiguration(vstd::RNG & rand) const;
+	Rewardable::Configuration generateConfiguration(IGameRandomizer & gameRandomizer) const;
 	void assignBonuses(std::vector<Bonus> & bonuses) const;
 
 	const IObjectInterface * getObject() const override;
@@ -69,7 +69,7 @@ public:
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
 	bool wasVisited(const CGHeroInstance * contextHero) const override;
 	
-	void newTurn(IGameEventCallback & gameEvents) const override;
+	void newTurn(IGameEventCallback & gameEvents, IGameRandomizer & gameRandomizer) const override;
 	
 	/// gives second part of reward after hero level-ups for proper granting of spells/mana
 	void heroLevelUpDone(IGameEventCallback & gameEvents, const CGHeroInstance *hero) const override;
@@ -77,7 +77,7 @@ public:
 	/// applies player selection of reward
 	void blockingDialogAnswered(IGameEventCallback & gameEvents, const CGHeroInstance *hero, int32_t answer) const override;
 	
-	TownRewardableBuildingInstance(CGTownInstance * town, const BuildingID & index, vstd::RNG & rand);
+	TownRewardableBuildingInstance(CGTownInstance * town, const BuildingID & index, IGameRandomizer & gameRandomizer);
 	TownRewardableBuildingInstance(IGameInfoCallback *cb);
 	
 	template <typename Handler> void serialize(Handler &h)

+ 0 - 1
lib/networkPacks/NetPackVisitor.h

@@ -55,7 +55,6 @@ public:
 	virtual void visitRemoveBonus(RemoveBonus & pack) {}
 	virtual void visitSetCommanderProperty(SetCommanderProperty & pack) {}
 	virtual void visitAddQuest(AddQuest & pack) {}
-	virtual void visitUpdateArtHandlerLists(UpdateArtHandlerLists & pack) {}
 	virtual void visitChangeFormation(ChangeFormation & pack) {}
 	virtual void visitRemoveObject(RemoveObject & pack) {}
 	virtual void visitTryMoveHero(TryMoveHero & pack) {}

+ 0 - 5
lib/networkPacks/NetPacksLib.cpp

@@ -198,11 +198,6 @@ void AddQuest::visitTyped(ICPackVisitor & visitor)
 	visitor.visitAddQuest(*this);
 }
 
-void UpdateArtHandlerLists::visitTyped(ICPackVisitor & visitor)
-{
-	visitor.visitUpdateArtHandlerLists(*this);
-}
-
 void ChangeFormation::visitTyped(ICPackVisitor & visitor)
 {
 	visitor.visitChangeFormation(*this);

+ 0 - 12
lib/networkPacks/PacksForClient.h

@@ -540,18 +540,6 @@ struct DLL_LINKAGE AddQuest : public CPackForClient
 	}
 };
 
-struct DLL_LINKAGE UpdateArtHandlerLists : public CPackForClient
-{
-	std::map<ArtifactID, int> allocatedArtifacts;
-
-	void visitTyped(ICPackVisitor & visitor) override;
-
-	template <typename Handler> void serialize(Handler & h)
-	{
-		h & allocatedArtifacts;
-	}
-};
-
 struct DLL_LINKAGE ChangeFormation : public CPackForClient
 {
 	ObjectInstanceID hid;

+ 73 - 72
lib/rewardable/Info.cpp

@@ -15,6 +15,7 @@
 #include "Limiter.h"
 #include "Reward.h"
 
+#include "../callback/IGameRandomizer.h"
 #include "../texts/CGeneralTextHandler.h"
 #include "../json/JsonRandom.h"
 #include "../GameLibrary.h"
@@ -108,14 +109,14 @@ void Rewardable::Info::init(const JsonNode & objectConfig, const std::string & o
 	loadString(parameters["onGuardedMessage"], TextIdentifier(objectName, "onGuarded"));
 }
 
-Rewardable::LimitersList Rewardable::Info::configureSublimiters(Rewardable::Configuration & object, vstd::RNG & rng, IGameInfoCallback * cb, const JsonNode & source) const
+Rewardable::LimitersList Rewardable::Info::configureSublimiters(Rewardable::Configuration & object, IGameRandomizer & gameRandomizer, IGameInfoCallback * cb, const JsonNode & source) const
 {
 	Rewardable::LimitersList result;
 	for (const auto & input : source.Vector())
 	{
 		auto newLimiter = std::make_shared<Rewardable::Limiter>();
 
-		configureLimiter(object, rng, cb, *newLimiter, input);
+		configureLimiter(object, gameRandomizer, cb, *newLimiter, input);
 
 		result.push_back(newLimiter);
 	}
@@ -123,81 +124,81 @@ Rewardable::LimitersList Rewardable::Info::configureSublimiters(Rewardable::Conf
 	return result;
 }
 
-void Rewardable::Info::configureLimiter(Rewardable::Configuration & object, vstd::RNG & rng, IGameInfoCallback * cb, Rewardable::Limiter & limiter, const JsonNode & source) const
+void Rewardable::Info::configureLimiter(Rewardable::Configuration & object, IGameRandomizer & gameRandomizer, IGameInfoCallback * cb, Rewardable::Limiter & limiter, const JsonNode & source) const
 {
 	auto const & variables = object.variables.values;
-	JsonRandom randomizer(cb);
+	JsonRandom randomizer(cb, gameRandomizer);
 
-	limiter.dayOfWeek = randomizer.loadValue(source["dayOfWeek"], rng, variables);
-	limiter.daysPassed = randomizer.loadValue(source["daysPassed"], rng, variables);
-	limiter.heroExperience = randomizer.loadValue(source["heroExperience"], rng, variables);
-	limiter.heroLevel = randomizer.loadValue(source["heroLevel"], rng, variables);
+	limiter.dayOfWeek = randomizer.loadValue(source["dayOfWeek"], variables);
+	limiter.daysPassed = randomizer.loadValue(source["daysPassed"], variables);
+	limiter.heroExperience = randomizer.loadValue(source["heroExperience"], variables);
+	limiter.heroLevel = randomizer.loadValue(source["heroLevel"], variables);
 	limiter.canLearnSkills = source["canLearnSkills"].Bool();
 	limiter.commanderAlive = source["commanderAlive"].Bool();
 	limiter.hasExtraCreatures = source["hasExtraCreatures"].Bool();
 
-	limiter.manaPercentage = randomizer.loadValue(source["manaPercentage"], rng, variables);
-	limiter.manaPoints = randomizer.loadValue(source["manaPoints"], rng, variables);
+	limiter.manaPercentage = randomizer.loadValue(source["manaPercentage"], variables);
+	limiter.manaPoints = randomizer.loadValue(source["manaPoints"], variables);
 
-	limiter.resources = randomizer.loadResources(source["resources"], rng, variables);
+	limiter.resources = randomizer.loadResources(source["resources"], variables);
 
-	limiter.primary = randomizer.loadPrimaries(source["primary"], rng, variables);
-	limiter.secondary = randomizer.loadSecondaries(source["secondary"], rng, variables);
-	limiter.artifacts = randomizer.loadArtifacts(source["artifacts"], rng, variables);
-	limiter.availableSlots = randomizer.loadArtifactSlots(source["availableSlots"], rng, variables);
-	limiter.spells  = randomizer.loadSpells(source["spells"], rng, variables);
-	limiter.scrolls  = randomizer.loadSpells(source["scrolls"], rng, variables);
-	limiter.canLearnSpells  = randomizer.loadSpells(source["canLearnSpells"], rng, variables);
-	limiter.creatures = randomizer.loadCreatures(source["creatures"], rng, variables);
-	limiter.canReceiveCreatures = randomizer.loadCreatures(source["canReceiveCreatures"], rng, variables);
+	limiter.primary = randomizer.loadPrimaries(source["primary"], variables);
+	limiter.secondary = randomizer.loadSecondaries(source["secondary"], variables);
+	limiter.artifacts = randomizer.loadArtifacts(source["artifacts"], variables);
+	limiter.availableSlots = randomizer.loadArtifactSlots(source["availableSlots"], variables);
+	limiter.spells  = randomizer.loadSpells(source["spells"], variables);
+	limiter.scrolls  = randomizer.loadSpells(source["scrolls"], variables);
+	limiter.canLearnSpells  = randomizer.loadSpells(source["canLearnSpells"], variables);
+	limiter.creatures = randomizer.loadCreatures(source["creatures"], variables);
+	limiter.canReceiveCreatures = randomizer.loadCreatures(source["canReceiveCreatures"], variables);
 	
-	limiter.players = randomizer.loadColors(source["colors"], rng, variables);
-	limiter.heroes = randomizer.loadHeroes(source["heroes"], rng);
-	limiter.heroClasses = randomizer.loadHeroClasses(source["heroClasses"], rng);
+	limiter.players = randomizer.loadColors(source["colors"], variables);
+	limiter.heroes = randomizer.loadHeroes(source["heroes"]);
+	limiter.heroClasses = randomizer.loadHeroClasses(source["heroClasses"]);
 
-	limiter.allOf  = configureSublimiters(object, rng, cb, source["allOf"] );
-	limiter.anyOf  = configureSublimiters(object, rng, cb, source["anyOf"] );
-	limiter.noneOf = configureSublimiters(object, rng, cb, source["noneOf"] );
+	limiter.allOf  = configureSublimiters(object, gameRandomizer, cb, source["allOf"]);
+	limiter.anyOf  = configureSublimiters(object, gameRandomizer, cb, source["anyOf"]);
+	limiter.noneOf = configureSublimiters(object, gameRandomizer, cb, source["noneOf"]);
 }
 
-void Rewardable::Info::configureReward(Rewardable::Configuration & object, vstd::RNG & rng, IGameInfoCallback * cb, Rewardable::Reward & reward, const JsonNode & source) const
+void Rewardable::Info::configureReward(Rewardable::Configuration & object, IGameRandomizer & gameRandomizer, IGameInfoCallback * cb, Rewardable::Reward & reward, const JsonNode & source) const
 {
 	auto const & variables = object.variables.values;
-	JsonRandom randomizer(cb);
+	JsonRandom randomizer(cb, gameRandomizer);
 
-	reward.resources = randomizer.loadResources(source["resources"], rng, variables);
+	reward.resources = randomizer.loadResources(source["resources"], variables);
 
-	reward.heroExperience = randomizer.loadValue(source["heroExperience"], rng, variables);
-	reward.heroLevel = randomizer.loadValue(source["heroLevel"], rng, variables);
+	reward.heroExperience = randomizer.loadValue(source["heroExperience"], variables);
+	reward.heroLevel = randomizer.loadValue(source["heroLevel"], variables);
 
-	reward.manaDiff = randomizer.loadValue(source["manaPoints"], rng, variables);
-	reward.manaOverflowFactor = randomizer.loadValue(source["manaOverflowFactor"], rng, variables);
-	reward.manaPercentage = randomizer.loadValue(source["manaPercentage"], rng, variables, -1);
+	reward.manaDiff = randomizer.loadValue(source["manaPoints"], variables);
+	reward.manaOverflowFactor = randomizer.loadValue(source["manaOverflowFactor"], variables);
+	reward.manaPercentage = randomizer.loadValue(source["manaPercentage"], variables, -1);
 
-	reward.movePoints = randomizer.loadValue(source["movePoints"], rng, variables);
-	reward.movePercentage = randomizer.loadValue(source["movePercentage"], rng, variables, -1);
+	reward.movePoints = randomizer.loadValue(source["movePoints"], variables);
+	reward.movePercentage = randomizer.loadValue(source["movePercentage"], variables, -1);
 
 	reward.removeObject = source["removeObject"].Bool();
 	reward.heroBonuses = randomizer.loadBonuses(source["bonuses"]);
 	reward.commanderBonuses = randomizer.loadBonuses(source["commanderBonuses"]);
 	reward.playerBonuses = randomizer.loadBonuses(source["playerBonuses"]);
 
-	reward.guards = randomizer.loadCreatures(source["guards"], rng, variables);
+	reward.guards = randomizer.loadCreatures(source["guards"], variables);
 
-	reward.primary = randomizer.loadPrimaries(source["primary"], rng, variables);
-	reward.secondary = randomizer.loadSecondaries(source["secondary"], rng, variables);
+	reward.primary = randomizer.loadPrimaries(source["primary"], variables);
+	reward.secondary = randomizer.loadSecondaries(source["secondary"], variables);
 
-	reward.grantedArtifacts = randomizer.loadArtifacts(source["artifacts"], rng, variables);
-	reward.takenArtifacts = randomizer.loadArtifacts(source["takenArtifacts"], rng, variables);
-	reward.takenArtifactSlots = randomizer.loadArtifactSlots(source["takenArtifactSlots"], rng, variables);
-	reward.grantedScrolls = randomizer.loadSpells(source["scrolls"], rng, variables);
-	reward.takenScrolls = randomizer.loadSpells(source["takenScrolls"], rng, variables);
-	reward.spells = randomizer.loadSpells(source["spells"], rng, variables);
-	reward.creatures = randomizer.loadCreatures(source["creatures"], rng, variables);
-	reward.takenCreatures = randomizer.loadCreatures(source["takenCreatures"], rng, variables);
+	reward.grantedArtifacts = randomizer.loadArtifacts(source["artifacts"], variables);
+	reward.takenArtifacts = randomizer.loadArtifacts(source["takenArtifacts"], variables);
+	reward.takenArtifactSlots = randomizer.loadArtifactSlots(source["takenArtifactSlots"], variables);
+	reward.grantedScrolls = randomizer.loadSpells(source["scrolls"], variables);
+	reward.takenScrolls = randomizer.loadSpells(source["takenScrolls"], variables);
+	reward.spells = randomizer.loadSpells(source["spells"], variables);
+	reward.creatures = randomizer.loadCreatures(source["creatures"], variables);
+	reward.takenCreatures = randomizer.loadCreatures(source["takenCreatures"], variables);
 	if(!source["spellCast"].isNull() && source["spellCast"].isStruct())
 	{
-		reward.spellCast.first = randomizer.loadSpell(source["spellCast"]["spell"], rng, variables);
+		reward.spellCast.first = randomizer.loadSpell(source["spellCast"]["spell"], variables);
 		reward.spellCast.second = source["spellCast"]["schoolLevel"].Integer();
 	}
 
@@ -206,13 +207,13 @@ void Rewardable::Info::configureReward(Rewardable::Configuration & object, vstd:
 		auto const & entry = source["revealTiles"];
 
 		reward.revealTiles = RewardRevealTiles();
-		reward.revealTiles->radius = randomizer.loadValue(entry["radius"], rng, variables);
+		reward.revealTiles->radius = randomizer.loadValue(entry["radius"], variables);
 		reward.revealTiles->hide = entry["hide"].Bool();
 
-		reward.revealTiles->scoreSurface = randomizer.loadValue(entry["surface"], rng, variables);
-		reward.revealTiles->scoreSubterra = randomizer.loadValue(entry["subterra"], rng, variables);
-		reward.revealTiles->scoreWater = randomizer.loadValue(entry["water"], rng, variables);
-		reward.revealTiles->scoreRock = randomizer.loadValue(entry["rock"], rng, variables);
+		reward.revealTiles->scoreSurface = randomizer.loadValue(entry["surface"], variables);
+		reward.revealTiles->scoreSubterra = randomizer.loadValue(entry["subterra"], variables);
+		reward.revealTiles->scoreWater = randomizer.loadValue(entry["water"], variables);
+		reward.revealTiles->scoreRock = randomizer.loadValue(entry["rock"], variables);
 	}
 
 	for ( auto node : source["changeCreatures"].Struct() )
@@ -226,16 +227,16 @@ void Rewardable::Info::configureReward(Rewardable::Configuration & object, vstd:
 	}
 }
 
-void Rewardable::Info::configureResetInfo(Rewardable::Configuration & object, vstd::RNG & rng, Rewardable::ResetInfo & resetParameters, const JsonNode & source) const
+void Rewardable::Info::configureResetInfo(Rewardable::Configuration & object, IGameRandomizer & gameRandomizer, Rewardable::ResetInfo & resetParameters, const JsonNode & source) const
 {
 	resetParameters.period   = static_cast<ui32>(source["period"].Float());
 	resetParameters.visitors = source["visitors"].Bool();
 	resetParameters.rewards  = source["rewards"].Bool();
 }
 
-void Rewardable::Info::configureVariables(Rewardable::Configuration & object, vstd::RNG & rng, IGameInfoCallback * cb, const JsonNode & source) const
+void Rewardable::Info::configureVariables(Rewardable::Configuration & object, IGameRandomizer & gameRandomizer, IGameInfoCallback * cb, const JsonNode & source) const
 {
-	JsonRandom randomizer(cb);
+	JsonRandom randomizer(cb, gameRandomizer);
 
 	for(const auto & category : source.Struct())
 	{
@@ -246,19 +247,19 @@ void Rewardable::Info::configureVariables(Rewardable::Configuration & object, vs
 			int32_t value = -1;
 
 			if (category.first == "number")
-				value = randomizer.loadValue(input, rng, object.variables.values);
+				value = randomizer.loadValue(input, object.variables.values);
 
 			if (category.first == "artifact")
-				value = randomizer.loadArtifact(input, rng, object.variables.values).getNum();
+				value = randomizer.loadArtifact(input, object.variables.values).getNum();
 
 			if (category.first == "spell")
-				value = randomizer.loadSpell(input, rng, object.variables.values).getNum();
+				value = randomizer.loadSpell(input, object.variables.values).getNum();
 
 			if (category.first == "primarySkill")
-				value = randomizer.loadPrimary(input, rng, object.variables.values).getNum();
+				value = randomizer.loadPrimary(input, object.variables.values).getNum();
 
 			if (category.first == "secondarySkill")
-				value = randomizer.loadSecondary(input, rng, object.variables.values).getNum();
+				value = randomizer.loadSecondary(input, object.variables.values).getNum();
 
 			object.initVariable(category.first, entry.first, value);
 		}
@@ -350,7 +351,7 @@ void Rewardable::Info::replaceTextPlaceholders(MetaString & target, const Variab
 
 void Rewardable::Info::configureRewards(
 		Rewardable::Configuration & object,
-		vstd::RNG & rng,
+		IGameRandomizer & gameRandomizer,
 		IGameInfoCallback * cb,
 		const JsonNode & source,
 		Rewardable::EEventType event,
@@ -371,7 +372,7 @@ void Rewardable::Info::configureRewards(
 			{
 				const JsonNode & preset = object.getPresetVariable("dice", diceID);
 				if (preset.isNull())
-					object.initVariable("dice", diceID, rng.nextInt(0, 99));
+					object.initVariable("dice", diceID, gameRandomizer.getDefault().nextInt(0, 99));
 				else
 					object.initVariable("dice", diceID, preset.Integer());
 
@@ -394,8 +395,8 @@ void Rewardable::Info::configureRewards(
 		}
 
 		Rewardable::VisitInfo info;
-		configureLimiter(object, rng, cb, info.limiter, reward["limiter"]);
-		configureReward(object, rng, cb, info.reward, reward);
+		configureLimiter(object, gameRandomizer, cb, info.limiter, reward["limiter"]);
+		configureReward(object, gameRandomizer, cb, info.reward, reward);
 
 		info.visitType = event;
 		info.message = loadMessage(reward["message"], TextIdentifier(objectTextID, modeName, i));
@@ -408,16 +409,16 @@ void Rewardable::Info::configureRewards(
 	}
 }
 
-void Rewardable::Info::configureObject(Rewardable::Configuration & object, vstd::RNG & rng, IGameInfoCallback * cb) const
+void Rewardable::Info::configureObject(Rewardable::Configuration & object, IGameRandomizer & gameRandomizer, IGameInfoCallback * cb) const
 {
 	object.info.clear();
 	object.variables.values.clear();
 
-	configureVariables(object, rng, cb, parameters["variables"]);
+	configureVariables(object, gameRandomizer, cb, parameters["variables"]);
 
-	configureRewards(object, rng, cb, parameters["rewards"], Rewardable::EEventType::EVENT_FIRST_VISIT, "rewards");
-	configureRewards(object, rng, cb, parameters["onVisited"], Rewardable::EEventType::EVENT_ALREADY_VISITED, "onVisited");
-	configureRewards(object, rng, cb, parameters["onEmpty"], Rewardable::EEventType::EVENT_NOT_AVAILABLE, "onEmpty");
+	configureRewards(object, gameRandomizer, cb, parameters["rewards"], Rewardable::EEventType::EVENT_FIRST_VISIT, "rewards");
+	configureRewards(object, gameRandomizer, cb, parameters["onVisited"], Rewardable::EEventType::EVENT_ALREADY_VISITED, "onVisited");
+	configureRewards(object, gameRandomizer, cb, parameters["onEmpty"], Rewardable::EEventType::EVENT_NOT_AVAILABLE, "onEmpty");
 
 	object.onSelect = loadMessage(parameters["onSelectMessage"], TextIdentifier(objectTextID, "onSelect"));
 	object.description = loadMessage(parameters["description"], TextIdentifier(objectTextID, "description"));
@@ -460,7 +461,7 @@ void Rewardable::Info::configureObject(Rewardable::Configuration & object, vstd:
 		object.info.push_back(onGuarded);
 	}
 
-	configureResetInfo(object, rng, object.resetParameters, parameters["resetParameters"]);
+	configureResetInfo(object, gameRandomizer, object.resetParameters, parameters["resetParameters"]);
 
 	object.canRefuse = parameters["canRefuse"].Bool();
 	object.showScoutedPreview = parameters["showScoutedPreview"].Bool();
@@ -494,7 +495,7 @@ void Rewardable::Info::configureObject(Rewardable::Configuration & object, vstd:
 	}
 
 	if (object.visitMode == Rewardable::VISIT_LIMITER)
-		configureLimiter(object, rng, cb, object.visitLimiter, parameters["visitLimiter"]);
+		configureLimiter(object, gameRandomizer, cb, object.visitLimiter, parameters["visitLimiter"]);
 
 }
 

+ 8 - 12
lib/rewardable/Info.h

@@ -15,13 +15,9 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-namespace vstd
-{
-class RNG;
-}
-
 class MetaString;
 class IGameInfoCallback;
+class IGameRandomizer;
 
 namespace Rewardable
 {
@@ -42,14 +38,14 @@ class DLL_LINKAGE Info : public IObjectInfo
 	void replaceTextPlaceholders(MetaString & target, const Variables & variables) const;
 	void replaceTextPlaceholders(MetaString & target, const Variables & variables, const VisitInfo & info) const;
 
-	void configureVariables(Rewardable::Configuration & object, vstd::RNG & rng, IGameInfoCallback * cb, const JsonNode & source) const;
-	void configureRewards(Rewardable::Configuration & object, vstd::RNG & rng, IGameInfoCallback * cb, const JsonNode & source, Rewardable::EEventType mode, const std::string & textPrefix) const;
+	void configureVariables(Rewardable::Configuration & object, IGameRandomizer & gameRandomizer, IGameInfoCallback * cb, const JsonNode & source) const;
+	void configureRewards(Rewardable::Configuration & object, IGameRandomizer & gameRandomizer, IGameInfoCallback * cb, const JsonNode & source, Rewardable::EEventType mode, const std::string & textPrefix) const;
 
-	void configureLimiter(Rewardable::Configuration & object, vstd::RNG & rng, IGameInfoCallback * cb, Rewardable::Limiter & limiter, const JsonNode & source) const;
-	Rewardable::LimitersList configureSublimiters(Rewardable::Configuration & object, vstd::RNG & rng, IGameInfoCallback * cb, const JsonNode & source) const;
+	void configureLimiter(Rewardable::Configuration & object, IGameRandomizer & gameRandomizer, IGameInfoCallback * cb, Rewardable::Limiter & limiter, const JsonNode & source) const;
+	Rewardable::LimitersList configureSublimiters(Rewardable::Configuration & object, IGameRandomizer & gameRandomizer, IGameInfoCallback * cb, const JsonNode & source) const;
 
-	void configureReward(Rewardable::Configuration & object, vstd::RNG & rng, IGameInfoCallback * cb, Rewardable::Reward & info, const JsonNode & source) const;
-	void configureResetInfo(Rewardable::Configuration & object, vstd::RNG & rng, Rewardable::ResetInfo & info, const JsonNode & source) const;
+	void configureReward(Rewardable::Configuration & object, IGameRandomizer & gameRandomizer, IGameInfoCallback * cb, Rewardable::Reward & info, const JsonNode & source) const;
+	void configureResetInfo(Rewardable::Configuration & object, IGameRandomizer & gameRandomizer, Rewardable::ResetInfo & info, const JsonNode & source) const;
 public:
 	const JsonNode & getParameters() const;
 
@@ -70,7 +66,7 @@ public:
 
 	bool hasGuards() const override;
 
-	void configureObject(Rewardable::Configuration & object, vstd::RNG & rng, IGameInfoCallback * cb) const;
+	void configureObject(Rewardable::Configuration & object, IGameRandomizer & gameRandomizer, IGameInfoCallback * cb) const;
 
 	void init(const JsonNode & objectConfig, const std::string & objectTextID);
 

+ 2 - 1
lib/serializer/ESerializationVersion.h

@@ -41,8 +41,9 @@ enum class ESerializationVersion : int32_t
 	STORE_UID_COUNTER_IN_CMAP,  // fix crash caused by conflicting instanceName after loading game
 	REWARDABLE_EXTENSIONS, // new functionality for rewardable objects
 	FLAGGABLE_BONUS_SYSTEM_NODE, // flaggable objects now contain bonus system node
+	RANDOMIZATION_REWORK, // random rolls logic has been moved to server
 
-	CURRENT = FLAGGABLE_BONUS_SYSTEM_NODE,
+	CURRENT = RANDOMIZATION_REWORK,
 };
 
 static_assert(ESerializationVersion::MINIMAL <= ESerializationVersion::CURRENT, "Invalid serialization version definition!");

+ 0 - 1
lib/serializer/RegisterTypes.h

@@ -159,7 +159,6 @@ void registerTypes(Serializer &s)
 	s.template registerType<PlayerEndsGame>(103);
 	s.template registerType<PlayerReinitInterface>(104);
 	s.template registerType<RemoveBonus>(105);
-	s.template registerType<UpdateArtHandlerLists>(106);
 	s.template registerType<ChangeFormation>(107);
 	s.template registerType<RemoveObject>(108);
 	s.template registerType<TryMoveHero>(109);

+ 13 - 26
server/CGameHandler.cpp

@@ -30,6 +30,7 @@
 #include "../lib/CPlayerState.h"
 #include "../lib/CRandomGenerator.h"
 #include "../lib/CSoundBase.h"
+#include "../lib/CSkillHandler.h"
 #include "../lib/CThreadHelper.h"
 #include "../lib/GameConstants.h"
 #include "../lib/UnlockGuard.h"
@@ -42,6 +43,7 @@
 #include "../lib/int3.h"
 
 #include "../lib/battle/BattleInfo.h"
+#include "../lib/callback/GameRandomizer.h"
 
 #include "../lib/entities/artifact/ArtifactUtils.h"
 #include "../lib/entities/artifact/CArtifact.h"
@@ -156,7 +158,7 @@ void CGameHandler::levelUpHero(const CGHeroInstance * hero)
 
 	// give primary skill
 	logGlobal->trace("%s got level %d", hero->getNameTranslated(), hero->level);
-	auto primarySkill = hero->nextPrimarySkill(getRandomGenerator());
+	auto primarySkill = randomizer->rollPrimarySkillForLevelup(hero);
 
 	SetPrimSkill sps;
 	sps.id = hero->id;
@@ -169,7 +171,7 @@ void CGameHandler::levelUpHero(const CGHeroInstance * hero)
 	hlu.player = hero->tempOwner;
 	hlu.heroId = hero->id;
 	hlu.primskill = primarySkill;
-	hlu.skills = hero->getLevelUpProposedSecondarySkills(heroPool->getHeroSkillsRandomGenerator(hero->getHeroTypeID()));
+	hlu.skills = hero->getLevelupSkillCandidates(*randomizer);
 
 	if (hlu.skills.size() == 0)
 	{
@@ -510,7 +512,7 @@ CGameHandler::CGameHandler(CVCMIServer * lobby)
 	, turnOrder(std::make_unique<TurnOrderProcessor>(this))
 	, queries(std::make_unique<QueriesProcessor>())
 	, playerMessages(std::make_unique<PlayerMessageProcessor>(this))
-	, randomNumberGenerator(std::make_unique<CRandomGenerator>())
+	, randomizer(std::make_unique<GameRandomizer>(*this))
 	, complainNoCreatures("No creatures to split")
 	, complainNotEnoughCreatures("Cannot split that stack, not enough creatures!")
 	, complainInvalidSlot("Invalid slot accessed!")
@@ -539,25 +541,19 @@ void CGameHandler::init(StartInfo *si, Load::ProgressAccumulator & progressTrack
 {
 	int requestedSeed = settings["server"]["seed"].Integer();
 	if (requestedSeed != 0)
-		randomNumberGenerator->setSeed(requestedSeed);
-	logGlobal->info("Using random seed: %d", randomNumberGenerator->nextInt());
+		randomizer->setSeed(requestedSeed);
+	logGlobal->info("Using random seed: %d", randomizer->getDefault().nextInt());
 
 	CMapService mapService;
 	gs = std::make_shared<CGameState>(this);
 	gs->preInit(LIBRARY);
 	logGlobal->info("Gamestate created!");
-	gs->init(&mapService, si, getRandomGenerator(), progressTracking);
+	gs->init(&mapService, si, *randomizer, progressTracking);
 	logGlobal->info("Gamestate initialized!");
 
 	for (const auto & elem : gameState().players)
 		turnOrder->addPlayer(elem.first);
 
-	for (const auto & elem : gameState().getMap().getObjects<CGHeroInstance>())
-		heroPool->getHeroSkillsRandomGenerator(elem->getHeroTypeID()); // init RMG seed
-
-	for (const auto & elem : gameState().getMap().getHeroesInPool())
-		heroPool->getHeroSkillsRandomGenerator(elem); // init RMG seed
-
 	reinitScripting();
 }
 
@@ -687,7 +683,7 @@ void CGameHandler::onNewTurn()
 	{
 		SetAvailableArtifacts saa;
 		saa.id = ObjectInstanceID::NONE;
-		pickAllowedArtsSet(saa.arts, getRandomGenerator());
+		saa.arts = randomizer->rollMarketArtifactSet();
 		sendAndApply(saa);
 	}
 
@@ -700,10 +696,8 @@ void CGameHandler::onNewTurn()
 	for (auto & elem : gameState().getMap().getObjects())
 	{
 		if (elem)
-			elem->newTurn(*this);
+			elem->newTurn(*this, *randomizer);
 	}
-
-	synchronizeArtifactHandlerLists(); //new day events may have changed them. TODO better of managing that
 }
 
 void CGameHandler::start(bool resume)
@@ -4063,13 +4057,6 @@ void CGameHandler::spawnWanderingMonsters(CreatureID creatureID)
 	}
 }
 
-void CGameHandler::synchronizeArtifactHandlerLists()
-{
-	UpdateArtHandlerLists uahl;
-	uahl.allocatedArtifacts = gameState().allocatedArtifacts;
-	sendAndApply(uahl);
-}
-
 bool CGameHandler::isBlockedByQueries(const CPackForServer *pack, PlayerColor player)
 {
 	if (dynamic_cast<const PlayerMessage *>(pack) != nullptr)
@@ -4237,7 +4224,7 @@ void CGameHandler::showInfoDialog(InfoWindow * iw)
 
 vstd::RNG & CGameHandler::getRandomGenerator()
 {
-	return *randomNumberGenerator;
+	return randomizer->getDefault();
 }
 
 #if SCRIPTING_ENABLED
@@ -4266,7 +4253,7 @@ std::shared_ptr<CGObjectInstance> CGameHandler::createNewObject(const int3 & vis
 	auto handler = LIBRARY->objtypeh->getHandlerFor(objectID, subID);
 
 	auto o = handler->create(gameState().cb, nullptr);
-	handler->configureObject(o.get(), getRandomGenerator());
+	handler->configureObject(o.get(), *randomizer);
 	assert(o->ID == objectID);
 	gameState().getMap().generateUniqueInstanceName(o.get());
 
@@ -4317,7 +4304,7 @@ void CGameHandler::createHole(const int3 & visitablePosition, PlayerColor initia
 
 void CGameHandler::newObject(std::shared_ptr<CGObjectInstance> object, PlayerColor initiator)
 {
-	object->initObj(getRandomGenerator());
+	object->initObj(*randomizer);
 
 	NewObject no;
 	no.newObject = object;

+ 3 - 3
server/CGameHandler.h

@@ -27,6 +27,7 @@ class CConnection;
 class CCommanderInstance;
 class EVictoryLossCheckResult;
 class CRandomGenerator;
+class GameRandomizer;
 
 struct CPackForServer;
 struct NewTurn;
@@ -65,7 +66,7 @@ public:
 	std::unique_ptr<TurnOrderProcessor> turnOrder;
 	std::unique_ptr<TurnTimerHandler> turnTimerHandler;
 	std::unique_ptr<NewTurnProcessor> newTurnProcessor;
-	std::unique_ptr<CRandomGenerator> randomNumberGenerator;
+	std::unique_ptr<GameRandomizer> randomizer;
 	std::shared_ptr<CGameState> gs;
 
 	//use enums as parameters, because doMove(sth, true, false, true) is not readable
@@ -150,7 +151,6 @@ public:
 	bool saveArtifactsCostume(const PlayerColor & player, const ObjectInstanceID heroID, uint32_t costumeIdx);
 	bool switchArtifactsCostume(const PlayerColor & player, const ObjectInstanceID heroID, uint32_t costumeIdx);
 	bool eraseArtifactByClient(const ArtifactLocation & al);
-	void synchronizeArtifactHandlerLists();
 
 	void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override;
 	void stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override;
@@ -247,7 +247,7 @@ public:
 	template <typename Handler> void serialize(Handler &h)
 	{
 		h & QID;
-		h & *randomNumberGenerator;
+		h & *randomizer;
 		h & *battles;
 		h & *heroPool;
 		h & *playerMessages;

+ 20 - 34
server/battles/BattleActionProcessor.cpp

@@ -21,6 +21,7 @@
 #include "../../lib/battle/CObstacleInstance.h"
 #include "../../lib/battle/IBattleState.h"
 #include "../../lib/battle/BattleAction.h"
+#include "../../lib/callback/GameRandomizer.h"
 #include "../../lib/entities/building/TownFortifications.h"
 #include "../../lib/gameState/CGameState.h"
 #include "../../lib/networkPacks/PacksForClientBattle.h"
@@ -938,37 +939,22 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
 		bat.flags |= BattleAttack::COUNTER;
 
 	const int attackerLuck = attacker->luckVal();
+	ObjectInstanceID ownerArmy = battle.getBattle()->getSideArmy(attacker->unitSide())->id;
 
-	if(attackerLuck > 0)
-	{
-		auto goodLuckChanceVector = gameHandler->getSettings().getVector(EGameSettings::COMBAT_GOOD_LUCK_CHANCE);
-		int luckDiceSize = gameHandler->getSettings().getInteger(EGameSettings::COMBAT_LUCK_DICE_SIZE);
-		size_t chanceIndex = std::min<size_t>(goodLuckChanceVector.size(), attackerLuck) - 1; // array index, so 0-indexed
-
-		if(goodLuckChanceVector.size() > 0 && gameHandler->getRandomGenerator().nextInt(1, luckDiceSize) <= goodLuckChanceVector[chanceIndex])
-			bat.flags |= BattleAttack::LUCKY;
-	}
+	if(attackerLuck > 0 && gameHandler->randomizer->rollGoodLuck(ownerArmy, attackerLuck))
+		bat.flags |= BattleAttack::LUCKY;
 
-	if(attackerLuck < 0)
-	{
-		auto badLuckChanceVector = gameHandler->getSettings().getVector(EGameSettings::COMBAT_BAD_LUCK_CHANCE);
-		int luckDiceSize = gameHandler->getSettings().getInteger(EGameSettings::COMBAT_LUCK_DICE_SIZE);
-		size_t chanceIndex = std::min<size_t>(badLuckChanceVector.size(), -attackerLuck) - 1; // array index, so 0-indexed
-
-		if(badLuckChanceVector.size() > 0 && gameHandler->getRandomGenerator().nextInt(1, luckDiceSize) <= badLuckChanceVector[chanceIndex])
-			bat.flags |= BattleAttack::UNLUCKY;
-	}
+	if(attackerLuck < 0 && gameHandler->randomizer->rollBadLuck(ownerArmy, -attackerLuck))
+		bat.flags |= BattleAttack::UNLUCKY;
 
-	if (gameHandler->getRandomGenerator().nextInt(99) < attacker->valOfBonuses(BonusType::DOUBLE_DAMAGE_CHANCE))
-	{
+	if (gameHandler->randomizer->rollCombatAbility(ownerArmy, attacker->valOfBonuses(BonusType::DOUBLE_DAMAGE_CHANCE)))
 		bat.flags |= BattleAttack::DEATH_BLOW;
-	}
 
 	const auto * owner = battle.battleGetFightingHero(attacker->unitSide());
 	if(owner)
 	{
 		int chance = owner->valOfBonuses(BonusType::BONUS_DAMAGE_CHANCE, BonusSubtypeID(attacker->creatureId()));
-		if (chance > gameHandler->getRandomGenerator().nextInt(99))
+		if (gameHandler->randomizer->rollCombatAbility(ownerArmy, chance))
 			bat.flags |= BattleAttack::BALLISTA_DOUBLE_DMG;
 	}
 
@@ -1126,6 +1112,8 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
 
 void BattleActionProcessor::attackCasting(const CBattleInfoCallback & battle, bool ranged, BonusType attackMode, const battle::Unit * attacker, const CStack * defender)
 {
+	ObjectInstanceID ownerArmy = battle.getBattle()->getSideArmy(attacker->unitSide())->id;
+
 	if(attacker->hasBonusOfType(attackMode))
 	{
 		TConstBonusListPtr spells = attacker->getBonuses(Selector::type()(attackMode));
@@ -1169,7 +1157,7 @@ void BattleActionProcessor::attackCasting(const CBattleInfoCallback & battle, bo
 				continue;
 
 			//check if spell should be cast (probability handling)
-			if(gameHandler->getRandomGenerator().nextInt(99) >= chance)
+			if (!gameHandler->randomizer->rollCombatAbility(ownerArmy, chance))
 				continue;
 
 			//casting
@@ -1325,9 +1313,11 @@ void BattleActionProcessor::handleAfterAttackCasting(const CBattleInfoCallback &
 
 	int64_t acidDamage = 0;
 	TConstBonusListPtr acidBreath = attacker->getBonuses(Selector::type()(BonusType::ACID_BREATH));
+	ObjectInstanceID ownerArmy = battle.getBattle()->getSideArmy(attacker->unitSide())->id;
+
 	for(const auto & b : *acidBreath)
 	{
-		if(b->additionalInfo[0] > gameHandler->getRandomGenerator().nextInt(99))
+		if (gameHandler->randomizer->rollCombatAbility(ownerArmy, b->additionalInfo[0]))
 			acidDamage += b->val;
 	}
 
@@ -1351,10 +1341,8 @@ void BattleActionProcessor::handleAfterAttackCasting(const CBattleInfoCallback &
 
 	if(attacker->hasBonusOfType(BonusType::TRANSMUTATION) && defender->isLiving()) //transmutation mechanics, similar to WoG werewolf ability
 	{
-		double chanceToTrigger = attacker->valOfBonuses(BonusType::TRANSMUTATION) / 100.0f;
-		vstd::amin(chanceToTrigger, 1); //cap at 100%
-
-		if(gameHandler->getRandomGenerator().nextDouble(0, 1) > chanceToTrigger)
+		int chanceToTrigger = attacker->valOfBonuses(BonusType::TRANSMUTATION);
+		if (!gameHandler->randomizer->rollCombatAbility(ownerArmy, chanceToTrigger))
 			return;
 
 		int bonusAdditionalInfo = attacker->getBonus(Selector::type()(BonusType::TRANSMUTATION))->additionalInfo[0];
@@ -1401,24 +1389,22 @@ void BattleActionProcessor::handleAfterAttackCasting(const CBattleInfoCallback &
 
 	if(attacker->hasBonusOfType(BonusType::DESTRUCTION, BonusCustomSubtype::destructionKillPercentage) || attacker->hasBonusOfType(BonusType::DESTRUCTION, BonusCustomSubtype::destructionKillAmount))
 	{
-		double chanceToTrigger = 0;
+		int chanceToTrigger = 0;
 		int amountToDie = 0;
 
 		if(attacker->hasBonusOfType(BonusType::DESTRUCTION, BonusCustomSubtype::destructionKillPercentage)) //killing by percentage
 		{
-			chanceToTrigger = attacker->valOfBonuses(BonusType::DESTRUCTION, BonusCustomSubtype::destructionKillPercentage) / 100.0f;
+			chanceToTrigger = attacker->valOfBonuses(BonusType::DESTRUCTION, BonusCustomSubtype::destructionKillPercentage);
 			int percentageToDie = attacker->getBonus(Selector::type()(BonusType::DESTRUCTION).And(Selector::subtype()(BonusCustomSubtype::destructionKillPercentage)))->additionalInfo[0];
 			amountToDie = static_cast<int>(defender->getCount() * percentageToDie * 0.01f);
 		}
 		else if(attacker->hasBonusOfType(BonusType::DESTRUCTION, BonusCustomSubtype::destructionKillAmount)) //killing by count
 		{
-			chanceToTrigger = attacker->valOfBonuses(BonusType::DESTRUCTION, BonusCustomSubtype::destructionKillAmount) / 100.0f;
+			chanceToTrigger = attacker->valOfBonuses(BonusType::DESTRUCTION, BonusCustomSubtype::destructionKillAmount);
 			amountToDie = attacker->getBonus(Selector::type()(BonusType::DESTRUCTION).And(Selector::subtype()(BonusCustomSubtype::destructionKillAmount)))->additionalInfo[0];
 		}
 
-		vstd::amin(chanceToTrigger, 1); //cap trigger chance at 100%
-
-		if(gameHandler->getRandomGenerator().nextDouble(0, 1) > chanceToTrigger)
+		if (!gameHandler->randomizer->rollCombatAbility(ownerArmy, chanceToTrigger))
 			return;
 
 		BattleStackAttacked bsa;

+ 12 - 19
server/battles/BattleFlowProcessor.cpp

@@ -19,6 +19,7 @@
 #include "../../lib/IGameSettings.h"
 #include "../../lib/battle/CBattleInfoCallback.h"
 #include "../../lib/battle/IBattleState.h"
+#include "../../lib/callback/GameRandomizer.h"
 #include "../../lib/entities/building/TownFortifications.h"
 #include "../../lib/gameState/CGameState.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
@@ -347,11 +348,8 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CBattleInfoCallback & bat
 	int nextStackMorale = next->moraleVal();
 	if(!next->hadMorale && !next->waited() && nextStackMorale < 0)
 	{
-		auto badMoraleChanceVector = gameHandler->getSettings().getVector(EGameSettings::COMBAT_BAD_MORALE_CHANCE);
-		int moraleDiceSize = gameHandler->getSettings().getInteger(EGameSettings::COMBAT_MORALE_DICE_SIZE);
-		size_t chanceIndex = std::min<size_t>(badMoraleChanceVector.size(), -nextStackMorale) - 1; // array index, so 0-indexed
-
-		if(badMoraleChanceVector.size() > 0 && gameHandler->getRandomGenerator().nextInt(1, moraleDiceSize) <= badMoraleChanceVector[chanceIndex])
+		ObjectInstanceID ownerArmy = battle.getBattle()->getSideArmy(next->unitSide())->id;
+		if (gameHandler->randomizer->rollBadMorale(ownerArmy, -nextStackMorale))
 		{
 			//unit loses its turn - empty freeze action
 			BattleAction ba;
@@ -392,7 +390,7 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CBattleInfoCallback & bat
 	const CreatureID stackCreatureId = next->unitType()->getId();
 
 	if ((stackCreatureId == CreatureID::ARROW_TOWERS || stackCreatureId == CreatureID::BALLISTA)
-		&& (!curOwner || gameHandler->getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(BonusType::MANUAL_CONTROL, BonusSubtypeID(stackCreatureId))))
+	   && (!curOwner || !gameHandler->randomizer->rollCombatAbility(curOwner->id, curOwner->valOfBonuses(BonusType::MANUAL_CONTROL, BonusSubtypeID(stackCreatureId)))))
 	{
 		BattleAction attack;
 		attack.actionType = EActionType::SHOOT;
@@ -464,7 +462,7 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CBattleInfoCallback & bat
 			return true;
 		}
 
-		if (!curOwner || gameHandler->getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(BonusType::MANUAL_CONTROL, BonusSubtypeID(CreatureID(CreatureID::CATAPULT))))
+		if (!curOwner || !gameHandler->randomizer->rollCombatAbility(curOwner->id, curOwner->valOfBonuses(BonusType::MANUAL_CONTROL, BonusSubtypeID(CreatureID(CreatureID::CATAPULT)))))
 		{
 			BattleAction attack;
 			attack.actionType = EActionType::CATAPULT;
@@ -489,7 +487,7 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CBattleInfoCallback & bat
 			return true;
 		}
 
-		if (!curOwner || gameHandler->getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(BonusType::MANUAL_CONTROL, BonusSubtypeID(CreatureID(CreatureID::FIRST_AID_TENT))))
+		if (!curOwner || !gameHandler->randomizer->rollCombatAbility(curOwner->id, curOwner->valOfBonuses(BonusType::MANUAL_CONTROL, BonusSubtypeID(CreatureID(CreatureID::FIRST_AID_TENT)))))
 		{
 			RandomGeneratorUtil::randomShuffle(possibleStacks, gameHandler->getRandomGenerator());
 			const CStack * toBeHealed = possibleStacks.front();
@@ -527,11 +525,8 @@ bool BattleFlowProcessor::rollGoodMorale(const CBattleInfoCallback & battle, con
 		&& next->canMove()
 		&& nextStackMorale > 0)
 	{
-		auto goodMoraleChanceVector = gameHandler->getSettings().getVector(EGameSettings::COMBAT_GOOD_MORALE_CHANCE);
-		int moraleDiceSize = gameHandler->getSettings().getInteger(EGameSettings::COMBAT_MORALE_DICE_SIZE);
-		size_t chanceIndex = std::min<size_t>(goodMoraleChanceVector.size(), nextStackMorale) - 1; // array index, so 0-indexed
-
-		if(goodMoraleChanceVector.size() > 0 && gameHandler->getRandomGenerator().nextInt(1, moraleDiceSize) <= goodMoraleChanceVector[chanceIndex])
+		ObjectInstanceID ownerArmy = battle.getBattle()->getSideArmy(next->unitSide())->id;
+		if (gameHandler->randomizer->rollGoodMorale(ownerArmy, nextStackMorale))
 		{
 			BattleTriggerEffect bte;
 			bte.battleID = battle.getBattle()->getBattleID();
@@ -743,6 +738,7 @@ void BattleFlowProcessor::stackTurnTrigger(const CBattleInfoCallback & battle, c
 		}
 		if (st->isLiving() && !st->hasBonusOfType(BonusType::FEARLESS))
 		{
+			ObjectInstanceID opponentArmyID = battle.battleGetArmyObject(battle.otherSide(st->unitSide()))->id;
 			bool fearsomeCreature = false;
 			for (const CStack * stack : battle.battleGetAllStacks(true))
 			{
@@ -752,13 +748,10 @@ void BattleFlowProcessor::stackTurnTrigger(const CBattleInfoCallback & battle, c
 					break;
 				}
 			}
-			if (fearsomeCreature)
+			if (fearsomeCreature && gameHandler->randomizer->rollCombatAbility(opponentArmyID, 10)) //fixed 10%
 			{
-				if (gameHandler->getRandomGenerator().nextInt(99) < 10) //fixed 10%
-				{
-					bte.effect = vstd::to_underlying(BonusType::FEAR);
-					gameHandler->sendAndApply(bte);
-				}
+				bte.effect = vstd::to_underlying(BonusType::FEAR);
+				gameHandler->sendAndApply(bte);
 			}
 		}
 		BonusList bl = *(st->getBonuses(Selector::type()(BonusType::ENCHANTER)));

+ 0 - 11
server/processors/HeroPoolProcessor.cpp

@@ -368,17 +368,6 @@ CGHeroInstance * HeroPoolProcessor::pickHeroFor(bool isNative, const PlayerColor
 	return *RandomGeneratorUtil::nextItem(possibleHeroes, getRandomGenerator(player));
 }
 
-vstd::RNG & HeroPoolProcessor::getHeroSkillsRandomGenerator(const HeroTypeID & hero)
-{
-	if (heroSeed.count(hero) == 0)
-	{
-		int seed = gameHandler->getRandomGenerator().nextInt();
-		heroSeed.emplace(hero, std::make_unique<CRandomGenerator>(seed));
-	}
-
-	return *heroSeed.at(hero);
-}
-
 vstd::RNG & HeroPoolProcessor::getRandomGenerator(const PlayerColor & player)
 {
 	if (playerSeed.count(player) == 0)

+ 6 - 6
server/processors/HeroPoolProcessor.h

@@ -38,9 +38,6 @@ class HeroPoolProcessor : boost::noncopyable
 	/// per-player random generators
 	std::map<PlayerColor, std::unique_ptr<CRandomGenerator>> playerSeed;
 
-	/// per-hero random generators used to randomize skills
-	std::map<HeroTypeID, std::unique_ptr<CRandomGenerator>> heroSeed;
-
 	void clearHeroFromSlot(const PlayerColor & color, TavernHeroSlot slot);
 	void selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot, bool needNativeHero, bool giveStartingArmy, const HeroTypeID & nextHero = HeroTypeID::NONE);
 
@@ -63,14 +60,17 @@ public:
 
 	void onNewWeek(const PlayerColor & color);
 
-	vstd::RNG & getHeroSkillsRandomGenerator(const HeroTypeID & hero);
-
 	/// Incoming net pack handling
 	bool hireHero(const ObjectInstanceID & objectID, const HeroTypeID & hid, const PlayerColor & player, const HeroTypeID & nextHero);
 
 	template <typename Handler> void serialize(Handler &h)
 	{
 		h & playerSeed;
-		h & heroSeed;
+		if (!h.hasFeature(Handler::Version::RANDOMIZATION_REWORK))
+		{
+			std::map<HeroTypeID, std::unique_ptr<CRandomGenerator>> heroSeedUnused;
+			h & heroSeedUnused;
+		}
+
 	}
 };

+ 3 - 2
server/processors/NewTurnProcessor.cpp

@@ -18,6 +18,7 @@
 #include "../../lib/IGameSettings.h"
 #include "../../lib/StartInfo.h"
 #include "../../lib/TerrainHandler.h"
+#include "../../lib/callback/GameRandomizer.h"
 #include "../../lib/constants/StringConstants.h"
 #include "../../lib/entities/building/CBuilding.h"
 #include "../../lib/entities/faction/CTownHandler.h"
@@ -524,7 +525,7 @@ std::tuple<EWeekType, CreatureID> NewTurnProcessor::pickWeekType(bool newMonth)
 		{
 			if (gameHandler->getSettings().getBoolean(EGameSettings::CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH))
 			{
-				CreatureID creatureID = LIBRARY->creh->pickRandomMonster(gameHandler->getRandomGenerator());
+				CreatureID creatureID = gameHandler->randomizer->rollCreature();
 				return { EWeekType::DOUBLE_GROWTH, creatureID};
 			}
 			else if (LIBRARY->creh->doubledCreatures.size())
@@ -551,7 +552,7 @@ std::tuple<EWeekType, CreatureID> NewTurnProcessor::pickWeekType(bool newMonth)
 			std::pair<int, CreatureID> newMonster(54, CreatureID());
 			do
 			{
-				newMonster.second = LIBRARY->creh->pickRandomMonster(gameHandler->getRandomGenerator());
+				newMonster.second = gameHandler->randomizer->rollCreature();
 			} while (newMonster.second.toEntity(LIBRARY)->getFactionID().toFaction()->town == nullptr); // find first non neutral creature
 
 			return { EWeekType::BONUS_GROWTH, newMonster.second};

+ 3 - 0
server/queries/MapQueries.cpp

@@ -25,6 +25,9 @@ TimerPauseQuery::TimerPauseQuery(CGameHandler * owner, PlayerColor player):
 
 bool TimerPauseQuery::blocksPack(const CPackForServer *pack) const
 {
+	if(dynamic_cast<const SaveGame*>(pack) != nullptr)
+		return false;
+
 	return blockAllButReply(pack);
 }
 

+ 4 - 3
test/game/CGameStateTest.cpp

@@ -20,12 +20,13 @@
 #include "../../lib/networkPacks/PacksForClient.h"
 #include "../../lib/networkPacks/PacksForClientBattle.h"
 #include "../../lib/networkPacks/SetStackEffect.h"
+#include "../../lib/CRandomGenerator.h"
 #include "../../lib/StartInfo.h"
 #include "../../lib/TerrainHandler.h"
 
 #include "../../lib/battle/BattleInfo.h"
 #include "../../lib/battle/BattleLayout.h"
-#include "../../lib/CRandomGenerator.h"
+#include "../../lib/callback/GameRandomizer.h"
 #include "../../lib/CStack.h"
 
 #include "../../lib/filesystem/ResourcePath.h"
@@ -179,9 +180,9 @@ public:
 			}
 		}
 
-		CRandomGenerator randomGenerator;
+		GameRandomizer randomizer(*gameState);
 		Load::ProgressAccumulator progressTracker;
-		gameState->init(&mapService, &si, randomGenerator, progressTracker, false);
+		gameState->init(&mapService, &si, randomizer, progressTracker, false);
 
 		ASSERT_NE(map, nullptr);
 		ASSERT_EQ(map->getHeroesOnMap().size(), 2);