From 29daec879bab0df1c027768c5c76955c70b6905d Mon Sep 17 00:00:00 2001 From: Malkierian Date: Fri, 24 Feb 2023 02:21:13 -0700 Subject: [PATCH] Shopsanity price range selection (#2517) * Adds price range select box for shopsanity when shopsanity is 1 or more items. Default option is the original functionality, now under "Random" select option. Other options include Affordable, which makes each item 10 rupees, and one option for each level of wallet available with shopsanity, where each wallet's max capacity is the upper limit on the price randomization. These still go in 5 rupee increments, like Random. Also keeps track of and saves/loads settings appropriately on game start. * Changed "Random" default shopsanity price selection to "Balanced" to be more properly descriptive. Refactored GetRandomShopPrice to try to keep Balanced as the default, with a sane fallback price of 150 should the weighted randomizer somehow fail. * Missed one change from Random to Balanced. * Changed wallet range to minimum of 5, and removed the multiple of 5 requirement (full rando within range). * Modified the system to add a checkbox for affordability which then adds a price cap that is just above the max value of the previous wallet tier. Effectively this keeps the wallet lock in place, but prevents anything from getting more expensive than 5 past the previous tier. * Fixed hover help text and tooltip formatting. * Changed wallet ranges to generate multiples of 5. May need weighted generation after playtesting. --- soh/soh/Enhancements/presets.h | 3 + .../3drando/setting_descriptions.cpp | 12 ++++ .../3drando/setting_descriptions.hpp | 7 +++ .../randomizer/3drando/settings.cpp | 12 +++- .../randomizer/3drando/settings.hpp | 3 + .../Enhancements/randomizer/3drando/shops.cpp | 55 ++++++++++++++++--- .../Enhancements/randomizer/randomizer.cpp | 39 +++++++++++++ .../Enhancements/randomizer/randomizerTypes.h | 12 ++++ 8 files changed, 134 insertions(+), 9 deletions(-) diff --git a/soh/soh/Enhancements/presets.h b/soh/soh/Enhancements/presets.h index 65c30f737..dd5369d18 100644 --- a/soh/soh/Enhancements/presets.h +++ b/soh/soh/Enhancements/presets.h @@ -236,6 +236,8 @@ const std::vector randomizerCvars = { "gRandomizeRewardCount", "gRandomizeScrubText", "gRandomizeShopsanity", + "gRandomizeShopsanityPrices", + "gRandomizeShopsanityPricesAffordable", "gRandomizeShuffleAdultTrade", "gRandomizeShuffleBeans", "gRandomizeShuffleBossEntrances", @@ -690,6 +692,7 @@ const std::vector hellModePresetEntries = { PRESET_ENTRY_S32("gRandomizeMqDungeons", RO_MQ_DUNGEONS_RANDOM_NUMBER), PRESET_ENTRY_S32("gRandomizeRainbowBridge", RO_BRIDGE_DUNGEON_REWARDS), PRESET_ENTRY_S32("gRandomizeShopsanity", RO_SHOPSANITY_FOUR_ITEMS), + PRESET_ENTRY_S32("gRandomizeShopsanityPrices", RO_SHOPSANITY_PRICE_TYCOON), PRESET_ENTRY_S32("gRandomizeShuffleAdultTrade", 1), PRESET_ENTRY_S32("gRandomizeShuffleBeans", 1), PRESET_ENTRY_S32("gRandomizeShuffleCows", 1), diff --git a/soh/soh/Enhancements/randomizer/3drando/setting_descriptions.cpp b/soh/soh/Enhancements/randomizer/3drando/setting_descriptions.cpp index 200739991..ed857e82c 100644 --- a/soh/soh/Enhancements/randomizer/3drando/setting_descriptions.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/setting_descriptions.cpp @@ -358,6 +358,18 @@ string_view shopsFour = "Vanilla shop items will be shuffled amo string_view shopsRandom = "Vanilla shop items will be shuffled among\n" // "different shops, and each shop will contain\n" // "1-4 non-vanilla shop items."; // + // +/*------------------------------ // +| SHOPSANITY PRICES | // +------------------------------*/ // +string_view shopPriceBalanced = "Weighted randomization, max 300."; // +string_view shopPriceStarter = "True randomization, max 95"; // +string_view shopPriceAdult = "True randomization, max 200"; // +string_view shopPriceGiant = "True randomization, max 500"; // +string_view shopPriceTycoon = "True randomization, max 995"; // +string_view shopPriceAffordable = "Cap shop prices to affordable value just above" // + "the previous tier wallet's max value"; // + // /*------------------------------ // | TOKENSANITY | // ------------------------------*/ // diff --git a/soh/soh/Enhancements/randomizer/3drando/setting_descriptions.hpp b/soh/soh/Enhancements/randomizer/3drando/setting_descriptions.hpp index 2749fa985..fae3ee995 100644 --- a/soh/soh/Enhancements/randomizer/3drando/setting_descriptions.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/setting_descriptions.hpp @@ -118,6 +118,13 @@ extern string_view shopsThree; extern string_view shopsFour; extern string_view shopsRandom; +extern string_view shopPriceBalanced; +extern string_view shopPriceStarter; +extern string_view shopPriceAdult; +extern string_view shopPriceGiant; +extern string_view shopPriceTycoon; +extern string_view shopPriceAffordable; + extern string_view tokensOff; extern string_view tokensDungeon; extern string_view tokensOverworld; diff --git a/soh/soh/Enhancements/randomizer/3drando/settings.cpp b/soh/soh/Enhancements/randomizer/3drando/settings.cpp index 632ed45c8..07e17f7e9 100644 --- a/soh/soh/Enhancements/randomizer/3drando/settings.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/settings.cpp @@ -179,8 +179,10 @@ namespace Settings { Option LinksPocketItem = Option::U8 ("Link's Pocket", {"Dungeon Reward", "Advancement", "Anything", "Nothing"}, {linksPocketDungeonReward, linksPocketAdvancement, linksPocketAnything, linksPocketNothing}); Option ShuffleSongs = Option::U8 ("Shuffle Songs", {"Song locations", "Dungeon rewards", "Anywhere"}, {songsSongLocations, songsDungeonRewards, songsAllLocations}); Option Shopsanity = Option::U8 ("Shopsanity", {"Off","0 Items","1 Item","2 Items","3 Items","4 Items","Random"}, {shopsOff, shopsZero, shopsOne, shopsTwo, shopsThree, shopsFour, shopsRandom}); - Option Tokensanity = Option::U8 ("Tokensanity", {"Off", "Dungeons", "Overworld", "All Tokens"}, {tokensOff, tokensDungeon, tokensOverworld, tokensAllTokens}); - Option Scrubsanity = Option::U8 ("Scrub Shuffle", {"Off", "Affordable", "Expensive", "Random Prices"}, {scrubsOff, scrubsAffordable, scrubsExpensive, scrubsRandomPrices}); + Option ShopsanityPrices = Option::U8 ("Shopsanity Prices", {"Balanced", "Starting Wallet", "Adult Wallet", "Giant's Wallet", "Tycoon's Wallet" }, {shopPriceBalanced, shopPriceStarter, shopPriceAdult, shopPriceGiant, shopPriceTycoon} ); + Option ShopsanityPricesAffordable = Option::Bool("Affordable Prices", {"Off", "On"}, {shopPriceAffordable} ); + Option Tokensanity = Option::U8 ("Tokensanity", {"Off", "Dungeons", "Overworld", "All Tokens"}, {tokensOff, tokensDungeon, tokensOverworld, tokensAllTokens}); + Option Scrubsanity = Option::U8 ("Scrub Shuffle", {"Off", "Affordable", "Expensive", "Random Prices"}, {scrubsOff, scrubsAffordable, scrubsExpensive, scrubsRandomPrices}); Option ShuffleCows = Option::Bool("Shuffle Cows", {"Off", "On"}, {shuffleCowsDesc}); Option ShuffleKokiriSword = Option::Bool("Shuffle Kokiri Sword", {"Off", "On"}, {kokiriSwordDesc}); Option ShuffleOcarinas = Option::Bool("Shuffle Ocarinas", {"Off", "On"}, {ocarinasDesc}); @@ -198,6 +200,8 @@ namespace Settings { &LinksPocketItem, &ShuffleSongs, &Shopsanity, + &ShopsanityPrices, + &ShopsanityPricesAffordable, &Tokensanity, &Scrubsanity, &ShuffleCows, @@ -2427,6 +2431,8 @@ namespace Settings { &ShuffleRewards, &ShuffleSongs, &Shopsanity, + &ShopsanityPrices, + &ShopsanityPricesAffordable, &Scrubsanity, &ShuffleCows, &ShuffleMagicBeans, @@ -2703,6 +2709,8 @@ namespace Settings { ShuffleSongs.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_SONGS]); Tokensanity.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_TOKENS]); Shopsanity.SetSelectedIndex(cvarSettings[RSK_SHOPSANITY]); + ShopsanityPrices.SetSelectedIndex(cvarSettings[RSK_SHOPSANITY_PRICES]); + ShopsanityPricesAffordable.SetSelectedIndex(cvarSettings[RSK_SHOPSANITY_PRICES_AFFORDABLE]); Scrubsanity.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_SCRUBS]); ShuffleCows.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_COWS]); ShuffleKokiriSword.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_KOKIRI_SWORD]); diff --git a/soh/soh/Enhancements/randomizer/3drando/settings.hpp b/soh/soh/Enhancements/randomizer/3drando/settings.hpp index 84851a3d6..a4f788c81 100644 --- a/soh/soh/Enhancements/randomizer/3drando/settings.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/settings.hpp @@ -421,6 +421,7 @@ typedef struct { uint8_t tokensanity; uint8_t scrubsanity; uint8_t shopsanity; + uint8_t shopsanityPrices; uint8_t shuffleCows; uint8_t shuffleKokiriSword; uint8_t shuffleOcarinas; @@ -937,6 +938,8 @@ void UpdateSettings(std::unordered_map cvarSettin extern Option LinksPocketItem; extern Option ShuffleSongs; extern Option Shopsanity; + extern Option ShopsanityPrices; + extern Option ShopsanityPricesAffordable; extern Option Tokensanity; extern Option Scrubsanity; extern Option ShuffleCows; diff --git a/soh/soh/Enhancements/randomizer/3drando/shops.cpp b/soh/soh/Enhancements/randomizer/3drando/shops.cpp index 72aab2eb6..1b4dbb28d 100644 --- a/soh/soh/Enhancements/randomizer/3drando/shops.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/shops.cpp @@ -127,15 +127,56 @@ static constexpr std::array ShopPriceProbability= { 0.833208595, 0.849243398, 0.864579161, 0.879211177, 0.893112051, 0.906263928, 0.918639420, 0.930222611, 0.940985829, 0.950914731, 0.959992180, 0.968187000, 0.975495390, 0.981884488, 0.987344345, 0.991851853, 0.995389113, 0.997937921, 0.999481947, 1.000000000, }; + +std::map affordableCaps = { + {RO_SHOPSANITY_PRICE_STARTER, 10}, + {RO_SHOPSANITY_PRICE_ADULT, 105}, + {RO_SHOPSANITY_PRICE_GIANT, 205}, + {RO_SHOPSANITY_PRICE_TYCOON, 505}, +}; + +// If affordable option is on, cap items at affordable price just above the max of the previous wallet tier +int CapPriceAffordable(int value, int cap) { + if (Settings::ShopsanityPricesAffordable.Is(true) && value > cap) + return cap; + return value; +} + +// Generate random number from 5 to wallet max +int GetPriceFromMax(int max) { + int temp = Random(1, max) * 5; // random range of 1 - wallet max / 5, where wallet max is the highest it goes as a multiple of 5 + return CapPriceAffordable(temp, affordableCaps.find(Settings::ShopsanityPrices.Value())->second); +} + int GetRandomShopPrice() { - double random = RandomDouble(); //Randomly generated probability value - for (size_t i = 0; i < ShopPriceProbability.size(); i++) { - if (random < ShopPriceProbability[i]) { - //The randomly generated value has surpassed the total probability up to this point, so this is the generated price - return i * 5; //i in range [0, 59], output in range [0, 295] in increments of 5 + int max = 0; + + if(Settings::ShopsanityPrices.Is(RO_SHOPSANITY_PRICE_STARTER)) {// check for xx wallet setting and set max amount as method for + max = 19; // 95/5 // setting true randomization } - } - return -1; //Shouldn't happen + else if (Settings::ShopsanityPrices.Is(RO_SHOPSANITY_PRICE_ADULT)) { + max = 40; // 200/5 + } + else if (Settings::ShopsanityPrices.Is(RO_SHOPSANITY_PRICE_GIANT)) { + max = 100; // 500/5 + } + else if (Settings::ShopsanityPrices.Is(RO_SHOPSANITY_PRICE_TYCOON)) { + max = 199; // 995/5 + } + if (max != 0) { + return GetPriceFromMax(max); + } + // Balanced is default, so if all other known cases fail, fall back to Balanced + int price = 150; // JUST in case something fails with the randomization, return sane price for balanced + double random = RandomDouble(); //Randomly generated probability value + for (size_t i = 0; i < ShopPriceProbability.size(); i++) { + if (random < ShopPriceProbability[i]) { + //The randomly generated value has surpassed the total probability up to this point, so this is the generated price + price = i * 5; //i in range [0, 59], output in range [0, 295] in increments of 5 + break; + } + } + return price; } //Similar to above, beta distribution with alpha = 1, beta = 2, diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index 080c5d1df..4a5be61f5 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -221,6 +221,7 @@ std::unordered_map SpoilerfileSettingNameToEn { "Shuffle Settings:Link's Pocket", RSK_LINKS_POCKET}, { "Shuffle Settings:Shuffle Gerudo Card", RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD }, { "Shuffle Settings:Shopsanity", RSK_SHOPSANITY }, + { "Shuffle Settings:Shopsanity Prices", RSK_SHOPSANITY_PRICES }, { "Shuffle Settings:Scrub Shuffle", RSK_SHUFFLE_SCRUBS }, { "Shuffle Settings:Shuffle Cows", RSK_SHUFFLE_COWS }, { "Shuffle Settings:Tokensanity", RSK_SHUFFLE_TOKENS }, @@ -727,6 +728,18 @@ void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) { gSaveContext.randoSettings[index].value = RO_SHOPSANITY_RANDOM; } break; + case RSK_SHOPSANITY_PRICES: + if (it.value() == "Random") { + gSaveContext.randoSettings[index].value = RO_SHOPSANITY_PRICE_BALANCED; + } else if (it.value() == "Starter Wallet") { + gSaveContext.randoSettings[index].value = RO_SHOPSANITY_PRICE_STARTER; + } else if (it.value() == "Adult's Wallet") { + gSaveContext.randoSettings[index].value = RO_SHOPSANITY_PRICE_ADULT; + } else if (it.value() == "Giant's Wallet") { + gSaveContext.randoSettings[index].value = RO_SHOPSANITY_PRICE_GIANT; + } else if (it.value() == "Tycoon's Wallet") { + gSaveContext.randoSettings[index].value = RO_SHOPSANITY_PRICE_TYCOON; + } case RSK_SHUFFLE_SCRUBS: if(it.value() == "Off") { gSaveContext.randoSettings[index].value = RO_SCRUBS_OFF; @@ -789,6 +802,7 @@ void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) { case RSK_MIX_INTERIOR_ENTRANCES: case RSK_MIX_GROTTO_ENTRANCES: case RSK_DECOUPLED_ENTRANCES: + case RSK_SHOPSANITY_PRICES_AFFORDABLE: if(it.value() == "Off") { gSaveContext.randoSettings[index].value = RO_GENERIC_OFF; } else if(it.value() == "On") { @@ -2824,6 +2838,8 @@ void GenerateRandomizerImgui(std::string seed = "") { cvarSettings[RSK_SHUFFLE_SONGS] = CVarGetInteger("gRandomizeShuffleSongs", RO_SONG_SHUFFLE_SONG_LOCATIONS); cvarSettings[RSK_SHUFFLE_TOKENS] = CVarGetInteger("gRandomizeShuffleTokens", RO_TOKENSANITY_OFF); cvarSettings[RSK_SHOPSANITY] = CVarGetInteger("gRandomizeShopsanity", RO_SHOPSANITY_OFF); + cvarSettings[RSK_SHOPSANITY_PRICES] = CVarGetInteger("gRandomizeShopsanityPrices", RO_SHOPSANITY_PRICE_BALANCED); + cvarSettings[RSK_SHOPSANITY_PRICES_AFFORDABLE] = CVarGetInteger("gRandomizeShopsanityPricesAffordable", RO_SHOPSANITY_OFF); cvarSettings[RSK_SHUFFLE_SCRUBS] = CVarGetInteger("gRandomizeShuffleScrubs", RO_SCRUBS_OFF); cvarSettings[RSK_SHUFFLE_COWS] = CVarGetInteger("gRandomizeShuffleCows", 0); cvarSettings[RSK_SHUFFLE_ADULT_TRADE] = CVarGetInteger("gRandomizeShuffleAdultTrade", 0); @@ -3018,6 +3034,7 @@ void DrawRandoEditor(bool& open) { static const char* randoLinksPocket[4] = { "Dungeon Reward", "Advancement", "Anything", "Nothing" }; static const char* randoShuffleSongs[3] = { "Song Locations", "Dungeon Rewards", "Anywhere" }; static const char* randoShopsanity[7] = { "Off", "0 Items", "1 Item", "2 Items", "3 Items", "4 Items", "Random" }; + static const char* randoShopsanityPrices[6] = { "Balanced", "Starter Wallet", "Adult Wallet", "Giant's Wallet", "Tycoon's Wallet", "Affordable" }; static const char* randoTokensanity[4] = { "Off", "Dungeons", "Overworld", "All Tokens" }; static const char* randoShuffleScrubs[4] = { "Off", "Affordable", "Expensive", "Random Prices" }; static const char* randoShuffleMerchants[3] = { "Off", "On (no hints)", "On (with hints)" }; @@ -3635,6 +3652,28 @@ void DrawRandoEditor(bool& open) { ); UIWidgets::EnhancementCombobox("gRandomizeShopsanity", randoShopsanity, RO_SHOPSANITY_MAX, RO_SHOPSANITY_OFF); + // Shopsanity Prices + switch (CVarGetInteger("gRandomizeShopsanity", RO_SHOPSANITY_OFF)) { + case RO_SHOPSANITY_OFF: + case RO_SHOPSANITY_ZERO_ITEMS: // no need to show it if there aren't shop slots in the pool + break; + default: + ImGui::Text(Settings::ShopsanityPrices.GetName().c_str()); + UIWidgets::InsertHelpHoverText( + "Balanced - The default randomization. Shop prices for shopsanity items will range between 0 to 300 rupees, " + "with a bias towards values slightly below the middle of the range, in multiples of 5.\n " + "\n" + "X Wallet - Randomized between 5 and the wallet's max size, in multiples of 5" + ); + UIWidgets::EnhancementCombobox("gRandomizeShopsanityPrices", randoShopsanityPrices, RO_SHOPSANITY_PRICE_MAX, RO_SHOPSANITY_PRICE_BALANCED); + UIWidgets::EnhancementCheckbox(Settings::ShopsanityPricesAffordable.GetName().c_str(), "gRandomizeShopsanityPricesAffordable", + CVarGetInteger("gRandomizeShopsanityPrices", RO_SHOPSANITY_PRICE_BALANCED) == RO_SHOPSANITY_PRICE_BALANCED, + "This can only apply to a wallet range."); + UIWidgets::InsertHelpHoverText("Cap item prices to a value just above the previous tier wallet's max value.\n" + "Affordable caps: starter = 10, adult = 105, giant = 205, tycoon = 505\n" + "Use this to enable wallet tier locking, but make shop items not as expensive as they could be."); + } + UIWidgets::PaddedSeparator(); // Shuffle Scrubs diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index 1fc7c7f0e..1dde6f23a 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -1013,6 +1013,8 @@ typedef enum { RSK_SHUFFLE_SONGS, RSK_SHUFFLE_TOKENS, RSK_SHOPSANITY, + RSK_SHOPSANITY_PRICES, + RSK_SHOPSANITY_PRICES_AFFORDABLE, RSK_SHUFFLE_SCRUBS, RSK_SHUFFLE_COWS, RSK_SHUFFLE_WEIRD_EGG, @@ -1191,6 +1193,16 @@ typedef enum { RO_SHOPSANITY_MAX, } RandoOptionShopsanity; +//Shopsanity price ranges +typedef enum { + RO_SHOPSANITY_PRICE_BALANCED, //Balanced random from 0-300 + RO_SHOPSANITY_PRICE_STARTER, //Wallets are random within their range, in increments of 5 rupees + RO_SHOPSANITY_PRICE_ADULT, + RO_SHOPSANITY_PRICE_GIANT, + RO_SHOPSANITY_PRICE_TYCOON, + RO_SHOPSANITY_PRICE_MAX, +} RandoOptionShopsanityPrices; + //Scrubsanity settings (off, affordable, expensive, random) typedef enum { RO_SCRUBS_OFF,