Forráskód Böngészése

GameRandomizer is now in lib, add implementation

Ivan Savenko 5 hónapja
szülő
commit
cc274c4d34

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

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

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

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

+ 11 - 0
lib/CCreatureHandler.cpp

@@ -1362,4 +1362,15 @@ void CCreatureHandler::afterLoadFinalization()
 
 }
 
+std::set<CreatureID> CCreatureHandler::getDefaultAllowed() const
+{
+	std::set<CreatureID> result;
+
+	for(auto & creature : objects)
+		if (creature && !creature->special)
+			result.insert(creature->getId());
+
+	return result;
+}
+
 VCMI_LIB_NAMESPACE_END

+ 2 - 0
lib/CCreatureHandler.h

@@ -237,6 +237,8 @@ public:
 
 	std::vector<JsonNode> loadLegacyData() override;
 
+	std::set<CreatureID> getDefaultAllowed() const;
+
 };
 
 VCMI_LIB_NAMESPACE_END

+ 3 - 0
lib/CMakeLists.txt

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

+ 286 - 0
lib/callback/GameRandomizer.cpp

@@ -0,0 +1,286 @@
+/*
+ * GameRandomizer.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "GameRandomizer.h"
+
+#include "IGameInfoCallback.h"
+
+#include "../../lib/CRandomGenerator.h"
+#include "../../lib/GameLibrary.h"
+#include "../../lib/CCreatureHandler.h"
+#include "../../lib/CSkillHandler.h"
+#include "../../lib/entities/artifact/CArtHandler.h"
+#include "../../lib/entities/artifact/EArtifactClass.h"
+#include "../../lib/entities/hero/CHeroClass.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+bool BiasedRandomizer::roll(vstd::RNG &generator, int successChance, int biasValue)
+{
+	int failChance = 100 - successChance;
+	int newRoll = generator.nextInt(0,99);
+	bool success = newRoll + accumulatedBias >= successChance;
+	if (success)
+		accumulatedBias -= failChance * biasValue / 100;
+	else
+		accumulatedBias += successChance * biasValue / 100;
+
+	return success;
+}
+
+GameRandomizer::GameRandomizer(const IGameInfoCallback & gameInfo)
+	: gameInfo(gameInfo)
+{
+}
+
+GameRandomizer::~GameRandomizer() = default;
+
+//bool GameRandomizer::rollGoodMorale(ObjectInstanceID actor, int moraleValue)
+//{
+//
+//}
+//
+//bool GameRandomizer::rollBadMorale(ObjectInstanceID actor, int moraleValue)
+//{
+//
+//}
+//
+//bool GameRandomizer::rollGoodLuck(ObjectInstanceID actor, int luckValue)
+//{
+//
+//}
+//
+//bool GameRandomizer::rollBadLuck(ObjectInstanceID actor, int luckValue)
+//{
+//
+//}
+//
+//bool GameRandomizer::rollCombatAbility(ObjectInstanceID actor, int percentageChance)
+//{
+//
+//}
+//
+//HeroTypeID GameRandomizer::rollHero(PlayerColor player, FactionID faction)
+//{
+//
+//}
+
+CreatureID GameRandomizer::rollCreature()
+{
+	std::vector<CreatureID> allowed;
+	for(const auto & creatureID : LIBRARY->creh->getDefaultAllowed())
+	{
+		const auto * creaturePtr = creatureID.toCreature();
+		if(!creaturePtr->excludeFromRandomization)
+			allowed.push_back(creaturePtr->getId());
+	}
+
+	if(allowed.empty())
+		throw std::runtime_error("Cannot pick a random creature!");
+
+	return *RandomGeneratorUtil::nextItem(allowed, getDefault());
+}
+
+CreatureID GameRandomizer::rollCreature(int tier)
+{
+	std::vector<CreatureID> allowed;
+	for(const auto & creatureID : LIBRARY->creh->getDefaultAllowed())
+	{
+		const auto * creaturePtr = creatureID.toCreature();
+		if(creaturePtr->excludeFromRandomization)
+			continue;
+
+		if(creaturePtr->getLevel() == tier)
+			allowed.push_back(creaturePtr->getId());
+	}
+
+	if(allowed.empty())
+		throw std::runtime_error("Cannot pick a random creature!");
+
+	return *RandomGeneratorUtil::nextItem(allowed, getDefault());
+}
+
+ArtifactID GameRandomizer::rollArtifact()
+{
+	std::set<ArtifactID> potentialPicks;
+
+	for(const auto & artifactID : LIBRARY->arth->getDefaultAllowed())
+	{
+		if(!LIBRARY->arth->legalArtifact(artifactID))
+			continue;
+
+		potentialPicks.insert(artifactID);
+	}
+
+	return rollArtifact(potentialPicks);
+}
+
+ArtifactID GameRandomizer::rollArtifact(EArtifactClass type)
+{
+	std::set<ArtifactID> potentialPicks;
+
+	for(const auto & artifactID : LIBRARY->arth->getDefaultAllowed())
+	{
+		if(!LIBRARY->arth->legalArtifact(artifactID))
+			continue;
+
+		if(!gameInfo.isAllowed(artifactID))
+			continue;
+
+		const auto * artifact = artifactID.toArtifact();
+
+		if(type != artifact->aClass)
+			continue;
+
+		potentialPicks.insert(artifactID);
+	}
+
+	return rollArtifact(potentialPicks);
+}
+
+ArtifactID GameRandomizer::rollArtifact(std::set<ArtifactID> potentialPicks)
+{
+	// No allowed artifacts at all - give Grail - this can't be banned (hopefully)
+	// FIXME: investigate how such cases are handled by H3 - some heavily customized user-made maps likely rely on H3 behavior
+	if(potentialPicks.empty())
+	{
+		logGlobal->warn("Failed to find artifact that matches requested parameters!");
+		return ArtifactID::GRAIL;
+	}
+
+	// Find how many times least used artifacts were picked by randomizer
+	int leastUsedTimes = std::numeric_limits<int>::max();
+	for(const auto & artifact : potentialPicks)
+		if(allocatedArtifacts[artifact] < leastUsedTimes)
+			leastUsedTimes = allocatedArtifacts[artifact];
+
+	// Pick all artifacts that were used least number of times
+	std::set<ArtifactID> preferredPicks;
+	for(const auto & artifact : potentialPicks)
+		if(allocatedArtifacts[artifact] == leastUsedTimes)
+			preferredPicks.insert(artifact);
+
+	assert(!preferredPicks.empty());
+
+	ArtifactID artID = *RandomGeneratorUtil::nextItem(preferredPicks, getDefault());
+	allocatedArtifacts[artID] += 1; // record +1 more usage
+	return artID;
+}
+
+std::vector<ArtifactID> GameRandomizer::rollMarketArtifactSet()
+{
+	std::vector<ArtifactID> out;
+	for(int j = 0; j < 3; j++)
+		out.push_back(rollArtifact(EArtifactClass::ART_TREASURE));
+	for(int j = 0; j < 3; j++)
+		out.push_back(rollArtifact(EArtifactClass::ART_MINOR));
+	out.push_back(rollArtifact(EArtifactClass::ART_MAJOR));
+
+	return out;
+}
+
+vstd::RNG & GameRandomizer::getDefault()
+{
+	return globalRandomNumberGenerator;
+}
+
+void GameRandomizer::setSeed(int newSeed)
+{
+	globalRandomNumberGenerator.setSeed(newSeed);
+}
+
+PrimarySkill GameRandomizer::rollPrimarySkillForLevelup(const CGHeroInstance * hero)
+{
+	const bool isLowLevelHero = hero->level < GameConstants::HERO_HIGH_LEVEL;
+	const auto & skillChances = isLowLevelHero ? hero->getHeroClass()->primarySkillLowLevel : hero->getHeroClass()->primarySkillHighLevel;
+	auto & heroRng = heroSkillSeed.at(hero->getHeroTypeID());
+
+	if(hero->isCampaignYog())
+	{
+		// Yog can only receive Attack or Defence on level-up
+		std::vector<int> yogChances = {skillChances[0], skillChances[1]};
+		return static_cast<PrimarySkill>(RandomGeneratorUtil::nextItemWeighted(yogChances, heroRng.seed));
+	}
+	return static_cast<PrimarySkill>(RandomGeneratorUtil::nextItemWeighted(skillChances, heroRng.seed));
+}
+
+SecondarySkill GameRandomizer::rollSecondarySkillForLevelup(const CGHeroInstance * hero, const std::set<SecondarySkill> & options)
+{
+	auto & heroRng = heroSkillSeed.at(hero->getHeroTypeID());
+
+	auto getObligatorySkills = [](CSkill::Obligatory obl)
+	{
+		std::set<SecondarySkill> obligatory;
+		for(auto i = 0; i < LIBRARY->skillh->size(); i++)
+			if((*LIBRARY->skillh)[SecondarySkill(i)]->obligatory(obl))
+				obligatory.insert(i); //Always return all obligatory skills
+
+		return obligatory;
+	};
+
+	auto intersect = [](const std::set<SecondarySkill> & left, const std::set<SecondarySkill> & right)
+	{
+		std::set<SecondarySkill> intersect;
+		std::set_intersection(left.begin(), left.end(), right.begin(), right.end(), std::inserter(intersect, intersect.begin()));
+		return intersect;
+	};
+
+	std::set<SecondarySkill> wisdomList = getObligatorySkills(CSkill::Obligatory::MAJOR);
+	std::set<SecondarySkill> schoolList = getObligatorySkills(CSkill::Obligatory::MINOR);
+
+	bool wantsWisdom = heroRng.wisdomCounter + 1 >= hero->maxlevelsToWisdom();
+	bool wantsSchool = heroRng.magicSchoolCounter + 1 >= hero->maxlevelsToMagicSchool();
+	bool selectWisdom = wantsWisdom && !intersect(options, wisdomList).empty();
+	bool selectSchool = wantsSchool && !intersect(options, schoolList).empty();
+
+	std::set<SecondarySkill> actualCandidates;
+
+	if(selectWisdom)
+		actualCandidates = intersect(options, wisdomList);
+	else if(selectSchool)
+		actualCandidates = intersect(options, schoolList);
+	else
+		actualCandidates = options;
+
+	assert(!actualCandidates.empty());
+
+	std::vector<int> weights;
+	std::vector<SecondarySkill> skills;
+
+	for(const auto & possible : actualCandidates)
+	{
+		skills.push_back(possible);
+		if(hero->getHeroClass()->secSkillProbability.count(possible) != 0)
+		{
+			int weight = hero->getHeroClass()->secSkillProbability.at(possible);
+			weights.push_back(std::max(1, weight));
+		}
+		else
+			weights.push_back(1); // H3 behavior - banned skills have minimal (1) chance to be picked
+	}
+
+	int selectedIndex = RandomGeneratorUtil::nextItemWeighted(weights, heroRng.seed);
+	SecondarySkill selectedSkill = skills.at(selectedIndex);
+
+
+	//deterministic secondary skills
+	++heroRng.magicSchoolCounter;
+	++heroRng.wisdomCounter;
+
+	if((*LIBRARY->skillh)[selectedSkill]->obligatory(CSkill::Obligatory::MAJOR))
+		heroRng.wisdomCounter = 0;
+	if((*LIBRARY->skillh)[selectedSkill]->obligatory(CSkill::Obligatory::MINOR))
+		heroRng.magicSchoolCounter = 0;
+
+	return selectedSkill;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 36 - 24
server/processors/RandomizationProcessor.h → lib/callback/GameRandomizer.h

@@ -1,5 +1,5 @@
 /*
- * RandomizationProcessor.h, part of VCMI engine
+ * GameRandomizer.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -9,17 +9,15 @@
  */
 #pragma once
 
-#include "../lib/callback/IGameRandomizer.h"
+#include "callback/IGameRandomizer.h"
+#include "CRandomGenerator.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class CRandomGenerator;
 class CGHeroInstance;
 
-VCMI_LIB_NAMESPACE_END
-
 /// Biased randomizer that has following properties:
-/// - at bias value of 0 it acts as statistical random generator
+/// - at bias value of 0 it acts as statistical random generator, just like vstd::RNG
 /// - at bias value of 100 it guarantees that it will take at most 100/chance rolls till succesfull roll
 /// - at bias value between 1..99 similar guarantee is also provided, but with larger number of rolls
 /// No matter what bias is, statistical probability on large number of rolls remains the same
@@ -33,12 +31,25 @@ public:
 	bool roll(vstd::RNG & generator, int successChance, int biasValue);
 };
 
-class RandomizationProcessor final : public IGameRandomizer
+class DLL_LINKAGE GameRandomizer final : public IGameRandomizer
 {
-	std::unique_ptr<CRandomGenerator> globalRandomNumberGenerator;
+	struct HeroSkillGenerator
+	{
+		CRandomGenerator seed;
+		int8_t magicSchoolCounter = 1;
+		int8_t wisdomCounter = 1;
+	};
+
+	const IGameInfoCallback & gameInfo;
+
+	/// Global RNG, for use when there is no specialized instance
+	CRandomGenerator globalRandomNumberGenerator;
+
+	/// Stores number of times each artifact was placed on map via randomization
+	std::map<ArtifactID, int> allocatedArtifacts;
 
-	std::map<HeroTypeID, std::unique_ptr<CRandomGenerator>> heroSeed;
-	std::map<PlayerColor, std::unique_ptr<CRandomGenerator>> playerTavern;
+	std::map<HeroTypeID, HeroSkillGenerator> heroSkillSeed;
+	std::map<PlayerColor, CRandomGenerator> playerTavern;
 
 	std::map<ObjectInstanceID, BiasedRandomizer> goodMoraleSeed;
 	std::map<ObjectInstanceID, BiasedRandomizer> badMoraleSeed;
@@ -48,19 +59,20 @@ class RandomizationProcessor final : public IGameRandomizer
 	std::map<ObjectInstanceID, BiasedRandomizer> combatAbilitySeed;
 
 public:
-	RandomizationProcessor();
+	explicit GameRandomizer(const IGameInfoCallback & gameInfo);
+	~GameRandomizer();
 
-	PrimarySkill rollPrimarySkillForLevelup(const CGHeroInstance * hero);
-	SecondarySkill rollSecondarySkillForLevelup(const CGHeroInstance * hero, const std::vector<SecondarySkill> & candidates);
+	PrimarySkill rollPrimarySkillForLevelup(const CGHeroInstance * hero) override;
+	SecondarySkill rollSecondarySkillForLevelup(const CGHeroInstance * hero, const std::set<SecondarySkill> & candidates) override;
+//
+//	bool rollGoodMorale(ObjectInstanceID actor, int moraleValue);
+//	bool rollBadMorale(ObjectInstanceID actor, int moraleValue);
+//	bool rollGoodLuck(ObjectInstanceID actor, int luckValue);
+//	bool rollBadLuck(ObjectInstanceID actor, int luckValue);
+//
+//	bool rollCombatAbility(ObjectInstanceID actor, int percentageChance);
 
-	bool rollGoodMorale(ObjectInstanceID actor, int moraleValue);
-	bool rollBadMorale(ObjectInstanceID actor, int moraleValue);
-	bool rollGoodLuck(ObjectInstanceID actor, int luckValue);
-	bool rollBadLuck(ObjectInstanceID actor, int luckValue);
-
-	bool rollCombatAbility(ObjectInstanceID actor, int percentageChance);
-
-	HeroTypeID rollHero(PlayerColor player, FactionID faction) override;
+//	HeroTypeID rollHero(PlayerColor player, FactionID faction) override;
 
 	CreatureID rollCreature() override;
 	CreatureID rollCreature(int tier) override;
@@ -70,8 +82,6 @@ public:
 	ArtifactID rollArtifact(std::set<ArtifactID> filtered) override;
 	std::vector<ArtifactID> rollMarketArtifactSet() override;
 
-	std::string rollTownName(FactionID faction) override;
-
 	vstd::RNG & getDefault() override;
 
 	void setSeed(int newSeed);
@@ -79,6 +89,8 @@ public:
 	template<typename Handler>
 	void serialize(Handler & h)
 	{
-		h & *globalRandomNumberGenerator;
+		h & globalRandomNumberGenerator;
 	}
 };
+
+VCMI_LIB_NAMESPACE_END

+ 6 - 5
lib/callback/IGameRandomizer.h

@@ -13,6 +13,8 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+class CGHeroInstance;
+
 enum class EArtifactClass;
 
 namespace vstd
@@ -22,10 +24,10 @@ namespace vstd
 
 /// Provides source of random rolls for game entities
 /// Instance of this interface only exists on server
-class IGameRandomizer : boost::noncopyable
+class DLL_LINKAGE IGameRandomizer : boost::noncopyable
 {
 public:
-	virtual ~IGameRandomizer();
+	virtual ~IGameRandomizer() = default;
 
 	virtual ArtifactID rollArtifact() = 0;
 	virtual ArtifactID rollArtifact(EArtifactClass type) = 0;
@@ -36,9 +38,8 @@ public:
 	virtual CreatureID rollCreature() = 0;
 	virtual CreatureID rollCreature(int tier) = 0;
 
-	virtual HeroTypeID rollHero(PlayerColor player, FactionID faction) = 0;
-
-	virtual std::string rollTownName(FactionID faction) = 0;
+	virtual PrimarySkill rollPrimarySkillForLevelup(const CGHeroInstance * hero) = 0;
+	virtual SecondarySkill rollSecondarySkillForLevelup(const CGHeroInstance * hero, const std::set<SecondarySkill> & candidates) = 0;
 
 	virtual vstd::RNG & getDefault() = 0;
 };

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

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

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

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

+ 2 - 2
lib/gameState/CGameState.cpp

@@ -607,7 +607,7 @@ void CGameState::initHeroes(IGameRandomizer & gameRandomizer)
 			logGlobal->warn("Hero with uninitialized owner!");
 			continue;
 		}
-		hero->initHero(gameRandomizer.getDefault());
+		hero->initHero(gameRandomizer);
 	}
 
 	// generate boats for all heroes on water
@@ -650,7 +650,7 @@ void CGameState::initHeroes(IGameRandomizer & gameRandomizer)
 			heroInPool = newHeroPtr.get();
 		}
 		map->generateUniqueInstanceName(heroInPool);
-		heroInPool->initHero(gameRandomizer.getDefault());
+		heroInPool->initHero(gameRandomizer);
 		heroesPool->addHeroToPool(htype);
 	}
 

+ 5 - 4
lib/gameState/CGameState.h

@@ -55,9 +55,6 @@ public:
 	ArtifactInstanceID saveCompatibilityLastAllocatedArtifactID;
 	std::vector<std::shared_ptr<CArtifactInstance>> saveCompatibilityUnregisteredArtifacts;
 
-	/// Stores number of times each artifact was placed on map via randomization
-	std::map<ArtifactID, int> allocatedArtifacts;
-
 	/// List of currently ongoing battles
 	std::vector<std::unique_ptr<BattleInfo>> currentBattles;
 	/// ID that can be allocated to next battle
@@ -182,7 +179,11 @@ public:
 		h & globalEffects;
 		h & currentRumor;
 		h & campaign;
-		h & allocatedArtifacts;
+		if (!h.hasFeature(Handler::Version::RANDOMIZATION_REWORK))
+		{
+			std::map<ArtifactID, int> allocatedArtifactsUnused;
+			h & allocatedArtifactsUnused;
+		}
 		h & statistic;
 
 		if(!h.saving && h.loadingGamestate)

+ 1 - 6
lib/gameState/GameStatePackVisitor.cpp

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

+ 0 - 1
lib/gameState/GameStatePackVisitor.h

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

+ 6 - 0
lib/json/JsonRandom.cpp

@@ -50,6 +50,12 @@ JsonRandomizationException::JsonRandomizationException(const std::string & messa
 	: std::runtime_error(message + " Input was: " + cleanupJson(input))
 {}
 
+JsonRandom::JsonRandom(IGameInfoCallback * cb, IGameRandomizer & gameRandomizer)
+	: GameCallbackHolder(cb)
+	, gameRandomizer(gameRandomizer)
+	, rng(gameRandomizer.getDefault())
+{
+}
 
 	si32 JsonRandom::loadVariable(const std::string & variableGroup, const std::string & value, const Variables & variables, si32 defaultValue)
 	{

+ 38 - 151
lib/mapObjects/CGHeroInstance.cpp

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

+ 14 - 30
lib/mapObjects/CGHeroInstance.h

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

+ 1 - 1
lib/mapObjects/CGTownInstance.cpp

@@ -494,7 +494,7 @@ void CGTownInstance::initObj(IGameRandomizer & gameRandomizer) ///initialize tow
 				creatures[level].second.push_back(getTown()->creatures[level][upgradeNum]);
 		}
 	}
-	initializeConfigurableBuildings(gameRandomizer.getDefault());
+	initializeConfigurableBuildings(gameRandomizer);
 	initializeNeutralTownGarrison(gameRandomizer.getDefault());
 	recreateBuildingsBonuses();
 	updateAppearance();

+ 0 - 1
lib/networkPacks/NetPackVisitor.h

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

+ 0 - 5
lib/networkPacks/NetPacksLib.cpp

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

+ 0 - 12
lib/networkPacks/PacksForClient.h

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

+ 2 - 1
lib/serializer/ESerializationVersion.h

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

+ 0 - 1
lib/serializer/RegisterTypes.h

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

+ 13 - 27
server/CGameHandler.cpp

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

+ 3 - 4
server/CGameHandler.h

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

+ 0 - 2
server/CMakeLists.txt

@@ -15,7 +15,6 @@ set(vcmiservercommon_SRCS
 		processors/HeroPoolProcessor.cpp
 		processors/NewTurnProcessor.cpp
 		processors/PlayerMessageProcessor.cpp
-		processors/RandomizationProcessor.cpp
 		processors/TurnOrderProcessor.cpp
 
 		CGameHandler.cpp
@@ -44,7 +43,6 @@ set(vcmiservercommon_HEADERS
 		processors/HeroPoolProcessor.h
 		processors/NewTurnProcessor.h
 		processors/PlayerMessageProcessor.h
-		processors/RandomizationProcessor.h
 		processors/TurnOrderProcessor.h
 
 		CGameHandler.h

+ 0 - 11
server/processors/HeroPoolProcessor.cpp

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

+ 6 - 6
server/processors/HeroPoolProcessor.h

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

+ 3 - 3
server/processors/NewTurnProcessor.cpp

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

+ 0 - 126
server/processors/RandomizationProcessor.cpp

@@ -1,126 +0,0 @@
-/*
- * RandomizationProcessor.cpp, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-#include "StdInc.h"
-#include "RandomizationProcessor.h"
-
-#include <vstd/RNG.h>
-
-bool BiasedRandomizer::roll(vstd::RNG &generator, int successChance, int biasValue)
-{
-	int failChance = 100 - successChance;
-	int newRoll = generator.nextInt(0,99);
-	bool success = newRoll + accumulatedBias >= successChance;
-	if (success)
-		accumulatedBias -= failChance * biasValue / 100;
-	else
-		accumulatedBias += successChance * biasValue / 100;
-
-	return success;
-}
-
-//void CGameInfoCallback::pickAllowedArtsSet(std::vector<ArtifactID> & out, vstd::RNG & rand)
-//{
-//	for (int j = 0; j < 3 ; j++)
-//		out.push_back(gameState().pickRandomArtifact(rand, EArtifactClass::ART_TREASURE));
-//	for (int j = 0; j < 3 ; j++)
-//		out.push_back(gameState().pickRandomArtifact(rand, EArtifactClass::ART_MINOR));
-//
-//	out.push_back(gameState().pickRandomArtifact(rand, EArtifactClass::ART_MAJOR));
-//}
-
-
-//ArtifactID CGameState::pickRandomArtifact(vstd::RNG & randomGenerator, std::optional<EArtifactClass> type, std::function<bool(ArtifactID)> accepts)
-//{
-//	std::set<ArtifactID> potentialPicks;
-//
-//	// Select artifacts that satisfy provided criteria
-//	for (auto const & artifactID : map->allowedArtifact)
-//	{
-//		if (!LIBRARY->arth->legalArtifact(artifactID))
-//			continue;
-//
-//		const auto * artifact = artifactID.toArtifact();
-//
-//		assert(artifact->aClass != EArtifactClass::ART_SPECIAL); // should be filtered out when allowedArtifacts is initialized
-//
-//		if (type.has_value() && *type != artifact->aClass)
-//			continue;
-//
-//		if (!accepts(artifact->getId()))
-//			continue;
-//
-//		potentialPicks.insert(artifact->getId());
-//	}
-//
-//	return pickRandomArtifact(randomGenerator, potentialPicks);
-//}
-//
-//ArtifactID CGameState::pickRandomArtifact(vstd::RNG & randomGenerator, std::set<ArtifactID> potentialPicks)
-//{
-//	// No allowed artifacts at all - give Grail - this can't be banned (hopefully)
-//	// FIXME: investigate how such cases are handled by H3 - some heavily customized user-made maps likely rely on H3 behavior
-//	if (potentialPicks.empty())
-//	{
-//		logGlobal->warn("Failed to find artifact that matches requested parameters!");
-//		return ArtifactID::GRAIL;
-//	}
-//
-//	// Find how many times least used artifacts were picked by randomizer
-//	int leastUsedTimes = std::numeric_limits<int>::max();
-//	for (auto const & artifact : potentialPicks)
-//		if (allocatedArtifacts[artifact] < leastUsedTimes)
-//			leastUsedTimes = allocatedArtifacts[artifact];
-//
-//	// Pick all artifacts that were used least number of times
-//	std::set<ArtifactID> preferredPicks;
-//	for (auto const & artifact : potentialPicks)
-//		if (allocatedArtifacts[artifact] == leastUsedTimes)
-//			preferredPicks.insert(artifact);
-//
-//	assert(!preferredPicks.empty());
-//
-//	ArtifactID artID = *RandomGeneratorUtil::nextItem(preferredPicks, randomGenerator);
-//	allocatedArtifacts[artID] += 1; // record +1 more usage
-//	return artID;
-//}
-//
-//ArtifactID CGameState::pickRandomArtifact(vstd::RNG & randomGenerator, std::function<bool(ArtifactID)> accepts)
-//{
-//	return pickRandomArtifact(randomGenerator, std::nullopt, std::move(accepts));
-//}
-//
-//ArtifactID CGameState::pickRandomArtifact(vstd::RNG & randomGenerator, std::optional<EArtifactClass> type)
-//{
-//	return pickRandomArtifact(randomGenerator, type, [](const ArtifactID &) { return true; });
-//}
-
-//CreatureID CCreatureHandler::pickRandomMonster(vstd::RNG & rand, int tier) const
-//{
-//	std::vector<CreatureID> allowed;
-//	for(const auto & creature : objects)
-//	{
-//		if(creature->special)
-//			continue;
-//
-//		if(creature->excludeFromRandomization)
-//			continue;
-//
-//		if (creature->level == tier || tier == -1)
-//			allowed.push_back(creature->getId());
-//	}
-//
-//	if(allowed.empty())
-//	{
-//		logGlobal->warn("Cannot pick a random creature of tier %d!", tier);
-//		return CreatureID::NONE;
-//	}
-//
-//	return *RandomGeneratorUtil::nextItem(allowed, rand);
-//}

+ 4 - 3
test/game/CGameStateTest.cpp

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