diff --git a/.gitignore b/.gitignore index 0fdd61f33..56200bfd8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ *.layout *.pro.user *.pro.user.* +/CMakeLists.txt.user diff --git a/AI/EmptyAI/CEmptyAI.cpp b/AI/EmptyAI/CEmptyAI.cpp index 7621186cd..8379f02ba 100644 --- a/AI/EmptyAI/CEmptyAI.cpp +++ b/AI/EmptyAI/CEmptyAI.cpp @@ -1,6 +1,8 @@ #include "StdInc.h" #include "CEmptyAI.h" +#include "CRandomGenerator.h" + void CEmptyAI::init(shared_ptr CB) { cb = CB; @@ -15,12 +17,12 @@ void CEmptyAI::yourTurn() void CEmptyAI::heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector &skills, QueryID queryID) { - cb->selectionMade(rand() % skills.size(), queryID); + cb->selectionMade(CRandomGenerator::getDefault().nextInt(skills.size() - 1), queryID); } void CEmptyAI::commanderGotLevel(const CCommanderInstance * commander, std::vector skills, QueryID queryID) { - cb->selectionMade(rand() % skills.size(), queryID); + cb->selectionMade(CRandomGenerator::getDefault().nextInt(skills.size() - 1), queryID); } void CEmptyAI::showBlockingDialog(const std::string &text, const std::vector &components, QueryID askID, const int soundID, bool selection, bool cancel) diff --git a/AI/StupidAI/StupidAI.cpp b/AI/StupidAI/StupidAI.cpp index a0e432ca0..7718fe6aa 100644 --- a/AI/StupidAI/StupidAI.cpp +++ b/AI/StupidAI/StupidAI.cpp @@ -104,8 +104,9 @@ BattleAction CStupidAI::activeStack( const CStack * stack ) if(stack->type->idNumber == CreatureID::CATAPULT) { BattleAction attack; - static const int wallHexes[] = {50, 183, 182, 130, 62, 29, 12, 95}; - attack.destinationTile = wallHexes[ rand()%ARRAY_COUNT(wallHexes) ]; + static const std::vector wallHexes = boost::assign::list_of(50)(183)(182)(130)(62)(29)(12)(95); + + attack.destinationTile = *RandomGeneratorUtil::nextItem(wallHexes, CRandomGenerator::getDefault()); attack.actionType = Battle::CATAPULT; attack.additionalInfo = 0; attack.side = side; diff --git a/Global.h b/Global.h index 72735c817..994977ea4 100644 --- a/Global.h +++ b/Global.h @@ -553,19 +553,6 @@ namespace vstd }); } - static inline int retreiveRandNum(const std::function &randGen) - { - if (randGen) - return randGen(); - else - return rand(); - } - - template const T & pickRandomElementOf(const std::vector &v, const std::function &randGen) - { - return v.at(retreiveRandNum(randGen) % v.size()); - } - template void advance(T &obj, int change) { diff --git a/client/CAnimation.cpp b/client/CAnimation.cpp index 8d756068e..3dcf75b71 100644 --- a/client/CAnimation.cpp +++ b/client/CAnimation.cpp @@ -4,6 +4,7 @@ #include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/ISimpleResourceLoader.h" #include "../lib/JsonNode.h" +#include "../lib/CRandomGenerator.h" #include "CBitmapHandler.h" #include "Graphics.h" @@ -1432,7 +1433,7 @@ void CCreatureAnim::loopPreview(bool warMachine) if (anim.size(elem)) available.push_back(elem); - size_t rnd = rand()%(available.size()*2); + size_t rnd = CRandomGenerator::getDefault().nextInt(available.size() * 2 - 1); if (rnd >= available.size()) { diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index e35891065..0200cc401 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -9,6 +9,7 @@ #include "../lib/GameConstants.h" #include "../lib/filesystem/Filesystem.h" #include "../lib/StringConstants.h" +#include "../lib/CRandomGenerator.h" /* * CMusicHandler.cpp, part of VCMI engine @@ -230,7 +231,7 @@ int CSoundHandler::playSound(std::string sound, int repeats) // Helper. Randomly select a sound from an array and play it int CSoundHandler::playSoundFromSet(std::vector &sound_vec) { - return playSound(sound_vec[rand() % sound_vec.size()]); + return playSound(*RandomGeneratorUtil::nextItem(sound_vec, CRandomGenerator::getDefault())); } void CSoundHandler::stopSound( int handler ) @@ -504,10 +505,7 @@ bool MusicEntry::play() if (!setName.empty()) { auto set = owner->musicsSet[setName]; - size_t entryID = rand() % set.size(); - auto iterator = set.begin(); - std::advance(iterator, entryID); - load(iterator->second); + load(RandomGeneratorUtil::nextItem(set, CRandomGenerator::getDefault())->second); } logGlobal->traceStream()<<"Playing music file "<String(), 0, 0); pos = bg->center(); } @@ -4136,7 +4137,7 @@ std::string CLoadingScreen::getBackground() } else { - return conf[ rand() % conf.size() ].String(); + return RandomGeneratorUtil::nextItem(conf, CRandomGenerator::getDefault())->String(); } } diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index d62b5093a..54b1375e1 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -26,6 +26,7 @@ #include "../CVideoHandler.h" #include "../../lib/CTownHandler.h" #include "../../lib/mapping/CMap.h" +#include "../../lib/CRandomGenerator.h" #include "CBattleAnimations.h" #include "CBattleInterfaceClasses.h" @@ -59,7 +60,7 @@ static void onAnimationFinished(const CStack *stack, CCreatureAnimation * anim) if (anim->framesInGroup(CCreatureAnim::MOUSEON) > 0) { - if (float(rand() % 100) < creature->animation.timeBetweenFidgets * 10) + if (CRandomGenerator::getDefault().nextDouble(99.0) < creature->animation.timeBetweenFidgets * 10) anim->playOnce(CCreatureAnim::MOUSEON); else anim->setType(CCreatureAnim::HOLDING); @@ -194,7 +195,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe logGlobal->errorStream() << bfieldType << " battlefield type does not have any backgrounds!"; else { - const std::string bgName = vstd::pickRandomElementOf(graphics->battleBacks[bfieldType], rand); + const std::string bgName = *RandomGeneratorUtil::nextItem(graphics->battleBacks[bfieldType], CRandomGenerator::getDefault()); background = BitmapHandler::loadBitmap(bgName, false); } } diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 01ab922ff..79308237d 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -17,6 +17,7 @@ #include "../lib/GameConstants.h" #include "../lib/CStopWatch.h" #include "CMT.h" +#include "../lib/CRandomGenerator.h" /* * mapHandler.cpp, part of VCMI engine @@ -129,7 +130,7 @@ void CMapHandler::prepareFOWDefs() elem[j].resize(sizes.z); for(int k = 0; k < sizes.z; ++k) { - elem[j][k] = rand()%graphics->FoWfullHide->ourImages.size(); + elem[j][k] = CRandomGenerator::getDefault().nextInt(graphics->FoWfullHide->ourImages.size() - 1); } } } @@ -199,6 +200,8 @@ void CMapHandler::borderAndTerrainBitmapInit() { int terBitmapNum = -1; + auto & rand = CRandomGenerator::getDefault(); + if(i==-1 && j==-1) terBitmapNum = 16; else if(i==-1 && j==(sizes.y)) @@ -208,15 +211,15 @@ void CMapHandler::borderAndTerrainBitmapInit() else if(i==(sizes.x) && j==(sizes.y)) terBitmapNum = 18; else if(j == -1 && i > -1 && i < sizes.y) - terBitmapNum = 22+rand()%2; + terBitmapNum = rand.nextInt(22, 23); else if(i == -1 && j > -1 && j < sizes.y) - terBitmapNum = 33+rand()%2; + terBitmapNum = rand.nextInt(33, 34); else if(j == sizes.y && i >-1 && i < sizes.x) - terBitmapNum = 29+rand()%2; + terBitmapNum = rand.nextInt(29, 30); else if(i == sizes.x && j > -1 && j < sizes.y) - terBitmapNum = 25+rand()%2; + terBitmapNum = rand.nextInt(25, 26); else - terBitmapNum = rand()%16; + terBitmapNum = rand.nextInt(15); if(terBitmapNum != -1) { @@ -1083,7 +1086,7 @@ ui8 CMapHandler::getPhaseShift(const CGObjectInstance *object) const auto i = animationPhase.find(object); if(i == animationPhase.end()) { - ui8 ret = rand() % 255; + ui8 ret = CRandomGenerator::getDefault().nextInt(254); animationPhase[object] = ret; return ret; } diff --git a/lib/BattleState.cpp b/lib/BattleState.cpp index 0f6876913..2e1bde0a8 100644 --- a/lib/BattleState.cpp +++ b/lib/BattleState.cpp @@ -87,7 +87,7 @@ std::pair< std::vector, int > BattleInfo::getPath(BattleHex start, Ba } ui32 BattleInfo::calculateDmg( const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, - bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg ) + bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg, CRandomGenerator & rand ) { TDmgRange range = calculateDmgRange(attacker, defender, shooting, charge, lucky, unlucky, deathBlow, ballistaDoubleDmg); @@ -97,7 +97,7 @@ ui32 BattleInfo::calculateDmg( const CStack* attacker, const CStack* defender, c int howManyToAv = std::min(10, attacker->count); for (int g=0; g BattleInfo::calculateResistedStacks(const CSpell * sp, const CGHeroInstance * caster, const CGHeroInstance * hero2, const std::vector & affectedCreatures, PlayerColor casterSideOwner, ECastingMode::ECastingMode mode, int usedSpellPower, int spellLevel) const +std::vector BattleInfo::calculateResistedStacks(const CSpell * sp, const CGHeroInstance * caster, const CGHeroInstance * hero2, + const std::vector & affectedCreatures, PlayerColor casterSideOwner, ECastingMode::ECastingMode mode, + int usedSpellPower, int spellLevel, CRandomGenerator & rand) const { std::vector ret; for(auto & affectedCreature : affectedCreatures) @@ -749,8 +751,11 @@ std::vector BattleInfo::calculateResistedStacks(const CSpell * sp, const C if(prob > 100) prob = 100; - if(rand()%100 < prob) //immunity from resistance + //immunity from resistance + if(rand.nextInt(99) < prob) + { ret.push_back((affectedCreature)->ID); + } } diff --git a/lib/BattleState.h b/lib/BattleState.h index 8e18a8ea2..30a3f50d9 100644 --- a/lib/BattleState.h +++ b/lib/BattleState.h @@ -129,7 +129,7 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb shared_ptr getObstacleOnTile(BattleHex tile) const; std::set getStoppers(bool whichSidePerspective) const; - ui32 calculateDmg(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg); //charge - number of hexes travelled before attack (for champion's jousting) + ui32 calculateDmg(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg, CRandomGenerator & rand); //charge - number of hexes travelled before attack (for champion's jousting) void calculateCasualties(std::map *casualties) const; //casualties are array of maps size 2 (attacker, defeneder), maps are (crid => amount) //void getPotentiallyAttackableHexes(AttackableTiles &at, const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos); //hexes around target that could be attacked in melee @@ -144,7 +144,7 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb const CGHeroInstance * getHero(PlayerColor player) const; //returns fighting hero that belongs to given player - std::vector calculateResistedStacks(const CSpell * sp, const CGHeroInstance * caster, const CGHeroInstance * hero2, const std::vector & affectedCreatures, PlayerColor casterSideOwner, ECastingMode::ECastingMode mode, int usedSpellPower, int spellLevel) const; + std::vector calculateResistedStacks(const CSpell * sp, const CGHeroInstance * caster, const CGHeroInstance * hero2, const std::vector & affectedCreatures, PlayerColor casterSideOwner, ECastingMode::ECastingMode mode, int usedSpellPower, int spellLevel, CRandomGenerator & rand) const; const CStack * battleGetStack(BattleHex pos, bool onlyAlive); //returns stack at given tile const CGHeroInstance * battleGetOwner(const CStack * stack) const; //returns hero that owns given stack; nullptr if none diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index cb23fd9bc..8d0e262db 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -2251,10 +2251,14 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) co } } - if (possibleSpells.size()) - return possibleSpells[rand() % possibleSpells.size()]; + if(!possibleSpells.empty()) + { + return *RandomGeneratorUtil::nextItem(possibleSpells, gs->getRandomGenerator()); + } else + { return SpellID::NONE; + } } SpellID CBattleInfoCallback::getRandomCastedSpell(const CStack * caster) const @@ -2269,7 +2273,7 @@ SpellID CBattleInfoCallback::getRandomCastedSpell(const CStack * caster) const { totalWeight += std::max(b->additionalInfo, 1); //minimal chance to cast is 1 } - int randomPos = rand() % totalWeight; + int randomPos = gs->getRandomGenerator().nextInt(totalWeight - 1); for(Bonus * b : *bl) { randomPos -= std::max(b->additionalInfo, 1); diff --git a/lib/CGameState.h b/lib/CGameState.h index d93156d9d..5ab648b74 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -458,7 +458,7 @@ public: template void serialize(Handler &h, const int version) { - h & scenarioOps & initialOpts & currentPlayer & day & map & players & teams & hpool & globalEffects; + h & scenarioOps & initialOpts & currentPlayer & day & map & players & teams & hpool & globalEffects & rand; BONUS_TREE_DESERIALIZATION_FIX } diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 77f990943..361398373 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -23,7 +23,7 @@ * */ -SecondarySkill CHeroClass::chooseSecSkill(const std::set & possibles, std::minstd_rand & distr) const //picks secondary skill out from given possibilities +SecondarySkill CHeroClass::chooseSecSkill(const std::set & possibles, CRandomGenerator & rand) const //picks secondary skill out from given possibilities { int totalProb = 0; for(auto & possible : possibles) @@ -32,12 +32,14 @@ SecondarySkill CHeroClass::chooseSecSkill(const std::set & possi } if (totalProb != 0) // may trigger if set contains only banned skills (0 probability) { - int ran = distr()%totalProb; + auto ran = rand.nextInt(totalProb - 1); for(auto & possible : possibles) { ran -= secSkillProbability[possible]; - if(ran<0) + if(ran < 0) + { return possible; + } } } // FIXME: select randomly? How H3 handles such rare situation? diff --git a/lib/CHeroHandler.h b/lib/CHeroHandler.h index 78a54b197..f32312f7a 100644 --- a/lib/CHeroHandler.h +++ b/lib/CHeroHandler.h @@ -21,6 +21,7 @@ class CGameInfo; class CGHeroInstance; struct BattleHex; class JsonNode; +class CRandomGenerator; struct SSpecialtyInfo { si32 type; @@ -132,7 +133,7 @@ public: CHeroClass(); bool isMagicHero() const; - SecondarySkill chooseSecSkill(const std::set & possibles, std::minstd_rand & distr) const; //picks secondary skill out from given possibilities + SecondarySkill chooseSecSkill(const std::set & possibles, CRandomGenerator & rand) const; //picks secondary skill out from given possibilities template void serialize(Handler &h, const int version) { diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 902ec4f36..54ee9da33 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -68,6 +68,7 @@ set(lib_SRCS CHeroHandler.cpp CModHandler.cpp CObstacleInstance.cpp + CRandomGenerator.cpp CSpellHandler.cpp CThreadHelper.cpp CTownHandler.cpp @@ -91,7 +92,6 @@ set(lib_HEADERS CondSh.h ConstTransitivePtr.h CBonusTypeHandler.h - CRandomGenerator.h CScriptingModule.h CStopWatch.h FunctionList.h diff --git a/lib/CObjectHandler.cpp b/lib/CObjectHandler.cpp index a3ae8de30..fc653685f 100644 --- a/lib/CObjectHandler.cpp +++ b/lib/CObjectHandler.cpp @@ -767,17 +767,14 @@ void CGHeroInstance::initHero() } assert(validTypes()); - if (exp == 0xffffffff) + level = 1; + if(exp == 0xffffffff) { initExp(); } - else if (ID != Obj::PRISON) + else { - level = VLC->heroh->level(exp); - } - else //warp hero at the beginning of next turn - { - level = 1; + levelUpAutomatically(); } if (VLC->modh->modules.COMMANDERS && !commander) @@ -953,7 +950,7 @@ void CGHeroInstance::initObj() if(!type) initHero(); //TODO: set up everything for prison before specialties are configured - skillsInfo.distribution.seed(rand()); + skillsInfo.rand.setSeed(cb->gameState()->getRandomGenerator().nextInt()); skillsInfo.resetMagicSchoolCounter(); skillsInfo.resetWisdomCounter(); @@ -1552,7 +1549,6 @@ EAlignment::EAlignment CGHeroInstance::getAlignment() const void CGHeroInstance::initExp() { exp = cb->gameState()->getRandomGenerator().nextInt(40, 89); - level = 1; } std::string CGHeroInstance::nodeName() const @@ -1630,7 +1626,7 @@ ArtBearer::ArtBearer CGHeroInstance::bearerType() const return ArtBearer::HERO; } -std::vector CGHeroInstance::levelUpProposedSkills() const +std::vector CGHeroInstance::getLevelUpProposedSecondarySkills() const { std::vector obligatorySkills; //hero is offered magic school or wisdom if possible if (!skillsInfo.wisdomCounter) @@ -1643,11 +1639,7 @@ std::vector CGHeroInstance::levelUpProposedSkills() const std::vector ss; ss += SecondarySkill::FIRE_MAGIC, SecondarySkill::AIR_MAGIC, SecondarySkill::WATER_MAGIC, SecondarySkill::EARTH_MAGIC; - auto rng = [=](ui32 val) mutable -> ui32 - { - return skillsInfo.distribution() % val; //must be determined - }; - std::random_shuffle(ss.begin(), ss.end(), rng); + std::shuffle(ss.begin(), ss.end(), skillsInfo.rand.getStdGenerator()); for (auto skill : ss) { @@ -1691,12 +1683,12 @@ std::vector CGHeroInstance::levelUpProposedSkills() const } else if(none.size() && canLearnSkill()) //hero have free skill slot { - skills.push_back(type->heroClass->chooseSecSkill(none, skillsInfo.distribution)); //new skill + skills.push_back(type->heroClass->chooseSecSkill(none, skillsInfo.rand)); //new skill none.erase(skills.back()); } else if(!basicAndAdv.empty()) { - skills.push_back(type->heroClass->chooseSecSkill(basicAndAdv, skillsInfo.distribution)); //upgrade existing + skills.push_back(type->heroClass->chooseSecSkill(basicAndAdv, skillsInfo.rand)); //upgrade existing basicAndAdv.erase(skills.back()); } @@ -1706,7 +1698,7 @@ std::vector CGHeroInstance::levelUpProposedSkills() const //3) give any other new skill if(!basicAndAdv.empty()) { - SecondarySkill s = type->heroClass->chooseSecSkill(basicAndAdv, skillsInfo.distribution);//upgrade existing + SecondarySkill s = type->heroClass->chooseSecSkill(basicAndAdv, skillsInfo.rand);//upgrade existing skills.push_back(s); basicAndAdv.erase(s); } @@ -1716,18 +1708,147 @@ std::vector CGHeroInstance::levelUpProposedSkills() const } else if(none.size() && canLearnSkill()) { - skills.push_back(type->heroClass->chooseSecSkill(none, skillsInfo.distribution)); //give new skill + skills.push_back(type->heroClass->chooseSecSkill(none, skillsInfo.rand)); //give new skill none.erase(skills.back()); } return skills; } +PrimarySkill::PrimarySkill CGHeroInstance::nextPrimarySkill() const +{ + assert(gainsLevel()); + int randomValue = cb->gameState()->getRandomGenerator().nextInt(99), pom = 0, primarySkill = 0; + const auto & skillChances = (level > 9) ? type->heroClass->primarySkillLowLevel : type->heroClass->primarySkillHighLevel; + + for(; primarySkill < GameConstants::PRIMARY_SKILLS; ++primarySkill) + { + pom += skillChances[primarySkill]; + if(randomValue < pom) + { + break; + } + } + + logGlobal->traceStream() << "The hero gets the primary skill " << primarySkill << " with a probability of " << randomValue << "%."; + return static_cast(primarySkill); +} + +boost::optional CGHeroInstance::nextSecondarySkill() const +{ + assert(gainsLevel()); + + boost::optional chosenSecondarySkill; + const auto proposedSecondarySkills = getLevelUpProposedSecondarySkills(); + if(!proposedSecondarySkills.empty()) + { + std::vector learnedSecondarySkills; + for(auto secondarySkill : proposedSecondarySkills) + { + if(getSecSkillLevel(secondarySkill) > 0) + { + learnedSecondarySkills.push_back(secondarySkill); + } + } + + auto & rand = cb->gameState()->getRandomGenerator(); + if(learnedSecondarySkills.empty()) + { + // there are only new skills to learn, so choose anyone of them + chosenSecondarySkill = *RandomGeneratorUtil::nextItem(proposedSecondarySkills, rand); + } + else + { + // preferably upgrade a already learned secondary skill + chosenSecondarySkill = *RandomGeneratorUtil::nextItem(learnedSecondarySkills, rand); + } + } + return chosenSecondarySkill; +} + +void CGHeroInstance::setPrimarySkill(PrimarySkill::PrimarySkill primarySkill, si64 value, ui8 abs) +{ + if(primarySkill < PrimarySkill::EXPERIENCE) + { + Bonus * skill = getBonusLocalFirst(Selector::type(Bonus::PRIMARY_SKILL) + .And(Selector::subtype(primarySkill)) + .And(Selector::sourceType(Bonus::HERO_BASE_SKILL))); + assert(skill); + + if(abs) + { + skill->val = value; + } + else + { + skill->val += value; + } + } + else if(primarySkill == PrimarySkill::EXPERIENCE) + { + if(abs) + { + exp = value; + } + else + { + exp += value; + } + } +} + bool CGHeroInstance::gainsLevel() const { return exp >= VLC->heroh->reqExp(level+1); } +void CGHeroInstance::levelUp(std::vector skills) +{ + ++level; + + //deterministic secondary skills + skillsInfo.magicSchoolCounter = (skillsInfo.magicSchoolCounter + 1) % maxlevelsToMagicSchool(); + skillsInfo.wisdomCounter = (skillsInfo.wisdomCounter + 1) % maxlevelsToWisdom(); + if(vstd::contains(skills, SecondarySkill::WISDOM)) + { + skillsInfo.resetWisdomCounter(); + } + + SecondarySkill spellSchools[] = { + SecondarySkill::FIRE_MAGIC, SecondarySkill::AIR_MAGIC, SecondarySkill::WATER_MAGIC, SecondarySkill::EARTH_MAGIC}; + for(auto skill : spellSchools) + { + if(vstd::contains(skills, skill)) + { + skillsInfo.resetMagicSchoolCounter(); + break; + } + } + + //specialty + Updatespecialty(); +} + +void CGHeroInstance::levelUpAutomatically() +{ + while(gainsLevel()) + { + const auto primarySkill = nextPrimarySkill(); + setPrimarySkill(primarySkill, 1, false); + + auto proposedSecondarySkills = getLevelUpProposedSecondarySkills(); + + const auto secondarySkill = nextSecondarySkill(); + if(secondarySkill) + { + setSecSkillLevel(*secondarySkill, 1, false); + } + + //TODO why has the secondary skills to be passed to the method? + levelUp(proposedSecondarySkills); + } +} + void CGDwelling::initObj() { switch(ID) @@ -2303,13 +2424,15 @@ void CGTownInstance::newTurn() 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)) { - int resID = rand()%4+2;//bonus to random rare resource + int resID = rand.nextInt(2, 5); //bonus to random rare resource resID = (resID==2)?1:resID; - int resVal = rand()%4+1;//with size 1..4 + int resVal = rand.nextInt(1, 4);//with size 1..4 cb->giveResource(tempOwner, static_cast(resID), resVal); cb->setObjProperty (id, ObjProperty::BONUS_VALUE_FIRST, resID); cb->setObjProperty (id, ObjProperty::BONUS_VALUE_SECOND, resVal); @@ -2334,11 +2457,11 @@ void CGTownInstance::newTurn() const } if (nativeCrits.size()) { - SlotID pos = nativeCrits[rand() % nativeCrits.size()]; + SlotID pos = *RandomGeneratorUtil::nextItem(nativeCrits, rand); StackLocation sl(this, pos); const CCreature *c = getCreature(pos); - if (rand()%100 < 90 || c->upgrades.empty()) //increase number if no upgrade available + if (rand.nextInt(99) < 90 || c->upgrades.empty()) //increase number if no upgrade available { cb->changeStackCount(sl, c->growth); } @@ -2347,9 +2470,9 @@ void CGTownInstance::newTurn() const cb->changeStackType(sl, VLC->creh->creatures[*c->upgrades.begin()]); } } - if ((stacksCount() < GameConstants::ARMY_SIZE && rand()%100 < 25) || Slots().empty()) //add new stack + if ((stacksCount() < GameConstants::ARMY_SIZE && rand.nextInt(99) < 25) || Slots().empty()) //add new stack { - int i = rand() % std::min (GameConstants::CREATURES_PER_TOWN, cb->getDate(Date::MONTH)<<1); + int i = rand.nextInt(std::min(GameConstants::CREATURES_PER_TOWN, cb->getDate(Date::MONTH) << 1) - 1); if (!town->creatures[i].empty()) { CreatureID c = town->creatures[i][0]; @@ -3316,7 +3439,7 @@ void CGCreature::initObj() amount = 1; } } - formation.randomFormation = rand(); + formation.randomFormation = cb->gameState()->getRandomGenerator().nextInt(); temppower = stacks[SlotID(0)]->count * 1000; refusedJoining = false; @@ -3532,10 +3655,10 @@ void CGCreature::fight( const CGHeroInstance *h ) const if (formation.randomFormation % 100 < 50) //upgrade { SlotID slotId = SlotID(stacks.size() / 2); - if(ui32 upgradesSize = getStack(slotId).type->upgrades.size()) + const auto & upgrades = getStack(slotId).type->upgrades; + if(!upgrades.empty()) { - auto it = getStack(slotId).type->upgrades.cbegin(); //pick random in case there are more - std::advance (it, rand() % upgradesSize); + auto it = RandomGeneratorUtil::nextItem(upgrades, cb->gameState()->getRandomGenerator()); cb->changeStackType(StackLocation(this, slotId), VLC->creh->creatures[*it]); } } @@ -3869,11 +3992,12 @@ void CGVisitableOPW::onHeroVisit( const CGHeroInstance * h ) const Component::EComponentType type = Component::RESOURCE; Res::ERes sub=Res::WOOD; int val=0; + auto & rand = cb->gameState()->getRandomGenerator(); switch (ID) { case Obj::MYSTICAL_GARDEN: - if (rand()%2) + if(rand.nextInt(1) == 1) { sub = Res::GEMS; val = 5; @@ -3886,8 +4010,8 @@ void CGVisitableOPW::onHeroVisit( const CGHeroInstance * h ) const break; case Obj::WINDMILL: mid = 170; - sub = static_cast((rand() % 5) + 1); - val = (rand() % 4) + 3; + sub = static_cast(rand.nextInt(1, 5)); + val = rand.nextInt(3, 6); break; case Obj::WATER_WHEEL: mid = 164; @@ -3923,16 +4047,27 @@ void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const switch(ID) { case Obj::MONOLITH1: //one way - find corresponding exit monolith + { if(vstd::contains(objs,Obj::MONOLITH2) && vstd::contains(objs[Obj::MONOLITH2],subID) && objs[Obj::MONOLITH2][subID].size()) - destinationid = objs[Obj::MONOLITH2][subID][rand()%objs[Obj::MONOLITH2][subID].size()]; + { + destinationid = *RandomGeneratorUtil::nextItem(objs[Obj::MONOLITH2][subID], cb->gameState()->getRandomGenerator()); + } else + { logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; + } break; + } case Obj::MONOLITH3://two way monolith - pick any other one case Obj::WHIRLPOOL: //Whirlpool if(vstd::contains(objs,ID) && vstd::contains(objs[ID],subID) && objs[ID][subID].size()>1) { - while ((destinationid = objs[ID][subID][rand()%objs[ID][subID].size()]) == id); //choose another exit + //choose another exit + do + { + destinationid = *RandomGeneratorUtil::nextItem(objs[ID][subID], cb->gameState()->getRandomGenerator()); + } while(destinationid == id); + if (ID == Obj::WHIRLPOOL) { if (!h->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION)) @@ -3983,9 +4118,8 @@ void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const if (ID == Obj::WHIRLPOOL) { std::set tiles = cb->getObj(destinationid)->getBlockedPos(); - auto it = tiles.begin(); - std::advance (it, rand() % tiles.size()); //picking random element of set is tiring - cb->moveHero (h->id, *it + int3(1,0,0), true); + auto & tile = *RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator()); + cb->moveHero(h->id, tile + int3(1,0,0), true); } else cb->moveHero (h->id,CGHeroInstance::convertPosition(cb->getObj(destinationid)->pos,true) - getVisitableOffset(), true); @@ -5156,7 +5290,7 @@ void CGBonusingObject::onHeroVisit( const CGHeroInstance * h ) const messageID = 55; iw.soundID = soundBase::LUCK; gbonus.bonus.type = Bonus::LUCK; - gbonus.bonus.val = rand()%5 - 1; + gbonus.bonus.val = cb->gameState()->getRandomGenerator().nextInt(-1, 3); descr_id = 69; gbonus.bdescr.addReplacement((gbonus.bonus.val<0 ? "-" : "+") + boost::lexical_cast(gbonus.bonus.val)); break; diff --git a/lib/CObjectHandler.h b/lib/CObjectHandler.h index c34675e01..cfe376a04 100644 --- a/lib/CObjectHandler.h +++ b/lib/CObjectHandler.h @@ -8,6 +8,7 @@ #include "int3.h" #include "GameConstants.h" #include "ResourceSet.h" +#include "CRandomGenerator.h" /* * CObjectHandler.h, part of VCMI engine @@ -387,8 +388,8 @@ public: struct DLL_LINKAGE SecondarySkillsInfo { //skills are determined, initialized at map start - //FIXME: remove mutable? - mutable std::minstd_rand distribution; + //FIXME remove mutable + mutable CRandomGenerator rand; ui8 magicSchoolCounter; ui8 wisdomCounter; @@ -397,39 +398,10 @@ public: template void serialize(Handler &h, const int version) { - h & magicSchoolCounter & wisdomCounter; - if (h.saving) - { - std::ostringstream stream; - stream << distribution; - std::string str = stream.str(); - h & str; - } - else - { - std::string str; - h & str; - std::istringstream stream(str); - stream >> distribution; - } + h & magicSchoolCounter & wisdomCounter & rand; } } skillsInfo; - ////////////////////////////////////////////////////////////////////////// - - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & static_cast(*this); - h & exp & level & name & biography & portrait & mana & secSkills & movement - & sex & inTownGarrison & spells & patrol & moveDir & skillsInfo; - h & visitedTown & boat; - h & type & specialty & commander; - BONUS_TREE_DESERIALIZATION_FIX - //visitied town pointer will be restored by map serialization method - } - ////////////////////////////////////////////////////////////////////////// int3 getSightCenter() const; //"center" tile from which the sight distance is calculated int getSightRadious() const; //sight distance (should be used if player-owned structure) ////////////////////////////////////////////////////////////////////////// @@ -451,9 +423,28 @@ public: int getCurrentLuck(int stack=-1, bool town=false) const; int getSpellCost(const CSpell *sp) const; //do not use during battles -> bonuses from army would be ignored + // ----- primary and secondary skill, experience, level handling ----- + + /// 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::PrimarySkill nextPrimarySkill() const; + + /// Returns the next secondary skill randomly on level up. Can only be called if hero can gain a level up. + boost::optional nextSecondarySkill() const; + + /// Gets 0, 1 or 2 secondary skills which are proposed on hero level up. + std::vector getLevelUpProposedSecondarySkills() const; + ui8 getSecSkillLevel(SecondarySkill skill) const; //0 - no skill + + /// Returns true if hero has free secondary skill slot. + bool canLearnSkill() const; + + void setPrimarySkill(PrimarySkill::PrimarySkill primarySkill, si64 value, ui8 abs); void setSecSkillLevel(SecondarySkill which, int val, bool abs);// abs == 0 - changes by value; 1 - sets to value - bool canLearnSkill() const; ///true if hero has free secondary skill slot + void levelUp(std::vector skills); int maxMovePoints(bool onLand) const; int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false) const; @@ -470,8 +461,6 @@ public: CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const; void showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const; ECanDig diggingStatus() const; //0 - can dig; 1 - lack of movement; 2 - - std::vector levelUpProposedSkills() const; - bool gainsLevel() const; //true if hero has lower level than should upon his experience ////////////////////////////////////////////////////////////////////////// @@ -506,6 +495,22 @@ public: const std::string & getHoverText() const override; protected: void setPropertyDer(ui8 what, ui32 val) override;//synchr + +private: + void levelUpAutomatically(); + +public: + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & static_cast(*this); + h & exp & level & name & biography & portrait & mana & secSkills & movement + & sex & inTownGarrison & spells & patrol & moveDir & skillsInfo; + h & visitedTown & boat; + h & type & specialty & commander; + BONUS_TREE_DESERIALIZATION_FIX + //visitied town pointer will be restored by map serialization method + } }; class DLL_LINKAGE CSpecObjInfo diff --git a/lib/CRandomGenerator.cpp b/lib/CRandomGenerator.cpp new file mode 100644 index 000000000..17ee4b2ec --- /dev/null +++ b/lib/CRandomGenerator.cpp @@ -0,0 +1,86 @@ + +/* + * CRandomGenerator.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 "CRandomGenerator.h" + +boost::thread_specific_ptr CRandomGenerator::defaultRand; + +CRandomGenerator::CRandomGenerator() +{ + resetSeed(); +} + +void CRandomGenerator::setSeed(int seed) +{ + rand.seed(seed); +} + +void CRandomGenerator::resetSeed() +{ + boost::hash stringHash; + auto threadIdHash = stringHash(boost::lexical_cast(boost::this_thread::get_id())); + setSeed(threadIdHash * std::time(nullptr)); +} + +TRandI CRandomGenerator::getIntRange(int lower, int upper) +{ + return boost::bind(TIntDist(lower, upper), boost::ref(rand)); +} + +int CRandomGenerator::nextInt(int upper) +{ + return getIntRange(0, upper)(); +} + +int CRandomGenerator::nextInt(int lower, int upper) +{ + return getIntRange(lower, upper)(); +} + +int CRandomGenerator::nextInt() +{ + return TIntDist()(rand); +} + +TRand CRandomGenerator::getDoubleRange(double lower, double upper) +{ + return boost::bind(TRealDist(lower, upper), boost::ref(rand)); +} + +double CRandomGenerator::nextDouble(double upper) +{ + return getDoubleRange(0, upper)(); +} + +double CRandomGenerator::nextDouble(double lower, double upper) +{ + return getDoubleRange(lower, upper)(); +} + +double CRandomGenerator::nextDouble() +{ + return TRealDist()(rand); +} + +CRandomGenerator & CRandomGenerator::getDefault() +{ + if(!defaultRand.get()) + { + defaultRand.reset(new CRandomGenerator()); + } + return *defaultRand.get(); +} + +TGenerator & CRandomGenerator::getStdGenerator() +{ + return rand; +} diff --git a/lib/CRandomGenerator.h b/lib/CRandomGenerator.h index adf75a48c..54f3a1bfa 100644 --- a/lib/CRandomGenerator.h +++ b/lib/CRandomGenerator.h @@ -19,77 +19,80 @@ typedef std::function TRand; /// The random generator randomly generates integers and real numbers("doubles") between /// a given range. This is a header only class and mainly a wrapper for -/// convenient usage of the standard random API. -class CRandomGenerator : boost::noncopyable +/// convenient usage of the standard random API. An instance of this RNG is not thread safe. +class DLL_LINKAGE CRandomGenerator : boost::noncopyable { public: - /// Seeds the generator with the current time by default. - CRandomGenerator() - { - rand.seed(static_cast(std::time(nullptr))); - } + /// Seeds the generator by default with the product of the current time in milliseconds and the + /// current thread ID. + CRandomGenerator(); - void setSeed(int seed) - { - rand.seed(seed); - } + void setSeed(int seed); + + /// Resets the seed to the product of the current time in milliseconds and the + /// current thread ID. + void resetSeed(); /// Generate several integer numbers within the same range. /// e.g.: auto a = gen.getIntRange(0,10); a(); a(); a(); /// requires: lower <= upper - TRandI getIntRange(int lower, int upper) - { - return boost::bind(TIntDist(lower, upper), boost::ref(rand)); - } + TRandI getIntRange(int lower, int upper); /// Generates an integer between 0 and upper. /// requires: 0 <= upper - int nextInt(int upper) - { - return getIntRange(0, upper)(); - } + int nextInt(int upper); /// requires: lower <= upper - int nextInt(int lower, int upper) - { - return getIntRange(lower, upper)(); - } + int nextInt(int lower, int upper); /// Generates an integer between 0 and the maximum value it can hold. - int nextInt() - { - return TIntDist()(rand); - } + int nextInt(); /// Generate several double/real numbers within the same range. /// e.g.: auto a = gen.getDoubleRange(4.5,10.2); a(); a(); a(); /// requires: lower <= upper - TRand getDoubleRange(double lower, double upper) - { - return boost::bind(TRealDist(lower, upper), boost::ref(rand)); - } + TRand getDoubleRange(double lower, double upper); /// Generates a double between 0 and upper. /// requires: 0 <= upper - double nextDouble(double upper) - { - return getDoubleRange(0, upper)(); - } + double nextDouble(double upper); /// requires: lower <= upper - double nextDouble(double lower, double upper) - { - return getDoubleRange(lower, upper)(); - } + double nextDouble(double lower, double upper); /// Generates a double between 0.0 and 1.0. - double nextDouble() - { - return TRealDist()(rand); - } + double nextDouble(); + + /// Gets a globally accessible RNG which will be constructed once per thread. For the + /// seed a combination of the thread ID and current time in milliseconds will be used. + static CRandomGenerator & getDefault(); + + /// Provide method so that this RNG can be used with legacy std:: API + TGenerator & getStdGenerator(); private: TGenerator rand; + static boost::thread_specific_ptr defaultRand; + +public: + template + void serialize(Handler & h, const int version) + { + if(h.saving) + { + std::ostringstream stream; + stream << rand; + std::string str = stream.str(); + h & str; + } + else + { + std::string str; + h & str; + std::istringstream stream(str); + stream >> rand; + } + } }; namespace RandomGeneratorUtil diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 162a57cc2..66a3dc9cf 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -1068,14 +1068,13 @@ struct HeroLevelUp : public Query//2000 const CGHeroInstance *hero; PrimarySkill::PrimarySkill primskill; - ui8 level; std::vector skills; HeroLevelUp(){type = 2000;}; template void serialize(Handler &h, const int version) { - h & queryID & hero & primskill & level & skills; + h & queryID & hero & primskill & skills; } }; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 7ab351079..df62e6723 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -49,28 +49,9 @@ DLL_LINKAGE void SetResources::applyGs( CGameState *gs ) DLL_LINKAGE void SetPrimSkill::applyGs( CGameState *gs ) { - CGHeroInstance *hero = gs->getHero(id); + CGHeroInstance * hero = gs->getHero(id); assert(hero); - - if(which < PrimarySkill::EXPERIENCE) - { - Bonus *skill = hero->getBonusLocalFirst(Selector::type(Bonus::PRIMARY_SKILL) - .And(Selector::subtype(which)) - .And(Selector::sourceType(Bonus::HERO_BASE_SKILL))); - assert(skill); - - if(abs) - skill->val = val; - else - skill->val += val; - } - else if(which == PrimarySkill::EXPERIENCE) - { - if(abs) - hero->exp = val; - else - hero->exp += val; - } + hero->setPrimarySkill(which, val, abs); } DLL_LINKAGE void SetSecSkill::applyGs( CGameState *gs ) @@ -1020,25 +1001,8 @@ DLL_LINKAGE void SetHoverName::applyGs( CGameState *gs ) DLL_LINKAGE void HeroLevelUp::applyGs( CGameState *gs ) { - CGHeroInstance* h = gs->getHero(hero->id); - h->level = level; - //deterministic secondary skills - h->skillsInfo.magicSchoolCounter = (h->skillsInfo.magicSchoolCounter + 1) % h->maxlevelsToMagicSchool(); - h->skillsInfo.wisdomCounter = (h->skillsInfo.wisdomCounter + 1) % h->maxlevelsToWisdom(); - if (vstd::contains(skills, SecondarySkill::WISDOM)) - h->skillsInfo.resetWisdomCounter(); - SecondarySkill spellSchools[] = { - SecondarySkill::FIRE_MAGIC, SecondarySkill::AIR_MAGIC, SecondarySkill::WATER_MAGIC, SecondarySkill::EARTH_MAGIC}; - for (auto skill : spellSchools) - { - if (vstd::contains(skills, skill)) - { - h->skillsInfo.resetMagicSchoolCounter(); - break; - } - } - //specialty - h->Updatespecialty(); + CGHeroInstance * h = gs->getHero(hero->id); + h->levelUp(skills); } DLL_LINKAGE void CommanderLevelUp::applyGs (CGameState *gs) diff --git a/lib/VCMI_lib.cbp b/lib/VCMI_lib.cbp index d25b6b7ea..2fde545d1 100644 --- a/lib/VCMI_lib.cbp +++ b/lib/VCMI_lib.cbp @@ -107,6 +107,7 @@ + diff --git a/lib/VCMI_lib.vcxproj b/lib/VCMI_lib.vcxproj index 9309a1a01..227d11c84 100644 --- a/lib/VCMI_lib.vcxproj +++ b/lib/VCMI_lib.vcxproj @@ -185,6 +185,7 @@ + @@ -323,4 +324,4 @@ - \ No newline at end of file + diff --git a/lib/VCMI_lib.vcxproj.filters b/lib/VCMI_lib.vcxproj.filters index ab2ee0b8c..b29d758c1 100644 --- a/lib/VCMI_lib.vcxproj.filters +++ b/lib/VCMI_lib.vcxproj.filters @@ -39,6 +39,7 @@ + @@ -393,4 +394,4 @@ Header Files - \ No newline at end of file + diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 34db00278..44cc84b2f 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -183,31 +183,21 @@ void CGameHandler::levelUpHero(const CGHeroInstance * hero) return; } - //give prim skill - logGlobal->traceStream() << hero->name <<" got level "<level; - int r = rand()%100, pom=0, x=0; + // give primary skill + logGlobal->traceStream() << hero->name << " got level "<< hero->level; + auto primarySkill = hero->nextPrimarySkill(); - auto & skillChances = (hero->level>9) ? hero->type->heroClass->primarySkillLowLevel : hero->type->heroClass->primarySkillHighLevel; - - for(;xtraceStream() << "The hero gets the primary skill with the no. " << x << " with a probability of " << r << "%."; SetPrimSkill sps; sps.id = hero->id; - sps.which = static_cast(x); + sps.which = primarySkill; sps.abs = false; sps.val = 1; sendAndApply(&sps); HeroLevelUp hlu; hlu.hero = hero; - hlu.primskill = static_cast(x); - hlu.level = hero->level+1; - hlu.skills = hero->levelUpProposedSkills(); + hlu.primskill = primarySkill; + hlu.skills = hero->getLevelUpProposedSecondarySkills(); if(hlu.skills.size() == 0) { @@ -217,11 +207,7 @@ void CGameHandler::levelUpHero(const CGHeroInstance * hero) else if(hlu.skills.size() == 1 || hero->tempOwner == PlayerColor::NEUTRAL) //choose skill automatically { sendAndApply(&hlu); - auto rng = [&]() mutable -> ui32 - { - return hero->skillsInfo.distribution(); //must be determined - }; - levelUpHero(hero, vstd::pickRandomElementOf (hlu.skills, rng)); + levelUpHero(hero, *RandomGeneratorUtil::nextItem(hlu.skills, hero->skillsInfo.rand)); } else if(hlu.skills.size() > 1) { @@ -359,7 +345,7 @@ void CGameHandler::levelUpCommander(const CCommanderInstance * c) else if(skillAmount == 1 || hero->tempOwner == PlayerColor::NEUTRAL) //choose skill automatically { sendAndApply(&clu); - levelUpCommander(c, vstd::pickRandomElementOf (clu.skills, rand)); + levelUpCommander(c, *RandomGeneratorUtil::nextItem(clu.skills, gs->getRandomGenerator())); } else if(skillAmount > 1) //apply and ask for secondary skill { @@ -520,7 +506,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) && rand() % 100 < eagleEyeChance) + if(sp->level <= maxLevel && !vstd::contains(finishingBattle->winnerHero->spells, sp->id) && gs->getRandomGenerator().nextInt(99) < eagleEyeChance) cs.spells.insert(sp->id); } } @@ -754,20 +740,20 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt if(!vstd::contains_if(gs->curB->sides, sideHeroBlocksLuck)) { - if(attackerLuck > 0 && rand()%24 < attackerLuck) + if(attackerLuck > 0 && gs->getRandomGenerator().nextInt(23) < attackerLuck) { bat.flags |= BattleAttack::LUCKY; } if (VLC->modh->settings.data["hardcodedFeatures"]["NEGATIVE_LUCK"].Bool()) // negative luck enabled { - if (attackerLuck < 0 && rand()%24 < abs(attackerLuck)) + if (attackerLuck < 0 && gs->getRandomGenerator().nextInt(23) < abs(attackerLuck)) { bat.flags |= BattleAttack::UNLUCKY; } } } - if (rand()%100 < att->valOfBonuses(Bonus::DOUBLE_DAMAGE_CHANCE)) + if(gs->getRandomGenerator().nextInt(99) < att->valOfBonuses(Bonus::DOUBLE_DAMAGE_CHANCE)) { bat.flags |= BattleAttack::DEATH_BLOW; } @@ -777,7 +763,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 > rand() % 100) + if(chance > gs->getRandomGenerator().nextInt(99)) { bat.flags |= BattleAttack::BALLISTA_DOUBLE_DMG; } @@ -823,8 +809,9 @@ void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, cons bsa.flags |= BattleStackAttacked::SECONDARY; //all other targets do not suffer from spells & spell-like abilities 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()); - def->prepareAttacked(bsa, gameState()->getRandomGenerator()); //calculate casualties + 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 //life drain handling if (att->hasBonusOfType(Bonus::LIFE_DRAIN) && def->isLiving()) @@ -865,7 +852,6 @@ void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, cons void CGameHandler::handleConnection(std::set players, CConnection &c) { setThreadName("CGameHandler::handleConnection"); - srand(time(nullptr)); try { @@ -1089,6 +1075,9 @@ void CGameHandler::init(StartInfo *si) gs->init(si); logGlobal->infoStream() << "Gamestate initialized!"; + // reset seed, so that clients can't predict any following random values + gs->getRandomGenerator().resetSeed(); + for(auto & elem : gs->players) { states.addPlayer(elem.first); @@ -1123,15 +1112,20 @@ void CGameHandler::setPortalDwelling(const CGTownInstance * town, bool forced=fa return; } - ui32 dwellpos = rand()%dwellings.size();//take random dwelling - ui32 creapos = rand()%dwellings.at(dwellpos)->creatures.size();//for multi-creature dwellings like Golem Factory - CreatureID creature = dwellings.at(dwellpos)->creatures.at(creapos).second[0]; + auto dwelling = *RandomGeneratorUtil::nextItem(dwellings, gs->getRandomGenerator()); - if (clear) - ssi.creatures[GameConstants::CREATURES_PER_TOWN].first = std::max((ui32)1, (VLC->creh->creatures.at(creature)->growth)/2); + // for multi-creature dwellings like Golem Factory + auto creatureId = RandomGeneratorUtil::nextItem(dwelling->creatures, gs->getRandomGenerator())->second[0]; + + if(clear) + { + ssi.creatures[GameConstants::CREATURES_PER_TOWN].first = std::max((ui32)1, (VLC->creh->creatures.at(creatureId)->growth)/2); + } else - ssi.creatures[GameConstants::CREATURES_PER_TOWN].first = VLC->creh->creatures.at(creature)->growth; - ssi.creatures[GameConstants::CREATURES_PER_TOWN].second.push_back(creature); + { + ssi.creatures[GameConstants::CREATURES_PER_TOWN].first = VLC->creh->creatures.at(creatureId)->growth; + } + ssi.creatures[GameConstants::CREATURES_PER_TOWN].second.push_back(creatureId); sendAndApply(&ssi); } } @@ -1149,7 +1143,6 @@ void CGameHandler::newTurn() bool newMonth = getDate(Date::DAY_OF_MONTH) == 28; std::map hadGold;//starting gold - for buildings like dwarven treasury - srand(time(nullptr)); if (firstTurn) { @@ -1182,7 +1175,7 @@ void CGameHandler::newTurn() } else { - int monthType = rand()%100; + int monthType = gs->getRandomGenerator().nextInt(99); if(newMonth) //new month { if (monthType < 40) //double growth @@ -1196,7 +1189,7 @@ void CGameHandler::newTurn() else if(VLC->creh->doubledCreatures.size()) { const std::vector doubledCreatures (VLC->creh->doubledCreatures.begin(), VLC->creh->doubledCreatures.end()); - n.creatureid = vstd::pickRandomElementOf(doubledCreatures, []{ return rand(); }); + n.creatureid = *RandomGeneratorUtil::nextItem(doubledCreatures, gs->getRandomGenerator()); } else { @@ -1427,12 +1420,12 @@ void CGameHandler::newTurn() if (newMonth) { iw.text.addTxt(MetaString::ARRAY_TXT, (130)); - iw.text.addReplacement(MetaString::ARRAY_TXT, 32 + rand()%10); + iw.text.addReplacement(MetaString::ARRAY_TXT, gs->getRandomGenerator().nextInt(32, 41)); } else { iw.text.addTxt(MetaString::ARRAY_TXT, (133)); - iw.text.addReplacement(MetaString::ARRAY_TXT, 43 + rand()%15); + iw.text.addReplacement(MetaString::ARRAY_TXT, gs->getRandomGenerator().nextInt(43, 57)); } } for (auto & elem : gs->players) @@ -3592,7 +3585,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) { if(currentHP.at(attackedPart) != EWallState::DESTROYED && // this part can be hit currentHP.at(attackedPart) != EWallState::NONE && - rand()%100 < getCatapultHitChance(attackedPart, sbi))//hit is successful + gs->getRandomGenerator().nextInt(99) < getCatapultHitChance(attackedPart, sbi))//hit is successful { hitSuccessfull = true; } @@ -3607,7 +3600,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) } if (allowedTargets.empty()) break; - attackedPart = allowedTargets.at(rand()%allowedTargets.size()); + attackedPart = *RandomGeneratorUtil::nextItem(allowedTargets, gs->getRandomGenerator()); } } while (!hitSuccessfull); @@ -3623,7 +3616,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 = rand()%100; + int dmgRand = gs->getRandomGenerator().nextInt(99); //accumulating dmgChance dmgChance[1] += dmgChance[0]; dmgChance[2] += dmgChance[1]; @@ -4071,7 +4064,7 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex } //checking if creatures resist - sc.resisted = gs->curB->calculateResistedStacks(spell, caster, secHero, attackedCres, casterColor, mode, usedSpellPower, spellLvl); + sc.resisted = gs->curB->calculateResistedStacks(spell, caster, secHero, attackedCres, casterColor, mode, usedSpellPower, spellLvl, gs->getRandomGenerator()); //calculating dmg to display if (spellID == SpellID::DEATH_STARE || spellID == SpellID::ACID_BREATH_DAMAGE) @@ -4465,7 +4458,7 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex for(auto & attackedCre : attackedCres) { int mirrorChance = (attackedCre)->valOfBonuses(Bonus::MAGIC_MIRROR); - if(mirrorChance > rand()%100) + if(mirrorChance > gs->getRandomGenerator().nextInt(99)) { std::vector mirrorTargets; std::vector & battleStacks = gs->curB->stacks; @@ -4479,7 +4472,7 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex } if (!mirrorTargets.empty()) { - int targetHex = mirrorTargets.at(rand() % mirrorTargets.size())->position; + int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, gs->getRandomGenerator()))->position; handleSpellCasting(spellID, 0, targetHex, 1 - casterSide, (attackedCre)->owner, nullptr, (caster ? caster : nullptr), usedSpellPower, ECastingMode::MAGIC_MIRROR, (attackedCre)); } } @@ -4652,7 +4645,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st) } if (fearsomeCreature) { - if (rand() % 100 < 10) //fixed 10% + if (gs->getRandomGenerator().nextInt(99) < 10) //fixed 10% { bte.effect = Bonus::FEAR; sendAndApply(&bte); @@ -4663,18 +4656,18 @@ void CGameHandler::stackTurnTrigger(const CStack * st) int side = gs->curB->whatSide(st->owner); if (bl.size() && st->casts && !gs->curB->sides.at(side).enchanterCounter) { - int index = rand() % bl.size(); - SpellID spellID = SpellID(bl[index]->subtype); + auto bonus = *RandomGeneratorUtil::nextItem(bl, gs->getRandomGenerator()); + auto spellID = SpellID(bonus->subtype); if (gs->curB->battleCanCastThisSpell(st->owner, SpellID(spellID).toSpell(), ECastingMode::ENCHANTER_CASTING) == ESpellCastProblem::OK) //TODO: select another available? { - int spellLeveL = bl[index]->val; //spell level + int spellLeveL = bonus->val; //spell level const CGHeroInstance * enemyHero = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner)); handleSpellCasting(spellID, spellLeveL, -1, side, st->owner, nullptr, enemyHero, 0, ECastingMode::ENCHANTER_CASTING, st); BattleSetStackProperty ssp; ssp.which = BattleSetStackProperty::ENCHANTER_COUNTER; ssp.absolute = false; - ssp.val = bl[index]->additionalInfo; //increase cooldown counter + ssp.val = bonus->additionalInfo; //increase cooldown counter ssp.stackID = st->ID; sendAndApply(&ssp); } @@ -5345,7 +5338,7 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta continue; //check if spell should be casted (probability handling) - if(rand()%100 >= chance) + if(gs->getRandomGenerator().nextInt(99) >= chance) continue; //casting //TODO: check if spell can be blocked or target is immune @@ -5383,10 +5376,10 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat ) double chanceToKill = attacker->valOfBonuses(Bonus::DEATH_STARE, 0) / 100.0f; vstd::amin(chanceToKill, 1); //cap at 100% - std::binomial_distribution<> distr(attacker->count, chanceToKill); - std::mt19937 rng(rand()); + std::binomial_distribution<> distribution(attacker->count, chanceToKill); + std::mt19937 rng(std::time(nullptr)); - int staredCreatures = distr(rng); + int staredCreatures = distribution(rng); double cap = 1 / std::max(chanceToKill, (double)(0.01));//don't divide by 0 int maxToKill = (attacker->count + cap - 1) / cap; //not much more than chance * count @@ -5405,7 +5398,7 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat ) TBonusListPtr acidBreath = attacker->getBonuses(Selector::type(Bonus::ACID_BREATH)); for(const Bonus *b : *acidBreath) { - if (b->additionalInfo > rand()%100) + if (b->additionalInfo > gs->getRandomGenerator().nextInt(99)) acidDamage += b->val; } if (acidDamage) @@ -5439,7 +5432,7 @@ bool CGameHandler::castSpell(const CGHeroInstance *h, SpellID spellID, const int case SpellID::SUMMON_BOAT: { //check if spell works at all - if(rand() % 100 >= s->getPower(schoolLevel)) //power is % chance of success + if(gs->getRandomGenerator().nextInt(99) >= s->getPower(schoolLevel)) //power is % chance of success { InfoWindow iw; iw.player = h->tempOwner; @@ -5501,7 +5494,7 @@ bool CGameHandler::castSpell(const CGHeroInstance *h, SpellID spellID, const int case SpellID::SCUTTLE_BOAT: { //check if spell works at all - if(rand() % 100 >= s->getPower(schoolLevel)) //power is % chance of success + if(gs->getRandomGenerator().nextInt(99) >= s->getPower(schoolLevel)) //power is % chance of success { InfoWindow iw; iw.player = h->tempOwner; @@ -5918,7 +5911,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( rand()%24 < -2 * nextStackMorale) + if(gs->getRandomGenerator().nextInt(23) < -2 * nextStackMorale) { //unit loses its turn - empty freeze action BattleAction ba; @@ -5983,7 +5976,7 @@ void CGameHandler::runBattle() if(!attackableBattleHexes.empty()) { BattleAction attack; - attack.destinationTile = attackableBattleHexes[rand() % attackableBattleHexes.size()]; + attack.destinationTile = *RandomGeneratorUtil::nextItem(attackableBattleHexes, gs->getRandomGenerator()); attack.actionType = Battle::CATAPULT; attack.additionalInfo = 0; attack.side = !next->attackerOwned; @@ -6077,7 +6070,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(rand()%24 < nextStackMorale) //this stack hasn't got morale this turn + if(gs->getRandomGenerator().nextInt(23) < nextStackMorale) //this stack hasn't got morale this turn { BattleTriggerEffect bte; diff --git a/server/StdInc.h b/server/StdInc.h index 206798251..747622649 100644 --- a/server/StdInc.h +++ b/server/StdInc.h @@ -8,7 +8,6 @@ //#include #include #include -#include #include #include //#include