Add 5, 6 & 7 item shopsanity. (#4280)

* Format shops.cpp

* Add 5, 6 & 7 item shopsanity

* Guarantee a bombchu refill

* Fish first shop item index

* Clean up NonShopItems

* Split count options

* Improve item ordering

Ensure potions, blue fire and fairy on shopsanity 6 and less.
There are no hearts in the first 28 items (the ones from n64 rando).

* Post-merge fixes
This commit is contained in:
Pepe20129 2024-10-03 01:31:11 +02:00 committed by GitHub
parent 7110e40374
commit 84130b8046
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1095 additions and 892 deletions

View File

@ -1320,7 +1320,16 @@ void DrawEquipmentTab() {
"Giant (500)",
};
// only display Tycoon wallet if you're in a save file that would allow it.
if (IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHOPSANITY) > RO_SHOPSANITY_ZERO_ITEMS) {
if (
IS_RANDO &&
!(
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHOPSANITY) == RO_SHOPSANITY_OFF ||
(
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHOPSANITY) == RO_SHOPSANITY_SPECIFIC_COUNT &&
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHOPSANITY_COUNT) == RO_SHOPSANITY_COUNT_ZERO_ITEMS
)
)
) {
const std::string walletName = "Tycoon (999)";
walletNamesImpl.push_back(walletName);
}

View File

@ -1121,7 +1121,8 @@ const std::vector<PresetEntry> hellModePresetEntries = {
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("LinksPocket"), RO_LINKS_POCKET_NOTHING),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("MQDungeons"), RO_MQ_DUNGEONS_RANDOM_NUMBER),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("RainbowBridge"), RO_BRIDGE_DUNGEON_REWARDS),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("Shopsanity"), RO_SHOPSANITY_FOUR_ITEMS),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("Shopsanity"), RO_SHOPSANITY_SPECIFIC_COUNT),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ShopsanityCount"), RO_SHOPSANITY_COUNT_FOUR_ITEMS),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ShopsanityPrices"), RO_SHOPSANITY_PRICE_TYCOON),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ShuffleAdultTrade"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ShuffleBeans"), 1),
@ -1177,7 +1178,7 @@ const std::vector<PresetEntry> BenchmarkPresetEntries = {
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ShuffleDungeonReward"), RO_DUNGEON_REWARDS_END_OF_DUNGEON),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("LinksPocket"), RO_LINKS_POCKET_DUNGEON_REWARD),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ShuffleSongs"), RO_SONG_SHUFFLE_ANYWHERE),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("Shopsanity"), RO_SHOPSANITY_FOUR_ITEMS),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("Shopsanity"), RO_SHOPSANITY_COUNT_FOUR_ITEMS),
//RANDOTODO add refactored price/scrub/merchant settings
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ShuffleTokens"), RO_TOKENSANITY_OFF),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ShuffleBeehives"), RO_GENERIC_ON),

View File

@ -1158,7 +1158,7 @@ int Fill() {
//Temporarily add shop items to the ItemPool so that entrance randomization
//can validate the world using deku/hylian shields
StartPerformanceTimer(PT_ENTRANCE_SHUFFLE);
AddElementsToPool(ItemPool, GetMinVanillaShopItems(32)); //assume worst case shopsanity 4
AddElementsToPool(ItemPool, GetMinVanillaShopItems(8)); //assume worst case shopsanity 7
if (ctx->GetOption(RSK_SHUFFLE_ENTRANCES)) {
SPDLOG_INFO("Shuffling Entrances...");
if (ctx->GetEntranceShuffler()->ShuffleAllEntrances() == ENTRANCE_SHUFFLE_FAILURE) {
@ -1177,34 +1177,39 @@ int Fill() {
//Place shop items first, since a buy shield is needed to place a dungeon reward on Gohma due to access
StartPerformanceTimer(PT_SHOPSANITY);
NonShopItems = {};
NonShopItems.clear();
if (ctx->GetOption(RSK_SHOPSANITY).Is(RO_SHOPSANITY_OFF)) {
SPDLOG_INFO("Placing Vanilla Shop Items...");
PlaceVanillaShopItems(); //Place vanilla shop items in vanilla location
} else {
SPDLOG_INFO("Shuffling Shop Items");
int total_replaced = 0;
if (ctx->GetOption(RSK_SHOPSANITY).IsNot(RO_SHOPSANITY_ZERO_ITEMS)) { //Shopsanity 1-4, random
//Initialize NonShopItems
ItemAndPrice init;
init.Name = Text{"No Item", "Sin objeto", "Pas d'objet"};
init.Price = -1;
init.Repurchaseable = false;
NonShopItems.assign(32, init);
//Indices from OoTR. So shopsanity one will overwrite 7, three will overwrite 7, 5, 8, etc.
const std::array<int, 4> indices = {7, 5, 8, 6};
if (ctx->GetOption(RSK_SHOPSANITY).Is(RO_SHOPSANITY_RANDOM) || ctx->GetOption(RSK_SHOPSANITY_COUNT).IsNot(RO_SHOPSANITY_COUNT_ZERO_ITEMS)) { //Shopsanity 1-7, random
/*
Indices from OoTR. So shopsanity one will overwrite 7, three will overwrite 7, 5, 8, etc.
8 6 2 4
7 5 1 3
*/
const std::array<int, 8> indices = { 7, 5, 8, 6, 3, 1, 4, 2 };
//Overwrite appropriate number of shop items
#define LOCATIONS_PER_SHOP 8
for (size_t i = 0; i < Rando::StaticData::GetShopLocations().size() / LOCATIONS_PER_SHOP; i++) {
int num_to_replace = GetShopsanityReplaceAmount(); //1-4 shop items will be overwritten, depending on settings
int num_to_replace = GetShopsanityReplaceAmount(); //1-7 shop items will be overwritten, depending on settings
total_replaced += num_to_replace;
for (int j = 0; j < num_to_replace; j++) {
int itemindex = indices[j];
int shopsanityPrice = GetRandomShopPrice();
NonShopItems[TransformShopIndex(i * LOCATIONS_PER_SHOP + itemindex - 1)].Price =
shopsanityPrice; // Set price to be retrieved by the patch and textboxes
NonShopItems[Rando::StaticData::GetShopLocations()[i * LOCATIONS_PER_SHOP + itemindex - 1]].Price = shopsanityPrice; // Set price to be retrieved by the patch and textboxes
ctx->GetItemLocation(Rando::StaticData::GetShopLocations()[i * LOCATIONS_PER_SHOP + itemindex - 1])->SetCustomPrice(shopsanityPrice);
}
for (int j = num_to_replace; j < 8; j++) {
ItemAndPrice init;
init.Name = Text { "No Item", "Sin objeto", "Pas d'objet" };
init.Price = -1;
init.Repurchaseable = false;
int itemindex = indices[j];
NonShopItems[Rando::StaticData::GetShopLocations()[i * LOCATIONS_PER_SHOP + itemindex - 1]] = init; // Set price to be retrieved by the patch and textboxes
}
}
#undef LOCATIONS_PER_SHOP
}

View File

@ -1159,9 +1159,15 @@ void GenerateItemPool() {
}
//Shopsanity
if (ctx->GetOption(RSK_SHOPSANITY).Is(RO_SHOPSANITY_OFF) || ctx->GetOption(RSK_SHOPSANITY).Is(RO_SHOPSANITY_ZERO_ITEMS)) {
if (
ctx->GetOption(RSK_SHOPSANITY).Is(RO_SHOPSANITY_OFF) ||
(
ctx->GetOption(RSK_SHOPSANITY).Is(RO_SHOPSANITY_SPECIFIC_COUNT) &&
ctx->GetOption(RSK_SHOPSANITY_COUNT).Is(RO_SHOPSANITY_COUNT_ZERO_ITEMS)
)
) {
AddItemsToPool(ItemPool, normalRupees);
} else { //Shopsanity 1-4, random
} else {
AddItemsToPool(ItemPool, shopsanityRupees); //Shopsanity gets extra large rupees
}

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,6 @@
#include "../context.h"
#include <vector>
#include <array>
struct ItemAndPrice {
Text Name;
@ -16,7 +15,5 @@ extern int GetRandomShopPrice();
extern int16_t GetRandomScrubPrice();
extern int GetShopsanityReplaceAmount();
extern Text GetIceTrapName(uint8_t id);
extern int GetShopIndex(RandomizerCheck loc);
extern int TransformShopIndex(int index);
extern std::vector<ItemAndPrice> NonShopItems;
extern std::map<RandomizerCheck, ItemAndPrice> NonShopItems;

View File

@ -160,7 +160,7 @@ void WriteIngameSpoilerLog() {
// PURPLE TODO: LOCALIZATION
auto locItem = itemLocation->GetPlacedItemName().GetEnglish();
if (itemLocation->GetPlacedRandomizerGet() == RG_ICE_TRAP && loc->GetRCType() == RCTYPE_SHOP) {
locItem = NonShopItems[TransformShopIndex(GetShopIndex(key))].Name.GetEnglish();
locItem = NonShopItems[key].Name.GetEnglish();
}
if (stringOffsetMap.find(locItem) == stringOffsetMap.end()) {
if (spoilerStringOffset + locItem.size() + 1 >= SPOILER_STRING_DATA_SIZE) {

View File

@ -106,9 +106,8 @@ void Context::PlaceItemInLocation(const RandomizerCheck locKey, const Randomizer
// If we're placing a non-shop item in a shop location, we want to record it for custom messages
if (StaticData::RetrieveItem(item).GetItemType() != ITEMTYPE_SHOP &&
StaticData::GetLocation(locKey)->GetRCType() == RCTYPE_SHOP) {
const int index = TransformShopIndex(GetShopIndex(locKey));
NonShopItems[index].Name = StaticData::RetrieveItem(item).GetName();
NonShopItems[index].Repurchaseable =
NonShopItems[locKey].Name = StaticData::RetrieveItem(item).GetName();
NonShopItems[locKey].Repurchaseable =
StaticData::RetrieveItem(item).GetItemType() == ITEMTYPE_REFILL ||
StaticData::RetrieveItem(item).GetHintKey() == RHT_PROGRESSIVE_BOMBCHUS;
}
@ -214,7 +213,7 @@ void Context::CreateItemOverrides() {
val.SetTrickName(GetIceTrapName(val.LooksLike()));
// If this is ice trap is in a shop, change the name based on what the model will look like
if (loc->GetRCType() == RCTYPE_SHOP) {
NonShopItems[TransformShopIndex(GetShopIndex(locKey))].Name = val.GetTrickName();
NonShopItems[locKey].Name = val.GetTrickName();
}
overrides[locKey] = val;
}

View File

@ -81,16 +81,22 @@ std::shared_ptr<GetItemEntry> Item::GetGIEntry() const { // NOLINT(*-no-recursio
if (giEntry != nullptr) {
return giEntry;
}
auto logic = Rando::Context::GetInstance()->GetLogic();
std::shared_ptr<Rando::Context> ctx = Rando::Context::GetInstance();
auto logic = ctx->GetLogic();
RandomizerGet actual = RG_NONE;
const bool tycoonWallet =
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHOPSANITY) > RO_SHOPSANITY_ZERO_ITEMS;
const u8 infiniteUpgrades = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_INFINITE_UPGRADES);
const bool tycoonWallet = !(
ctx->GetOption(RSK_SHOPSANITY).Is(RO_SHOPSANITY_OFF) ||
(
ctx->GetOption(RSK_SHOPSANITY).Is(RO_SHOPSANITY_SPECIFIC_COUNT) &&
ctx->GetOption(RSK_SHOPSANITY_COUNT).Is(RO_SHOPSANITY_COUNT_ZERO_ITEMS)
)
);
const u8 infiniteUpgrades = ctx->GetOption(RSK_INFINITE_UPGRADES).Value<u8>();
switch (randomizerGet) {
case RG_PROGRESSIVE_STICK_UPGRADE:
switch (logic->CurrentUpgrade(UPG_STICKS)) {
case 0:
if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_DEKU_STICK_BAG)) {
if (ctx->GetOption(RSK_SHUFFLE_DEKU_STICK_BAG)) {
actual = RG_DEKU_STICK_BAG;
break;
}
@ -119,7 +125,7 @@ std::shared_ptr<GetItemEntry> Item::GetGIEntry() const { // NOLINT(*-no-recursio
case RG_PROGRESSIVE_NUT_UPGRADE:
switch (logic->CurrentUpgrade(UPG_NUTS)) {
case 0:
if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_DEKU_NUT_BAG)) {
if (ctx->GetOption(RSK_SHUFFLE_DEKU_NUT_BAG)) {
actual = RG_DEKU_NUT_BAG;
break;
}

View File

@ -263,13 +263,19 @@ void Settings::CreateOptionDescriptions() {
"The deku nut bag is required to hold deku nuts.";
mOptionDescriptions[RSK_SHOPSANITY] = "Off - All shop items will be the same as vanilla.\n"
"\n"
"0 Items - Vanilla shop items will be shuffled among different shops.\n"
"\n"
"1-4 Items - Vanilla shop items will be shuffled among different shops, and "
"each shop will contain 1-4 non-vanilla shop items.\n"
"Specifc Count - Vanilla shop items will be shuffled among different shops, and "
"each shop will contain a specifc number (0-7) of non-vanilla shop items.\n"
"\n"
"Random - Vanilla shop items will be shuffled among different shops, and "
"each shop will contain a random number(1-4) of non-vanilla shop items.\n";
"each shop will contain a random number (1-7) of non-vanilla shop items.";
mOptionDescriptions[RSK_SHOPSANITY_COUNT] = "0 Items - Vanilla shop items will be shuffled among different shops.\n"
"\n"
"1-7 Items - Vanilla shop items will be shuffled among different shops, and "
"each shop will contain 1-7 non-vanilla shop items.\n"
/*
"\n"
"8 Items - All shops will contain 8 non-vanilla shop items.\n"
*/;
mOptionDescriptions[RSK_SHOPSANITY_PRICES] =
"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 "

View File

@ -367,7 +367,13 @@ ItemObtainability Randomizer::GetItemObtainabilityFromRandomizerGet(RandomizerGe
// Shopsanity with at least one item shuffled allows for a third wallet upgrade.
// This is needed since Plentiful item pool also adds a third progressive wallet
// but we should *not* get Tycoon's Wallet in that mode.
bool tycoonWallet = GetRandoSettingValue(RSK_SHOPSANITY) > RO_SHOPSANITY_ZERO_ITEMS;
bool tycoonWallet = !(
GetRandoSettingValue(RSK_SHOPSANITY) == RO_SHOPSANITY_OFF ||
(
GetRandoSettingValue(RSK_SHOPSANITY) == RO_SHOPSANITY_SPECIFIC_COUNT &&
GetRandoSettingValue(RSK_SHOPSANITY_COUNT) == RO_SHOPSANITY_COUNT_ZERO_ITEMS
)
);
// Same thing with the infinite upgrades, if we're not shuffling them
// and we're using the Plentiful item pool, we should prevent the infinite
@ -1138,9 +1144,13 @@ ShopItemIdentity Randomizer::IdentifyShopItem(s32 sceneNum, u8 slotIndex) {
shopItemIdentity.itemPrice = -1;
shopItemIdentity.enGirlAShopItem = 0x32;
if (slotIndex == 0) {
return shopItemIdentity;
}
Rando::Location* location = GetCheckObjectFromActor(ACTOR_EN_GIRLA,
// Bazaar (SHOP1) scene is reused, so if entering from Kak use debug scene to identify
(sceneNum == SCENE_BAZAAR && gSaveContext.entranceIndex == ENTR_BAZAAR_0) ? SCENE_TEST01 : sceneNum, slotIndex);
(sceneNum == SCENE_BAZAAR && gSaveContext.entranceIndex == ENTR_BAZAAR_0) ? SCENE_TEST01 : sceneNum, slotIndex - 1);
if (location->GetRandomizerCheck() != RC_UNKNOWN_CHECK) {
shopItemIdentity.randomizerInf = rcToRandomizerInf[location->GetRandomizerCheck()];

View File

@ -3767,6 +3767,7 @@ typedef enum {
RSK_SHUFFLE_SONGS,
RSK_SHUFFLE_TOKENS,
RSK_SHOPSANITY,
RSK_SHOPSANITY_COUNT,
RSK_SHOPSANITY_PRICES,
RSK_SHOPSANITY_PRICES_AFFORDABLE,
RSK_SHUFFLE_SCRUBS,
@ -3976,17 +3977,26 @@ typedef enum {
RO_BRIDGE_WILDCARD_REWARD,
} RandoOptionBridgeRewards;
//Shopsanity settings (off, 0-4 items, random)
//Shopsanity settings (off, specific count, random)
typedef enum {
RO_SHOPSANITY_OFF,
RO_SHOPSANITY_ZERO_ITEMS,
RO_SHOPSANITY_ONE_ITEM,
RO_SHOPSANITY_TWO_ITEMS,
RO_SHOPSANITY_THREE_ITEMS,
RO_SHOPSANITY_FOUR_ITEMS,
RO_SHOPSANITY_SPECIFIC_COUNT,
RO_SHOPSANITY_RANDOM,
} RandoOptionShopsanity;
//Shopsanity count settings (0-7 items)
typedef enum {
RO_SHOPSANITY_COUNT_ZERO_ITEMS,
RO_SHOPSANITY_COUNT_ONE_ITEM,
RO_SHOPSANITY_COUNT_TWO_ITEMS,
RO_SHOPSANITY_COUNT_THREE_ITEMS,
RO_SHOPSANITY_COUNT_FOUR_ITEMS,
RO_SHOPSANITY_COUNT_FIVE_ITEMS,
RO_SHOPSANITY_COUNT_SIX_ITEMS,
RO_SHOPSANITY_COUNT_SEVEN_ITEMS,
RO_SHOPSANITY_COUNT_EIGHT_ITEMS,
} RandoOptionShopsanityCount;
//Shopsanity price ranges
typedef enum {
RO_SHOPSANITY_PRICE_BALANCED, //Balanced random from 0-300

View File

@ -119,8 +119,16 @@ void RandomizerCheckObjects::UpdateImGuiVisibility() {
(CVarGetInteger(CVAR_RANDOMIZER_SETTING("MQDungeons"), RO_MQ_DUNGEONS_NONE) != RO_MQ_DUNGEONS_SET_NUMBER ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("MQDungeonCount"), 12) < 12) // at least one vanilla dungeon
) &&
(location.GetRCType() != RCTYPE_SHOP ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("Shopsanity"), RO_SHOPSANITY_OFF) > RO_SHOPSANITY_ZERO_ITEMS) &&
(
location.GetRCType() != RCTYPE_SHOP ||
!(
ctx->GetOption(RSK_SHOPSANITY).Is(RO_SHOPSANITY_OFF) ||
(
ctx->GetOption(RSK_SHOPSANITY).Is(RO_SHOPSANITY_SPECIFIC_COUNT) &&
ctx->GetOption(RSK_SHOPSANITY_COUNT).Is(RO_SHOPSANITY_COUNT_ZERO_ITEMS)
)
)
) &&
(location.GetRCType() != RCTYPE_SCRUB ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleScrubs"), RO_SCRUBS_OFF) != RO_SCRUBS_OFF ||
location.GetRandomizerCheck() == RC_HF_DEKU_SCRUB_GROTTO ||

View File

@ -1101,12 +1101,11 @@ void EndFloatWindows() {
}
void LoadSettings() {
//If in randomzer (n64ddFlag), then get the setting and check if in general we should be showing the settings
//If in randomzer, then get the setting and check if in general we should be showing the settings
//If in vanilla, _try_ to show items that at least are needed for 100%
showShops = IS_RANDO ? (
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHOPSANITY) != RO_SHOPSANITY_OFF &&
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHOPSANITY) != RO_SHOPSANITY_ZERO_ITEMS)
showShops = IS_RANDO ?
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHOPSANITY) != RO_SHOPSANITY_OFF
: false;
showBeans = IS_RANDO ?
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_MAGIC_BEANS) == RO_GENERIC_YES

View File

@ -390,7 +390,13 @@ ItemTrackerNumbers GetItemCurrentAndMax(ItemTrackerItem item) {
case ITEM_WALLET_ADULT:
case ITEM_WALLET_GIANT:
result.currentCapacity = IS_RANDO && !Flags_GetRandomizerInf(RAND_INF_HAS_WALLET) ? 0 : CUR_CAPACITY(UPG_WALLET);
result.maxCapacity = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHOPSANITY) > RO_SHOPSANITY_ZERO_ITEMS ? 999 : 500;
result.maxCapacity = !IS_RANDO || (
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHOPSANITY) == RO_SHOPSANITY_OFF ||
(
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHOPSANITY) == RO_SHOPSANITY_SPECIFIC_COUNT &&
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHOPSANITY_COUNT) == RO_SHOPSANITY_COUNT_ZERO_ITEMS
)
) ? 500 : 999;
result.currentAmmo = gSaveContext.rupees;
break;
case ITEM_BOMBCHU:

View File

@ -97,7 +97,8 @@ void Settings::CreateOptions() {
mOptions[RSK_SHUFFLE_DUNGEON_REWARDS] = Option::U8("Shuffle Dungeon Rewards", {"End of Dungeons", "Any Dungeon", "Overworld", "Anywhere"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleDungeonReward"), mOptionDescriptions[RSK_SHUFFLE_DUNGEON_REWARDS], WidgetType::Combobox, RO_DUNGEON_REWARDS_END_OF_DUNGEON);
mOptions[RSK_LINKS_POCKET] = Option::U8("Link's Pocket", {"Dungeon Reward", "Advancement", "Anything", "Nothing"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LinksPocket"), "", WidgetType::Combobox, RO_LINKS_POCKET_DUNGEON_REWARD);
mOptions[RSK_SHUFFLE_SONGS] = Option::U8("Shuffle Songs", {"Song Locations", "Dungeon Rewards", "Anywhere"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleSongs"), mOptionDescriptions[RSK_SHUFFLE_SONGS], WidgetType::Combobox, RO_SONG_SHUFFLE_SONG_LOCATIONS);
mOptions[RSK_SHOPSANITY] = Option::U8("Shopsanity", {"Off", "0 Items", "1 Item", "2 Items", "3 Items", "4 Items", "Random"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("Shopsanity"), mOptionDescriptions[RSK_SHOPSANITY], WidgetType::Combobox, RO_SHOPSANITY_OFF);
mOptions[RSK_SHOPSANITY] = Option::U8("Shopsanity", {"Off", "Specific Count", "Random"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("Shopsanity"), mOptionDescriptions[RSK_SHOPSANITY], WidgetType::Combobox, RO_SHOPSANITY_OFF);
mOptions[RSK_SHOPSANITY_COUNT] = Option::U8("Shopsanity Item Count", {NumOpts(0, 7/*8*/)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShopsanityCount"), mOptionDescriptions[RSK_SHOPSANITY_COUNT], WidgetType::Slider, 0, false, IMFLAG_NONE);
mOptions[RSK_SHOPSANITY_PRICES] = Option::U8("Shopsanity Prices", {"Balanced", "Starting Wallet", "Adult Wallet", "Giant's Wallet", "Tycoon's Wallet"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShopsanityPrices"), mOptionDescriptions[RSK_SHOPSANITY_PRICES], WidgetType::Combobox, RO_SHOPSANITY_PRICE_BALANCED, false, IMFLAG_NONE);
mOptions[RSK_SHOPSANITY_PRICES_AFFORDABLE] = Option::Bool("Affordable Prices", CVAR_RANDOMIZER_SETTING("ShopsanityPricesAffordable"), mOptionDescriptions[RSK_SHOPSANITY_PRICES_AFFORDABLE]);
mOptions[RSK_SHUFFLE_TOKENS] = Option::U8("Tokensanity", {"Off", "Dungeons", "Overworld", "All Tokens"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleTokens"), mOptionDescriptions[RSK_SHUFFLE_TOKENS], WidgetType::Combobox, RO_TOKENSANITY_OFF);
@ -672,6 +673,7 @@ void Settings::CreateOptions() {
}, false, WidgetContainerType::COLUMN);
mOptionGroups[RSG_SHUFFLE_NPCS_IMGUI] = OptionGroup::SubGroup("Shuffle NPCs & Merchants", {
&mOptions[RSK_SHOPSANITY],
&mOptions[RSK_SHOPSANITY_COUNT],
&mOptions[RSK_SHOPSANITY_PRICES],
&mOptions[RSK_SHOPSANITY_PRICES_AFFORDABLE],
&mOptions[RSK_FISHSANITY],
@ -876,6 +878,7 @@ void Settings::CreateOptions() {
&mOptions[RSK_LINKS_POCKET],
&mOptions[RSK_SHUFFLE_SONGS],
&mOptions[RSK_SHOPSANITY],
&mOptions[RSK_SHOPSANITY_COUNT],
&mOptions[RSK_SHOPSANITY_PRICES],
&mOptions[RSK_SHOPSANITY_PRICES_AFFORDABLE],
&mOptions[RSK_FISHSANITY],
@ -1084,6 +1087,7 @@ void Settings::CreateOptions() {
&mOptions[RSK_SHUFFLE_DUNGEON_REWARDS],
&mOptions[RSK_SHUFFLE_SONGS],
&mOptions[RSK_SHOPSANITY],
&mOptions[RSK_SHOPSANITY_COUNT],
&mOptions[RSK_SHOPSANITY_PRICES],
&mOptions[RSK_SHOPSANITY_PRICES_AFFORDABLE],
&mOptions[RSK_FISHSANITY],
@ -1125,6 +1129,7 @@ void Settings::CreateOptions() {
{ "Shuffle Settings:Shuffle Songs", RSK_SHUFFLE_SONGS },
{ "Shuffle Settings:Shuffle Gerudo Membership Card", RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD },
{ "Shuffle Settings:Shopsanity", RSK_SHOPSANITY },
{ "Shuffle Settings:Shopsanity Specific Count", RSK_SHOPSANITY_COUNT },
{ "Shuffle Settings:Shopsanity Prices", RSK_SHOPSANITY_PRICES },
{ "Shuffle Settings:Affordable Prices", RSK_SHOPSANITY_PRICES_AFFORDABLE },
{ "Shuffle Settings:Fishsanity", RSK_FISHSANITY },
@ -1605,13 +1610,20 @@ void Settings::UpdateOptionProperties() {
// Hide shopsanity prices if shopsanity is off or zero
switch (CVarGetInteger(CVAR_RANDOMIZER_SETTING("Shopsanity"), RO_SHOPSANITY_OFF)) {
case RO_SHOPSANITY_OFF:
case RO_SHOPSANITY_ZERO_ITEMS:
mOptions[RSK_SHOPSANITY].AddFlag(IMFLAG_SEPARATOR_BOTTOM);
mOptions[RSK_SHOPSANITY_COUNT].Hide();
mOptions[RSK_SHOPSANITY_PRICES].Hide();
mOptions[RSK_SHOPSANITY_PRICES_AFFORDABLE].Hide();
break;
case RO_SHOPSANITY_SPECIFIC_COUNT:
mOptions[RSK_SHOPSANITY].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM);
mOptions[RSK_SHOPSANITY_COUNT].Unhide();
mOptions[RSK_SHOPSANITY_PRICES].Unhide();
mOptions[RSK_SHOPSANITY_PRICES_AFFORDABLE].Unhide();
break;
default:
mOptions[RSK_SHOPSANITY].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM);
mOptions[RSK_SHOPSANITY_COUNT].Hide();
mOptions[RSK_SHOPSANITY_PRICES].Unhide();
mOptions[RSK_SHOPSANITY_PRICES_AFFORDABLE].Unhide();
break;
@ -2300,6 +2312,7 @@ void Settings::ParseJson(nlohmann::json spoilerFileJson) {
case RSK_CUCCO_COUNT:
case RSK_FISHSANITY_POND_COUNT:
case RSK_STARTING_SKULLTULA_TOKEN:
case RSK_SHOPSANITY_COUNT:
numericValueString = it.value();
mOptions[index].SetSelectedIndex(std::stoi(numericValueString));
break;
@ -2323,16 +2336,8 @@ void Settings::ParseJson(nlohmann::json spoilerFileJson) {
case RSK_SHOPSANITY:
if (it.value() == "Off") {
mOptions[index].SetSelectedIndex(RO_SHOPSANITY_OFF);
} else if (it.value() == "0 Items") {
mOptions[index].SetSelectedIndex(RO_SHOPSANITY_ZERO_ITEMS);
} else if (it.value() == "1 Item") {
mOptions[index].SetSelectedIndex(RO_SHOPSANITY_ONE_ITEM);
} else if (it.value() == "2 Items") {
mOptions[index].SetSelectedIndex(RO_SHOPSANITY_TWO_ITEMS);
} else if (it.value() == "3 Items") {
mOptions[index].SetSelectedIndex(RO_SHOPSANITY_THREE_ITEMS);
} else if (it.value() == "4 Items") {
mOptions[index].SetSelectedIndex(RO_SHOPSANITY_FOUR_ITEMS);
} else if (it.value() == "Specific Count") {
mOptions[index].SetSelectedIndex(RO_SHOPSANITY_SPECIFIC_COUNT);
} else if (it.value() == "Random") {
mOptions[index].SetSelectedIndex(RO_SHOPSANITY_RANDOM);
}

View File

@ -432,7 +432,7 @@ void EnOssan_SpawnItemsOnShelves(EnOssan* this, PlayState* play, ShopItem* shopI
} else {
itemParams = sShopItemReplaceFunc[shopItems->shopItemIndex](shopItems->shopItemIndex);
if (IS_RANDO && Randomizer_GetSettingValue(RSK_SHOPSANITY) != RO_SHOPSANITY_OFF) {
ShopItemIdentity shopItemIdentity = Randomizer_IdentifyShopItem(play->sceneNum, i);
ShopItemIdentity shopItemIdentity = Randomizer_IdentifyShopItem(play->sceneNum, i + 1);
if (shopItemIdentity.randomizerCheck != RC_UNKNOWN_CHECK) {
itemParams = shopItemIdentity.enGirlAShopItem;
@ -452,7 +452,7 @@ void EnOssan_SpawnItemsOnShelves(EnOssan* this, PlayState* play, ShopItem* shopI
shelves->actor.shape.rot.x, shelves->actor.shape.rot.y + sItemShelfRot[i],
shelves->actor.shape.rot.z, itemParams, true);
if (IS_RANDO && Randomizer_GetSettingValue(RSK_SHOPSANITY) != RO_SHOPSANITY_OFF) {
this->shelfSlots[i]->randoSlotIndex = i;
this->shelfSlots[i]->randoSlotIndex = i + 1;
}
}
}
@ -1391,7 +1391,7 @@ void EnOssan_GiveItemWithFanfare(PlayState* play, EnOssan* this) {
if (!IS_RANDO) {
Actor_OfferGetItem(&this->actor, play, this->shelfSlots[this->cursorIndex]->getItemId, 120.0f, 120.0f);
} else {
ShopItemIdentity shopItemIdentity = Randomizer_IdentifyShopItem(play->sceneNum, this->cursorIndex);
ShopItemIdentity shopItemIdentity = Randomizer_IdentifyShopItem(play->sceneNum, this->cursorIndex + 1);
// en_ossan/en_girla are also used for the happy mask shop, which never has randomized items
// and returns RC_UNKNOWN_CHECK, in which case we should fall back to vanilla logic
if (shopItemIdentity.randomizerCheck != RC_UNKNOWN_CHECK) {
@ -1738,7 +1738,7 @@ void EnOssan_State_GiveItemWithFanfare(EnOssan* this, PlayState* play, Player* p
if (!IS_RANDO) {
Actor_OfferGetItem(&this->actor, play, this->shelfSlots[this->cursorIndex]->getItemId, 120.0f, 120.0f);
} else {
ShopItemIdentity shopItemIdentity = Randomizer_IdentifyShopItem(play->sceneNum, this->cursorIndex);
ShopItemIdentity shopItemIdentity = Randomizer_IdentifyShopItem(play->sceneNum, this->cursorIndex + 1);
// en_ossan/en_girla are also used for the happy mask shop, which never has randomized items
// and returns RC_UNKNOWN_CHECK, in which case we should fall back to vanilla logic
if (shopItemIdentity.randomizerCheck != RC_UNKNOWN_CHECK) {
@ -1754,7 +1754,7 @@ void EnOssan_State_GiveItemWithFanfare(EnOssan* this, PlayState* play, Player* p
void EnOssan_State_ItemPurchased(EnOssan* this, PlayState* play, Player* player) {
EnGirlA* item;
EnGirlA* itemTemp;
ShopItemIdentity shopItemIdentity = Randomizer_IdentifyShopItem(play->sceneNum, this->cursorIndex);
ShopItemIdentity shopItemIdentity = Randomizer_IdentifyShopItem(play->sceneNum, this->cursorIndex + 1);
GetItemEntry getItemEntry;
if (shopItemIdentity.randomizerCheck != RC_UNKNOWN_CHECK) {
getItemEntry = Randomizer_GetItemFromKnownCheck(shopItemIdentity.randomizerCheck, shopItemIdentity.ogItemId);