1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Multiple changes to RNG usage to both client and server

Server should never use RNG from CGameState directly.
Instead server get's own RNG that's state is secret for client.
This commit is contained in:
Arseniy Shestakov 2016-09-08 00:54:26 +03:00
parent 960d93ff5f
commit 2ba3b20928
9 changed files with 61 additions and 49 deletions

View File

@ -119,14 +119,14 @@ void CPrivilagedInfoCallback::getAllTiles (std::unordered_set<int3, ShashInt3> &
}
}
void CPrivilagedInfoCallback::pickAllowedArtsSet(std::vector<const CArtifact*> &out)
void CPrivilagedInfoCallback::pickAllowedArtsSet(std::vector<const CArtifact*> &out, CRandomGenerator & rand)
{
for (int j = 0; j < 3 ; j++)
out.push_back(VLC->arth->artifacts[VLC->arth->pickRandomArtifact(gameState()->getRandomGenerator(), CArtifact::ART_TREASURE)]);
out.push_back(VLC->arth->artifacts[VLC->arth->pickRandomArtifact(rand, CArtifact::ART_TREASURE)]);
for (int j = 0; j < 3 ; j++)
out.push_back(VLC->arth->artifacts[VLC->arth->pickRandomArtifact(gameState()->getRandomGenerator(), CArtifact::ART_MINOR)]);
out.push_back(VLC->arth->artifacts[VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MINOR)]);
out.push_back(VLC->arth->artifacts[VLC->arth->pickRandomArtifact(gameState()->getRandomGenerator(), CArtifact::ART_MAJOR)]);
out.push_back(VLC->arth->artifacts[VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MAJOR)]);
}
void CPrivilagedInfoCallback::getAllowedSpells(std::vector<SpellID> &out, ui16 level)
@ -249,6 +249,11 @@ const CGCreature * IGameCallback::putNewMonster(CreatureID creID, int count, int
return dynamic_cast<const CGCreature*>(m);
}
CRandomGenerator & IGameCallback::getRandomGenerator()
{
return rand;
}
bool IGameCallback::isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero)
{
//only server knows

View File

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

View File

@ -332,7 +332,7 @@ void CGHeroInstance::initArmy(IArmyDescriptor *dst /*= nullptr*/)
dst = this;
int howManyStacks = 0; //how many stacks will hero receives <1 - 3>
int pom = cb->gameState()->getRandomGenerator().nextInt(99);
int pom = cb->getRandomGenerator().nextInt(99);
int warMachinesGiven = 0;
if(pom < 9)
@ -348,7 +348,7 @@ void CGHeroInstance::initArmy(IArmyDescriptor *dst /*= nullptr*/)
{
auto & stack = type->initialArmy[stackNo];
int count = cb->gameState()->getRandomGenerator().nextInt(stack.minAmount, stack.maxAmount);
int count = cb->getRandomGenerator().nextInt(stack.minAmount, stack.maxAmount);
if(stack.creature >= CreatureID::CATAPULT &&
stack.creature <= CreatureID::ARROW_TOWERS) //war machine
@ -1059,7 +1059,7 @@ CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &b
void CGHeroInstance::showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const
{
InfoWindow iw;
iw.soundID = soundBase::pickup01 + cb->gameState()->getRandomGenerator().nextInt(6);
iw.soundID = soundBase::pickup01 + cb->getRandomGenerator().nextInt(6);
iw.player = tempOwner;
iw.components.push_back(Component(raisedStack));

View File

@ -289,7 +289,7 @@ void CGBlackMarket::newTurn() const
SetAvailableArtifacts saa;
saa.id = id.getNum();
cb->pickAllowedArtsSet(saa.arts);
cb->pickAllowedArtsSet(saa.arts, cb->getRandomGenerator());
cb->sendAndApply(&saa);
}

View File

@ -152,7 +152,7 @@ void CGDwelling::newTurn() const
if(ID == Obj::REFUGEE_CAMP) //if it's a refugee camp, we need to pick an available creature
{
cb->setObjProperty(id, ObjProperty::AVAILABLE_CREATURE, VLC->creh->pickRandomMonster(cb->gameState()->getRandomGenerator()));
cb->setObjProperty(id, ObjProperty::AVAILABLE_CREATURE, VLC->creh->pickRandomMonster(cb->getRandomGenerator()));
}
bool change = false;
@ -641,7 +641,7 @@ void CGTownInstance::newTurn() const
{
if (cb->getDate(Date::DAY_OF_WEEK) == 1) //reset on new week
{
auto & rand = cb->gameState()->getRandomGenerator();
auto & rand = cb->getRandomGenerator();
//give resources for Rampart, Mystic Pond
if (hasBuilt(BuildingID::MYSTIC_POND, ETownType::RAMPART)

View File

@ -158,7 +158,7 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
grantRewardWithMessage(rewards[0]);
break;
case SELECT_RANDOM: // select one randomly //TODO: use weights
grantRewardWithMessage(rewards[cb->gameState()->getRandomGenerator().nextInt(rewards.size()-1)]);
grantRewardWithMessage(rewards[cb->getRandomGenerator().nextInt(rewards.size()-1)]);
break;
}
return;

View File

@ -434,7 +434,7 @@ void CGCreature::fight( const CGHeroInstance *h ) const
const auto & upgrades = getStack(slotID).type->upgrades;
if(!upgrades.empty())
{
auto it = RandomGeneratorUtil::nextItem(upgrades, cb->gameState()->getRandomGenerator());
auto it = RandomGeneratorUtil::nextItem(upgrades, cb->getRandomGenerator());
cb->changeStackType(StackLocation(this, slotID), VLC->creh->creatures[*it]);
}
}
@ -987,7 +987,7 @@ ObjectInstanceID CGTeleport::getRandomExit(const CGHeroInstance * h) const
{
auto passableExits = getPassableExits(cb->gameState(), h, getAllExits(true));
if(passableExits.size())
return *RandomGeneratorUtil::nextItem(passableExits, cb->gameState()->getRandomGenerator());
return *RandomGeneratorUtil::nextItem(passableExits, cb->getRandomGenerator());
return ObjectInstanceID();
}
@ -1283,7 +1283,7 @@ void CGWhirlpool::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer
{
auto obj = cb->getObj(getRandomExit(hero));
std::set<int3> tiles = obj->getBlockedPos();
dPos = CGHeroInstance::convertPosition(*RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator()), true);
dPos = CGHeroInstance::convertPosition(*RandomGeneratorUtil::nextItem(tiles, cb->getRandomGenerator()), true);
}
cb->moveHero(hero->id, dPos, true);
@ -1714,7 +1714,7 @@ void CGScholar::onHeroVisit( const CGHeroInstance * h ) const
))) //hero doesn't have a spellbook or already knows the spell or doesn't have Wisdom
{
type = PRIM_SKILL;
bid = cb->gameState()->getRandomGenerator().nextInt(GameConstants::PRIMARY_SKILLS - 1);
bid = cb->getRandomGenerator().nextInt(GameConstants::PRIMARY_SKILLS - 1);
}
InfoWindow iw;

View File

@ -364,7 +364,7 @@ void CGameHandler::levelUpCommander(const CCommanderInstance * c)
else if(skillAmount == 1 || hero->tempOwner == PlayerColor::NEUTRAL) //choose skill automatically
{
sendAndApply(&clu);
levelUpCommander(c, *RandomGeneratorUtil::nextItem(clu.skills, gs->getRandomGenerator()));
levelUpCommander(c, *RandomGeneratorUtil::nextItem(clu.skills, getRandomGenerator()));
}
else if(skillAmount > 1) //apply and ask for secondary skill
{
@ -525,7 +525,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
int maxLevel = eagleEyeLevel + 1;
double eagleEyeChance = finishingBattle->winnerHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::EAGLE_EYE);
for(const CSpell *sp : gs->curB->sides.at(!battleResult.data->winner).usedSpellsHistory)
if(sp->level <= maxLevel && !vstd::contains(finishingBattle->winnerHero->spells, sp->id) && gs->getRandomGenerator().nextInt(99) < eagleEyeChance)
if(sp->level <= maxLevel && !vstd::contains(finishingBattle->winnerHero->spells, sp->id) && getRandomGenerator().nextInt(99) < eagleEyeChance)
cs.spells.insert(sp->id);
}
}
@ -789,20 +789,20 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
if(!vstd::contains_if(gs->curB->sides, sideHeroBlocksLuck))
{
if(attackerLuck > 0 && gs->getRandomGenerator().nextInt(23) < attackerLuck)
if(attackerLuck > 0 && getRandomGenerator().nextInt(23) < attackerLuck)
{
bat.flags |= BattleAttack::LUCKY;
}
if (VLC->modh->settings.data["hardcodedFeatures"]["NEGATIVE_LUCK"].Bool()) // negative luck enabled
{
if (attackerLuck < 0 && gs->getRandomGenerator().nextInt(23) < abs(attackerLuck))
if (attackerLuck < 0 && getRandomGenerator().nextInt(23) < abs(attackerLuck))
{
bat.flags |= BattleAttack::UNLUCKY;
}
}
}
if(gs->getRandomGenerator().nextInt(99) < att->valOfBonuses(Bonus::DOUBLE_DAMAGE_CHANCE))
if(getRandomGenerator().nextInt(99) < att->valOfBonuses(Bonus::DOUBLE_DAMAGE_CHANCE))
{
bat.flags |= BattleAttack::DEATH_BLOW;
}
@ -812,7 +812,7 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
static const int artilleryLvlToChance[] = {0, 50, 75, 100};
const CGHeroInstance * owner = gs->curB->getHero(att->owner);
int chance = artilleryLvlToChance[owner->getSecSkillLevel(SecondarySkill::ARTILLERY)];
if(chance > gs->getRandomGenerator().nextInt(99))
if(chance > getRandomGenerator().nextInt(99))
{
bat.flags |= BattleAttack::BALLISTA_DOUBLE_DMG;
}
@ -875,8 +875,8 @@ void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, cons
bsa.attackerID = att->ID;
bsa.stackAttacked = def->ID;
bsa.damageAmount = gs->curB->calculateDmg(att, def, gs->curB->battleGetOwner(att), gs->curB->battleGetOwner(def),
bat.shot(), distance, bat.lucky(), bat.unlucky(), bat.deathBlow(), bat.ballistaDoubleDmg(), gs->getRandomGenerator());
def->prepareAttacked(bsa, gs->getRandomGenerator()); //calculate casualties
bat.shot(), distance, bat.lucky(), bat.unlucky(), bat.deathBlow(), bat.ballistaDoubleDmg(), getRandomGenerator());
def->prepareAttacked(bsa, getRandomGenerator()); //calculate casualties
//life drain handling
if (att->hasBonusOfType(Bonus::LIFE_DRAIN) && def->isLiving())
@ -913,7 +913,7 @@ void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, cons
bsa2.effect = 11;
bsa2.damageAmount = (std::min(def->totalHelth(), bsa.damageAmount) * def->valOfBonuses(Bonus::FIRE_SHIELD)) / 100; //TODO: scale with attack/defense
att->prepareAttacked(bsa2, gameState()->getRandomGenerator());
att->prepareAttacked(bsa2, getRandomGenerator());
bat.bsa.push_back(bsa2);
}
}
@ -1337,7 +1337,7 @@ void CGameHandler::init(StartInfo *si)
logGlobal->info("Gamestate initialized!");
// reset seed, so that clients can't predict any following random values
gs->getRandomGenerator().resetSeed();
getRandomGenerator().resetSeed();
for(auto & elem : gs->players)
{
@ -1373,10 +1373,10 @@ void CGameHandler::setPortalDwelling(const CGTownInstance * town, bool forced=fa
return;
}
auto dwelling = *RandomGeneratorUtil::nextItem(dwellings, gs->getRandomGenerator());
auto dwelling = *RandomGeneratorUtil::nextItem(dwellings, getRandomGenerator());
// for multi-creature dwellings like Golem Factory
auto creatureId = RandomGeneratorUtil::nextItem(dwelling->creatures, gs->getRandomGenerator())->second[0];
auto creatureId = RandomGeneratorUtil::nextItem(dwelling->creatures, getRandomGenerator())->second[0];
if(clear)
{
@ -1436,7 +1436,7 @@ void CGameHandler::newTurn()
}
else
{
int monthType = gs->getRandomGenerator().nextInt(99);
int monthType = getRandomGenerator().nextInt(99);
if(newMonth) //new month
{
if (monthType < 40) //double growth
@ -1444,13 +1444,13 @@ void CGameHandler::newTurn()
n.specialWeek = NewTurn::DOUBLE_GROWTH;
if (VLC->modh->settings.ALL_CREATURES_GET_DOUBLE_MONTHS)
{
std::pair<int, CreatureID> newMonster(54, VLC->creh->pickRandomMonster(gs->getRandomGenerator()));
std::pair<int, CreatureID> newMonster(54, VLC->creh->pickRandomMonster(getRandomGenerator()));
n.creatureid = newMonster.second;
}
else if(VLC->creh->doubledCreatures.size())
{
const std::vector<CreatureID> doubledCreatures (VLC->creh->doubledCreatures.begin(), VLC->creh->doubledCreatures.end());
n.creatureid = *RandomGeneratorUtil::nextItem(doubledCreatures, gs->getRandomGenerator());
n.creatureid = *RandomGeneratorUtil::nextItem(doubledCreatures, getRandomGenerator());
}
else
{
@ -1466,7 +1466,7 @@ void CGameHandler::newTurn()
if (monthType < 25)
{
n.specialWeek = NewTurn::BONUS_GROWTH; //+5
std::pair<int, CreatureID> newMonster(54, VLC->creh->pickRandomMonster(gs->getRandomGenerator()));
std::pair<int, CreatureID> newMonster(54, VLC->creh->pickRandomMonster(getRandomGenerator()));
//TODO do not pick neutrals
n.creatureid = newMonster.second;
}
@ -1515,7 +1515,7 @@ void CGameHandler::newTurn()
for (int j = 0; j < GameConstants::AVAILABLE_HEROES_PER_PLAYER; j++)
{
//first hero - native if possible, second hero -> any other class
if(CGHeroInstance *h = gs->hpool.pickHeroFor(j == 0, elem.first, getNativeTown(elem.first), pool, gs->getRandomGenerator(), banned))
if(CGHeroInstance *h = gs->hpool.pickHeroFor(j == 0, elem.first, getNativeTown(elem.first), pool, getRandomGenerator(), banned))
{
sah.hid[j] = h->subID;
h->initArmy(&sah.army[j]);
@ -1647,7 +1647,7 @@ void CGameHandler::newTurn()
{
SetAvailableArtifacts saa;
saa.id = -1;
pickAllowedArtsSet(saa.arts);
pickAllowedArtsSet(saa.arts, getRandomGenerator());
sendAndApply(&saa);
}
sendAndApply(&n);
@ -1691,12 +1691,12 @@ void CGameHandler::newTurn()
if (newMonth)
{
iw.text.addTxt(MetaString::ARRAY_TXT, (130));
iw.text.addReplacement(MetaString::ARRAY_TXT, gs->getRandomGenerator().nextInt(32, 41));
iw.text.addReplacement(MetaString::ARRAY_TXT, getRandomGenerator().nextInt(32, 41));
}
else
{
iw.text.addTxt(MetaString::ARRAY_TXT, (133));
iw.text.addReplacement(MetaString::ARRAY_TXT, gs->getRandomGenerator().nextInt(43, 57));
iw.text.addReplacement(MetaString::ARRAY_TXT, getRandomGenerator().nextInt(43, 57));
}
}
for (auto & elem : gs->players)
@ -3569,7 +3569,7 @@ bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, PlayerColor pl
const CGHeroInstance *newHero = nullptr;
if (theOtherHero) //on XXL maps all heroes can be imprisoned :(
{
newHero = gs->hpool.pickHeroFor(false, player, getNativeTown(player), pool, gs->getRandomGenerator(), theOtherHero->type->heroClass);
newHero = gs->hpool.pickHeroFor(false, player, getNativeTown(player), pool, getRandomGenerator(), theOtherHero->type->heroClass);
}
SetAvailableHeroes sah;
@ -3978,7 +3978,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
{
if(currentHP.at(attackedPart) != EWallState::DESTROYED && // this part can be hit
currentHP.at(attackedPart) != EWallState::NONE &&
gs->getRandomGenerator().nextInt(99) < getCatapultHitChance(attackedPart, sbi))//hit is successful
getRandomGenerator().nextInt(99) < getCatapultHitChance(attackedPart, sbi))//hit is successful
{
hitSuccessfull = true;
}
@ -3993,7 +3993,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
}
if (allowedTargets.empty())
break;
attackedPart = *RandomGeneratorUtil::nextItem(allowedTargets, gs->getRandomGenerator());
attackedPart = *RandomGeneratorUtil::nextItem(allowedTargets, getRandomGenerator());
}
}
while (!hitSuccessfull);
@ -4009,7 +4009,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
int dmgChance[] = { sbi.noDmg, sbi.oneDmg, sbi.twoDmg }; //dmgChance[i] - chance for doing i dmg when hit is successful
int dmgRand = gs->getRandomGenerator().nextInt(99);
int dmgRand = getRandomGenerator().nextInt(99);
//accumulating dmgChance
dmgChance[1] += dmgChance[0];
dmgChance[2] += dmgChance[1];
@ -4507,7 +4507,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
}
if (fearsomeCreature)
{
if (gs->getRandomGenerator().nextInt(99) < 10) //fixed 10%
if (getRandomGenerator().nextInt(99) < 10) //fixed 10%
{
bte.effect = Bonus::FEAR;
sendAndApply(&bte);
@ -4521,7 +4521,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
bool cast = false;
while (!bl.empty() && !cast)
{
auto bonus = *RandomGeneratorUtil::nextItem(bl, gs->getRandomGenerator());
auto bonus = *RandomGeneratorUtil::nextItem(bl, getRandomGenerator());
auto spellID = SpellID(bonus->subtype);
const CSpell * spell = SpellID(spellID).toSpell();
bl.remove_if([&bonus](Bonus * b){return b==bonus;});
@ -4636,7 +4636,7 @@ void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, c
bsa.damageAmount = damage;
bsa.stackAttacked = curStack->ID;
bsa.attackerID = -1;
curStack->prepareAttacked(bsa, gameState()->getRandomGenerator());
curStack->prepareAttacked(bsa, getRandomGenerator());
StacksInjured si;
si.stacks.push_back(bsa);
@ -5233,7 +5233,7 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
continue;
//check if spell should be cast (probability handling)
if(gs->getRandomGenerator().nextInt(99) >= chance)
if(getRandomGenerator().nextInt(99) >= chance)
continue;
//casting
@ -5317,7 +5317,7 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
TBonusListPtr acidBreath = attacker->getBonuses(Selector::type(Bonus::ACID_BREATH));
for(const Bonus *b : *acidBreath)
{
if (b->additionalInfo > gs->getRandomGenerator().nextInt(99))
if (b->additionalInfo > getRandomGenerator().nextInt(99))
acidDamage += b->val;
}
if (acidDamage)
@ -5629,7 +5629,7 @@ void CGameHandler::runBattle()
|| NBonus::hasOfType(gs->curB->battleGetFightingHero(1), Bonus::BLOCK_MORALE)) //checking if gs->curB->heroes have (or don't have) morale blocking bonuses)
)
{
if(gs->getRandomGenerator().nextInt(23) < -2 * nextStackMorale)
if(getRandomGenerator().nextInt(23) < -2 * nextStackMorale)
{
//unit loses its turn - empty freeze action
BattleAction ba;
@ -5701,7 +5701,7 @@ void CGameHandler::runBattle()
{
BattleAction attack;
attack.destinationTile = *RandomGeneratorUtil::nextItem(attackableBattleHexes,
gs->getRandomGenerator());
getRandomGenerator());
attack.actionType = Battle::CATAPULT;
attack.additionalInfo = 0;
attack.side = !next->attackerOwned;
@ -5806,7 +5806,7 @@ void CGameHandler::runBattle()
|| NBonus::hasOfType(gs->curB->battleGetFightingHero(1), Bonus::BLOCK_MORALE)) //checking if gs->curB->heroes have (or don't have) morale blocking bonuses
)
{
if(gs->getRandomGenerator().nextInt(23) < nextStackMorale) //this stack hasn't got morale this turn
if(getRandomGenerator().nextInt(23) < nextStackMorale) //this stack hasn't got morale this turn
{
BattleTriggerEffect bte;
@ -6247,7 +6247,7 @@ void ServerSpellCastEnvironment::sendAndApply(CPackForClient * info) const
CRandomGenerator & ServerSpellCastEnvironment::getRandomGenerator() const
{
return gh->gameState()->getRandomGenerator();
return gh->getRandomGenerator();
}
void ServerSpellCastEnvironment::complain(const std::string& problem) const

View File

@ -247,6 +247,10 @@ public:
template <typename Handler> void serialize(Handler &h, const int version)
{
h & QID & states & finishingBattle;
if(version >= 760)
{
h & rand;
}
}
void sendMessageToAll(const std::string &message);