Browse Source

Merge remote-tracking branch 'remotes/origin/develop' into SpellsRefactoring8

# Conflicts:
#	client/battle/CBattleInterface.cpp
#	lib/spells/BattleSpellMechanics.cpp
AlexVinS 9 years ago
parent
commit
505e53c17d

+ 2 - 2
AI/BattleAI/BattleAI.cpp

@@ -701,7 +701,7 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo
 	for(int i  = 0; i < totalAttacks; i++)
 	{
 		std::pair<ui32, ui32> retaliation(0,0);
-		auto attackDmg = cbc->battleEstimateDamage(curBai, &retaliation);
+		auto attackDmg = cbc->battleEstimateDamage(CRandomGenerator::getDefault(), curBai, &retaliation);
 		ap.damageDealt = (attackDmg.first + attackDmg.second) / 2;
 		ap.damageReceived = (retaliation.first + retaliation.second) / 2;
 
@@ -785,7 +785,7 @@ int PotentialTargets::bestActionValue() const
 
 void EnemyInfo::calcDmg(const CStack * ourStack)
 {
-	TDmgRange retal, dmg = cbc->battleEstimateDamage(ourStack, s, &retal);
+	TDmgRange retal, dmg = cbc->battleEstimateDamage(CRandomGenerator::getDefault(), ourStack, s, &retal);
 	adi = (dmg.first + dmg.second) / 2;
 	adr = (retal.first + retal.second) / 2;
 }

+ 1 - 1
AI/StupidAI/StupidAI.cpp

@@ -44,7 +44,7 @@ struct EnemyInfo
 	{}
 	void calcDmg(const CStack * ourStack)
 	{
-		TDmgRange retal, dmg = cbc->battleEstimateDamage(ourStack, s, &retal);
+		TDmgRange retal, dmg = cbc->battleEstimateDamage(CRandomGenerator::getDefault(), ourStack, s, &retal);
 		adi = (dmg.first + dmg.second) / 2;
 		adr = (retal.first + retal.second) / 2;
 	}

+ 5 - 5
client/battle/CBattleInterface.cpp

@@ -1587,7 +1587,7 @@ void CBattleInterface::activateStack()
 		if(randomSpellcaster)
 			creatureSpellToCast = -1; //spell will be set later on cast
 		else
-			creatureSpellToCast = curInt->cb->battleGetRandomStackSpell(s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move
+			creatureSpellToCast = curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move
 		//TODO: what if creature can cast BOTH random genie spell and aimed spell?
 		//TODO: faerie dragon type spell should be selected by server
 	}
@@ -2059,7 +2059,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 			{
 				if (shere && ourStack && shere != sactive) //only positive spells for other allied creatures
 				{
-					int spellID = curInt->cb->battleGetRandomStackSpell(shere, CBattleInfoCallback::RANDOM_GENIE);
+					int spellID = curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), shere, CBattleInfoCallback::RANDOM_GENIE);
 					if (spellID > -1)
 					{
 						legalAction = true;
@@ -2203,7 +2203,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 					}
 				};
 
-				std::string estDmgText = formatDmgRange(curInt->cb->battleEstimateDamage(sactive, shere)); //calculating estimated dmg
+				std::string estDmgText = formatDmgRange(curInt->cb->battleEstimateDamage(CRandomGenerator::getDefault(), sactive, shere)); //calculating estimated dmg
 				consoleMsg = (boost::format(CGI->generaltexth->allTexts[36]) % shere->getName() % estDmgText).str(); //Attack %s (%s damage)
 			}
 				break;
@@ -2215,7 +2215,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 					cursorFrame = ECursor::COMBAT_SHOOT;
 
 				realizeAction = [=] {giveCommand(Battle::SHOOT, myNumber, activeStack->ID);};
-				std::string estDmgText = formatDmgRange(curInt->cb->battleEstimateDamage(sactive, shere)); //calculating estimated dmg
+				std::string estDmgText = formatDmgRange(curInt->cb->battleEstimateDamage(CRandomGenerator::getDefault(), sactive, shere)); //calculating estimated dmg
 				//printing - Shoot %s (%d shots left, %s damage)
 				consoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % sactive->shots % estDmgText).str();
 			}
@@ -2359,7 +2359,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 					}
 					else //unknown random spell
 					{
-						giveCommand(Battle::MONSTER_SPELL, myNumber, sactive->ID, curInt->cb->battleGetRandomStackSpell(shere, CBattleInfoCallback::RANDOM_GENIE));
+						giveCommand(Battle::MONSTER_SPELL, myNumber, sactive->ID, curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), shere, CBattleInfoCallback::RANDOM_GENIE));
 					}
 				}
 				else

+ 11 - 11
lib/CBattleCallback.cpp

@@ -521,15 +521,15 @@ std::set<BattleHex> CBattleInfoCallback::battleGetAttackedHexes(const CStack* at
 	return attackedHexes;
 }
 
-SpellID CBattleInfoCallback::battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const
+SpellID CBattleInfoCallback::battleGetRandomStackSpell(CRandomGenerator & rand, const CStack * stack, ERandomSpell mode) const
 {
 	switch (mode)
 	{
 	case RANDOM_GENIE:
-		return getRandomBeneficialSpell(stack); //target
+		return getRandomBeneficialSpell(rand, stack); //target
 		break;
 	case RANDOM_AIMED:
-		return getRandomCastedSpell(stack); //caster
+		return getRandomCastedSpell(rand, stack); //caster
 		break;
 	default:
 		logGlobal->errorStream() << "Incorrect mode of battleGetRandomSpell (" << mode <<")";
@@ -1076,15 +1076,15 @@ TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const
 	return calculateDmgRange(bai);
 }
 
-TDmgRange CBattleInfoCallback::battleEstimateDamage(const CStack * attacker, const CStack * defender, TDmgRange * retaliationDmg) const
+TDmgRange CBattleInfoCallback::battleEstimateDamage(CRandomGenerator & rand, const CStack * attacker, const CStack * defender, TDmgRange * retaliationDmg) const
 {
 	RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
 	const bool shooting = battleCanShoot(attacker, defender->position);
 	const BattleAttackInfo bai(attacker, defender, shooting);
-	return battleEstimateDamage(bai, retaliationDmg);
+	return battleEstimateDamage(rand, bai, retaliationDmg);
 }
 
-std::pair<ui32, ui32> CBattleInfoCallback::battleEstimateDamage(const BattleAttackInfo &bai, std::pair<ui32, ui32> * retaliationDmg /*= nullptr*/) const
+std::pair<ui32, ui32> CBattleInfoCallback::battleEstimateDamage(CRandomGenerator & rand, const BattleAttackInfo &bai, std::pair<ui32, ui32> * retaliationDmg /*= nullptr*/) const
 {
 	RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
 
@@ -1106,7 +1106,7 @@ std::pair<ui32, ui32> CBattleInfoCallback::battleEstimateDamage(const BattleAtta
 			{
 				BattleStackAttacked bsa;
 				bsa.damageAmount = ret.*pairElems[i];
-				bai.defender->prepareAttacked(bsa, gs->getRandomGenerator(), bai.defenderCount);
+				bai.defender->prepareAttacked(bsa, rand, bai.defenderCount);
 
 				auto retaliationAttack = bai.reverse();
 				retaliationAttack.attackerCount = bsa.newAmount;
@@ -1753,7 +1753,7 @@ std::set<const CStack*> CBattleInfoCallback:: batteAdjacentCreatures(const CStac
 	return stacks;
 }
 
-SpellID CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) const
+SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, const CStack * subject) const
 {
 	RETURN_IF_NOT_BATTLE(SpellID::NONE);
 	//This is complete list. No spells from mods.
@@ -1875,7 +1875,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) co
 
 	if(!beneficialSpells.empty())
 	{
-		return *RandomGeneratorUtil::nextItem(beneficialSpells, gs->getRandomGenerator());
+		return *RandomGeneratorUtil::nextItem(beneficialSpells, rand);
 	}
 	else
 	{
@@ -1883,7 +1883,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) co
 	}
 }
 
-SpellID CBattleInfoCallback::getRandomCastedSpell(const CStack * caster) const
+SpellID CBattleInfoCallback::getRandomCastedSpell(CRandomGenerator & rand,const CStack * caster) const
 {
 	RETURN_IF_NOT_BATTLE(SpellID::NONE);
 
@@ -1895,7 +1895,7 @@ SpellID CBattleInfoCallback::getRandomCastedSpell(const CStack * caster) const
 	{
 		totalWeight += std::max(b->additionalInfo, 1); //minimal chance to cast is 1
 	}
-	int randomPos = gs->getRandomGenerator().nextInt(totalWeight - 1);
+	int randomPos = rand.nextInt(totalWeight - 1);
 	for(Bonus * b : *bl)
 	{
 		randomPos -= std::max(b->additionalInfo, 1);

+ 6 - 5
lib/CBattleCallback.h

@@ -22,6 +22,7 @@ struct CObstacleInstance;
 class IBonusBearer;
 struct InfoAboutHero;
 class CArmedInstance;
+class CRandomGenerator;
 
 namespace boost
 {class shared_mutex;}
@@ -266,8 +267,8 @@ public:
 	TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
 
 	//hextowallpart  //int battleGetWallUnderHex(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found
-	std::pair<ui32, ui32> battleEstimateDamage(const BattleAttackInfo &bai, std::pair<ui32, ui32> * retaliationDmg = nullptr) const; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair <min dmg, max dmg>
-	std::pair<ui32, ui32> battleEstimateDamage(const CStack * attacker, const CStack * defender, std::pair<ui32, ui32> * retaliationDmg = nullptr) const; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair <min dmg, max dmg>
+	std::pair<ui32, ui32> battleEstimateDamage(CRandomGenerator & rand, const BattleAttackInfo &bai, std::pair<ui32, ui32> * retaliationDmg = nullptr) const; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair <min dmg, max dmg>
+	std::pair<ui32, ui32> battleEstimateDamage(CRandomGenerator & rand, const CStack * attacker, const CStack * defender, std::pair<ui32, ui32> * retaliationDmg = nullptr) const; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair <min dmg, max dmg>
 	si8 battleHasDistancePenalty( const CStack * stack, BattleHex destHex ) const;
 	si8 battleHasDistancePenalty(const IBonusBearer *bonusBearer, BattleHex shooterPosition, BattleHex destHex ) const;
 	si8 battleHasWallPenalty(const CStack * stack, BattleHex destHex) const; //checks if given stack has wall penalty
@@ -285,9 +286,9 @@ public:
 	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode) const; //checks if given player can cast given spell
 	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks if given player can cast given spell at given tile in given mode
 
-	SpellID battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const;
-	SpellID getRandomBeneficialSpell(const CStack * subject) const;
-	SpellID getRandomCastedSpell(const CStack * caster) const; //called at the beginning of turn for Faerie Dragon
+	SpellID battleGetRandomStackSpell(CRandomGenerator & rand, const CStack * stack, ERandomSpell mode) const;
+	SpellID getRandomBeneficialSpell(CRandomGenerator & rand, const CStack * subject) const;
+	SpellID getRandomCastedSpell(CRandomGenerator & rand, const CStack * caster) const; //called at the beginning of turn for Faerie Dragon
 
 	const CStack * getStackIf(std::function<bool(const CStack*)> pred) const;
 

+ 47 - 57
lib/CGameState.cpp

@@ -476,13 +476,13 @@ int CGameState::pickUnusedHeroTypeRandomly(PlayerColor owner)
 	// select random hero native to "our" faction
 	if(!factionHeroes.empty())
 	{
-		return RandomGeneratorUtil::nextItem(factionHeroes, rand)->getNum();
+		return RandomGeneratorUtil::nextItem(factionHeroes, getRandomGenerator())->getNum();
 	}
 
 	logGlobal->warnStream() << "Cannot find free hero of appropriate faction for player " << owner << " - trying to get first available...";
 	if(!otherHeroes.empty())
 	{
-		return RandomGeneratorUtil::nextItem(otherHeroes, rand)->getNum();
+		return RandomGeneratorUtil::nextItem(otherHeroes, getRandomGenerator())->getNum();
 	}
 
 	logGlobal->error("No free allowed heroes!");
@@ -500,29 +500,29 @@ std::pair<Obj,int> CGameState::pickObject (CGObjectInstance *obj)
 	switch(obj->ID)
 	{
 	case Obj::RANDOM_ART:
-		return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(rand, CArtifact::ART_TREASURE | CArtifact::ART_MINOR | CArtifact::ART_MAJOR | CArtifact::ART_RELIC));
+		return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_TREASURE | CArtifact::ART_MINOR | CArtifact::ART_MAJOR | CArtifact::ART_RELIC));
 	case Obj::RANDOM_TREASURE_ART:
-		return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(rand, CArtifact::ART_TREASURE));
+		return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_TREASURE));
 	case Obj::RANDOM_MINOR_ART:
-		return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MINOR));
+		return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_MINOR));
 	case Obj::RANDOM_MAJOR_ART:
-		return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MAJOR));
+		return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_MAJOR));
 	case Obj::RANDOM_RELIC_ART:
-		return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(rand, CArtifact::ART_RELIC));
+		return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_RELIC));
 	case Obj::RANDOM_HERO:
 		return std::make_pair(Obj::HERO, pickNextHeroType(obj->tempOwner));
 	case Obj::RANDOM_MONSTER:
-		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand));
+		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator()));
 	case Obj::RANDOM_MONSTER_L1:
-		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 1));
+		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 1));
 	case Obj::RANDOM_MONSTER_L2:
-		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 2));
+		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 2));
 	case Obj::RANDOM_MONSTER_L3:
-		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 3));
+		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 3));
 	case Obj::RANDOM_MONSTER_L4:
-		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 4));
+		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 4));
 	case Obj::RANDOM_RESOURCE:
-		return std::make_pair(Obj::RESOURCE,rand.nextInt(6)); //now it's OH3 style, use %8 for mithril
+		return std::make_pair(Obj::RESOURCE,getRandomGenerator().nextInt(6)); //now it's OH3 style, use %8 for mithril
 	case Obj::RANDOM_TOWN:
 		{
 			PlayerColor align = PlayerColor((static_cast<CGTownInstance*>(obj))->alignment);
@@ -542,18 +542,18 @@ std::pair<Obj,int> CGameState::pickObject (CGObjectInstance *obj)
 			{
 				do
 				{
-					f = rand.nextInt(VLC->townh->factions.size() - 1);
+					f = getRandomGenerator().nextInt(VLC->townh->factions.size() - 1);
 				}
 				while (VLC->townh->factions[f]->town == nullptr); // find playable faction
 			}
 			return std::make_pair(Obj::TOWN,f);
 		}
 	case Obj::RANDOM_MONSTER_L5:
-		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 5));
+		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 5));
 	case Obj::RANDOM_MONSTER_L6:
-		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 6));
+		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 6));
 	case Obj::RANDOM_MONSTER_L7:
-		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 7));
+		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 7));
 	case Obj::RANDOM_DWELLING:
 	case Obj::RANDOM_DWELLING_LVL:
 	case Obj::RANDOM_DWELLING_FACTION:
@@ -564,7 +564,7 @@ std::pair<Obj,int> CGameState::pickObject (CGObjectInstance *obj)
 			//if castle alignment available
 			if (auto info = dynamic_cast<CCreGenAsCastleInfo*>(dwl->info))
 			{
-				faction = rand.nextInt(VLC->townh->factions.size() - 1);
+				faction = getRandomGenerator().nextInt(VLC->townh->factions.size() - 1);
 				if (info->asCastle)
 				{
 					for(auto & elem : map->objects)
@@ -593,7 +593,7 @@ std::pair<Obj,int> CGameState::pickObject (CGObjectInstance *obj)
 					{
 						if((faction>7) && (info->castles[1]&(1<<(faction-8))))
 							break;
-						faction = rand.nextInt(GameConstants::F_NUMBER - 1);
+						faction = getRandomGenerator().nextInt(GameConstants::F_NUMBER - 1);
 					}
 				}
 			}
@@ -605,7 +605,7 @@ std::pair<Obj,int> CGameState::pickObject (CGObjectInstance *obj)
 			//if level set to range
 			if (auto info = dynamic_cast<CCreGenLeveledInfo*>(dwl->info))
 			{
-				level = rand.nextInt(info->minLevel, info->maxLevel);
+				level = getRandomGenerator().nextInt(info->minLevel, info->maxLevel);
 			}
 			else // fixed level
 			{
@@ -639,7 +639,7 @@ std::pair<Obj,int> CGameState::pickObject (CGObjectInstance *obj)
 			if (result.first == Obj::NO_OBJ)
 			{
 				logGlobal->errorStream() << "Error: failed to find dwelling for "<< VLC->townh->factions[faction]->name << " of level " << int(level);
-				result = std::make_pair(Obj::CREATURE_GENERATOR1, *RandomGeneratorUtil::nextItem(VLC->objtypeh->knownSubObjects(Obj::CREATURE_GENERATOR1), rand));
+				result = std::make_pair(Obj::CREATURE_GENERATOR1, *RandomGeneratorUtil::nextItem(VLC->objtypeh->knownSubObjects(Obj::CREATURE_GENERATOR1), getRandomGenerator()));
 			}
 
 			return result;
@@ -727,23 +727,10 @@ CGameState::~CGameState()
 		ptr.second.dellNull();
 }
 
-BattleInfo * CGameState::setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance *town)
-{
-	const TerrainTile &t = map->getTile(tile);
-	ETerrainType terrain = t.terType;
-	if(map->isCoastalTile(tile)) //coastal tile is always ground
-		terrain = ETerrainType::SAND;
-
-	BFieldType terType = battleGetBattlefieldType(tile);
-	if (heroes[0] && heroes[0]->boat && heroes[1] && heroes[1]->boat)
-		terType = BFieldType::SHIP_TO_SHIP;
-	return BattleInfo::setupBattle(tile, terrain, terType, armies, heroes, creatureBank, town);
-}
-
 void CGameState::init(StartInfo * si)
 {
 	logGlobal->infoStream() << "\tUsing random seed: "<< si->seedToBeUsed;
-	rand.setSeed(si->seedToBeUsed);
+	getRandomGenerator().setSeed(si->seedToBeUsed);
 	scenarioOps = CMemorySerializer::deepCopy(*si).release();
 	initialOpts = CMemorySerializer::deepCopy(*si).release();
 	si = nullptr;
@@ -800,7 +787,7 @@ void CGameState::init(StartInfo * si)
 	logGlobal->debug("\tChecking objectives");
 	map->checkForObjectives(); //needs to be run when all objects are properly placed
 
-	auto seedAfterInit = rand.nextInt();
+	auto seedAfterInit = getRandomGenerator().nextInt();
 	logGlobal->infoStream() << "Seed after init is " << seedAfterInit << " (before was " << scenarioOps->seedToBeUsed << ")";
 	if(scenarioOps->seedPostInit > 0)
 	{
@@ -925,8 +912,8 @@ void CGameState::initDuel()
 			for(TSecSKill secSkill : ss.heroSecSkills)
 				h->setSecSkillLevel(SecondarySkill(secSkill.first), secSkill.second, 1);
 
-			h->initHero(HeroTypeID(h->subID));
-			obj->initObj();
+			h->initHero(getRandomGenerator(), HeroTypeID(h->subID));
+			obj->initObj(getRandomGenerator());
 		}
 		else
 		{
@@ -1026,7 +1013,7 @@ void CGameState::initGrailPosition()
 
 		if(!allowedPos.empty())
 		{
-			map->grailPos = *RandomGeneratorUtil::nextItem(allowedPos, rand);
+			map->grailPos = *RandomGeneratorUtil::nextItem(allowedPos, getRandomGenerator());
 		}
 		else
 		{
@@ -1042,7 +1029,7 @@ void CGameState::initRandomFactionsForPlayers()
 	{
 		if(elem.second.castle==-1)
 		{
-			auto randomID = rand.nextInt(map->players[elem.first.getNum()].allowedFactions.size() - 1);
+			auto randomID = getRandomGenerator().nextInt(map->players[elem.first.getNum()].allowedFactions.size() - 1);
 			auto iter = map->players[elem.first.getNum()].allowedFactions.begin();
 			std::advance(iter, randomID);
 
@@ -1171,7 +1158,7 @@ void CGameState::placeCampaignHeroes()
 					auto unusedHeroTypeIds = getUnusedAllowedHeroes();
 					if(!unusedHeroTypeIds.empty())
 					{
-						heroTypeId = (*RandomGeneratorUtil::nextItem(unusedHeroTypeIds, rand)).getNum();
+						heroTypeId = (*RandomGeneratorUtil::nextItem(unusedHeroTypeIds, getRandomGenerator())).getNum();
 					}
 					else
 					{
@@ -1252,7 +1239,7 @@ CGameState::CrossoverHeroesList CGameState::getCrossoverHeroesFromPreviousScenar
 	return crossoverHeroes;
 }
 
-void CGameState::prepareCrossoverHeroes(std::vector<CGameState::CampaignHeroReplacement> & campaignHeroReplacements, const CScenarioTravel & travelOptions) const
+void CGameState::prepareCrossoverHeroes(std::vector<CGameState::CampaignHeroReplacement> & campaignHeroReplacements, const CScenarioTravel & travelOptions)
 {
 	// create heroes list for convenience iterating
 	std::vector<CGHeroInstance *> crossoverHeroes;
@@ -1268,7 +1255,7 @@ void CGameState::prepareCrossoverHeroes(std::vector<CGameState::CampaignHeroRepl
 		//trimming experience
 		for(CGHeroInstance * cgh : crossoverHeroes)
 		{
-			cgh->initExp();
+			cgh->initExp(getRandomGenerator());
 		}
 	}
 
@@ -1475,7 +1462,7 @@ void CGameState::initHeroes()
 			continue;
 		}
 
-		hero->initHero();
+		hero->initHero(getRandomGenerator());
 		getPlayer(hero->getOwner())->heroes.push_back(hero);
 		map->allHeroes[hero->type->ID.getNum()] = hero;
 	}
@@ -1491,7 +1478,7 @@ void CGameState::initHeroes()
 	{
 		if(!vstd::contains(heroesToCreate, HeroTypeID(ph->subID)))
 			continue;
-		ph->initHero();
+		ph->initHero(getRandomGenerator());
 		hpool.heroesPool[ph->subID] = ph;
 		hpool.pavailable[ph->subID] = 0xff;
 		heroesToCreate.erase(ph->type->ID);
@@ -1502,7 +1489,7 @@ void CGameState::initHeroes()
 	for(HeroTypeID htype : heroesToCreate) //all not used allowed heroes go with default state into the pool
 	{
 		auto  vhi = new CGHeroInstance();
-		vhi->initHero(htype);
+		vhi->initHero(getRandomGenerator(), htype);
 
 		int typeID = htype.getNum();
 		map->allHeroes[typeID] = vhi;
@@ -1660,24 +1647,24 @@ void CGameState::initStartingBonus()
 	{
 		//starting bonus
 		if(scenarioOps->playerInfos[elem.first].bonus==PlayerSettings::RANDOM)
-			scenarioOps->playerInfos[elem.first].bonus = static_cast<PlayerSettings::Ebonus>(rand.nextInt(2));
+			scenarioOps->playerInfos[elem.first].bonus = static_cast<PlayerSettings::Ebonus>(getRandomGenerator().nextInt(2));
 		switch(scenarioOps->playerInfos[elem.first].bonus)
 		{
 		case PlayerSettings::GOLD:
-			elem.second.resources[Res::GOLD] += rand.nextInt(5, 10) * 100;
+			elem.second.resources[Res::GOLD] += getRandomGenerator().nextInt(5, 10) * 100;
 			break;
 		case PlayerSettings::RESOURCE:
 			{
 				int res = VLC->townh->factions[scenarioOps->playerInfos[elem.first].castle]->town->primaryRes;
 				if(res == Res::WOOD_AND_ORE)
 				{
-					int amount = rand.nextInt(5, 10);
+					int amount = getRandomGenerator().nextInt(5, 10);
 					elem.second.resources[Res::WOOD] += amount;
 					elem.second.resources[Res::ORE] += amount;
 				}
 				else
 				{
-					elem.second.resources[res] += rand.nextInt(3, 6);
+					elem.second.resources[res] += getRandomGenerator().nextInt(3, 6);
 				}
 				break;
 			}
@@ -1689,7 +1676,7 @@ void CGameState::initStartingBonus()
 					break;
 				}
 				CArtifact *toGive;
-				toGive = VLC->arth->artifacts[VLC->arth->pickRandomArtifact(rand, CArtifact::ART_TREASURE)];
+				toGive = VLC->arth->artifacts[VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_TREASURE)];
 
 				CGHeroInstance *hero = elem.second.heroes[0];
 				giveHeroArtifact(hero, toGive->id);
@@ -1742,7 +1729,7 @@ void CGameState::initTowns()
 		}
 		if(vti->name.empty())
 		{
-			vti->name = *RandomGeneratorUtil::nextItem(vti->town->names, rand);
+			vti->name = *RandomGeneratorUtil::nextItem(vti->town->names, getRandomGenerator());
 		}
 
 		//init buildings
@@ -1754,7 +1741,7 @@ void CGameState::initTowns()
 				vti->builtBuildings.insert(BuildingID::TAVERN);
 
 			vti->builtBuildings.insert(BuildingID::DWELL_FIRST);
-			if(rand.nextInt(1) == 1)
+			if(getRandomGenerator().nextInt(1) == 1)
 			{
 				vti->builtBuildings.insert(BuildingID::DWELL_LVL_2);
 			}
@@ -1826,7 +1813,7 @@ void CGameState::initTowns()
 			if (total == 0) // remaining spells have 0 probability
 				break;
 
-			auto r = rand.nextInt(total - 1);
+			auto r = getRandomGenerator().nextInt(total - 1);
 			for(ui32 ps=0; ps<vti->possibleSpells.size();ps++)
 			{
 				r -= vti->possibleSpells[ps].toSpell()->getProbability(vti->subID);
@@ -1859,7 +1846,7 @@ void CGameState::initMapObjects()
 		if(obj)
 		{
 			logGlobal->traceStream() << boost::format ("Calling Init for object %d, %s, %s") % obj->id.getNum() % obj->typeName % obj->subTypeName;
-			obj->initObj();
+			obj->initObj(getRandomGenerator());
 		}
 	}
 	for(CGObjectInstance *obj : map->objects)
@@ -1919,7 +1906,7 @@ void CGameState::initVisitingAndGarrisonedHeroes()
 	}
 }
 
-BFieldType CGameState::battleGetBattlefieldType(int3 tile)
+BFieldType CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & rand)
 {
 	if(tile==int3() && curB)
 		tile = curB->tile;
@@ -3263,9 +3250,12 @@ TeamState::TeamState(TeamState && other):
 	std::swap(fogOfWarMap, other.fogOfWarMap);
 }
 
-
 CRandomGenerator & CGameState::getRandomGenerator()
 {
+	//if(scenarioOps && scenarioOps->seedPostInit)
+	//{
+	//	logGlobal->trace("CGameState::getRandomGenerator used after initialization!");
+	//}
 	//logGlobal->traceStream() << "Fetching CGameState::rand with seed " << rand.nextInt();
 	return rand;
 }

+ 10 - 3
lib/CGameState.h

@@ -218,7 +218,7 @@ public:
 	void giveHeroArtifact(CGHeroInstance *h, ArtifactID aid);
 
 	void apply(CPack *pack);
-	BFieldType battleGetBattlefieldType(int3 tile);
+	BFieldType battleGetBattlefieldType(int3 tile, CRandomGenerator & rand);
 	UpgradeInfo getUpgradeInfo(const CStackInstance &stack);
 	PlayerRelations::PlayerRelations getPlayerRelations(PlayerColor color1, PlayerColor color2);
 	bool checkForVisitableDir(const int3 & src, const int3 & dst) const; //check if src tile is visitable from dst tile
@@ -236,7 +236,6 @@ public:
 
 	void obtainPlayersStats(SThievesGuildInfo & tgi, int level); //fills tgi with info about other players that is available at given level of thieves' guild
 	std::map<ui32, ConstTransitivePtr<CGHeroInstance> > unusedHeroesFromPool(); //heroes pool without heroes that are available in taverns
-	BattleInfo * setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance *town);
 
 	bool isVisible(int3 pos, PlayerColor player);
 	bool isVisible(const CGObjectInstance *obj, boost::optional<PlayerColor> player);
@@ -244,6 +243,14 @@ public:
 	int getDate(Date::EDateType mode=Date::DAY) const; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month
 
 	// ----- getters, setters -----
+
+	/// This RNG should only be used inside GS or CPackForClient-derived applyGs
+	/// If this doesn't work for your code that mean you need a new netpack
+	///
+	/// Client-side must use CRandomGenerator::getDefault which is not serialized
+	///
+	/// CGameHandler have it's own getter for CRandomGenerator::getDefault
+	/// Any server-side code outside of GH must use CRandomGenerator::getDefault
 	CRandomGenerator & getRandomGenerator();
 
 	template <typename Handler> void serialize(Handler &h, const int version)
@@ -292,7 +299,7 @@ private:
 	std::vector<CampaignHeroReplacement> generateCampaignHeroesToReplace(CrossoverHeroesList & crossoverHeroes);
 
 	/// gets prepared and copied hero instances with crossover heroes from prev. scenario and travel options from current scenario
-	void prepareCrossoverHeroes(std::vector<CampaignHeroReplacement> & campaignHeroReplacements, const CScenarioTravel & travelOptions) const;
+	void prepareCrossoverHeroes(std::vector<CampaignHeroReplacement> & campaignHeroReplacements, const CScenarioTravel & travelOptions);
 
 	void replaceHeroesPlaceholders(const std::vector<CampaignHeroReplacement> & campaignHeroReplacements);
 	void placeStartingHeroes();

+ 1 - 1
lib/Connection.h

@@ -27,7 +27,7 @@
 #include "mapping/CCampaignHandler.h" //for CCampaignState
 #include "rmg/CMapGenerator.h" // for CMapGenOptions
 
-const ui32 version = 760;
+const ui32 version = 761;
 const ui32 minSupportedVersion = 753;
 
 class CISer;

+ 4 - 4
lib/IGameCallback.cpp

@@ -119,14 +119,14 @@ void CPrivilagedInfoCallback::getAllTiles (std::unordered_set<int3, ShashInt3> &
 	}
 }
 
-void CPrivilagedInfoCallback::pickAllowedArtsSet(std::vector<const CArtifact*> &out)
+void CPrivilagedInfoCallback::pickAllowedArtsSet(std::vector<const CArtifact*> &out, CRandomGenerator & rand)
 {
 	for (int j = 0; j < 3 ; j++)
-		out.push_back(VLC->arth->artifacts[VLC->arth->pickRandomArtifact(gameState()->getRandomGenerator(), CArtifact::ART_TREASURE)]);
+		out.push_back(VLC->arth->artifacts[VLC->arth->pickRandomArtifact(rand, CArtifact::ART_TREASURE)]);
 	for (int j = 0; j < 3 ; j++)
-		out.push_back(VLC->arth->artifacts[VLC->arth->pickRandomArtifact(gameState()->getRandomGenerator(), CArtifact::ART_MINOR)]);
+		out.push_back(VLC->arth->artifacts[VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MINOR)]);
 
-	out.push_back(VLC->arth->artifacts[VLC->arth->pickRandomArtifact(gameState()->getRandomGenerator(), CArtifact::ART_MAJOR)]);
+	out.push_back(VLC->arth->artifacts[VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MAJOR)]);
 }
 
 void CPrivilagedInfoCallback::getAllowedSpells(std::vector<SpellID> &out, ui16 level)

+ 2 - 1
lib/IGameCallback.h

@@ -1,6 +1,7 @@
 #pragma once
 
 #include "CGameInfoCallback.h" // for CGameInfoCallback
+#include "CRandomGenerator.h"
 
 /*
  * IGameCallback.h, part of VCMI engine
@@ -32,7 +33,7 @@ public:
 	void getFreeTiles (std::vector<int3> &tiles) const; //used for random spawns
 	void getTilesInRange(std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, boost::optional<PlayerColor> player = boost::optional<PlayerColor>(), int mode = 0, bool patrolDistance = false) const;  //mode 1 - only unrevealed tiles; mode 0 - all, mode -1 -  only unrevealed
 	void getAllTiles (std::unordered_set<int3, ShashInt3> &tiles, boost::optional<PlayerColor> player = boost::optional<PlayerColor>(), int level=-1, int surface=0) const; //returns all tiles on given level (-1 - both levels, otherwise number of level); surface: 0 - land and water, 1 - only land, 2 - only water
-	void pickAllowedArtsSet(std::vector<const CArtifact*> &out); //gives 3 treasures, 3 minors, 1 major -> used by Black Market and Artifact Merchant
+	void pickAllowedArtsSet(std::vector<const CArtifact*> &out, CRandomGenerator & rand); //gives 3 treasures, 3 minors, 1 major -> used by Black Market and Artifact Merchant
 	void getAllowedSpells(std::vector<SpellID> &out, ui16 level);
 
 	template<typename Saver>

+ 17 - 0
lib/NetPacks.h

@@ -1093,6 +1093,23 @@ struct ChangeObjectVisitors : public CPackForClient // 1003
 	}
 };
 
+struct PrepareHeroLevelUp : public CPackForClient//1999
+{
+	DLL_LINKAGE void applyGs(CGameState *gs);
+
+	const CGHeroInstance *hero;
+
+	/// Do not serialize, used by server only
+	std::vector<SecondarySkill> skills;
+
+	PrepareHeroLevelUp(){type = 1999;};
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & hero;
+	}
+};
+
 struct HeroLevelUp : public Query//2000
 {
 	void applyCl(CClient *cl);

+ 17 - 2
lib/NetPacksLib.cpp

@@ -624,7 +624,7 @@ DLL_LINKAGE void HeroRecruited::applyGs( CGameState *gs )
 	h->attachTo(p);
 	if(fresh)
 	{
-		h->initObj();
+		h->initObj(gs->getRandomGenerator());
 	}
 	gs->map->addBlockVisTiles(h);
 
@@ -689,7 +689,7 @@ DLL_LINKAGE void NewObject::applyGs( CGameState *gs )
 
 	gs->map->objects.push_back(o);
 	gs->map->addBlockVisTiles(o);
-	o->initObj();
+	o->initObj(gs->getRandomGenerator());
 	gs->map->calculateGuardingGreaturePositions();
 
 	logGlobal->debugStream() << "added object id=" << id << "; address=" << (intptr_t)o << "; name=" << o->getObjectName();
@@ -1162,6 +1162,21 @@ DLL_LINKAGE void SetObjectProperty::applyGs( CGameState *gs )
 	}
 }
 
+DLL_LINKAGE void PrepareHeroLevelUp::applyGs(CGameState *gs)
+{
+	CGHeroInstance * h = gs->getHero(hero->id);
+	auto proposedSkills = h->getLevelUpProposedSecondarySkills();
+
+	if(skills.size() == 1 || hero->tempOwner == PlayerColor::NEUTRAL) //choose skill automatically
+	{
+		skills.push_back(*RandomGeneratorUtil::nextItem(proposedSkills, h->skillsInfo.rand));
+	}
+	else
+	{
+		skills = proposedSkills;
+	}
+}
+
 DLL_LINKAGE void HeroLevelUp::applyGs( CGameState *gs )
 {
 	CGHeroInstance * h = gs->getHero(hero->id);

+ 5 - 4
lib/mapObjects/CBank.cpp

@@ -34,11 +34,11 @@ CBank::~CBank()
 {
 }
 
-void CBank::initObj()
+void CBank::initObj(CRandomGenerator & rand)
 {
 	daycounter = 0;
 	resetDuration = 0;
-	VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, cb->gameState()->getRandomGenerator());
+	VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand);
 }
 
 std::string CBank::getHoverText(PlayerColor player) const
@@ -64,7 +64,8 @@ void CBank::setPropertyDer (ui8 what, ui32 val)
 				daycounter+=val;
 			break;
 		case ObjProperty::BANK_RESET:
-			initObj();
+			// FIXME: Object reset must be done by separate netpack from server
+			initObj(cb->gameState()->getRandomGenerator());
 			daycounter = 1; //yes, 1 since "today" daycounter won't be incremented
 			break;
 		case ObjProperty::BANK_CLEAR:
@@ -73,7 +74,7 @@ void CBank::setPropertyDer (ui8 what, ui32 val)
 	}
 }
 
-void CBank::newTurn() const
+void CBank::newTurn(CRandomGenerator & rand) const
 {
 	if (bc == nullptr)
 	{

+ 2 - 2
lib/mapObjects/CBank.h

@@ -31,9 +31,9 @@ public:
 
 	void setConfig(const BankConfig & bc);
 
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 	std::string getHoverText(PlayerColor player) const override;
-	void newTurn() const override;
+	void newTurn(CRandomGenerator & rand) const override;
 	bool wasVisited (PlayerColor player) const override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;

+ 22 - 23
lib/mapObjects/CGHeroInstance.cpp

@@ -244,10 +244,10 @@ CGHeroInstance::CGHeroInstance()
 	secSkills.push_back(std::make_pair(SecondarySkill::DEFAULT, -1));
 }
 
-void CGHeroInstance::initHero(HeroTypeID SUBID)
+void CGHeroInstance::initHero(CRandomGenerator & rand, HeroTypeID SUBID)
 {
 	subID = SUBID.getNum();
-	initHero();
+	initHero(rand);
 }
 
 void CGHeroInstance::setType(si32 ID, si32 subID)
@@ -259,7 +259,7 @@ void CGHeroInstance::setType(si32 ID, si32 subID)
 	randomizeArmy(type->heroClass->faction);
 }
 
-void CGHeroInstance::initHero()
+void CGHeroInstance::initHero(CRandomGenerator & rand)
 {
 	assert(validTypes(true));
 	if(!type)
@@ -302,17 +302,17 @@ void CGHeroInstance::initHero()
 	setFormation(false);
 	if (!stacksCount()) //standard army//initial army
 	{
-		initArmy();
+		initArmy(rand);
 	}
 	assert(validTypes());
 
 	if(exp == 0xffffffff)
 	{
-		initExp();
+		initExp(rand);
 	}
 	else
 	{
-		levelUpAutomatically();
+		levelUpAutomatically(rand);
 	}
 
 	if (VLC->modh->modules.COMMANDERS && !commander)
@@ -326,13 +326,13 @@ void CGHeroInstance::initHero()
 		mana = manaLimit();
 }
 
-void CGHeroInstance::initArmy(IArmyDescriptor *dst /*= nullptr*/)
+void CGHeroInstance::initArmy(CRandomGenerator & rand, IArmyDescriptor *dst /*= nullptr*/)
 {
 	if(!dst)
 		dst = this;
 
 	int howManyStacks = 0; //how many stacks will hero receives <1 - 3>
-	int pom = cb->gameState()->getRandomGenerator().nextInt(99);
+	int pom = rand.nextInt(99);
 	int warMachinesGiven = 0;
 
 	if(pom < 9)
@@ -348,7 +348,7 @@ void CGHeroInstance::initArmy(IArmyDescriptor *dst /*= nullptr*/)
 	{
 		auto & stack = type->initialArmy[stackNo];
 
-		int count = cb->gameState()->getRandomGenerator().nextInt(stack.minAmount, stack.maxAmount);
+		int count = rand.nextInt(stack.minAmount, stack.maxAmount);
 
 		if(stack.creature >= CreatureID::CATAPULT &&
 		   stack.creature <= CreatureID::ARROW_TOWERS) //war machine
@@ -483,7 +483,7 @@ void CGHeroInstance::SecondarySkillsInfo::resetWisdomCounter()
 	wisdomCounter = 1;
 }
 
-void CGHeroInstance::initObj()
+void CGHeroInstance::initObj(CRandomGenerator & rand)
 {
 	blockVisit = true;
 	auto  hs = new HeroSpecial();
@@ -491,9 +491,9 @@ void CGHeroInstance::initObj()
 	attachTo(hs); //do we ever need to detach it?
 
 	if(!type)
-		initHero(); //TODO: set up everything for prison before specialties are configured
+		initHero(rand); //TODO: set up everything for prison before specialties are configured
 
-	skillsInfo.rand.setSeed(cb->gameState()->getRandomGenerator().nextInt());
+	skillsInfo.rand.setSeed(rand.nextInt());
 	skillsInfo.resetMagicSchoolCounter();
 	skillsInfo.resetWisdomCounter();
 
@@ -1063,10 +1063,10 @@ CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &b
  * @param raisedStack Pair where the first element represents ID of the raised creature
  * and the second element the amount.
  */
-void CGHeroInstance::showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const
+void CGHeroInstance::showNecromancyDialog(const CStackBasicDescriptor &raisedStack, CRandomGenerator & rand) const
 {
 	InfoWindow iw;
-	iw.soundID = soundBase::pickup01 + cb->gameState()->getRandomGenerator().nextInt(6);
+	iw.soundID = soundBase::pickup01 + rand.nextInt(6);
 	iw.player = tempOwner;
 	iw.components.push_back(Component(raisedStack));
 
@@ -1169,9 +1169,9 @@ EAlignment::EAlignment CGHeroInstance::getAlignment() const
 	return type->heroClass->getAlignment();
 }
 
-void CGHeroInstance::initExp()
+void CGHeroInstance::initExp(CRandomGenerator & rand)
 {
-	exp = cb->gameState()->getRandomGenerator().nextInt(40, 89);
+	exp = rand.nextInt(40, 89);
 }
 
 std::string CGHeroInstance::nodeName() const
@@ -1344,10 +1344,10 @@ std::vector<SecondarySkill> CGHeroInstance::getLevelUpProposedSecondarySkills()
 	return skills;
 }
 
-PrimarySkill::PrimarySkill CGHeroInstance::nextPrimarySkill() const
+PrimarySkill::PrimarySkill CGHeroInstance::nextPrimarySkill(CRandomGenerator & rand) const
 {
 	assert(gainsLevel());
-	int randomValue = cb->gameState()->getRandomGenerator().nextInt(99), pom = 0, primarySkill = 0;
+	int randomValue = rand.nextInt(99), pom = 0, primarySkill = 0;
 	const auto & skillChances = (level > 9) ? type->heroClass->primarySkillLowLevel : type->heroClass->primarySkillHighLevel;
 
 	for(; primarySkill < GameConstants::PRIMARY_SKILLS; ++primarySkill)
@@ -1363,7 +1363,7 @@ PrimarySkill::PrimarySkill CGHeroInstance::nextPrimarySkill() const
 	return static_cast<PrimarySkill::PrimarySkill>(primarySkill);
 }
 
-boost::optional<SecondarySkill> CGHeroInstance::nextSecondarySkill() const
+boost::optional<SecondarySkill> CGHeroInstance::nextSecondarySkill(CRandomGenerator & rand) const
 {
 	assert(gainsLevel());
 
@@ -1380,7 +1380,6 @@ boost::optional<SecondarySkill> CGHeroInstance::nextSecondarySkill() const
 			}
 		}
 
-		auto & rand = cb->gameState()->getRandomGenerator();
 		if(learnedSecondarySkills.empty())
 		{
 			// there are only new skills to learn, so choose anyone of them
@@ -1459,16 +1458,16 @@ void CGHeroInstance::levelUp(std::vector<SecondarySkill> skills)
 	Updatespecialty();
 }
 
-void CGHeroInstance::levelUpAutomatically()
+void CGHeroInstance::levelUpAutomatically(CRandomGenerator & rand)
 {
 	while(gainsLevel())
 	{
-		const auto primarySkill = nextPrimarySkill();
+		const auto primarySkill = nextPrimarySkill(rand);
 		setPrimarySkill(primarySkill, 1, false);
 
 		auto proposedSecondarySkills = getLevelUpProposedSecondarySkills();
 
-		const auto secondarySkill = nextSecondarySkill();
+		const auto secondarySkill = nextSecondarySkill(rand);
 		if(secondarySkill)
 		{
 			setSecSkillLevel(*secondarySkill, 1, false);

+ 9 - 9
lib/mapObjects/CGHeroInstance.h

@@ -163,10 +163,10 @@ public:
 	bool gainsLevel() const;
 
 	/// Returns the next primary skill on level up. Can only be called if hero can gain a level up.
-	PrimarySkill::PrimarySkill nextPrimarySkill() const;
+	PrimarySkill::PrimarySkill nextPrimarySkill(CRandomGenerator & rand) const;
 
 	/// Returns the next secondary skill randomly on level up. Can only be called if hero can gain a level up.
-	boost::optional<SecondarySkill> nextSecondarySkill() const;
+	boost::optional<SecondarySkill> nextSecondarySkill(CRandomGenerator & rand) const;
 
 	/// Gets 0, 1 or 2 secondary skills which are proposed on hero level up.
 	std::vector<SecondarySkill> getLevelUpProposedSecondarySkills() const;
@@ -192,20 +192,20 @@ public:
 
 	bool canCastThisSpell(const CSpell * spell) const; //determines if this hero can cast given spell; takes into account existing spell in spellbook, existing spellbook and artifact bonuses
 	CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const;
-	void showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const;
+	void showNecromancyDialog(const CStackBasicDescriptor &raisedStack, CRandomGenerator & rand) const;
 	EDiggingStatus diggingStatus() const;
 
 	//////////////////////////////////////////////////////////////////////////
 
 	void setType(si32 ID, si32 subID) override;
 
-	void initHero();
-	void initHero(HeroTypeID SUBID);
+	void initHero(CRandomGenerator & rand);
+	void initHero(CRandomGenerator & rand, HeroTypeID SUBID);
 
 	void putArtifact(ArtifactPosition pos, CArtifactInstance *art);
 	void putInBackpack(CArtifactInstance *art);
-	void initExp();
-	void initArmy(IArmyDescriptor *dst = nullptr);
+	void initExp(CRandomGenerator & rand);
+	void initArmy(CRandomGenerator & rand, IArmyDescriptor *dst = nullptr);
 	//void giveArtifact (ui32 aid);
 	void pushPrimSkill(PrimarySkill::PrimarySkill which, int val);
 	ui8 maxlevelsToMagicSchool() const;
@@ -250,7 +250,7 @@ public:
 
 	void deserializationFix();
 
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	std::string getObjectName() const override;
 protected:
@@ -258,7 +258,7 @@ protected:
 	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 
 private:
-	void levelUpAutomatically();
+	void levelUpAutomatically(CRandomGenerator & rand);
 
 public:
 	template <typename Handler> void serialize(Handler &h, const int version)

+ 4 - 4
lib/mapObjects/CGMarket.cpp

@@ -282,18 +282,18 @@ std::vector<int> CGBlackMarket::availableItemsIds(EMarketMode::EMarketMode mode)
 	}
 }
 
-void CGBlackMarket::newTurn() const
+void CGBlackMarket::newTurn(CRandomGenerator & rand) const
 {
 	if(cb->getDate(Date::DAY_OF_MONTH) != 1) //new month
 		return;
 
 	SetAvailableArtifacts saa;
 	saa.id = id.getNum();
-	cb->pickAllowedArtsSet(saa.arts);
+	cb->pickAllowedArtsSet(saa.arts, rand);
 	cb->sendAndApply(&saa);
 }
 
-void CGUniversity::initObj()
+void CGUniversity::initObj(CRandomGenerator & rand)
 {
 	std::vector<int> toChoose;
 	for(int i = 0; i < GameConstants::SKILL_QUANTITY; ++i)
@@ -313,7 +313,7 @@ void CGUniversity::initObj()
 	for(int i = 0; i < 4; ++i)
 	{
 		// move randomly one skill to selected and remove from list
-		auto it = RandomGeneratorUtil::nextItem(toChoose, cb->gameState()->getRandomGenerator());
+		auto it = RandomGeneratorUtil::nextItem(toChoose, rand);
 		skills.push_back(*it);
 		toChoose.erase(it);
 	}

+ 2 - 2
lib/mapObjects/CGMarket.h

@@ -61,7 +61,7 @@ class DLL_LINKAGE CGBlackMarket : public CGMarket
 public:
 	std::vector<const CArtifact *> artifacts; //available artifacts
 
-	void newTurn() const override; //reset artifacts for black market every month
+	void newTurn(CRandomGenerator & rand) const override; //reset artifacts for black market every month
 	std::vector<int> availableItemsIds(EMarketMode::EMarketMode mode) const override;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
@@ -77,7 +77,7 @@ public:
 	std::vector<int> skills; //available skills
 
 	std::vector<int> availableItemsIds(EMarketMode::EMarketMode mode) const override;
-	void initObj() override;//set skills for trade
+	void initObj(CRandomGenerator & rand) override;//set skills for trade
 	void onHeroVisit(const CGHeroInstance * h) const override; //open window
 
 	template <typename Handler> void serialize(Handler &h, const int version)

+ 1 - 1
lib/mapObjects/CGPandoraBox.cpp

@@ -34,7 +34,7 @@ static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16
 	showInfoDialog(playerID,txtID,soundID);
 }
 
-void CGPandoraBox::initObj()
+void CGPandoraBox::initObj(CRandomGenerator & rand)
 {
 	blockVisit = (ID==Obj::PANDORAS_BOX); //block only if it's really pandora's box (events also derive from that class)
 	hasGuardians = stacks.size();

+ 1 - 1
lib/mapObjects/CGPandoraBox.h

@@ -36,7 +36,7 @@ public:
 	CCreatureSet creatures; //gained creatures
 
 	CGPandoraBox() : gainedExp(0), manaDiff(0), moraleDiff(0), luckDiff(0){};
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
 	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;

+ 6 - 8
lib/mapObjects/CGTownInstance.cpp

@@ -25,14 +25,14 @@
 std::vector<const CArtifact *> CGTownInstance::merchantArtifacts;
 std::vector<int> CGTownInstance::universitySkills;
 
-void CGDwelling::initObj()
+void CGDwelling::initObj(CRandomGenerator & rand)
 {
 	switch(ID)
 	{
 	case Obj::CREATURE_GENERATOR1:
 	case Obj::CREATURE_GENERATOR4:
 		{
-			VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, cb->gameState()->getRandomGenerator());
+			VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand);
 
 			if (getOwner() != PlayerColor::NEUTRAL)
 				cb->gameState()->players[getOwner()].dwellings.push_back (this);
@@ -141,7 +141,7 @@ void CGDwelling::onHeroVisit( const CGHeroInstance * h ) const
 	cb->showBlockingDialog(&bd);
 }
 
-void CGDwelling::newTurn() const
+void CGDwelling::newTurn(CRandomGenerator & rand) const
 {
 	if(cb->getDate(Date::DAY_OF_WEEK) != 1) //not first day of week
 		return;
@@ -152,7 +152,7 @@ void CGDwelling::newTurn() const
 
 	if(ID == Obj::REFUGEE_CAMP) //if it's a refugee camp, we need to pick an available creature
 	{
-		cb->setObjProperty(id, ObjProperty::AVAILABLE_CREATURE, VLC->creh->pickRandomMonster(cb->gameState()->getRandomGenerator()));
+		cb->setObjProperty(id, ObjProperty::AVAILABLE_CREATURE, VLC->creh->pickRandomMonster(rand));
 	}
 
 	bool change = false;
@@ -595,7 +595,7 @@ std::string CGTownInstance::getObjectName() const
 	return name + ", " + town->faction->name;
 }
 
-void CGTownInstance::initObj()
+void CGTownInstance::initObj(CRandomGenerator & rand)
 ///initialize town structures
 {
 	blockVisit = true;
@@ -637,12 +637,10 @@ void CGTownInstance::initObj()
 	updateAppearance();
 }
 
-void CGTownInstance::newTurn() const
+void CGTownInstance::newTurn(CRandomGenerator & rand) const
 {
 	if (cb->getDate(Date::DAY_OF_WEEK) == 1) //reset on new week
 	{
-		auto & rand = cb->gameState()->getRandomGenerator();
-
 		//give resources for Rampart, Mystic Pond
 		if (hasBuilt(BuildingID::MYSTIC_POND, ETownType::RAMPART)
 			&& cb->getDate(Date::DAY) != 1 && (tempOwner < PlayerColor::PLAYER_LIMIT))

+ 4 - 4
lib/mapObjects/CGTownInstance.h

@@ -56,9 +56,9 @@ protected:
 	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 
 private:
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
-	void newTurn() const override;
+	void newTurn(CRandomGenerator & rand) const override;
 	void setPropertyDer(ui8 what, ui32 val) override;
 	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
 	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
@@ -249,10 +249,10 @@ public:
 	virtual ~CGTownInstance();
 
 	///IObjectInterface overrides
-	void newTurn() const override;
+	void newTurn(CRandomGenerator & rand) const override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void onHeroLeave(const CGHeroInstance * h) const override;
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
 	std::string getObjectName() const override;
 protected:

+ 3 - 3
lib/mapObjects/CObjectHandler.cpp

@@ -66,7 +66,7 @@ void IObjectInterface::onHeroVisit(const CGHeroInstance * h) const
 void IObjectInterface::onHeroLeave(const CGHeroInstance * h) const
 {}
 
-void IObjectInterface::newTurn () const
+void IObjectInterface::newTurn(CRandomGenerator & rand) const
 {}
 
 IObjectInterface::~IObjectInterface()
@@ -75,7 +75,7 @@ IObjectInterface::~IObjectInterface()
 IObjectInterface::IObjectInterface()
 {}
 
-void IObjectInterface::initObj()
+void IObjectInterface::initObj(CRandomGenerator & rand)
 {}
 
 void IObjectInterface::setProperty( ui8 what, ui32 val )
@@ -207,7 +207,7 @@ void CGObjectInstance::setType(si32 ID, si32 subID)
 	cb->gameState()->map->addBlockVisTiles(this);
 }
 
-void CGObjectInstance::initObj()
+void CGObjectInstance::initObj(CRandomGenerator & rand)
 {
 	switch(ID)
 	{

+ 4 - 3
lib/mapObjects/CObjectHandler.h

@@ -22,6 +22,7 @@ class CGObjectInstance;
 struct MetaString;
 struct BattleResult;
 class JsonSerializeFormat;
+class CRandomGenerator;
 
 // This one teleport-specific, but has to be available everywhere in callbacks and netpacks
 // For now it's will be there till teleports code refactored and moved into own file
@@ -37,8 +38,8 @@ public:
 
 	virtual void onHeroVisit(const CGHeroInstance * h) const;
 	virtual void onHeroLeave(const CGHeroInstance * h) const;
-	virtual void newTurn() const;
-	virtual void initObj(); //synchr
+	virtual void newTurn(CRandomGenerator & rand) const;
+	virtual void initObj(CRandomGenerator & rand); //synchr
 	virtual void setProperty(ui8 what, ui32 val);//synchr
 
 	//Called when queries created DURING HERO VISIT are resolved
@@ -166,7 +167,7 @@ public:
 
 	/** OVERRIDES OF IObjectInterface **/
 
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	/// method for synchronous update. Note: For new properties classes should override setPropertyDer instead
 	void setProperty(ui8 what, ui32 val) override final;

+ 11 - 11
lib/mapObjects/CQuest.cpp

@@ -420,16 +420,16 @@ void CGSeerHut::setObjToKill()
 	}
 }
 
-void CGSeerHut::init()
+void CGSeerHut::init(CRandomGenerator & rand)
 {
-	seerName = *RandomGeneratorUtil::nextItem(VLC->generaltexth->seerNames, cb->gameState()->getRandomGenerator());
-	quest->textOption = cb->gameState()->getRandomGenerator().nextInt(2);
-	quest->completedOption = cb->gameState()->getRandomGenerator().nextInt(1, 3);
+	seerName = *RandomGeneratorUtil::nextItem(VLC->generaltexth->seerNames, rand);
+	quest->textOption = rand.nextInt(2);
+	quest->completedOption = rand.nextInt(1, 3);
 }
 
-void CGSeerHut::initObj()
+void CGSeerHut::initObj(CRandomGenerator & rand)
 {
-	init();
+	init(rand);
 
 	quest->progress = CQuest::NOT_ACTIVE;
 	if(quest->missionType)
@@ -549,7 +549,7 @@ void CGSeerHut::setPropertyDer (ui8 what, ui32 val)
 	}
 }
 
-void CGSeerHut::newTurn() const
+void CGSeerHut::newTurn(CRandomGenerator & rand) const
 {
 	if(quest->lastDay >= 0 && quest->lastDay <= cb->getDate() - 1) //time is up
 	{
@@ -761,11 +761,11 @@ void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer)
 	finishQuest(hero, answer);
 }
 
-void CGQuestGuard::init()
+void CGQuestGuard::init(CRandomGenerator & rand)
 {
 	blockVisit = true;
-	quest->textOption = cb->gameState()->getRandomGenerator().nextInt(3, 5);
-	quest->completedOption = cb->gameState()->getRandomGenerator().nextInt(4, 5);
+	quest->textOption = rand.nextInt(3, 5);
+	quest->completedOption = rand.nextInt(4, 5);
 }
 
 void CGQuestGuard::completeQuest(const CGHeroInstance *h) const
@@ -825,7 +825,7 @@ void CGKeymasterTent::onHeroVisit( const CGHeroInstance * h ) const
 	showInfoDialog(h,txt_id,soundBase::CAVEHEAD);
 }
 
-void CGBorderGuard::initObj()
+void CGBorderGuard::initObj(CRandomGenerator & rand)
 {
 	//ui32 m13489val = subID; //store color as quest info
 	blockVisit = true;

+ 5 - 5
lib/mapObjects/CQuest.h

@@ -108,13 +108,13 @@ public:
 	std::string seerName;
 
 	CGSeerHut();
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 	std::string getHoverText(PlayerColor player) const override;
-	void newTurn() const override;
+	void newTurn(CRandomGenerator & rand) const override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
 
-	virtual void init();
+	virtual void init(CRandomGenerator & rand);
 	int checkDirection() const; //calculates the region of map where monster is placed
 	void setObjToKill(); //remember creatures / heroes to kill after they are initialized
 	const CGHeroInstance *getHeroToKill(bool allowNull = false) const;
@@ -139,7 +139,7 @@ class DLL_LINKAGE CGQuestGuard : public CGSeerHut
 {
 public:
 	CGQuestGuard() : CGSeerHut(){};
-	void init() override;
+	void init(CRandomGenerator & rand) override;
 	void completeQuest (const CGHeroInstance * h) const override;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
@@ -185,7 +185,7 @@ class DLL_LINKAGE CGBorderGuard : public CGKeys, public IQuestObject
 {
 public:
 	CGBorderGuard() : IQuestObject(){};
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
 

+ 31 - 31
lib/mapObjects/CRewardableObject.cpp

@@ -158,7 +158,7 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
 						grantRewardWithMessage(rewards[0]);
 						break;
 					case SELECT_RANDOM: // select one randomly //TODO: use weights
-						grantRewardWithMessage(rewards[cb->gameState()->getRandomGenerator().nextInt(rewards.size()-1)]);
+						grantRewardWithMessage(rewards[CRandomGenerator::getDefault().nextInt(rewards.size()-1)]);
 						break;
 				}
 				return;
@@ -431,7 +431,7 @@ void CRewardableObject::setPropertyDer(ui8 what, ui32 val)
 	}
 }
 
-void CRewardableObject::newTurn() const
+void CRewardableObject::newTurn(CRandomGenerator & rand) const
 {
 	if (resetDuration != 0 && cb->getDate(Date::DAY) > 1 && (cb->getDate(Date::DAY) % resetDuration) == 1)
 		cb->setObjProperty(id, ObjProperty::REWARD_RESET, 0);
@@ -450,11 +450,11 @@ CRewardableObject::CRewardableObject():
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 /// Helper, selects random art class based on weights
-static int selectRandomArtClass(int treasure, int minor, int major, int relic)
+static int selectRandomArtClass(CRandomGenerator & rand, int treasure, int minor, int major, int relic)
 {
 	int total = treasure + minor + major + relic;
 	assert(total != 0);
-	int hlp = IObjectInterface::cb->gameState()->getRandomGenerator().nextInt(total - 1);
+	int hlp = rand.nextInt(total - 1);
 
 	if(hlp < treasure)
 		return CArtifact::ART_TREASURE;
@@ -466,10 +466,10 @@ static int selectRandomArtClass(int treasure, int minor, int major, int relic)
 }
 
 /// Helper, adds random artifact to reward selecting class based on weights
-static void loadRandomArtifact(CVisitInfo & info, int treasure, int minor, int major, int relic)
+static void loadRandomArtifact(CRandomGenerator & rand, CVisitInfo & info, int treasure, int minor, int major, int relic)
 {
-	int artClass = selectRandomArtClass(treasure, minor, major, relic);
-	ArtifactID artID = VLC->arth->pickRandomArtifact(IObjectInterface::cb->gameState()->getRandomGenerator(), artClass);
+	int artClass = selectRandomArtClass(rand, treasure, minor, major, relic);
+	ArtifactID artID = VLC->arth->pickRandomArtifact(rand, artClass);
 	info.reward.artifacts.push_back(artID);
 }
 
@@ -479,7 +479,7 @@ CGPickable::CGPickable()
 	selectMode = SELECT_PLAYER;
 }
 
-void CGPickable::initObj()
+void CGPickable::initObj(CRandomGenerator & rand)
 {
 	blockVisit = true;
 	switch(ID)
@@ -487,8 +487,8 @@ void CGPickable::initObj()
 	case Obj::CAMPFIRE:
 		{
 			soundID = soundBase::experience;
-			int givenRes = cb->gameState()->getRandomGenerator().nextInt(5);
-			int givenAmm = cb->gameState()->getRandomGenerator().nextInt(4, 6);
+			int givenRes = rand.nextInt(5);
+			int givenAmm = rand.nextInt(4, 6);
 
 			info.resize(1);
 			info[0].reward.resources[givenRes] = givenAmm;
@@ -499,7 +499,7 @@ void CGPickable::initObj()
 		}
 	case Obj::FLOTSAM:
 		{
-			int type = cb->gameState()->getRandomGenerator().nextInt(3);
+			int type = rand.nextInt(3);
 			soundID = soundBase::GENIE;
 			switch(type)
 			{
@@ -540,7 +540,7 @@ void CGPickable::initObj()
 	case Obj::SEA_CHEST:
 		{
 			soundID = soundBase::chest;
-			int hlp = cb->gameState()->getRandomGenerator().nextInt(99);
+			int hlp = rand.nextInt(99);
 			if(hlp < 20)
 			{
 				info.resize(1);
@@ -557,7 +557,7 @@ void CGPickable::initObj()
 			else
 			{
 				info.resize(1);
-				loadRandomArtifact(info[0], 100, 0, 0, 0);
+				loadRandomArtifact(rand, info[0], 100, 0, 0, 0);
 				info[0].reward.resources[Res::GOLD] = 1000;
 				info[0].message.addTxt(MetaString::ADVOB_TXT, 117);
 				info[0].message.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back());
@@ -569,7 +569,7 @@ void CGPickable::initObj()
 		{
 			soundID = soundBase::experience;
 			info.resize(1);
-			loadRandomArtifact(info[0], 55, 20, 20, 5);
+			loadRandomArtifact(rand, info[0], 55, 20, 20, 5);
 			info[0].message.addTxt(MetaString::ADVOB_TXT, 125);
 			info[0].message.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back());
 			info[0].reward.removeObject = true;
@@ -577,12 +577,12 @@ void CGPickable::initObj()
 		break;
 	case Obj::TREASURE_CHEST:
 		{
-			int hlp = cb->gameState()->getRandomGenerator().nextInt(99);
+			int hlp = rand.nextInt(99);
 			if(hlp >= 95)
 			{
 				soundID = soundBase::treasure;
 				info.resize(1);
-				loadRandomArtifact(info[0], 100, 0, 0, 0);
+				loadRandomArtifact(rand, info[0], 100, 0, 0, 0);
 				info[0].message.addTxt(MetaString::ADVOB_TXT,145);
 				info[0].message.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back());
 				info[0].reward.removeObject = true;
@@ -631,7 +631,7 @@ CGBonusingObject::CGBonusingObject()
 	selectMode = SELECT_FIRST;
 }
 
-void CGBonusingObject::initObj()
+void CGBonusingObject::initObj(CRandomGenerator & rand)
 {
 	auto configureBonusDuration = [&](CVisitInfo & visit, Bonus::BonusDuration duration, Bonus::BonusType type, si32 value, si32 descrID)
 	{
@@ -817,7 +817,7 @@ CGOnceVisitable::CGOnceVisitable()
 	selectMode = SELECT_FIRST;
 }
 
-void CGOnceVisitable::initObj()
+void CGOnceVisitable::initObj(CRandomGenerator & rand)
 {
 	switch(ID)
 	{
@@ -826,10 +826,10 @@ void CGOnceVisitable::initObj()
 			onEmpty.addTxt(MetaString::ADVOB_TXT, 38);
 			soundID = soundBase::MYSTERY;
 			blockVisit = true;
-			if(cb->gameState()->getRandomGenerator().nextInt(99) < 20)
+			if(rand.nextInt(99) < 20)
 			{
 				info.resize(1);
-				loadRandomArtifact(info[0], 10, 10, 10, 0);
+				loadRandomArtifact(rand, info[0], 10, 10, 10, 0);
 				info[0].message.addTxt(MetaString::ADVOB_TXT, 37);
 			}
 		}
@@ -839,8 +839,8 @@ void CGOnceVisitable::initObj()
 			soundID = soundBase::GENIE;
 			onEmpty.addTxt(MetaString::ADVOB_TXT, 65);
 			info.resize(1);
-			int type =  cb->gameState()->getRandomGenerator().nextInt(5); //any basic resource without gold
-			int value = cb->gameState()->getRandomGenerator().nextInt(1, 4);
+			int type =  rand.nextInt(5); //any basic resource without gold
+			int value = rand.nextInt(1, 4);
 			info[0].reward.resources[type] = value;
 			info[0].message.addTxt(MetaString::ADVOB_TXT, 64);
 		}
@@ -851,7 +851,7 @@ void CGOnceVisitable::initObj()
 			onSelect.addTxt(MetaString::ADVOB_TXT, 161);
 
 			info.resize(2);
-			loadRandomArtifact(info[0], 30, 50, 25, 5);
+			loadRandomArtifact(rand, info[0], 30, 50, 25, 5);
 
 			Bonus bonus(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::OBJECT, -3, ID);
 			info[0].reward.bonuses.push_back(bonus);
@@ -866,19 +866,19 @@ void CGOnceVisitable::initObj()
 			soundID = soundBase::GENIE;
 			onVisited.addTxt(MetaString::ADVOB_TXT, 156);
 
-			int hlp = cb->gameState()->getRandomGenerator().nextInt(99);
+			int hlp = rand.nextInt(99);
 
 			if(hlp < 40) //minor or treasure art
 			{
 				info.resize(1);
-				loadRandomArtifact(info[0], 10, 10, 0, 0);
+				loadRandomArtifact(rand, info[0], 10, 10, 0, 0);
 				info[0].message.addTxt(MetaString::ADVOB_TXT, 155);
 			}
 			else if(hlp < 90) //2 - 5 of non-gold resource
 			{
 				info.resize(1);
-				int type  = cb->gameState()->getRandomGenerator().nextInt(5);
-				int value = cb->gameState()->getRandomGenerator().nextInt(2, 5);
+				int type  = rand.nextInt(5);
+				int value = rand.nextInt(2, 5);
 				info[0].reward.resources[type] = value;
 				info[0].message.addTxt(MetaString::ADVOB_TXT, 154);
 			}
@@ -896,7 +896,7 @@ CGVisitableOPH::CGVisitableOPH()
 	selectMode = SELECT_PLAYER;
 }
 
-void CGVisitableOPH::initObj()
+void CGVisitableOPH::initObj(CRandomGenerator & rand)
 {
 	switch(ID)
 	{
@@ -951,7 +951,7 @@ void CGVisitableOPH::initObj()
 			info[0].reward.gainedLevels = 1;
 			onVisited.addTxt(MetaString::ADVOB_TXT, 147);
 			info.resize(1);
-			switch (cb->gameState()->getRandomGenerator().nextInt(2))
+			switch (rand.nextInt(2))
 			{
 			case 0: // free
 				onSelect.addTxt(MetaString::ADVOB_TXT, 148);
@@ -1029,7 +1029,7 @@ CGVisitableOPW::CGVisitableOPW()
 	resetDuration = 7;
 }
 
-void CGVisitableOPW::initObj()
+void CGVisitableOPW::initObj(CRandomGenerator & rand)
 {
 	switch (ID)
 	{
@@ -1087,7 +1087,7 @@ void CGVisitableOPW::setPropertyDer(ui8 what, ui32 val)
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-void CGMagicSpring::initObj()
+void CGMagicSpring::initObj(CRandomGenerator & rand)
 {
 	CVisitInfo visit; // TODO: "player above max mana" limiter. Use logical expressions for limiters?
 	visit.reward.manaPercentage = 200;

+ 7 - 7
lib/mapObjects/CRewardableObject.h

@@ -227,7 +227,7 @@ public:
 	void onHeroVisit(const CGHeroInstance *h) const override;
 
 	///possibly resets object state
-	void newTurn() const override;
+	void newTurn(CRandomGenerator & rand) const override;
 
 	/// gives second part of reward after hero level-ups for proper granting of spells/mana
 	void heroLevelUpDone(const CGHeroInstance *hero) const override;
@@ -255,7 +255,7 @@ public:
 class DLL_LINKAGE CGPickable : public CRewardableObject //campfire, treasure chest, Flotsam, Shipwreck Survivor, Sea Chest
 {
 public:
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 
 	CGPickable();
 
@@ -273,7 +273,7 @@ protected:
 	void grantReward(ui32 rewardID, const CGHeroInstance * hero) const override;
 
 public:
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 
 	CGBonusingObject();
 
@@ -290,7 +290,7 @@ public:
 class DLL_LINKAGE CGOnceVisitable : public CRewardableObject // wagon, corpse, lean to, warriors tomb
 {
 public:
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 
 	CGOnceVisitable();
 
@@ -303,7 +303,7 @@ public:
 class DLL_LINKAGE CGVisitableOPH : public CRewardableObject //objects visitable only once per hero
 {
 public:
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 
 	CGVisitableOPH();
 
@@ -316,7 +316,7 @@ public:
 class DLL_LINKAGE CGVisitableOPW : public CRewardableObject //objects visitable once per week
 {
 public:
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 
 	CGVisitableOPW();
 
@@ -335,7 +335,7 @@ protected:
 	std::vector<ui32> getAvailableRewards(const CGHeroInstance * hero) const override;
 
 public:
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 	std::vector<int3> getVisitableOffsets() const;
 	int3 getVisitableOffset() const override;
 

+ 37 - 37
lib/mapObjects/MiscObjects.cpp

@@ -201,7 +201,7 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const
 
 }
 
-void CGCreature::initObj()
+void CGCreature::initObj(CRandomGenerator & rand)
 {
 	blockVisit = true;
 	switch(character)
@@ -210,13 +210,13 @@ void CGCreature::initObj()
 		character = -4;
 		break;
 	case 1:
-		character = cb->gameState()->getRandomGenerator().nextInt(1, 7);
+		character = rand.nextInt(1, 7);
 		break;
 	case 2:
-		character = cb->gameState()->getRandomGenerator().nextInt(1, 10);
+		character = rand.nextInt(1, 10);
 		break;
 	case 3:
-		character = cb->gameState()->getRandomGenerator().nextInt(4, 10);
+		character = rand.nextInt(4, 10);
 		break;
 	case 4:
 		character = 10;
@@ -228,7 +228,7 @@ void CGCreature::initObj()
 	CCreature &c = *VLC->creh->creatures[subID];
 	if(amount == 0)
 	{
-		amount = cb->gameState()->getRandomGenerator().nextInt(c.ammMin, c.ammMax);
+		amount = rand.nextInt(c.ammMin, c.ammMax);
 
 		if(amount == 0) //armies with 0 creatures are illegal
 		{
@@ -241,7 +241,7 @@ void CGCreature::initObj()
 	refusedJoining = false;
 }
 
-void CGCreature::newTurn() const
+void CGCreature::newTurn(CRandomGenerator & rand) const
 {//Works only for stacks of single type of size up to 2 millions
 	if (!notGrowingTeam)
 	{
@@ -434,7 +434,7 @@ void CGCreature::fight( const CGHeroInstance *h ) const
 			const auto & upgrades = getStack(slotID).type->upgrades;
 			if(!upgrades.empty())
 			{
-				auto it = RandomGeneratorUtil::nextItem(upgrades, cb->gameState()->getRandomGenerator());
+				auto it = RandomGeneratorUtil::nextItem(upgrades, CRandomGenerator::getDefault());
 				cb->changeStackType(StackLocation(this, slotID), VLC->creh->creatures[*it]);
 			}
 		}
@@ -676,7 +676,7 @@ void CGMine::onHeroVisit( const CGHeroInstance * h ) const
 
 }
 
-void CGMine::newTurn() const
+void CGMine::newTurn(CRandomGenerator & rand) const
 {
 	if(cb->getDate() == 1)
 		return;
@@ -687,12 +687,12 @@ void CGMine::newTurn() const
 	cb->giveResource(tempOwner, producedResource, producedQuantity);
 }
 
-void CGMine::initObj()
+void CGMine::initObj(CRandomGenerator & rand)
 {
 	if(isAbandoned())
 	{
 		//set guardians
-		int howManyTroglodytes = cb->gameState()->getRandomGenerator().nextInt(100, 199);
+		int howManyTroglodytes = rand.nextInt(100, 199);
 		auto troglodytes = new CStackInstance(CreatureID::TROGLODYTES, howManyTroglodytes);
 		putStack(SlotID(0), troglodytes);
 
@@ -703,7 +703,7 @@ void CGMine::initObj()
 				possibleResources.push_back(static_cast<Res::ERes>(i));
 
 		assert(!possibleResources.empty());
-		producedResource = *RandomGeneratorUtil::nextItem(possibleResources, cb->gameState()->getRandomGenerator());
+		producedResource = *RandomGeneratorUtil::nextItem(possibleResources, rand);
 		tempOwner = PlayerColor::NEUTRAL;
 	}
 	else
@@ -859,7 +859,7 @@ CGResource::CGResource()
 	amount = 0;
 }
 
-void CGResource::initObj()
+void CGResource::initObj(CRandomGenerator & rand)
 {
 	blockVisit = true;
 
@@ -868,13 +868,13 @@ void CGResource::initObj()
 		switch(subID)
 		{
 		case 6:
-			amount = cb->gameState()->getRandomGenerator().nextInt(500, 1000);
+			amount = rand.nextInt(500, 1000);
 			break;
 		case 0: case 2:
-			amount = cb->gameState()->getRandomGenerator().nextInt(6, 10);
+			amount = rand.nextInt(6, 10);
 			break;
 		default:
-			amount = cb->gameState()->getRandomGenerator().nextInt(3, 5);
+			amount = rand.nextInt(3, 5);
 			break;
 		}
 	}
@@ -987,7 +987,7 @@ ObjectInstanceID CGTeleport::getRandomExit(const CGHeroInstance * h) const
 {
 	auto passableExits = getPassableExits(cb->gameState(), h, getAllExits(true));
 	if(passableExits.size())
-		return *RandomGeneratorUtil::nextItem(passableExits, cb->gameState()->getRandomGenerator());
+		return *RandomGeneratorUtil::nextItem(passableExits, CRandomGenerator::getDefault());
 
 	return ObjectInstanceID();
 }
@@ -1119,7 +1119,7 @@ void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer,
 	cb->moveHero(hero->id, dPos, true);
 }
 
-void CGMonolith::initObj()
+void CGMonolith::initObj(CRandomGenerator & rand)
 {
 	std::vector<Obj> IDs;
 	IDs.push_back(ID);
@@ -1164,7 +1164,7 @@ void CGSubterraneanGate::onHeroVisit( const CGHeroInstance * h ) const
 	cb->showTeleportDialog(&td);
 }
 
-void CGSubterraneanGate::initObj()
+void CGSubterraneanGate::initObj(CRandomGenerator & rand)
 {
 	type = BOTH;
 }
@@ -1283,7 +1283,7 @@ void CGWhirlpool::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer
 	{
 		auto obj = cb->getObj(getRandomExit(hero));
 		std::set<int3> tiles = obj->getBlockedPos();
-		dPos = CGHeroInstance::convertPosition(*RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator()), true);
+		dPos = CGHeroInstance::convertPosition(*RandomGeneratorUtil::nextItem(tiles, CRandomGenerator::getDefault()), true);
 	}
 
 	cb->moveHero(hero->id, dPos, true);
@@ -1299,7 +1299,7 @@ bool CGWhirlpool::isProtected(const CGHeroInstance * h)
 	return false;
 }
 
-void CGArtifact::initObj()
+void CGArtifact::initObj(CRandomGenerator & rand)
 {
 	blockVisit = true;
 	if(ID == Obj::ARTIFACT)
@@ -1441,14 +1441,14 @@ void CGArtifact::serializeJsonOptions(JsonSerializeFormat& handler)
 	}
 }
 
-void CGWitchHut::initObj()
+void CGWitchHut::initObj(CRandomGenerator & rand)
 {
 	if (allowedAbilities.empty()) //this can happen for RMG. regular maps load abilities from map file
 	{
 		for (int i = 0; i < GameConstants::SKILL_QUANTITY; i++)
 			allowedAbilities.push_back(i);
 	}
-	ability = *RandomGeneratorUtil::nextItem(allowedAbilities, cb->gameState()->getRandomGenerator());
+	ability = *RandomGeneratorUtil::nextItem(allowedAbilities, rand);
 }
 
 void CGWitchHut::onHeroVisit( const CGHeroInstance * h ) const
@@ -1627,7 +1627,7 @@ void CGShrine::onHeroVisit( const CGHeroInstance * h ) const
 	cb->showInfoDialog(&iw);
 }
 
-void CGShrine::initObj()
+void CGShrine::initObj(CRandomGenerator & rand)
 {
 	if(spell == SpellID::NONE) //spell not set
 	{
@@ -1641,7 +1641,7 @@ void CGShrine::initObj()
 			return;
 		}
 
-		spell = *RandomGeneratorUtil::nextItem(possibilities, cb->gameState()->getRandomGenerator());
+		spell = *RandomGeneratorUtil::nextItem(possibilities, rand);
 	}
 }
 
@@ -1669,12 +1669,12 @@ void CGShrine::serializeJsonOptions(JsonSerializeFormat& handler)
 	handler.serializeId("spell", &CSpellHandler::decodeSpell, &CSpellHandler::encodeSpell, SpellID(SpellID::NONE), spell);
 }
 
-void CGSignBottle::initObj()
+void CGSignBottle::initObj(CRandomGenerator & rand)
 {
 	//if no text is set than we pick random from the predefined ones
 	if(message.empty())
 	{
-		message = *RandomGeneratorUtil::nextItem(VLC->generaltexth->randsign, cb->gameState()->getRandomGenerator());
+		message = *RandomGeneratorUtil::nextItem(VLC->generaltexth->randsign, rand);
 	}
 
 	if(ID == Obj::OCEAN_BOTTLE)
@@ -1714,7 +1714,7 @@ void CGScholar::onHeroVisit( const CGHeroInstance * h ) const
 		))) //hero doesn't have a spellbook or already knows the spell or doesn't have Wisdom
 	{
 		type = PRIM_SKILL;
-		bid = cb->gameState()->getRandomGenerator().nextInt(GameConstants::PRIMARY_SKILLS - 1);
+		bid = CRandomGenerator::getDefault().nextInt(GameConstants::PRIMARY_SKILLS - 1);
 	}
 
 	InfoWindow iw;
@@ -1749,25 +1749,25 @@ void CGScholar::onHeroVisit( const CGHeroInstance * h ) const
 	cb->removeObject(this);
 }
 
-void CGScholar::initObj()
+void CGScholar::initObj(CRandomGenerator & rand)
 {
 	blockVisit = true;
 	if(bonusType == RANDOM)
 	{
-		bonusType = static_cast<EBonusType>(cb->gameState()->getRandomGenerator().nextInt(2));
+		bonusType = static_cast<EBonusType>(rand.nextInt(2));
 		switch(bonusType)
 		{
 		case PRIM_SKILL:
-			bonusID = cb->gameState()->getRandomGenerator().nextInt(GameConstants::PRIMARY_SKILLS -1);
+			bonusID = rand.nextInt(GameConstants::PRIMARY_SKILLS -1);
 			break;
 		case SECONDARY_SKILL:
-			bonusID = cb->gameState()->getRandomGenerator().nextInt(GameConstants::SKILL_QUANTITY -1);
+			bonusID = rand.nextInt(GameConstants::SKILL_QUANTITY -1);
 			break;
 		case SPELL:
 			std::vector<SpellID> possibilities;
 			for (int i = 1; i < 6; ++i)
 				cb->getAllowedSpells (possibilities, i);
-			bonusID = *RandomGeneratorUtil::nextItem(possibilities, cb->gameState()->getRandomGenerator());
+			bonusID = *RandomGeneratorUtil::nextItem(possibilities, rand);
 			break;
 		}
 	}
@@ -1874,7 +1874,7 @@ void CGMagi::reset()
 	eyelist.clear();
 }
 
-void CGMagi::initObj()
+void CGMagi::initObj(CRandomGenerator & rand)
 {
 	if (ID == Obj::EYE_OF_MAGI)
 	{
@@ -1920,12 +1920,12 @@ void CGMagi::onHeroVisit(const CGHeroInstance * h) const
 	}
 
 }
-void CGBoat::initObj()
+void CGBoat::initObj(CRandomGenerator & rand)
 {
 	hero = nullptr;
 }
 
-void CGSirens::initObj()
+void CGSirens::initObj(CRandomGenerator & rand)
 {
 	blockVisit = true;
 }
@@ -2111,7 +2111,7 @@ void CGObelisk::onHeroVisit( const CGHeroInstance * h ) const
 
 }
 
-void CGObelisk::initObj()
+void CGObelisk::initObj(CRandomGenerator & rand)
 {
 	obeliskCount++;
 }
@@ -2172,7 +2172,7 @@ void CGLighthouse::onHeroVisit( const CGHeroInstance * h ) const
 	}
 }
 
-void CGLighthouse::initObj()
+void CGLighthouse::initObj(CRandomGenerator & rand)
 {
 	if(tempOwner < PlayerColor::PLAYER_LIMIT)
 	{

+ 17 - 17
lib/mapObjects/MiscObjects.h

@@ -59,8 +59,8 @@ public:
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	std::string getHoverText(PlayerColor player) const override;
 	std::string getHoverText(const CGHeroInstance * hero) const override;
-	void initObj() override;
-	void newTurn() const override;
+	void initObj(CRandomGenerator & rand) override;
+	void newTurn(CRandomGenerator & rand) const override;
 	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
 	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
 
@@ -105,7 +105,7 @@ public:
 	std::string message;
 
 	void onHeroVisit(const CGHeroInstance * h) const override;
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
@@ -125,7 +125,7 @@ public:
 	std::string getHoverText(PlayerColor player) const override;
 	std::string getHoverText(const CGHeroInstance * hero) const override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & static_cast<CPlayersVisited&>(*this);
@@ -144,7 +144,7 @@ public:
 
 	CGScholar() : bonusType(EBonusType::RANDOM){};
 	void onHeroVisit(const CGHeroInstance * h) const override;
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & static_cast<CGObjectInstance&>(*this);
@@ -187,7 +187,7 @@ public:
 	std::string getObjectName() const override;
 
 	void pick( const CGHeroInstance * h ) const;
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
@@ -206,7 +206,7 @@ public:
 
 	CGResource();
 	void onHeroVisit(const CGHeroInstance * h) const override;
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
 	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
 	std::string getHoverText(PlayerColor player) const override;
@@ -227,7 +227,7 @@ class DLL_LINKAGE CGShrine : public CPlayersVisited
 public:
 	SpellID spell; //id of spell or NONE if random
 	void onHeroVisit(const CGHeroInstance * h) const override;
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 	std::string getHoverText(PlayerColor player) const override;
 	std::string getHoverText(const CGHeroInstance * hero) const override;
 
@@ -252,8 +252,8 @@ private:
 	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
 
 	void flagMine(PlayerColor player) const;
-	void newTurn() const override;
-	void initObj() override;
+	void newTurn(CRandomGenerator & rand) const override;
+	void initObj(CRandomGenerator & rand) override;
 
 	std::string getObjectName() const override;
 	std::string getHoverText(PlayerColor player) const override;
@@ -329,7 +329,7 @@ class DLL_LINKAGE CGMonolith : public CGTeleport
 protected:
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const override;
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 
 public:
 	template <typename Handler> void serialize(Handler &h, const int version)
@@ -341,7 +341,7 @@ public:
 class DLL_LINKAGE CGSubterraneanGate : public CGMonolith
 {
 	void onHeroVisit(const CGHeroInstance * h) const override;
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 
 public:
 	static void postInit();
@@ -382,7 +382,7 @@ class DLL_LINKAGE CGSirens : public CGObjectInstance
 public:
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	std::string getHoverText(const CGHeroInstance * hero) const override;
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
@@ -407,7 +407,7 @@ public:
 	ui8 direction;
 	const CGHeroInstance *hero;  //hero on board
 
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 
 	CGBoat()
 	{
@@ -443,7 +443,7 @@ public:
 
 	static void reset();
 
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
@@ -478,7 +478,7 @@ public:
 	static std::map<TeamID, ui8> visited; //map: team_id => how many obelisks has been visited
 
 	void onHeroVisit(const CGHeroInstance * h) const override;
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 	std::string getHoverText(PlayerColor player) const override;
 	static void reset();
 
@@ -494,7 +494,7 @@ class DLL_LINKAGE CGLighthouse : public CGObjectInstance
 {
 public:
 	void onHeroVisit(const CGHeroInstance * h) const override;
-	void initObj() override;
+	void initObj(CRandomGenerator & rand) override;
 	std::string getHoverText(PlayerColor player) const override;
 
 	template <typename Handler> void serialize(Handler &h, const int version)

+ 1 - 0
lib/registerTypes/RegisterTypes.h

@@ -253,6 +253,7 @@ void registerTypesClientPacks1(Serializer &s)
 	s.template registerType<CPackForClient, SetCommanderProperty>();
 	s.template registerType<CPackForClient, ChangeObjectVisitors>();
 	s.template registerType<CPackForClient, ShowWorldViewEx>();
+	s.template registerType<CPackForClient, PrepareHeroLevelUp>();
 }
 
 template<typename Serializer>

+ 1 - 2
lib/spells/BattleSpellMechanics.cpp

@@ -449,8 +449,7 @@ void PatchObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env
 		if(hex.getX() > 0 && hex.getX() < 16 && !(parameters.cb->battleGetStackByPos(hex, false)) && !(parameters.cb->battleGetObstacleOnPos(hex, false)))
 			availableTiles.push_back(hex);
 	}
-	boost::range::random_shuffle(availableTiles);
-
+	RandomGeneratorUtil::randomShuffle(availableTiles, env->getRandomGenerator());
 	const int patchesForSkill[] = {4, 4, 6, 8};
 	const int patchesToPut = std::min<int>(patchesForSkill[parameters.spellLvl], availableTiles.size());
 

+ 64 - 45
server/CGameHandler.cpp

@@ -204,7 +204,7 @@ void CGameHandler::levelUpHero(const CGHeroInstance * hero)
 
 	// give primary skill
 	logGlobal->trace("%s got level %d", hero->name, hero->level);
-	auto primarySkill = hero->nextPrimarySkill();
+	auto primarySkill = hero->nextPrimarySkill(getRandomGenerator());
 
 	SetPrimSkill sps;
 	sps.id = hero->id;
@@ -213,20 +213,24 @@ void CGameHandler::levelUpHero(const CGHeroInstance * hero)
 	sps.val = 1;
 	sendAndApply(&sps);
 
+	PrepareHeroLevelUp pre;
+	pre.hero = hero;
+	sendAndApply(&pre);
+
 	HeroLevelUp hlu;
 	hlu.hero = hero;
 	hlu.primskill = primarySkill;
-	hlu.skills = hero->getLevelUpProposedSecondarySkills();
+	hlu.skills = pre.skills;
 
 	if(hlu.skills.size() == 0)
 	{
 		sendAndApply(&hlu);
 		levelUpHero(hero);
 	}
-	else if(hlu.skills.size() == 1  ||  hero->tempOwner == PlayerColor::NEUTRAL)  //choose skill automatically
+	else if(hlu.skills.size() == 1)
 	{
 		sendAndApply(&hlu);
-		levelUpHero(hero, *RandomGeneratorUtil::nextItem(hlu.skills, hero->skillsInfo.rand));
+		levelUpHero(hero, pre.skills.front());
 	}
 	else if(hlu.skills.size() > 1)
 	{
@@ -364,7 +368,7 @@ void CGameHandler::levelUpCommander(const CCommanderInstance * c)
 	else if(skillAmount == 1  ||  hero->tempOwner == PlayerColor::NEUTRAL) //choose skill automatically
 	{
 		sendAndApply(&clu);
-		levelUpCommander(c, *RandomGeneratorUtil::nextItem(clu.skills, gs->getRandomGenerator()));
+		levelUpCommander(c, *RandomGeneratorUtil::nextItem(clu.skills, getRandomGenerator()));
 	}
 	else if(skillAmount > 1) //apply and ask for secondary skill
 	{
@@ -525,7 +529,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 			int maxLevel = eagleEyeLevel + 1;
 			double eagleEyeChance = finishingBattle->winnerHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::EAGLE_EYE);
 			for(const CSpell *sp : gs->curB->sides.at(!battleResult.data->winner).usedSpellsHistory)
-				if(sp->level <= maxLevel && !vstd::contains(finishingBattle->winnerHero->spells, sp->id) && gs->getRandomGenerator().nextInt(99) < eagleEyeChance)
+				if(sp->level <= maxLevel && !vstd::contains(finishingBattle->winnerHero->spells, sp->id) && getRandomGenerator().nextInt(99) < eagleEyeChance)
 					cs.spells.insert(sp->id);
 		}
 	}
@@ -716,7 +720,7 @@ void CGameHandler::battleAfterLevelUp( const BattleResult &result )
 
 	if (necroSlot != SlotID())
 	{
-		finishingBattle->winnerHero->showNecromancyDialog(raisedStack);
+		finishingBattle->winnerHero->showNecromancyDialog(raisedStack, getRandomGenerator());
 		addToSlot(StackLocation(finishingBattle->winnerHero, necroSlot), raisedStack.type, raisedStack.count);
 	}
 
@@ -789,20 +793,20 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
 
 	if(!vstd::contains_if(gs->curB->sides, sideHeroBlocksLuck))
 	{
-		if(attackerLuck > 0  && gs->getRandomGenerator().nextInt(23) < attackerLuck)
+		if(attackerLuck > 0  && getRandomGenerator().nextInt(23) < attackerLuck)
 		{
 			bat.flags |= BattleAttack::LUCKY;
 		}
 		if (VLC->modh->settings.data["hardcodedFeatures"]["NEGATIVE_LUCK"].Bool()) // negative luck enabled
 		{
-			if (attackerLuck < 0 && gs->getRandomGenerator().nextInt(23) < abs(attackerLuck))
+			if (attackerLuck < 0 && getRandomGenerator().nextInt(23) < abs(attackerLuck))
 			{
 				bat.flags |= BattleAttack::UNLUCKY;
 			}
 		}
 	}
 
-	if(gs->getRandomGenerator().nextInt(99) < att->valOfBonuses(Bonus::DOUBLE_DAMAGE_CHANCE))
+	if(getRandomGenerator().nextInt(99) < att->valOfBonuses(Bonus::DOUBLE_DAMAGE_CHANCE))
 	{
 		bat.flags |= BattleAttack::DEATH_BLOW;
 	}
@@ -812,7 +816,7 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
 		static const int artilleryLvlToChance[] = {0, 50, 75, 100};
 		const CGHeroInstance * owner = gs->curB->getHero(att->owner);
 		int chance = artilleryLvlToChance[owner->getSecSkillLevel(SecondarySkill::ARTILLERY)];
-		if(chance > gs->getRandomGenerator().nextInt(99))
+		if(chance > getRandomGenerator().nextInt(99))
 		{
 			bat.flags |= BattleAttack::BALLISTA_DOUBLE_DMG;
 		}
@@ -875,8 +879,8 @@ void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, cons
 	bsa.attackerID = att->ID;
 	bsa.stackAttacked = def->ID;
 	bsa.damageAmount = gs->curB->calculateDmg(att, def, gs->curB->battleGetOwner(att), gs->curB->battleGetOwner(def),
-		bat.shot(), distance, bat.lucky(), bat.unlucky(), bat.deathBlow(), bat.ballistaDoubleDmg(), gs->getRandomGenerator());
-	def->prepareAttacked(bsa, gs->getRandomGenerator()); //calculate casualties
+		bat.shot(), distance, bat.lucky(), bat.unlucky(), bat.deathBlow(), bat.ballistaDoubleDmg(), getRandomGenerator());
+	def->prepareAttacked(bsa, getRandomGenerator()); //calculate casualties
 
 	//life drain handling
 	if (att->hasBonusOfType(Bonus::LIFE_DRAIN) && def->isLiving())
@@ -913,7 +917,7 @@ void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, cons
 		bsa2.effect = 11;
 
 		bsa2.damageAmount = (std::min(def->totalHelth(), bsa.damageAmount) * def->valOfBonuses(Bonus::FIRE_SHIELD)) / 100; //TODO: scale with attack/defense
-		att->prepareAttacked(bsa2, gameState()->getRandomGenerator());
+		att->prepareAttacked(bsa2, getRandomGenerator());
 		bat.bsa.push_back(bsa2);
 	}
 }
@@ -1337,7 +1341,7 @@ void CGameHandler::init(StartInfo *si)
 	logGlobal->info("Gamestate initialized!");
 
 	// reset seed, so that clients can't predict any following random values
-	gs->getRandomGenerator().resetSeed();
+	getRandomGenerator().resetSeed();
 
 	for(auto & elem : gs->players)
 	{
@@ -1373,10 +1377,10 @@ void CGameHandler::setPortalDwelling(const CGTownInstance * town, bool forced=fa
 				return;
 			}
 
-			auto dwelling = *RandomGeneratorUtil::nextItem(dwellings, gs->getRandomGenerator());
+			auto dwelling = *RandomGeneratorUtil::nextItem(dwellings, getRandomGenerator());
 
 			// for multi-creature dwellings like Golem Factory
-			auto creatureId = RandomGeneratorUtil::nextItem(dwelling->creatures, gs->getRandomGenerator())->second[0];
+			auto creatureId = RandomGeneratorUtil::nextItem(dwelling->creatures, getRandomGenerator())->second[0];
 
 			if(clear)
 			{
@@ -1436,7 +1440,7 @@ void CGameHandler::newTurn()
 		}
 		else
 		{
-			int monthType = gs->getRandomGenerator().nextInt(99);
+			int monthType = getRandomGenerator().nextInt(99);
 			if(newMonth) //new month
 			{
 				if (monthType < 40) //double growth
@@ -1444,13 +1448,13 @@ void CGameHandler::newTurn()
 					n.specialWeek = NewTurn::DOUBLE_GROWTH;
 					if (VLC->modh->settings.ALL_CREATURES_GET_DOUBLE_MONTHS)
 					{
-						std::pair<int, CreatureID> newMonster(54, VLC->creh->pickRandomMonster(gs->getRandomGenerator()));
+						std::pair<int, CreatureID> newMonster(54, VLC->creh->pickRandomMonster(getRandomGenerator()));
 						n.creatureid = newMonster.second;
 					}
 					else if(VLC->creh->doubledCreatures.size())
 					{
 						const std::vector<CreatureID> doubledCreatures (VLC->creh->doubledCreatures.begin(), VLC->creh->doubledCreatures.end());
-						n.creatureid = *RandomGeneratorUtil::nextItem(doubledCreatures, gs->getRandomGenerator());
+						n.creatureid = *RandomGeneratorUtil::nextItem(doubledCreatures, getRandomGenerator());
 					}
 					else
 					{
@@ -1466,7 +1470,7 @@ void CGameHandler::newTurn()
 				if (monthType < 25)
 				{
 					n.specialWeek = NewTurn::BONUS_GROWTH; //+5
-					std::pair<int, CreatureID> newMonster(54, VLC->creh->pickRandomMonster(gs->getRandomGenerator()));
+					std::pair<int, CreatureID> newMonster(54, VLC->creh->pickRandomMonster(getRandomGenerator()));
 					//TODO do not pick neutrals
 					n.creatureid = newMonster.second;
 				}
@@ -1515,10 +1519,10 @@ void CGameHandler::newTurn()
 			for (int j = 0; j < GameConstants::AVAILABLE_HEROES_PER_PLAYER; j++)
 			{
 				//first hero - native if possible, second hero -> any other class
-				if(CGHeroInstance *h = gs->hpool.pickHeroFor(j == 0, elem.first, getNativeTown(elem.first), pool, gs->getRandomGenerator(), banned))
+				if(CGHeroInstance *h = gs->hpool.pickHeroFor(j == 0, elem.first, getNativeTown(elem.first), pool, getRandomGenerator(), banned))
 				{
 					sah.hid[j] = h->subID;
-					h->initArmy(&sah.army[j]);
+					h->initArmy(getRandomGenerator(), &sah.army[j]);
 					banned = h->type->heroClass;
 				}
 				else
@@ -1647,7 +1651,7 @@ void CGameHandler::newTurn()
 	{
 		SetAvailableArtifacts saa;
 		saa.id = -1;
-		pickAllowedArtsSet(saa.arts);
+		pickAllowedArtsSet(saa.arts, getRandomGenerator());
 		sendAndApply(&saa);
 	}
 	sendAndApply(&n);
@@ -1691,12 +1695,12 @@ void CGameHandler::newTurn()
 					if (newMonth)
 					{
 						iw.text.addTxt(MetaString::ARRAY_TXT, (130));
-						iw.text.addReplacement(MetaString::ARRAY_TXT, gs->getRandomGenerator().nextInt(32, 41));
+						iw.text.addReplacement(MetaString::ARRAY_TXT, getRandomGenerator().nextInt(32, 41));
 					}
 					else
 					{
 						iw.text.addTxt(MetaString::ARRAY_TXT, (133));
-						iw.text.addReplacement(MetaString::ARRAY_TXT, gs->getRandomGenerator().nextInt(43, 57));
+						iw.text.addReplacement(MetaString::ARRAY_TXT, getRandomGenerator().nextInt(43, 57));
 					}
 			}
 			for (auto & elem : gs->players)
@@ -1713,7 +1717,7 @@ void CGameHandler::newTurn()
 	for(auto & elem : gs->map->objects)
 	{
 		if(elem)
-			elem->newTurn();
+			elem->newTurn(getRandomGenerator());
 	}
 
 	synchronizeArtifactHandlerLists(); //new day events may have changed them. TODO better of managing that
@@ -1859,9 +1863,18 @@ void CGameHandler::setupBattle( int3 tile, const CArmedInstance *armies[2], cons
 {
 	battleResult.set(nullptr);
 
+	const auto t = gs->getTile(tile);
+	ETerrainType terrain = t->terType;
+	if(gs->map->isCoastalTile(tile)) //coastal tile is always ground
+		terrain = ETerrainType::SAND;
+
+	BFieldType terType = gs->battleGetBattlefieldType(tile, getRandomGenerator());
+	if (heroes[0] && heroes[0]->boat && heroes[1] && heroes[1]->boat)
+		terType = BFieldType::SHIP_TO_SHIP;
+
 	//send info about battles
 	BattleStart bs;
-	bs.info = gs->setupBattle(tile, armies, heroes, creatureBank,	town);
+	bs.info = BattleInfo::setupBattle(tile, terrain, terType, armies, heroes, creatureBank, town);
 	sendAndApply(&bs);
 }
 
@@ -3569,7 +3582,7 @@ bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, PlayerColor pl
 	const CGHeroInstance *newHero = nullptr;
 	if (theOtherHero) //on XXL maps all heroes can be imprisoned :(
 	{
-		newHero = gs->hpool.pickHeroFor(false, player, getNativeTown(player), pool, gs->getRandomGenerator(), theOtherHero->type->heroClass);
+		newHero = gs->hpool.pickHeroFor(false, player, getNativeTown(player), pool, getRandomGenerator(), theOtherHero->type->heroClass);
 	}
 
 	SetAvailableHeroes sah;
@@ -3978,7 +3991,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 				{
 					if(currentHP.at(attackedPart) != EWallState::DESTROYED && // this part can be hit
 					   currentHP.at(attackedPart) != EWallState::NONE &&
-					   gs->getRandomGenerator().nextInt(99) < getCatapultHitChance(attackedPart, sbi))//hit is successful
+					   getRandomGenerator().nextInt(99) < getCatapultHitChance(attackedPart, sbi))//hit is successful
 					{
 						hitSuccessfull = true;
 					}
@@ -3993,7 +4006,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 						}
 						if (allowedTargets.empty())
 							break;
-						attackedPart = *RandomGeneratorUtil::nextItem(allowedTargets, gs->getRandomGenerator());
+						attackedPart = *RandomGeneratorUtil::nextItem(allowedTargets, getRandomGenerator());
 					}
 				}
 				while (!hitSuccessfull);
@@ -4009,7 +4022,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 
 				int dmgChance[] = { sbi.noDmg, sbi.oneDmg, sbi.twoDmg }; //dmgChance[i] - chance for doing i dmg when hit is successful
 
-				int dmgRand = gs->getRandomGenerator().nextInt(99);
+				int dmgRand = getRandomGenerator().nextInt(99);
 				//accumulating dmgChance
 				dmgChance[1] += dmgChance[0];
 				dmgChance[2] += dmgChance[1];
@@ -4164,8 +4177,8 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			const Bonus * spellcaster = stack->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPELLCASTER, spellID));
 
 			//TODO special bonus for genies ability
-			if(randSpellcaster && battleGetRandomStackSpell(stack, CBattleInfoCallback::RANDOM_AIMED) < 0)
-				spellID = battleGetRandomStackSpell(stack, CBattleInfoCallback::RANDOM_GENIE);
+			if(randSpellcaster && battleGetRandomStackSpell(getRandomGenerator(), stack, CBattleInfoCallback::RANDOM_AIMED) < 0)
+				spellID = battleGetRandomStackSpell(getRandomGenerator(), stack, CBattleInfoCallback::RANDOM_GENIE);
 
 			if(spellID < 0)
 				complain("That stack can't cast spells!");
@@ -4507,7 +4520,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
 			}
 			if (fearsomeCreature)
 			{
-				if (gs->getRandomGenerator().nextInt(99) < 10) //fixed 10%
+				if (getRandomGenerator().nextInt(99) < 10) //fixed 10%
 				{
 					bte.effect = Bonus::FEAR;
 					sendAndApply(&bte);
@@ -4521,7 +4534,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
 			bool cast = false;
 			while (!bl.empty() && !cast)
 			{
-				auto bonus = *RandomGeneratorUtil::nextItem(bl, gs->getRandomGenerator());
+				auto bonus = *RandomGeneratorUtil::nextItem(bl, getRandomGenerator());
 				auto spellID = SpellID(bonus->subtype);
 				const CSpell * spell = SpellID(spellID).toSpell();
 				bl.remove_if([&bonus](Bonus * b){return b==bonus;});
@@ -4635,7 +4648,7 @@ void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, c
 	bsa.damageAmount = damage;
 	bsa.stackAttacked = curStack->ID;
 	bsa.attackerID = -1;
-	curStack->prepareAttacked(bsa, gameState()->getRandomGenerator());
+	curStack->prepareAttacked(bsa, getRandomGenerator());
 
 	StacksInjured si;
 	si.stacks.push_back(bsa);
@@ -5232,7 +5245,7 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
 				continue;
 
 			//check if spell should be cast (probability handling)
-			if(gs->getRandomGenerator().nextInt(99) >= chance)
+			if(getRandomGenerator().nextInt(99) >= chance)
 				continue;
 
 			//casting
@@ -5314,7 +5327,7 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
 	TBonusListPtr acidBreath = attacker->getBonuses(Selector::type(Bonus::ACID_BREATH));
 	for(const Bonus *b : *acidBreath)
 	{
-		if (b->additionalInfo > gs->getRandomGenerator().nextInt(99))
+		if (b->additionalInfo > getRandomGenerator().nextInt(99))
 			acidDamage += b->val;
 	}
 	if (acidDamage)
@@ -5625,7 +5638,7 @@ void CGameHandler::runBattle()
 				   || NBonus::hasOfType(gs->curB->battleGetFightingHero(1), Bonus::BLOCK_MORALE)) //checking if gs->curB->heroes have (or don't have) morale blocking bonuses)
 				)
 			{
-				if(gs->getRandomGenerator().nextInt(23) < -2 * nextStackMorale)
+				if(getRandomGenerator().nextInt(23) < -2 * nextStackMorale)
 				{
 					//unit loses its turn - empty freeze action
 					BattleAction ba;
@@ -5697,7 +5710,7 @@ void CGameHandler::runBattle()
 				{
 					BattleAction attack;
 					attack.destinationTile = *RandomGeneratorUtil::nextItem(attackableBattleHexes,
-												gs->getRandomGenerator());
+												getRandomGenerator());
 					attack.actionType = Battle::CATAPULT;
 					attack.additionalInfo = 0;
 					attack.side = !next->attackerOwned;
@@ -5723,7 +5736,7 @@ void CGameHandler::runBattle()
 
 				if(!curOwner || curOwner->getSecSkillLevel(SecondarySkill::FIRST_AID) == 0) //no hero or hero has no first aid
 				{
-					range::random_shuffle(possibleStacks);
+					RandomGeneratorUtil::randomShuffle(possibleStacks, getRandomGenerator());
 					const CStack * toBeHealed = possibleStacks.front();
 
 					BattleAction heal;
@@ -5802,7 +5815,7 @@ void CGameHandler::runBattle()
 							|| NBonus::hasOfType(gs->curB->battleGetFightingHero(1), Bonus::BLOCK_MORALE)) //checking if gs->curB->heroes have (or don't have) morale blocking bonuses
 						)
 					{
-						if(gs->getRandomGenerator().nextInt(23) < nextStackMorale) //this stack hasn't got morale this turn
+						if(getRandomGenerator().nextInt(23) < nextStackMorale) //this stack hasn't got morale this turn
 
 							{
 								BattleTriggerEffect bte;
@@ -5926,7 +5939,8 @@ void CGameHandler::spawnWanderingMonsters(CreatureID creatureID)
 	std::vector<int3> tiles;
 	getFreeTiles(tiles);
 	ui32 amount = tiles.size() / 200; //Chance is 0.5% for each tile
-	std::random_shuffle(tiles.begin(), tiles.end());
+
+	RandomGeneratorUtil::randomShuffle(tiles, getRandomGenerator());
 	logGlobal->trace("Spawning wandering monsters. Found %d free tiles. Creature type: %d", tiles.size(), creatureID.num);
 	const CCreature *cre = VLC->creh->creatures.at(creatureID);
 	for (int i = 0; i < amount; ++i)
@@ -6230,6 +6244,11 @@ CGameHandler::FinishingBattleHelper::FinishingBattleHelper()
 	winnerHero = loserHero = nullptr;
 }
 
+CRandomGenerator & CGameHandler::getRandomGenerator()
+{
+	return CRandomGenerator::getDefault();
+}
+
 ///ServerSpellCastEnvironment
 ServerSpellCastEnvironment::ServerSpellCastEnvironment(CGameHandler * gh): gh(gh)
 {
@@ -6243,7 +6262,7 @@ void ServerSpellCastEnvironment::sendAndApply(CPackForClient * info) const
 
 CRandomGenerator & ServerSpellCastEnvironment::getRandomGenerator() const
 {
-	return gh->gameState()->getRandomGenerator();
+	return gh->getRandomGenerator();
 }
 
 void ServerSpellCastEnvironment::complain(const std::string& problem) const

+ 6 - 0
server/CGameHandler.h

@@ -247,6 +247,10 @@ public:
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & QID & states & finishingBattle;
+		if(version >= 761)
+		{
+			h & getRandomGenerator();
+		}
 	}
 
 	void sendMessageToAll(const std::string &message);
@@ -290,6 +294,8 @@ public:
 	void spawnWanderingMonsters(CreatureID creatureID);
 	friend class CVCMIServer;
 
+	CRandomGenerator & getRandomGenerator();
+
 private:
 	ServerSpellCastEnvironment * spellEnv;