From 3bbb80d8498d5becc90c727cad9dab98725dbc66 Mon Sep 17 00:00:00 2001 From: ephphatha Date: Sun, 7 May 2023 13:38:21 +1000 Subject: [PATCH] Introduce temporary function to advance the global rng state and discard results --- Source/engine/random.cpp | 8 ++++++++ Source/engine/random.hpp | 10 +++++++++- Source/items.cpp | 31 +++++++++++++------------------ Source/levels/drlg_l1.cpp | 12 ++++++------ Source/levels/drlg_l3.cpp | 2 +- Source/levels/drlg_l4.cpp | 4 ++-- Source/levels/themes.cpp | 2 +- Source/objects.cpp | 2 +- test/random_test.cpp | 4 ++-- 9 files changed, 43 insertions(+), 32 deletions(-) diff --git a/Source/engine/random.cpp b/Source/engine/random.cpp index 584ffe893..bf39dd04d 100644 --- a/Source/engine/random.cpp +++ b/Source/engine/random.cpp @@ -29,6 +29,14 @@ uint32_t GetLCGEngineState() return sglGameSeed; } +void DiscardRandomValues(unsigned count) +{ + while (count != 0) { + GenerateSeed(); + count--; + } +} + uint32_t GenerateSeed() { sglGameSeed = (RndMult * sglGameSeed) + RndInc; diff --git a/Source/engine/random.hpp b/Source/engine/random.hpp index 951fe4910..53cde3bd3 100644 --- a/Source/engine/random.hpp +++ b/Source/engine/random.hpp @@ -29,6 +29,14 @@ void SetRndSeed(uint32_t seed); */ uint32_t GetLCGEngineState(); +/** + * @brief Advance the global RandomNumberEngine state by the specified number of rounds + * + * Only used to maintain vanilla compatibility until logic requiring reproducable random number generation is isolated. + * @param count How many values to discard + */ +void DiscardRandomValues(unsigned count); + /** * @brief Advances the global RandomNumberEngine state and returns the new value */ @@ -46,7 +54,7 @@ uint32_t GenerateSeed(); * * @return A random number in the range [0,2^31) or -2^31 */ -int32_t AdvanceRndSeed(); +[[nodiscard]] int32_t AdvanceRndSeed(); /** * @brief Generates a random integer less than the given limit using the vanilla RNG diff --git a/Source/items.cpp b/Source/items.cpp index 8ea29bb73..cac2a2287 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -1464,7 +1464,7 @@ _unique_items CheckUnique(Item &item, int lvl, int uper, bool recreate) if (numu == 0) return UITEM_INVALID; - AdvanceRndSeed(); + DiscardRandomValues(1); uint8_t itemData = 0; while (numu > 0) { if (uok[itemData]) @@ -2156,7 +2156,7 @@ void RecreateWitchItem(const Player &player, Item &item, _item_indexes idx, int GetItemAttrs(item, idx, lvl); } else if (gbIsHellfire && idx >= 114 && idx <= 117) { SetRndSeed(iseed); - AdvanceRndSeed(); + DiscardRandomValues(1); GetItemAttrs(item, idx, lvl); } else { SetRndSeed(iseed); @@ -2299,18 +2299,15 @@ std::string GetTranslatedItemNameMagical(const Item &item, bool hellfireItem, bo int minlvl; int maxlvl; if ((item._iCreateInfo & CF_SMITHPREMIUM) != 0) { - AdvanceRndSeed(); // RndVendorItem - AdvanceRndSeed(); // GetItemAttrs + DiscardRandomValues(2); // RndVendorItem and GetItemAttrs minlvl = lvl / 2; maxlvl = lvl; } else if ((item._iCreateInfo & CF_BOY) != 0) { - AdvanceRndSeed(); // RndVendorItem - AdvanceRndSeed(); // GetItemAttrs + DiscardRandomValues(2); // RndVendorItem and GetItemAttrs minlvl = lvl; maxlvl = lvl * 2; } else if ((item._iCreateInfo & CF_WITCH) != 0) { - AdvanceRndSeed(); // RndVendorItem - AdvanceRndSeed(); // GetItemAttrs + DiscardRandomValues(2); // RndVendorItem and GetItemAttrs int iblvl = -1; if (GenerateRnd(100) <= 5) iblvl = 2 * lvl; @@ -2319,11 +2316,11 @@ std::string GetTranslatedItemNameMagical(const Item &item, bool hellfireItem, bo minlvl = iblvl / 2; maxlvl = iblvl; } else { - AdvanceRndSeed(); // GetItemAttrs + DiscardRandomValues(1); // GetItemAttrs int iblvl = GetItemBLevel(lvl, item._iMiscId, onlygood, item._iCreateInfo & CF_UPER15); minlvl = iblvl / 2; maxlvl = iblvl; - AdvanceRndSeed(); // CheckUnique + DiscardRandomValues(1); // CheckUnique } if (minlvl > 25) @@ -2357,8 +2354,7 @@ std::string GetTranslatedItemNameMagical(const Item &item, bool hellfireItem, bo else if (!hellfireItem && FlipCoin(4)) { affixItemType = AffixItemType::Staff; } else { - AdvanceRndSeed(); // Spell - AdvanceRndSeed(); // Charges + DiscardRandomValues(2); // Spell and Charges int preidx = GetStaffPrefixId(maxlvl, onlygood, hellfireItem); if (preidx == -1 || item._iSpell == SpellID::Null) { @@ -2394,14 +2390,13 @@ std::string GetTranslatedItemNameMagical(const Item &item, bool hellfireItem, bo [&pPrefix](const PLStruct &prefix) { pPrefix = &prefix; // GenerateRnd(prefix.power.param2 - prefix.power.param2 + 1) - AdvanceRndSeed(); + DiscardRandomValues(1); switch (pPrefix->power.type) { case IPL_TOHIT_DAMP: - AdvanceRndSeed(); - AdvanceRndSeed(); + DiscardRandomValues(2); break; case IPL_TOHIT_DAMP_CURSE: - AdvanceRndSeed(); + DiscardRandomValues(1); break; default: break; @@ -2503,7 +2498,7 @@ void InitItems() } if (!setlevel) { - AdvanceRndSeed(); /* unused */ + DiscardRandomValues(1); if (Quests[Q_ROCK].IsAvailable()) SpawnRock(); if (Quests[Q_ANVIL].IsAvailable()) @@ -4251,7 +4246,7 @@ void SpawnWitch(int lvl) if (lvl >= AllItemsList[bookType].iMinMLvl) { item._iSeed = AdvanceRndSeed(); SetRndSeed(item._iSeed); - AdvanceRndSeed(); + DiscardRandomValues(1); GetItemAttrs(item, bookType, lvl); item._iCreateInfo = lvl | CF_WITCH; item._iIdentified = true; diff --git a/Source/levels/drlg_l1.cpp b/Source/levels/drlg_l1.cpp index d48e2b975..de8f327bb 100644 --- a/Source/levels/drlg_l1.cpp +++ b/Source/levels/drlg_l1.cpp @@ -701,42 +701,42 @@ void AddWall() continue; if (dungeon[i][j] == Corner) { - AdvanceRndSeed(); + DiscardRandomValues(1); int maxX = HorizontalWallOk({ i, j }); if (maxX != -1) { HorizontalWall({ i, j }, HWall, maxX); } } if (dungeon[i][j] == Corner) { - AdvanceRndSeed(); + DiscardRandomValues(1); int maxY = VerticalWallOk({ i, j }); if (maxY != -1) { VerticalWall({ i, j }, VWall, maxY); } } if (dungeon[i][j] == VWallEnd) { - AdvanceRndSeed(); + DiscardRandomValues(1); int maxX = HorizontalWallOk({ i, j }); if (maxX != -1) { HorizontalWall({ i, j }, DWall, maxX); } } if (dungeon[i][j] == HWallEnd) { - AdvanceRndSeed(); + DiscardRandomValues(1); int maxY = VerticalWallOk({ i, j }); if (maxY != -1) { VerticalWall({ i, j }, DWall, maxY); } } if (dungeon[i][j] == HWall) { - AdvanceRndSeed(); + DiscardRandomValues(1); int maxX = HorizontalWallOk({ i, j }); if (maxX != -1) { HorizontalWall({ i, j }, HWall, maxX); } } if (dungeon[i][j] == VWall) { - AdvanceRndSeed(); + DiscardRandomValues(1); int maxY = VerticalWallOk({ i, j }); if (maxY != -1) { VerticalWall({ i, j }, VWall, maxY); diff --git a/Source/levels/drlg_l3.cpp b/Source/levels/drlg_l3.cpp index 009a5fbb0..b2ea22e6a 100644 --- a/Source/levels/drlg_l3.cpp +++ b/Source/levels/drlg_l3.cpp @@ -1683,7 +1683,7 @@ void Fence() for (WorldTileCoord j = 1; j < DMAXY; j++) { // BUGFIX: Change '0' to '1' (fixed) for (WorldTileCoord i = 1; i < DMAXX; i++) { // BUGFIX: Change '0' to '1' (fixed) // note the comma operator is used here to advance the RNG state - if (dungeon[i][j] == 7 && (AdvanceRndSeed(), !IsNearThemeRoom({ i, j }))) { + if (dungeon[i][j] == 7 && (DiscardRandomValues(1), !IsNearThemeRoom({ i, j }))) { if (FlipCoin()) { int y1 = j; // BUGFIX: Check `y1 >= 0` first (fixed) diff --git a/Source/levels/drlg_l4.cpp b/Source/levels/drlg_l4.cpp index 791e6f571..cea20655a 100644 --- a/Source/levels/drlg_l4.cpp +++ b/Source/levels/drlg_l4.cpp @@ -446,7 +446,7 @@ void AddWall() } for (auto d : { 10, 12, 13, 15, 16, 21, 22 }) { if (d == dungeon[i][j]) { - AdvanceRndSeed(); + DiscardRandomValues(1); int x = HorizontalWallOk(i, j); if (x != -1) { HorizontalWall(i, j, x); @@ -455,7 +455,7 @@ void AddWall() } for (auto d : { 8, 9, 11, 14, 15, 16, 21, 23 }) { if (d == dungeon[i][j]) { - AdvanceRndSeed(); + DiscardRandomValues(1); int y = VerticalWallOk(i, j); if (y != -1) { VerticalWall(i, j, y); diff --git a/Source/levels/themes.cpp b/Source/levels/themes.cpp index 22ad177af..f4cc16c15 100644 --- a/Source/levels/themes.cpp +++ b/Source/levels/themes.cpp @@ -514,7 +514,7 @@ void Theme_Treasure(int t) int treasrnd[4] = { 4, 9, 7, 10 }; int monstrnd[4] = { 6, 8, 3, 7 }; - AdvanceRndSeed(); + DiscardRandomValues(1); for (int yp = 0; yp < MAXDUNY; yp++) { for (int xp = 0; xp < MAXDUNX; xp++) { if (dTransVal[xp][yp] == themes[t].ttval && IsTileNotSolid({ xp, yp })) { diff --git a/Source/objects.cpp b/Source/objects.cpp index 7ea0d81cf..c987677b4 100644 --- a/Source/objects.cpp +++ b/Source/objects.cpp @@ -3869,7 +3869,7 @@ void InitObjects() if (currlevel == 16) { AddDiabObjs(); } else { - AdvanceRndSeed(); + DiscardRandomValues(1); if (currlevel == 9 && !UseMultiplayerQuests()) AddSlainHero(); if (Quests[Q_MUSHROOM].IsAvailable()) diff --git a/test/random_test.cpp b/test/random_test.cpp index bc15bca5b..f57b74e10 100644 --- a/test/random_test.cpp +++ b/test/random_test.cpp @@ -25,8 +25,8 @@ TEST(RandomTest, RandomEngineParams) // C++11 defines the default seed for a LCG engine as 1. The ten thousandth value is commonly used for sanity checking // a sequence, so as we've had one round since state 1 we need to discard another 9998 values to get to the 10000th state. // To make off by one errors more visible test the 9999th value as well as 10000th - for (auto i = 2; i <= 9998; i++) - GenerateSeed(); + DiscardRandomValues(9997); + uint32_t expectedState = 3495122800U; EXPECT_EQ(GenerateSeed(), expectedState) << "Wrong engine state after 9999 invocations"; expectedState = 3007658545U;