1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-30 09:45:36 +00:00

Merge branch 'refactor/prng-2' into 'master'

Save random state and refactor usage of generators

See merge request OpenMW/openmw!1715
This commit is contained in:
psi29a 2022-03-23 09:50:54 +00:00
commit 6d55317d57
51 changed files with 329 additions and 154 deletions

View file

@ -9,6 +9,7 @@
#include <deque>
#include <components/esm3/cellid.hpp>
#include <components/misc/rng.hpp>
#include <osg/Timer>
@ -111,6 +112,9 @@ namespace MWBase
virtual ~World() {}
virtual void setRandomSeed(uint32_t seed) = 0;
///< \param seed The seed used when starting a new game.
virtual void startNewGame (bool bypass) = 0;
///< \param bypass Bypass regular game start.
@ -658,6 +662,8 @@ namespace MWBase
virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0;
virtual std::vector<MWWorld::Ptr> getAll(const std::string& id) = 0;
virtual Misc::Rng::Generator& getPrng() = 0;
};
}

View file

@ -120,7 +120,8 @@ namespace MWClass
if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
{
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfActivator");
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfActivator", prng);
std::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
if(sound) action->setSound(sound->mId);
@ -156,6 +157,7 @@ namespace MWClass
int type = getSndGenTypeFromName(name);
std::vector<const ESM::SoundGenerator*> fallbacksounds;
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if (!creatureId.empty())
{
std::vector<const ESM::SoundGenerator*> sounds;
@ -168,9 +170,9 @@ namespace MWClass
}
if (!sounds.empty())
return sounds[Misc::Rng::rollDice(sounds.size())]->mSound;
return sounds[Misc::Rng::rollDice(sounds.size(), prng)]->mSound;
if (!fallbacksounds.empty())
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound;
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size(), prng)]->mSound;
}
else
{
@ -180,7 +182,7 @@ namespace MWClass
fallbacksounds.push_back(&*sound);
if (!fallbacksounds.empty())
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound;
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size(), prng)]->mSound;
}
return std::string();

View file

@ -56,7 +56,8 @@ namespace MWClass
if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
{
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfItem");
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfItem", prng);
std::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
if(sound) action->setSound(sound->mId);

View file

@ -32,7 +32,8 @@ namespace MWClass
{
ContainerCustomData::ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell)
{
unsigned int seed = Misc::Rng::rollDice(std::numeric_limits<int>::max());
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
unsigned int seed = Misc::Rng::rollDice(std::numeric_limits<int>::max(), prng);
// setting ownership not needed, since taking items from a container inherits the
// container's owner automatically
mStore.fillNonRandom(container.mInventory, "", seed);
@ -139,7 +140,8 @@ namespace MWClass
if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
{
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfContainer");
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfContainer", prng);
std::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
if(sound) action->setSound(sound->mId);

View file

@ -159,7 +159,8 @@ namespace MWClass
resetter.mPtr = {};
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId(), prng);
if (hasInventory)
getInventoryStore(ptr).autoEquip(ptr);
@ -264,8 +265,8 @@ namespace MWClass
osg::Vec3f hitPosition (result.second);
float hitchance = MWMechanics::getHitChance(ptr, victim, ref->mBase->mData.mCombat);
if(Misc::Rng::roll0to99() >= hitchance)
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if(Misc::Rng::roll0to99(prng) >= hitchance)
{
victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, osg::Vec3f(), false);
MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
@ -392,7 +393,8 @@ namespace MWClass
float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->mValue.getFloat();
float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified()
* getGmst().iKnockDownOddsMult->mValue.getInteger() * 0.01f + getGmst().iKnockDownOddsBase->mValue.getInteger();
if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99())
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99(prng))
stats.setKnockedDown(true);
else
stats.setHitRecovery(true); // Is this supposed to always occur?
@ -429,7 +431,8 @@ namespace MWClass
if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
{
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfCreature");
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfCreature", prng);
std::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
if(sound) action->setSound(sound->mId);
@ -642,10 +645,11 @@ namespace MWClass
}
}
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if (!sounds.empty())
return sounds[Misc::Rng::rollDice(sounds.size())]->mSound;
return sounds[Misc::Rng::rollDice(sounds.size(), prng)]->mSound;
if (!fallbacksounds.empty())
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound;
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size(), prng)]->mSound;
return std::string();
}

View file

@ -127,7 +127,8 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::CreatureLevList> *ref =
ptr.get<ESM::CreatureLevList>();
std::string id = MWMechanics::getLevelledItem(ref->mBase, true);
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
std::string id = MWMechanics::getLevelledItem(ref->mBase, true, prng);
if (!id.empty())
{

View file

@ -394,7 +394,8 @@ namespace MWClass
// inventory
// setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items
getInventoryStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
getInventoryStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId(), prng);
getInventoryStore(ptr).autoEquip(ptr);
}
@ -584,7 +585,7 @@ namespace MWClass
float hitchance = MWMechanics::getHitChance(ptr, victim, getSkill(ptr, weapskill));
if (Misc::Rng::roll0to99() >= hitchance)
if (Misc::Rng::roll0to99(world->getPrng()) >= hitchance)
{
othercls.onHit(victim, 0.0f, false, weapon, ptr, osg::Vec3f(), false);
MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
@ -726,15 +727,16 @@ namespace MWClass
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const GMST& gmst = getGmst();
int chance = store.get<ESM::GameSetting>().find("iVoiceHitOdds")->mValue.getInteger();
if (Misc::Rng::roll0to99() < chance)
int chance = store.get<ESM::GameSetting>().find("iVoiceHitOdds")->mValue.getInteger();
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if (Misc::Rng::roll0to99(prng) < chance)
MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
// Check for knockdown
float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->mValue.getFloat();
float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified()
* gmst.iKnockDownOddsMult->mValue.getInteger() * 0.01f + gmst.iKnockDownOddsBase->mValue.getInteger();
if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99())
if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99(prng))
stats.setKnockedDown(true);
else
stats.setHitRecovery(true); // Is this supposed to always occur?
@ -757,7 +759,7 @@ namespace MWClass
MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_RightPauldron,
MWWorld::InventoryStore::Slot_LeftGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet
};
int hitslot = hitslots[Misc::Rng::rollDice(20)];
int hitslot = hitslots[Misc::Rng::rollDice(20, prng)];
float unmitigatedDamage = damage;
float x = damage / (damage + getArmorRating(ptr));
@ -773,7 +775,7 @@ namespace MWClass
// If there's no item in the carried left slot or if it is not a shield redistribute the hit.
if (!hasArmor && hitslot == MWWorld::InventoryStore::Slot_CarriedLeft)
{
if (Misc::Rng::rollDice(2) == 0)
if (Misc::Rng::rollDice(2, prng) == 0)
hitslot = MWWorld::InventoryStore::Slot_Cuirass;
else
hitslot = MWWorld::InventoryStore::Slot_LeftPauldron;
@ -865,7 +867,8 @@ namespace MWClass
if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
{
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfNPC");
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfNPC", prng);
std::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
if(sound) action->setSound(sound->mId);

View file

@ -87,7 +87,8 @@ namespace MWGui
std::set<int> skills;
for (int day=0; day<mDays; ++day)
{
int skill = Misc::Rng::rollDice(ESM::Skill::Length);
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
int skill = Misc::Rng::rollDice(ESM::Skill::Length, prng);
skills.insert(skill);
MWMechanics::SkillValue& value = player.getClass().getNpcStats(player).getSkill(skill);

View file

@ -24,13 +24,13 @@ namespace MWGui
float chance = player.getClass().getSkill(player, ESM::Skill::Sneak);
mSourceModel->update();
// build list of items that player is unable to find when attempts to pickpocket.
if (hideItems)
{
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
for (size_t i = 0; i<mSourceModel->getItemCount(); ++i)
{
if (Misc::Rng::roll0to99() > chance)
if (Misc::Rng::roll0to99(prng) > chance)
mHiddenItems.push_back(mSourceModel->getItem(i));
}
}

View file

@ -191,7 +191,7 @@ namespace MWGui
if (!region->mSleepList.empty())
{
// figure out if player will be woken while sleeping
int x = Misc::Rng::rollDice(hoursToWait);
int x = Misc::Rng::rollDice(hoursToWait, world->getPrng());
float fSleepRandMod = world->getStore().get<ESM::GameSetting>().find("fSleepRandMod")->mValue.getFloat();
if (x < fSleepRandMod * hoursToWait)
{

View file

@ -60,6 +60,11 @@ namespace MWMechanics
mIsTurningToPlayer = turning;
}
Misc::TimerStatus Actor::updateEngageCombatTimer(float duration)
{
return mEngageCombat.update(duration, MWBase::Environment::get().getWorld()->getPrng());
}
void Actor::setPositionAdjusted(bool adjusted)
{
mPositionAdjusted = adjusted;

View file

@ -43,10 +43,7 @@ namespace MWMechanics
bool isTurningToPlayer() const;
void setTurningToPlayer(bool turning);
Misc::TimerStatus updateEngageCombatTimer(float duration)
{
return mEngageCombat.update(duration);
}
Misc::TimerStatus updateEngageCombatTimer(float duration);
void setPositionAdjusted(bool adjusted);
bool getPositionAdjusted() const;
@ -57,7 +54,7 @@ namespace MWMechanics
float mTargetAngleRadians{0.f};
GreetingState mGreetingState{Greet_None};
bool mIsTurningToPlayer{false};
Misc::DeviatingPeriodicTimer mEngageCombat{1.0f, 0.25f, Misc::Rng::deviate(0, 0.25f)};
Misc::DeviatingPeriodicTimer mEngageCombat{1.0f, 0.25f, Misc::Rng::deviate(0, 0.25f, MWBase::Environment::get().getWorld()->getPrng())};
bool mPositionAdjusted;
};

View file

@ -305,7 +305,7 @@ namespace MWMechanics
// We chose to use the chance MW would have when run at 60 FPS with the default value of the GMST.
const float delta = MWBase::Environment::get().getFrameDuration() * 6.f;
static const float fVoiceIdleOdds = world->getStore().get<ESM::GameSetting>().find("fVoiceIdleOdds")->mValue.getFloat();
if (Misc::Rng::rollProbability() * 10000.f < fVoiceIdleOdds * delta && world->getLOS(getPlayer(), actor))
if (Misc::Rng::rollProbability(world->getPrng()) * 10000.f < fVoiceIdleOdds * delta && world->getLOS(getPlayer(), actor))
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
}

View file

@ -80,7 +80,8 @@ bool MWMechanics::AiAvoidDoor::isStuck(const osg::Vec3f& actorPos) const
void MWMechanics::AiAvoidDoor::adjustDirection()
{
mDirection = Misc::Rng::rollDice(MAX_DIRECTIONS);
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
mDirection = Misc::Rng::rollDice(MAX_DIRECTIONS, prng);
}
float MWMechanics::AiAvoidDoor::getAdjustedAngle() const

View file

@ -370,7 +370,8 @@ namespace MWMechanics
if (!points.empty())
{
ESM::Pathgrid::Point dest = points[Misc::Rng::rollDice(points.size())];
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
ESM::Pathgrid::Point dest = points[Misc::Rng::rollDice(points.size(), prng)];
coords.toWorld(dest);
state = AiCombatStorage::FleeState_RunToDestination;
@ -464,8 +465,38 @@ namespace MWMechanics
sequence.mPackages.push_back(std::move(package));
}
AiCombatStorage::AiCombatStorage() :
mAttackCooldown(0.0f),
mReaction(MWBase::Environment::get().getWorld()->getPrng()),
mTimerCombatMove(0.0f),
mReadyToAttack(false),
mAttack(false),
mAttackRange(0.0f),
mCombatMove(false),
mRotateMove(false),
mLastTargetPos(0, 0, 0),
mCell(nullptr),
mCurrentAction(),
mActionCooldown(0.0f),
mStrength(),
mForceNoShortcut(false),
mShortcutFailPos(),
mMovement(),
mFleeState(FleeState_None),
mLOS(false),
mUpdateLOSTimer(0.0f),
mFleeBlindRunTimer(0.0f),
mUseCustomDestination(false),
mCustomDestination()
{
}
void AiCombatStorage::startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target)
{
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
// get the range of the target's weapon
MWWorld::Ptr targetWeapon = MWWorld::Ptr();
const MWWorld::Class& targetClass = target.getClass();
@ -483,7 +514,7 @@ namespace MWMechanics
if (mMovement.mPosition[0] || mMovement.mPosition[1])
{
mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability();
mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(prng);
mCombatMove = true;
}
else if (isDistantCombat)
@ -537,11 +568,11 @@ namespace MWMechanics
// if actor is within range of target's weapon.
if (std::abs(angleToTarget) > osg::PI / 4)
moveDuration = 0.2f;
else if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25)
moveDuration = 0.1f + 0.1f * Misc::Rng::rollClosedProbability();
else if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability(prng) < 0.25)
moveDuration = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(prng);
if (moveDuration > 0)
{
mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right
mMovement.mPosition[0] = Misc::Rng::rollProbability(prng) < 0.5 ? 1.0f : -1.0f; // to the left/right
mTimerCombatMove = moveDuration;
mCombatMove = true;
}
@ -580,7 +611,8 @@ namespace MWMechanics
if (!distantCombat)
characterController.setAIAttackType(chooseBestAttack(weapon));
mStrength = Misc::Rng::rollClosedProbability();
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
mStrength = Misc::Rng::rollClosedProbability(prng);
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
@ -592,11 +624,11 @@ namespace MWMechanics
// Say a provoking combat phrase
const int iVoiceAttackOdds = store.get<ESM::GameSetting>().find("iVoiceAttackOdds")->mValue.getInteger();
if (Misc::Rng::roll0to99() < iVoiceAttackOdds)
if (Misc::Rng::roll0to99(prng) < iVoiceAttackOdds)
{
MWBase::Environment::get().getDialogueManager()->say(actor, "attack");
}
mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(), baseDelay + 0.9);
mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(prng), baseDelay + 0.9);
}
else
mAttackCooldown -= AI_REACTION_TIME;
@ -657,7 +689,8 @@ std::string chooseBestAttack(const ESM::Weapon* weapon)
int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2;
int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2;
float roll = Misc::Rng::rollClosedProbability() * (slash + chop + thrust);
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
float roll = Misc::Rng::rollClosedProbability(prng) * (slash + chop + thrust);
if(roll <= slash)
attackType = "slash";
else if(roll <= (slash + thrust))

View file

@ -59,29 +59,7 @@ namespace MWMechanics
bool mUseCustomDestination;
osg::Vec3f mCustomDestination;
AiCombatStorage():
mAttackCooldown(0.0f),
mTimerCombatMove(0.0f),
mReadyToAttack(false),
mAttack(false),
mAttackRange(0.0f),
mCombatMove(false),
mRotateMove(false),
mLastTargetPos(0,0,0),
mCell(nullptr),
mCurrentAction(),
mActionCooldown(0.0f),
mStrength(),
mForceNoShortcut(false),
mShortcutFailPos(),
mMovement(),
mFleeState(FleeState_None),
mLOS(false),
mUpdateLOSTimer(0.0f),
mFleeBlindRunTimer(0.0f),
mUseCustomDestination(false),
mCustomDestination()
{}
AiCombatStorage();
void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target);
void updateCombatMove(float duration);

View file

@ -44,6 +44,7 @@ namespace
MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) :
mTypeId(typeId),
mOptions(options),
mReaction(MWBase::Environment::get().getWorld()->getPrng()),
mTargetActorRefId(""),
mTargetActorId(-1),
mRotateOnTheRunChecks(0),

View file

@ -13,13 +13,20 @@ namespace MWMechanics
public:
static constexpr float sDeviation = 0.1f;
Misc::TimerStatus update(float duration) { return mImpl.update(duration); }
AiReactionTimer(Misc::Rng::Generator& prng)
: mPrng{ prng }
, mImpl{ AI_REACTION_TIME, sDeviation, Misc::Rng::deviate(0, sDeviation, prng) }
{
}
void reset() { mImpl.reset(Misc::Rng::deviate(0, sDeviation)); }
Misc::TimerStatus update(float duration) { return mImpl.update(duration, mPrng); }
void reset() { mImpl.reset(Misc::Rng::deviate(0, sDeviation, mPrng)); }
private:
Misc::DeviatingPeriodicTimer mImpl {AI_REACTION_TIME, sDeviation,
Misc::Rng::deviate(0, sDeviation)};
Misc::Rng::Generator& mPrng;
Misc::DeviatingPeriodicTimer mImpl;
};
}

View file

@ -36,11 +36,13 @@ namespace MWMechanics
{
AiTravel::AiTravel(float x, float y, float z, bool repeat, AiTravel*)
: TypedAiPackage<AiTravel>(repeat), mX(x), mY(y), mZ(z), mHidden(false)
, mDestinationCheck(MWBase::Environment::get().getWorld()->getPrng())
{
}
AiTravel::AiTravel(float x, float y, float z, AiInternalTravel* derived)
: TypedAiPackage<AiTravel>(derived), mX(x), mY(y), mZ(z), mHidden(true)
, mDestinationCheck(MWBase::Environment::get().getWorld()->getPrng())
{
}
@ -51,6 +53,7 @@ namespace MWMechanics
AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel)
: TypedAiPackage<AiTravel>(travel->mRepeat), mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(false)
, mDestinationCheck(MWBase::Environment::get().getWorld()->getPrng())
{
// Hidden ESM::AiSequence::AiTravel package should be converted into MWMechanics::AiInternalTravel type
assert(!travel->mHidden);

View file

@ -59,7 +59,8 @@ namespace MWMechanics
osg::Vec3f getRandomPointAround(const osg::Vec3f& position, const float distance)
{
const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * osg::PI;
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
const float randomDirection = Misc::Rng::rollClosedProbability(prng) * 2.0f * osg::PI;
osg::Matrixf rotation;
rotation.makeRotate(randomDirection, osg::Vec3f(0.0, 0.0, 1.0));
return position + osg::Vec3f(distance, 0.0, 0.0) * rotation;
@ -102,6 +103,22 @@ namespace MWMechanics
{
return std::vector<unsigned char>(std::begin(idle), std::end(idle));
}
}
AiWanderStorage::AiWanderStorage() :
mReaction(MWBase::Environment::get().getWorld()->getPrng()),
mState(Wander_ChooseAction),
mIsWanderingManually(false),
mCanWanderAlongPathGrid(true),
mIdleAnimation(0),
mBadIdles(),
mPopulateAvailableNodes(true),
mAllowedNodes(),
mTrimCurrentNode(false),
mCheckIdlePositionTimer(0),
mStuckCount(0)
{
}
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat):
@ -250,9 +267,10 @@ namespace MWMechanics
getAllowedNodes(actor, actor.getCell()->getCell(), storage);
}
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if (canActorMoveByZAxis(actor) && mDistance > 0) {
// Typically want to idle for a short time before the next wander
if (Misc::Rng::rollDice(100) >= 92 && storage.mState != AiWanderStorage::Wander_Walking) {
if (Misc::Rng::rollDice(100, prng) >= 92 && storage.mState != AiWanderStorage::Wander_Walking) {
wanderNearStart(actor, storage, mDistance);
}
@ -262,7 +280,7 @@ namespace MWMechanics
// randomly idle or wander near spawn point
else if(storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually) {
// Typically want to idle for a short time before the next wander
if (Misc::Rng::rollDice(100) >= 96) {
if (Misc::Rng::rollDice(100, prng) >= 96) {
wanderNearStart(actor, storage, mDistance);
} else {
storage.setState(AiWanderStorage::Wander_IdleNow);
@ -330,10 +348,12 @@ namespace MWMechanics
const auto navigator = world->getNavigator();
const auto navigatorFlags = getNavigatorFlags(actor);
const auto areaCosts = getAreaCosts(actor);
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
do {
// Determine a random location within radius of original position
const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability() * 0.8f) * wanderDistance;
const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability(prng) * 0.8f) * wanderDistance;
if (!isWaterCreature && !isFlyingCreature)
{
// findRandomPointAroundCircle uses wanderDistance as limit for random and not as exact distance
@ -544,7 +564,8 @@ namespace MWMechanics
void AiWander::setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos)
{
unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size());
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng);
ESM::Pathgrid::Point dest(storage.mAllowedNodes[randNode]);
ToWorldCoordinates(dest, actor.getCell()->getCell());
@ -650,11 +671,11 @@ namespace MWMechanics
for(unsigned int counter = 0; counter < mIdle.size(); counter++)
{
static float fIdleChanceMultiplier = MWBase::Environment::get().getWorld()->getStore()
.get<ESM::GameSetting>().find("fIdleChanceMultiplier")->mValue.getFloat();
MWBase::World* world = MWBase::Environment::get().getWorld();
static float fIdleChanceMultiplier = world->getStore().get<ESM::GameSetting>().find("fIdleChanceMultiplier")->mValue.getFloat();
unsigned short idleChance = static_cast<unsigned short>(fIdleChanceMultiplier * mIdle[counter]);
unsigned short randSelect = (int)(Misc::Rng::rollProbability() * int(100 / fIdleChanceMultiplier));
unsigned short randSelect = (int)(Misc::Rng::rollProbability(world->getPrng()) * int(100 / fIdleChanceMultiplier));
if(randSelect < idleChance && randSelect > idleRoll)
{
selectedAnimation = counter + GroupIndex_MinIdle;
@ -678,7 +699,8 @@ namespace MWMechanics
if (storage.mAllowedNodes.empty())
return;
int index = Misc::Rng::rollDice(storage.mAllowedNodes.size());
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
int index = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng);
ESM::Pathgrid::Point dest = storage.mAllowedNodes[index];
ESM::Pathgrid::Point worldDest = dest;
ToWorldCoordinates(worldDest, actor.getCell()->getCell());
@ -700,7 +722,7 @@ namespace MWMechanics
// AI will try to move the NPC towards every neighboring node until suitable place will be found
for (int i = 0; i < initialSize; i++)
{
int randomIndex = Misc::Rng::rollDice(points.size());
int randomIndex = Misc::Rng::rollDice(points.size(), prng);
ESM::Pathgrid::Point connDest = points[randomIndex];
// add an offset towards random neighboring node

View file

@ -55,18 +55,7 @@ namespace MWMechanics
float mCheckIdlePositionTimer;
int mStuckCount;
AiWanderStorage():
mState(Wander_ChooseAction),
mIsWanderingManually(false),
mCanWanderAlongPathGrid(true),
mIdleAnimation(0),
mBadIdles(),
mPopulateAvailableNodes(true),
mAllowedNodes(),
mTrimCurrentNode(false),
mCheckIdlePositionTimer(0),
mStuckCount(0)
{};
AiWanderStorage();
void setState(const WanderState wanderState, const bool isManualWander = false)
{

View file

@ -289,7 +289,8 @@ void MWMechanics::Alchemy::addPotion (const std::string& name)
newRecord.mName = name;
int index = Misc::Rng::rollDice(6);
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
int index = Misc::Rng::rollDice(6, prng);
assert (index>=0 && index<6);
static const char *meshes[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" };
@ -527,8 +528,8 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::createSingle ()
removeIngredients();
return Result_RandomFailure;
}
if (getAlchemyFactor() < Misc::Rng::roll0to99())
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if (getAlchemyFactor() < Misc::Rng::roll0to99(prng))
{
removeIngredients();
return Result_RandomFailure;

View file

@ -193,11 +193,13 @@ public:
std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const
{
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
int numAnims=0;
while (mAnimation->hasAnimation(prefix + std::to_string(numAnims+1)))
++numAnims;
int roll = Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims]
int roll = Misc::Rng::rollDice(numAnims, prng) + 1; // [1, numAnims]
if (num)
*num = roll;
return prefix + std::to_string(roll);
@ -209,12 +211,13 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle)
bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown();
bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock();
bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr);
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if(mHitState == CharState_None)
{
if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0
|| mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0))
{
mTimeUntilWake = Misc::Rng::rollClosedProbability() * 2 + 1; // Wake up after 1 to 3 seconds
mTimeUntilWake = Misc::Rng::rollClosedProbability(prng) * 2 + 1; // Wake up after 1 to 3 seconds
if (isSwimming && mAnimation->hasAnimation("swimknockout"))
{
mHitState = CharState_SwimKnockOut;
@ -678,7 +681,8 @@ void CharacterController::refreshIdleAnims(const std::string& weapShortGroup, Ch
// play until the Loop Stop key 2 to 5 times, then play until the Stop key
// this replicates original engine behavior for the "Idle1h" 1st-person animation
numLoops = 1 + Misc::Rng::rollDice(4);
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
numLoops = 1 + Misc::Rng::rollDice(4, prng);
}
}
@ -1131,6 +1135,8 @@ bool CharacterController::updateCarriedLeftVisible(const int weaptype) const
bool CharacterController::updateState(CharacterState idle)
{
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
const MWWorld::Class &cls = mPtr.getClass();
CreatureStats &stats = cls.getCreatureStats(mPtr);
int weaptype = ESM::Weapon::None;
@ -1288,7 +1294,7 @@ bool CharacterController::updateState(CharacterState idle)
if(isWerewolf)
{
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfEquip");
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfEquip", prng);
if(sound)
{
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
@ -1563,7 +1569,7 @@ bool CharacterController::updateState(CharacterState idle)
mUpperBodyState = UpperCharState_StartToMinAttack;
if (isRandomAttackAnimation(mCurrentWeapon))
{
mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability());
mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng));
playSwishSound(0.0f);
}
}
@ -1596,7 +1602,7 @@ bool CharacterController::updateState(CharacterState idle)
// most creatures don't actually have an attack wind-up animation, so use a uniform random value
// (even some creatures that can use weapons don't have a wind-up animation either, e.g. Rieklings)
// Note: vanilla MW uses a random value for *all* non-player actors, but we probably don't need to go that far.
attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability());
attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng));
}
if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown)
@ -1606,7 +1612,7 @@ bool CharacterController::updateState(CharacterState idle)
if(isWerewolf)
{
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfSwing");
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfSwing", prng);
if(sound)
sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f);
}
@ -2771,7 +2777,8 @@ void CharacterController::setAIAttackType(const std::string& attackType)
void CharacterController::setAttackTypeRandomly(std::string& attackType)
{
float random = Misc::Rng::rollProbability();
MWBase::World* world = MWBase::Environment::get().getWorld();
float random = Misc::Rng::rollProbability(world->getPrng());
if (random >= 2/3.f)
attackType = "thrust";
else if (random >= 1/3.f)

View file

@ -117,7 +117,8 @@ namespace MWMechanics
const int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger();
int x = std::clamp<int>(blockerTerm - attackerTerm, iBlockMinChance, iBlockMaxChance);
if (Misc::Rng::roll0to99() < x)
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if (Misc::Rng::roll0to99(prng) < x)
{
// Reduce shield durability by incoming damage
int shieldhealth = shield->getClass().getItemHealth(*shield);
@ -212,7 +213,7 @@ namespace MWMechanics
int skillValue = attacker.getClass().getSkill(attacker, weaponSkill);
if (Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue))
if (Misc::Rng::roll0to99(world->getPrng()) >= getHitChance(attacker, victim, skillValue))
{
victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false);
MWMechanics::reduceWeaponCondition(damage, false, weapon, attacker);
@ -262,7 +263,7 @@ namespace MWMechanics
if (victim != getPlayer() && !appliedEnchantment)
{
float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->mValue.getFloat();
if (Misc::Rng::rollProbability() < fProjectileThrownStoreChance / 100.f)
if (Misc::Rng::rollProbability(world->getPrng()) < fProjectileThrownStoreChance / 100.f)
victim.getClass().getContainerStore(victim).add(projectile, 1, victim);
}
@ -315,6 +316,7 @@ namespace MWMechanics
bool godmode = attacker == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
if (godmode)
return;
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
for (int i=0; i<3; ++i)
{
float magnitude = victim.getClass().getCreatureStats(victim).getMagicEffects().get(ESM::MagicEffect::FireShield+i).getMagnitude();
@ -334,7 +336,7 @@ namespace MWMechanics
saveTerm *= 1.25f * normalisedFatigue;
float x = std::max(0.f, saveTerm - Misc::Rng::roll0to99());
float x = std::max(0.f, saveTerm - Misc::Rng::roll0to99(prng));
int element = ESM::MagicEffect::FireDamage;
if (i == 1)
@ -444,7 +446,8 @@ namespace MWMechanics
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
if(isWerewolf)
{
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfHit");
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfHit", prng);
if(sound)
sndMgr->playSound3D(victim, sound->mId, 1.0f, 1.0f);
}

View file

@ -52,7 +52,8 @@ namespace MWMechanics
continue;
int x = static_cast<int>(fDiseaseXferChance * 100 * resist);
if (Misc::Rng::rollDice(10000) < x)
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if (Misc::Rng::rollDice(10000, prng) < x)
{
// Contracted disease!
actor.getClass().getCreatureStats(actor).getSpells().add(spell);

View file

@ -76,7 +76,8 @@ namespace MWMechanics
if(mSelfEnchanting)
{
if(getEnchantChance() <= (Misc::Rng::roll0to99()))
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if(getEnchantChance() <= (Misc::Rng::roll0to99(prng)))
return false;
mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2);

View file

@ -19,7 +19,7 @@ namespace MWMechanics
{
/// @return ID of resulting item, or empty if none
inline std::string getLevelledItem (const ESM::LevelledListBase* levItem, bool creature, Misc::Rng::Generator& prng = Misc::Rng::getGenerator())
inline std::string getLevelledItem (const ESM::LevelledListBase* levItem, bool creature, Misc::Rng::Generator& prng)
{
const std::vector<ESM::LevelledListBase::LevelItem>& items = levItem->mList;

View file

@ -651,7 +651,8 @@ namespace MWMechanics
float x = 0;
float y = 0;
int roll = Misc::Rng::roll0to99();
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
int roll = Misc::Rng::roll0to99(prng);
if (type == PT_Admire)
{
@ -1570,8 +1571,8 @@ namespace MWMechanics
}
float target = x - y;
return (Misc::Rng::roll0to99() >= target);
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
return (Misc::Rng::roll0to99(prng) >= target);
}
void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target)

View file

@ -41,7 +41,8 @@ namespace MWMechanics
int iPickMaxChance = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("iPickMaxChance")->mValue.getInteger();
int roll = Misc::Rng::roll0to99();
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
int roll = Misc::Rng::roll0to99(prng);
if (t < pcSneak / iPickMinChance)
{
return (roll > int(pcSneak / iPickMinChance));

View file

@ -49,7 +49,8 @@ bool rechargeItem(const MWWorld::Ptr &item, const MWWorld::Ptr &gem)
intelligenceTerm = 1;
float x = (player.getClass().getSkill(player, ESM::Skill::Enchant) + intelligenceTerm + luckTerm) * stats.getFatigueTerm();
int roll = Misc::Rng::roll0to99();
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
int roll = Misc::Rng::roll0to99(prng);
if (roll < x)
{
std::string soul = gem.getCellRef().getSoul();

View file

@ -44,7 +44,8 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair)
float x = (0.1f * pcStrength + 0.1f * pcLuck + armorerSkill) * fatigueTerm;
int roll = Misc::Rng::roll0to99();
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
int roll = Misc::Rng::roll0to99(prng);
if (roll <= x)
{
int y = static_cast<int>(fRepairAmountMult * toolQuality * roll);

View file

@ -54,7 +54,8 @@ namespace MWMechanics
resultMessage = "#{sLockImpossible}";
else
{
if (Misc::Rng::roll0to99() <= x)
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if (Misc::Rng::roll0to99(prng) <= x)
{
lock.getCellRef().unlock();
resultMessage = "#{sLockSuccess}";
@ -98,7 +99,8 @@ namespace MWMechanics
resultMessage = "#{sTrapImpossible}";
else
{
if (Misc::Rng::roll0to99() <= x)
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if (Misc::Rng::roll0to99(prng) <= x)
{
trap.getCellRef().setTrap("");

View file

@ -87,7 +87,8 @@ namespace MWMechanics
: ESM::MagicEffect::ResistBlightDisease;
float x = target.getClass().getCreatureStats(target).getMagicEffects().get(requiredResistance).getMagnitude();
if (Misc::Rng::roll0to99() <= x)
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if (Misc::Rng::roll0to99(prng) <= x)
{
// Fully resisted, show message
if (target == getPlayer())
@ -339,7 +340,8 @@ namespace MWMechanics
// Check success
float successChance = getSpellSuccessChance(spell, mCaster, nullptr, true, false);
if (Misc::Rng::roll0to99() >= successChance)
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if (Misc::Rng::roll0to99(prng) >= successChance)
{
if (mCaster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}");
@ -403,7 +405,8 @@ namespace MWMechanics
+ 0.1f * creatureStats.getAttribute (ESM::Attribute::Luck).getModified())
* creatureStats.getFatigueTerm();
int roll = Misc::Rng::roll0to99();
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
int roll = Misc::Rng::roll0to99(prng);
if (roll > x)
{
// "X has no effect on you"

View file

@ -35,7 +35,8 @@ namespace
{
if(effect.mMinMagnitude == effect.mMaxMagnitude)
return effect.mMinMagnitude;
return effect.mMinMagnitude + Misc::Rng::rollDice(effect.mMaxMagnitude - effect.mMinMagnitude + 1);
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
return effect.mMinMagnitude + Misc::Rng::rollDice(effect.mMaxMagnitude - effect.mMinMagnitude + 1, prng);
}
void modifyAiSetting(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, ESM::MagicEffect::Effects creatureEffect, MWMechanics::CreatureStats::AiSetting setting, float magnitude, bool& invalid)
@ -305,6 +306,7 @@ namespace
bool canAbsorb = !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_SpellAbsorption) && magnitudes.get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f;
if(canReflect || canAbsorb)
{
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
for(const auto& activeParam : stats.getActiveSpells())
{
for(const auto& activeEffect : activeParam.getEffects())
@ -313,14 +315,14 @@ namespace
continue;
if(activeEffect.mEffectId == ESM::MagicEffect::Reflect)
{
if(canReflect && Misc::Rng::roll0to99() < activeEffect.mMagnitude)
if(canReflect && Misc::Rng::roll0to99(prng) < activeEffect.mMagnitude)
{
return MWMechanics::MagicApplicationResult::REFLECTED;
}
}
else if(activeEffect.mEffectId == ESM::MagicEffect::SpellAbsorption)
{
if(canAbsorb && Misc::Rng::roll0to99() < activeEffect.mMagnitude)
if(canAbsorb && Misc::Rng::roll0to99(prng) < activeEffect.mMagnitude)
{
absorbSpell(spellParams.getId(), caster, target);
return MWMechanics::MagicApplicationResult::REMOVED;
@ -405,8 +407,11 @@ void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, co
if(params.getType() == ESM::ActiveSpells::Type_Temporary)
{
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(params.getId());
if(spell && spell->mData.mType == ESM::Spell::ST_Spell)
return Misc::Rng::roll0to99() < magnitude;
if (spell && spell->mData.mType == ESM::Spell::ST_Spell)
{
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
return Misc::Rng::roll0to99(prng) < magnitude;
}
}
return false;
}, target);

View file

@ -51,7 +51,8 @@ namespace MWMechanics
if (castChance > 0)
x *= 50 / castChance;
float roll = Misc::Rng::rollClosedProbability() * 100;
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
float roll = Misc::Rng::rollClosedProbability(prng) * 100;
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
roll -= resistance;

View file

@ -57,7 +57,8 @@ namespace MWMechanics
+ gmst.find("fBargainOfferBase")->mValue.getFloat()
+ int(pcTerm - npcTerm);
int roll = Misc::Rng::rollDice(100) + 1;
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
int roll = Misc::Rng::rollDice(100, prng) + 1;
// reject if roll fails
// (or if player tries to buy things and get money)

View file

@ -61,7 +61,8 @@ namespace
}
else
{
std::string itemId = MWMechanics::getLevelledItem(itemPtr.get<ESM::ItemLevList>()->mBase, false);
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
std::string itemId = MWMechanics::getLevelledItem(itemPtr.get<ESM::ItemLevList>()->mBase, false, prng);
if (itemId.empty())
return;
MWWorld::ManualRef manualRef(MWBase::Environment::get().getWorld()->getStore(), itemId, 1);

View file

@ -111,7 +111,8 @@ namespace MWScript
throw std::runtime_error (
"random: argument out of range (Don't be so negative!)");
runtime.push (static_cast<Interpreter::Type_Float>(::Misc::Rng::rollDice(limit))); // [o, limit)
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
runtime.push (static_cast<Interpreter::Type_Float>(::Misc::Rng::rollDice(limit, prng))); // [o, limit)
}
};

View file

@ -466,6 +466,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
case ESM::REC_LEVI:
case ESM::REC_CREA:
case ESM::REC_CONT:
case ESM::REC_RAND:
MWBase::Environment::get().getWorld()->readRecord(reader, n.toInt(), contentFileMap);
break;

View file

@ -333,7 +333,8 @@ namespace MWWorld
if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
{
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfItem");
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfItem", prng);
std::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
if(sound) action->setSound(sound->mId);

View file

@ -221,7 +221,7 @@ namespace MWWorld
virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const;
///< @return true if the two specified objects can stack with each other
void fill (const ESM::InventoryList& items, const std::string& owner, Misc::Rng::Generator& seed = Misc::Rng::getGenerator());
void fill (const ESM::InventoryList& items, const std::string& owner, Misc::Rng::Generator& seed);
///< Insert items into *this.
void fillNonRandom (const ESM::InventoryList& items, const std::string& owner, unsigned int seed);

View file

@ -125,7 +125,7 @@ namespace MWWorld
return (dit != mDynamic.end());
}
template<typename T>
const T *Store<T>::searchRandom(const std::string &id) const
const T *Store<T>::searchRandom(const std::string &id, Misc::Rng::Generator& prng) const
{
std::vector<const T*> results;
std::copy_if(mShared.begin(), mShared.end(), std::back_inserter(results),
@ -134,7 +134,7 @@ namespace MWWorld
return Misc::StringUtils::ciCompareLen(id, item->mId, id.size()) == 0;
});
if(!results.empty())
return results[Misc::Rng::rollDice(results.size())];
return results[Misc::Rng::rollDice(results.size(), prng)];
return nullptr;
}
template<typename T>

View file

@ -10,6 +10,7 @@
#include <components/esm/records.hpp>
#include <components/misc/stringops.hpp>
#include <components/misc/rng.hpp>
#include "../mwdialogue/keywordsearch.hpp"
@ -183,7 +184,7 @@ namespace MWWorld
bool isDynamic(const std::string &id) const;
/** Returns a random record that starts with the named ID, or nullptr if not found. */
const T *searchRandom(const std::string &id) const;
const T *searchRandom(const std::string &id, Misc::Rng::Generator& prng) const;
// calls `search` and throws an exception if not found
const T *find(const std::string &id) const;

View file

@ -283,6 +283,9 @@ namespace MWWorld
MWBase::Environment::get().getWindowManager()->updatePlayer();
mCurrentDate->setup(mGlobalVariables);
// Initial seed.
mPrng.seed(mRandomSeed);
}
void World::clear()
@ -338,6 +341,10 @@ namespace MWWorld
void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const
{
writer.startRecord(ESM::REC_RAND);
writer.writeHNOString("RAND", Misc::Rng::serialize(mPrng));
writer.endRecord(ESM::REC_RAND);
// Active cells could have a dirty fog of war, sync it to the CellStore first
for (CellStore* cellstore : mWorldScene->getActiveCells())
{
@ -376,6 +383,12 @@ namespace MWWorld
reader.getHNT(mTeleportEnabled, "TELE");
reader.getHNT(mLevitationEnabled, "LEVT");
return;
case ESM::REC_RAND:
{
auto data = reader.getHNOString("RAND");
Misc::Rng::deserialize(data, mPrng);
}
break;
case ESM::REC_PLAY:
mStore.checkPlayer();
mPlayer->readRecord(reader, type);
@ -544,7 +557,12 @@ namespace MWWorld
mProjectileManager->clear();
}
const ESM::Cell *World::getExterior (const std::string& cellName) const
void World::setRandomSeed(uint32_t seed)
{
mRandomSeed = seed;
}
const ESM::Cell* World::getExterior(const std::string& cellName) const
{
// first try named cells
const ESM::Cell *cell = mStore.get<ESM::Cell>().searchExtByName (cellName);
@ -3709,9 +3727,10 @@ namespace MWWorld
static int iNumberCreatures = mStore.get<ESM::GameSetting>().find("iNumberCreatures")->mValue.getInteger();
int numCreatures = 1 + Misc::Rng::rollDice(iNumberCreatures); // [1, iNumberCreatures]
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
for (int i=0; i<numCreatures; ++i)
{
std::string selectedCreature = MWMechanics::getLevelledItem(list, true);
std::string selectedCreature = MWMechanics::getLevelledItem(list, true, prng);
if (selectedCreature.empty())
continue;
@ -3999,4 +4018,10 @@ namespace MWWorld
{
return mCells.getAll(id);
}
Misc::Rng::Generator& World::getPrng()
{
return mPrng;
}
}

View file

@ -4,6 +4,7 @@
#include <osg/ref_ptr>
#include <components/settings/settings.hpp>
#include <components/misc/rng.hpp>
#include "../mwbase/world.hpp"
@ -84,7 +85,7 @@ namespace MWWorld
GroundcoverStore mGroundcoverStore;
LocalScripts mLocalScripts;
MWWorld::Globals mGlobalVariables;
Misc::Rng::Generator mPrng;
Cells mCells;
std::string mCurrentWorldSpace;
@ -129,6 +130,8 @@ namespace MWWorld
std::map<MWWorld::Ptr, MWWorld::DoorState> mDoorStates;
///< only holds doors that are currently moving. 1 = opening, 2 = closing
uint32_t mRandomSeed{};
// not implemented
World (const World&);
World& operator= (const World&);
@ -193,6 +196,8 @@ namespace MWWorld
virtual ~World();
void setRandomSeed(uint32_t seed) override;
void startNewGame (bool bypass) override;
///< \param bypass Bypass regular game start.
@ -736,6 +741,8 @@ namespace MWWorld
void reportStats(unsigned int frameNumber, osg::Stats& stats) const override;
std::vector<MWWorld::Ptr> getAll(const std::string& id) override;
Misc::Rng::Generator& getPrng() override;
};
}

View file

@ -820,12 +820,12 @@ namespace
const auto result = findRandomPointAroundCircle(*mNavigator, mAgentHalfExtents, mStart, 100.0, Flag_walk);
ASSERT_THAT(result, Optional(Vec3fEq(69.6253509521484375, 531.29852294921875, -2.6667339801788330078125)))
ASSERT_THAT(result, Optional(Vec3fEq(70.35845947265625, 335.592041015625, -2.6667339801788330078125)))
<< (result ? *result : osg::Vec3f());
const auto distance = (*result - mStart).length();
EXPECT_FLOAT_EQ(distance, 73.536231994628906) << distance;
EXPECT_FLOAT_EQ(distance, 125.80865478515625) << distance;
}
TEST_F(DetourNavigatorNavigatorTest, multiple_threads_should_lock_tiles)

View file

@ -178,6 +178,9 @@ enum RecNameInts : unsigned int
// format 16 - Lua scripts in saved games
REC_LUAM = fourCC("LUAM"), // LuaManager data
// format 21 - Random state in saved games.
REC_RAND = fourCC("RAND"), // Random state.
};
/// Common subrecords

View file

@ -4,7 +4,7 @@
#include "esmwriter.hpp"
unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE;
int ESM::SavedGame::sCurrentFormat = 20;
int ESM::SavedGame::sCurrentFormat = 21;
void ESM::SavedGame::load (ESMReader &esm)
{

View file

@ -2,6 +2,9 @@
#include <chrono>
#include <random>
#include <sstream>
#include <components/debug/debuglog.hpp>
namespace Misc::Rng
{
@ -12,33 +15,72 @@ namespace Misc::Rng
return sGenerator;
}
std::string serialize(const Generator& prng)
{
std::stringstream ss;
ss << prng;
return ss.str();
}
void deserialize(std::string_view data, Generator& prng)
{
std::stringstream ss;
ss << data;
ss.seekg(0);
ss >> prng;
}
unsigned int generateDefaultSeed()
{
auto res = static_cast<unsigned int>(std::chrono::high_resolution_clock::now().time_since_epoch().count());
return res;
}
void init(unsigned int seed)
{
sGenerator.seed(seed);
}
float rollProbability()
{
return std::uniform_real_distribution<float>(0, 1 - std::numeric_limits<float>::epsilon())(getGenerator());
}
float rollProbability(Generator& prng)
{
return std::uniform_real_distribution<float>(0, 1 - std::numeric_limits<float>::epsilon())(prng);
}
float rollClosedProbability()
{
return std::uniform_real_distribution<float>(0, 1)(getGenerator());
}
float rollClosedProbability(Generator& prng)
{
return std::uniform_real_distribution<float>(0, 1)(prng);
}
int rollDice(int max)
{
return max > 0 ? std::uniform_int_distribution<int>(0, max - 1)(getGenerator()) : 0;
}
int rollDice(int max, Generator& prng)
{
return max > 0 ? std::uniform_int_distribution<int>(0, max - 1)(prng) : 0;
}
unsigned int generateDefaultSeed()
float deviate(float mean, float deviation)
{
return static_cast<unsigned int>(std::chrono::high_resolution_clock::now().time_since_epoch().count());
return std::uniform_real_distribution<float>(mean - deviation, mean + deviation)(getGenerator());
}
float deviate(float mean, float deviation, Generator& prng)
{
return std::uniform_real_distribution<float>(mean - deviation, mean + deviation)(prng);
}
}

View file

@ -3,17 +3,21 @@
#include <cassert>
#include <random>
#include <string_view>
/*
Provides central implementation of the RNG logic
*/
namespace Misc::Rng
{
using Generator = std::mt19937;
/// The use of a rather minimalistic prng is preferred to avoid saving a lot of state in the save game.
using Generator = std::minstd_rand;
Generator& getGenerator();
std::string serialize(const Generator& prng);
void deserialize(std::string_view data, Generator& prng);
/// returns default seed for RNG
unsigned int generateDefaultSeed();
@ -21,19 +25,23 @@ namespace Misc::Rng
void init(unsigned int seed = generateDefaultSeed());
/// return value in range [0.0f, 1.0f) <- note open upper range.
float rollProbability(Generator& prng = getGenerator());
float rollProbability();
float rollProbability(Generator& prng);
/// return value in range [0.0f, 1.0f] <- note closed upper range.
float rollClosedProbability(Generator& prng = getGenerator());
float rollClosedProbability();
float rollClosedProbability(Generator& prng);
/// return value in range [0, max) <- note open upper range.
int rollDice(int max, Generator& prng = getGenerator());
int rollDice(int max);
int rollDice(int max, Generator& prng);
/// return value in range [0, 99]
inline int roll0to99(Generator& prng = getGenerator()) { return rollDice(100, prng); }
float deviate(float mean, float deviation, Generator& prng = getGenerator());
inline int roll0to99(Generator& prng) { return rollDice(100, prng); }
inline int roll0to99() { return rollDice(100); }
float deviate(float mean, float deviation);
float deviate(float mean, float deviation, Generator& prng);
}
#endif

View file

@ -18,7 +18,7 @@ namespace Misc
: mPeriod(period), mDeviation(deviation), mTimeLeft(timeLeft)
{}
TimerStatus update(float duration)
TimerStatus update(float duration, Rng::Generator& prng)
{
if (mTimeLeft > 0)
{
@ -26,7 +26,7 @@ namespace Misc
return TimerStatus::Waiting;
}
mTimeLeft = Rng::deviate(mPeriod, mDeviation);
mTimeLeft = Rng::deviate(mPeriod, mDeviation, prng);
return TimerStatus::Elapsed;
}