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:
commit
6d55317d57
51 changed files with 329 additions and 154 deletions
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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("");
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue