2
0

GameRandomizer.cpp 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. /*
  2. * GameRandomizer.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "GameRandomizer.h"
  12. #include "IGameInfoCallback.h"
  13. #include "../../lib/CRandomGenerator.h"
  14. #include "../../lib/GameLibrary.h"
  15. #include "../../lib/CCreatureHandler.h"
  16. #include "../../lib/CSkillHandler.h"
  17. #include "../../lib/IGameSettings.h"
  18. #include "../../lib/entities/artifact/CArtHandler.h"
  19. #include "../../lib/entities/artifact/EArtifactClass.h"
  20. #include "../../lib/entities/hero/CHeroClass.h"
  21. #include "../../lib/mapObjects/CGHeroInstance.h"
  22. VCMI_LIB_NAMESPACE_BEGIN
  23. BiasedRandomizer::BiasedRandomizer(int seed)
  24. : seed(seed)
  25. {
  26. }
  27. bool BiasedRandomizer::roll(int successChance, int totalWeight, int biasValue)
  28. {
  29. assert(successChance > 0);
  30. assert(totalWeight >= successChance);
  31. int failChance = totalWeight - successChance;
  32. int newRoll = seed.nextInt(1,totalWeight);
  33. // accumulated bias is stored as premultiplied to avoid precision loss on division
  34. // so multiply everything else in equation to compensate
  35. // precision loss is small, and generally insignificant, but better to play it safe
  36. bool success = newRoll * totalWeight - accumulatedBias <= successChance * totalWeight;
  37. if (success)
  38. accumulatedBias -= failChance * biasValue;
  39. else
  40. accumulatedBias += successChance * biasValue;
  41. return success;
  42. }
  43. GameRandomizer::GameRandomizer(const IGameInfoCallback & gameInfo)
  44. : gameInfo(gameInfo)
  45. {
  46. }
  47. GameRandomizer::~GameRandomizer() = default;
  48. bool GameRandomizer::rollMoraleLuck(std::map<ObjectInstanceID, BiasedRandomizer> & seeds, ObjectInstanceID actor, int moraleLuckValue, EGameSettings diceSize, EGameSettings diceWeights)
  49. {
  50. assert(moraleLuckValue > 0);
  51. auto goodLuckChanceVector = gameInfo.getSettings().getVector(diceWeights);
  52. int luckDiceSize = gameInfo.getSettings().getInteger(diceSize);
  53. size_t chanceIndex = std::min<size_t>(goodLuckChanceVector.size(), moraleLuckValue) - 1; // array index, so 0-indexed
  54. if (!seeds.count(actor))
  55. seeds.emplace(actor, getDefault().nextInt());
  56. if(goodLuckChanceVector.size() == 0)
  57. return false;
  58. return seeds.at(actor).roll(goodLuckChanceVector[chanceIndex], luckDiceSize, biasValue);
  59. }
  60. bool GameRandomizer::rollGoodMorale(ObjectInstanceID actor, int moraleValue)
  61. {
  62. return rollMoraleLuck(goodMoraleSeed, actor, moraleValue, EGameSettings::COMBAT_MORALE_DICE_SIZE, EGameSettings::COMBAT_GOOD_MORALE_CHANCE);
  63. }
  64. bool GameRandomizer::rollBadMorale(ObjectInstanceID actor, int moraleValue)
  65. {
  66. return rollMoraleLuck(badMoraleSeed, actor, moraleValue, EGameSettings::COMBAT_MORALE_DICE_SIZE, EGameSettings::COMBAT_BAD_MORALE_CHANCE);
  67. }
  68. bool GameRandomizer::rollGoodLuck(ObjectInstanceID actor, int luckValue)
  69. {
  70. return rollMoraleLuck(goodLuckSeed, actor, luckValue, EGameSettings::COMBAT_LUCK_DICE_SIZE, EGameSettings::COMBAT_GOOD_LUCK_CHANCE);
  71. }
  72. bool GameRandomizer::rollBadLuck(ObjectInstanceID actor, int luckValue)
  73. {
  74. return rollMoraleLuck(badLuckSeed, actor, luckValue, EGameSettings::COMBAT_LUCK_DICE_SIZE, EGameSettings::COMBAT_BAD_LUCK_CHANCE);
  75. }
  76. //bool GameRandomizer::rollCombatAbility(ObjectInstanceID actor, int percentageChance)
  77. //{
  78. //
  79. //}
  80. //
  81. //HeroTypeID GameRandomizer::rollHero(PlayerColor player, FactionID faction)
  82. //{
  83. //
  84. //}
  85. CreatureID GameRandomizer::rollCreature()
  86. {
  87. std::vector<CreatureID> allowed;
  88. for(const auto & creatureID : LIBRARY->creh->getDefaultAllowed())
  89. {
  90. const auto * creaturePtr = creatureID.toCreature();
  91. if(!creaturePtr->excludeFromRandomization)
  92. allowed.push_back(creaturePtr->getId());
  93. }
  94. if(allowed.empty())
  95. throw std::runtime_error("Cannot pick a random creature!");
  96. return *RandomGeneratorUtil::nextItem(allowed, getDefault());
  97. }
  98. CreatureID GameRandomizer::rollCreature(int tier)
  99. {
  100. std::vector<CreatureID> allowed;
  101. for(const auto & creatureID : LIBRARY->creh->getDefaultAllowed())
  102. {
  103. const auto * creaturePtr = creatureID.toCreature();
  104. if(creaturePtr->excludeFromRandomization)
  105. continue;
  106. if(creaturePtr->getLevel() == tier)
  107. allowed.push_back(creaturePtr->getId());
  108. }
  109. if(allowed.empty())
  110. throw std::runtime_error("Cannot pick a random creature!");
  111. return *RandomGeneratorUtil::nextItem(allowed, getDefault());
  112. }
  113. ArtifactID GameRandomizer::rollArtifact()
  114. {
  115. std::set<ArtifactID> potentialPicks;
  116. for(const auto & artifactID : LIBRARY->arth->getDefaultAllowed())
  117. {
  118. if(!LIBRARY->arth->legalArtifact(artifactID))
  119. continue;
  120. potentialPicks.insert(artifactID);
  121. }
  122. return rollArtifact(potentialPicks);
  123. }
  124. ArtifactID GameRandomizer::rollArtifact(EArtifactClass type)
  125. {
  126. std::set<ArtifactID> potentialPicks;
  127. for(const auto & artifactID : LIBRARY->arth->getDefaultAllowed())
  128. {
  129. if(!LIBRARY->arth->legalArtifact(artifactID))
  130. continue;
  131. if(!gameInfo.isAllowed(artifactID))
  132. continue;
  133. const auto * artifact = artifactID.toArtifact();
  134. if(type != artifact->aClass)
  135. continue;
  136. potentialPicks.insert(artifactID);
  137. }
  138. return rollArtifact(potentialPicks);
  139. }
  140. ArtifactID GameRandomizer::rollArtifact(std::set<ArtifactID> potentialPicks)
  141. {
  142. // No allowed artifacts at all - give Grail - this can't be banned (hopefully)
  143. // FIXME: investigate how such cases are handled by H3 - some heavily customized user-made maps likely rely on H3 behavior
  144. if(potentialPicks.empty())
  145. {
  146. logGlobal->warn("Failed to find artifact that matches requested parameters!");
  147. return ArtifactID::GRAIL;
  148. }
  149. // Find how many times least used artifacts were picked by randomizer
  150. int leastUsedTimes = std::numeric_limits<int>::max();
  151. for(const auto & artifact : potentialPicks)
  152. if(allocatedArtifacts[artifact] < leastUsedTimes)
  153. leastUsedTimes = allocatedArtifacts[artifact];
  154. // Pick all artifacts that were used least number of times
  155. std::set<ArtifactID> preferredPicks;
  156. for(const auto & artifact : potentialPicks)
  157. if(allocatedArtifacts[artifact] == leastUsedTimes)
  158. preferredPicks.insert(artifact);
  159. assert(!preferredPicks.empty());
  160. ArtifactID artID = *RandomGeneratorUtil::nextItem(preferredPicks, getDefault());
  161. allocatedArtifacts[artID] += 1; // record +1 more usage
  162. return artID;
  163. }
  164. std::vector<ArtifactID> GameRandomizer::rollMarketArtifactSet()
  165. {
  166. std::vector<ArtifactID> out;
  167. for(int j = 0; j < 3; j++)
  168. out.push_back(rollArtifact(EArtifactClass::ART_TREASURE));
  169. for(int j = 0; j < 3; j++)
  170. out.push_back(rollArtifact(EArtifactClass::ART_MINOR));
  171. out.push_back(rollArtifact(EArtifactClass::ART_MAJOR));
  172. return out;
  173. }
  174. vstd::RNG & GameRandomizer::getDefault()
  175. {
  176. return globalRandomNumberGenerator;
  177. }
  178. void GameRandomizer::setSeed(int newSeed)
  179. {
  180. globalRandomNumberGenerator.setSeed(newSeed);
  181. }
  182. PrimarySkill GameRandomizer::rollPrimarySkillForLevelup(const CGHeroInstance * hero)
  183. {
  184. if (!heroSkillSeed.count(hero->getHeroTypeID()))
  185. heroSkillSeed.emplace(hero->getHeroTypeID(), getDefault().nextInt());
  186. const bool isLowLevelHero = hero->level < GameConstants::HERO_HIGH_LEVEL;
  187. const auto & skillChances = isLowLevelHero ? hero->getHeroClass()->primarySkillLowLevel : hero->getHeroClass()->primarySkillHighLevel;
  188. auto & heroRng = heroSkillSeed.at(hero->getHeroTypeID());
  189. if(hero->isCampaignYog())
  190. {
  191. // Yog can only receive Attack or Defence on level-up
  192. std::vector<int> yogChances = {skillChances[0], skillChances[1]};
  193. return static_cast<PrimarySkill>(RandomGeneratorUtil::nextItemWeighted(yogChances, heroRng.seed));
  194. }
  195. return static_cast<PrimarySkill>(RandomGeneratorUtil::nextItemWeighted(skillChances, heroRng.seed));
  196. }
  197. SecondarySkill GameRandomizer::rollSecondarySkillForLevelup(const CGHeroInstance * hero, const std::set<SecondarySkill> & options)
  198. {
  199. if (!heroSkillSeed.count(hero->getHeroTypeID()))
  200. heroSkillSeed.emplace(hero->getHeroTypeID(), getDefault().nextInt());
  201. auto & heroRng = heroSkillSeed.at(hero->getHeroTypeID());
  202. auto getObligatorySkills = [](CSkill::Obligatory obl)
  203. {
  204. std::set<SecondarySkill> obligatory;
  205. for(auto i = 0; i < LIBRARY->skillh->size(); i++)
  206. if((*LIBRARY->skillh)[SecondarySkill(i)]->obligatory(obl))
  207. obligatory.insert(i); //Always return all obligatory skills
  208. return obligatory;
  209. };
  210. auto intersect = [](const std::set<SecondarySkill> & left, const std::set<SecondarySkill> & right)
  211. {
  212. std::set<SecondarySkill> intersect;
  213. std::set_intersection(left.begin(), left.end(), right.begin(), right.end(), std::inserter(intersect, intersect.begin()));
  214. return intersect;
  215. };
  216. std::set<SecondarySkill> wisdomList = getObligatorySkills(CSkill::Obligatory::MAJOR);
  217. std::set<SecondarySkill> schoolList = getObligatorySkills(CSkill::Obligatory::MINOR);
  218. bool wantsWisdom = heroRng.wisdomCounter + 1 >= hero->maxlevelsToWisdom();
  219. bool wantsSchool = heroRng.magicSchoolCounter + 1 >= hero->maxlevelsToMagicSchool();
  220. bool selectWisdom = wantsWisdom && !intersect(options, wisdomList).empty();
  221. bool selectSchool = wantsSchool && !intersect(options, schoolList).empty();
  222. std::set<SecondarySkill> actualCandidates;
  223. if(selectWisdom)
  224. actualCandidates = intersect(options, wisdomList);
  225. else if(selectSchool)
  226. actualCandidates = intersect(options, schoolList);
  227. else
  228. actualCandidates = options;
  229. assert(!actualCandidates.empty());
  230. std::vector<int> weights;
  231. std::vector<SecondarySkill> skills;
  232. for(const auto & possible : actualCandidates)
  233. {
  234. skills.push_back(possible);
  235. if(hero->getHeroClass()->secSkillProbability.count(possible) != 0)
  236. {
  237. int weight = hero->getHeroClass()->secSkillProbability.at(possible);
  238. weights.push_back(std::max(1, weight));
  239. }
  240. else
  241. weights.push_back(1); // H3 behavior - banned skills have minimal (1) chance to be picked
  242. }
  243. int selectedIndex = RandomGeneratorUtil::nextItemWeighted(weights, heroRng.seed);
  244. SecondarySkill selectedSkill = skills.at(selectedIndex);
  245. //deterministic secondary skills
  246. ++heroRng.magicSchoolCounter;
  247. ++heroRng.wisdomCounter;
  248. if((*LIBRARY->skillh)[selectedSkill]->obligatory(CSkill::Obligatory::MAJOR))
  249. heroRng.wisdomCounter = 0;
  250. if((*LIBRARY->skillh)[selectedSkill]->obligatory(CSkill::Obligatory::MINOR))
  251. heroRng.magicSchoolCounter = 0;
  252. return selectedSkill;
  253. }
  254. VCMI_LIB_NAMESPACE_END