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.
This commit is contained in:
Malkierian 2023-02-24 02:21:13 -07:00 committed by GitHub
parent 23d2e4a2cc
commit 29daec879b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 134 additions and 9 deletions

View File

@ -236,6 +236,8 @@ const std::vector<const char*> randomizerCvars = {
"gRandomizeRewardCount",
"gRandomizeScrubText",
"gRandomizeShopsanity",
"gRandomizeShopsanityPrices",
"gRandomizeShopsanityPricesAffordable",
"gRandomizeShuffleAdultTrade",
"gRandomizeShuffleBeans",
"gRandomizeShuffleBossEntrances",
@ -690,6 +692,7 @@ const std::vector<PresetEntry> 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),

View File

@ -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 | //
------------------------------*/ //

View File

@ -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;

View File

@ -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]);

View File

@ -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<RandomizerSettingKey, uint8_t> 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;

View File

@ -127,15 +127,56 @@ static constexpr std::array<double, 60> 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<uint8_t, int> 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<uint8_t>())->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,

View File

@ -221,6 +221,7 @@ std::unordered_map<std::string, RandomizerSettingKey> 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

View File

@ -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,