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