From 8649085862b30d768b773eef1f7ea1e29afd8671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= <159546+serprex@users.noreply.github.com> Date: Tue, 9 Jun 2026 19:45:04 +0000 Subject: [PATCH] add option gating shields / tunics in shop behind finding one first (#6700) --- .../vanilla-behavior/GIVanillaBehavior.h | 9 +++++ .../Enhancements/randomizer/3drando/fill.cpp | 10 ++++- .../Enhancements/randomizer/hook_handlers.cpp | 30 ++++++++++++++ soh/soh/Enhancements/randomizer/item.cpp | 23 +++++++++++ soh/soh/Enhancements/randomizer/item.h | 1 + soh/soh/Enhancements/randomizer/logic.cpp | 39 +++++++++++++++++++ soh/soh/Enhancements/randomizer/logic.h | 1 + .../randomizer/option_descriptions.cpp | 4 ++ .../randomizerEnums/RandomizerInf.h | 7 ++++ .../randomizerEnums/RandomizerSettingKey.h | 1 + soh/soh/Enhancements/randomizer/settings.cpp | 3 ++ .../overlays/actors/ovl_En_GirlA/z_en_girla.c | 16 ++++++++ 12 files changed, 142 insertions(+), 2 deletions(-) diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index 778e7f2783..f96ff1ca5f 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -2716,6 +2716,15 @@ typedef enum { // - *EnGirlACanBuyResult VB_CAN_BUY_BOMBCHUS, + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - *EnGirlACanBuyResult + // - `RAND_INF` + VB_CAN_BUY_SHOP_SHIELD_OR_TUNIC, + // #### `result` // ```c // true diff --git a/soh/soh/Enhancements/randomizer/3drando/fill.cpp b/soh/soh/Enhancements/randomizer/3drando/fill.cpp index 158d125aa9..0214d0c11f 100644 --- a/soh/soh/Enhancements/randomizer/3drando/fill.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/fill.cpp @@ -402,7 +402,8 @@ bool AddCheckToLogic(LocationAccess& locPair, GetAccessibleLocationsStruct& gals (quest == RCQUEST_VANILLA && ctx->GetDungeons()->GetDungeonFromScene(parentRegion->scene)->IsVanilla()) || (quest == RCQUEST_MQ && ctx->GetDungeons()->GetDungeonFromScene(parentRegion->scene)->IsMQ())); - if (!location->IsAddedToPool() && locPair.ConditionsMet(parentRegion, logic->CalculatingAvailableChecks)) { + if (!location->IsAddedToPool() && locPair.ConditionsMet(parentRegion, logic->CalculatingAvailableChecks) && + !logic->ShopItemNotForSale(loc)) { location->AddToPool(); if (locItem == RG_NONE || logic->CalculatingAvailableChecks) { @@ -789,12 +790,17 @@ static void CalculateBarren() { NotBarren[RA_NONE] = true; NotBarren[RA_LINKS_POCKET] = true; + // When shop shields/tunics are gated behind finding a shield, those items become relevant, so + // regions holding a shield or tunic should not be hinted foolish. + const bool shieldTunicGate = ctx->GetOption(RSK_SHOP_SHIELDS_AND_TUNICS_ONLY_REFILL).Is(RO_GENERIC_ON); + for (RandomizerCheck loc : ctx->allLocations) { Rando::ItemLocation* itemLoc = ctx->GetItemLocation(loc); std::set locAreas = itemLoc->GetAreas(); for (auto locArea : locAreas) { // If a location has a major item or is a way of the hero location, it is not barren - if (NotBarren[locArea] == false && (itemLoc->GetPlacedItem().IsMajorItem() || itemLoc->IsWothCandidate())) { + if (NotBarren[locArea] == false && (itemLoc->GetPlacedItem().IsMajorItem() || itemLoc->IsWothCandidate() || + (shieldTunicGate && itemLoc->GetPlacedItem().IsShieldOrTunic()))) { NotBarren[locArea] = true; } } diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index 9822f41977..c2e2025861 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -59,6 +59,7 @@ extern "C" { #include "src/overlays/actors/ovl_Fishing/z_fishing.h" #include "src/overlays/actors/ovl_Obj_Bean/z_obj_bean.h" #include "src/overlays/actors/ovl_En_Heishi2/z_en_heishi2.h" +#include "src/overlays/actors/ovl_En_GirlA/z_en_girla.h" #include "draw.h" static ObjectExtension::Register RegisterDnsItemEntryOverride; @@ -445,6 +446,23 @@ void RandomizerOnItemReceiveHandler(GetItemEntry receivedItemEntry) { randomizerQueuedItemEntry = GET_ITEM_NONE; } + if (receivedItemEntry.modIndex == MOD_NONE) { + switch (receivedItemEntry.itemId) { + case ITEM_SHIELD_DEKU: + Flags_SetRandomizerInf(RAND_INF_HAS_FOUND_DEKU_SHIELD); + break; + case ITEM_SHIELD_HYLIAN: + Flags_SetRandomizerInf(RAND_INF_HAS_FOUND_HYLIAN_SHIELD); + break; + case ITEM_TUNIC_GORON: + Flags_SetRandomizerInf(RAND_INF_HAS_FOUND_GORON_TUNIC); + break; + case ITEM_TUNIC_ZORA: + Flags_SetRandomizerInf(RAND_INF_HAS_FOUND_ZORA_TUNIC); + break; + } + } + if (receivedItemEntry.modIndex == MOD_RANDOMIZER && receivedItemEntry.getItemId == RG_MAGIC_BEAN_PACK) { if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SKIP_PLANTING_BEANS)) { gSaveContext.sceneFlags[SCENE_DEATH_MOUNTAIN_CRATER].swch |= (1 << 3); @@ -959,6 +977,18 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l case VB_CRAWL: *should = *should && Flags_GetRandomizerInf(RAND_INF_CAN_CRAWL); break; + case VB_CAN_BUY_SHOP_SHIELD_OR_TUNIC: { + // Gate non-randomized shop shields/tunics behind finding a non-shop copy. + if (RAND_GET_OPTION(RSK_SHOP_SHIELDS_AND_TUNICS_ONLY_REFILL).Is(RO_GENERIC_ON)) { + EnGirlACanBuyResult* canBuy = va_arg(args, EnGirlACanBuyResult*); + RandomizerInf requiredInf = (RandomizerInf)va_arg(args, int); + if (!Flags_GetRandomizerInf(requiredInf)) { + *canBuy = CANBUY_RESULT_CANT_GET_NOW; + *should = true; + } + } + break; + } case VB_ALLOW_ENTRANCE_CS_FOR_EITHER_AGE: { s32 entranceIndex = va_arg(args, s32); diff --git a/soh/soh/Enhancements/randomizer/item.cpp b/soh/soh/Enhancements/randomizer/item.cpp index 3ab5080d0f..14a35a8d29 100644 --- a/soh/soh/Enhancements/randomizer/item.cpp +++ b/soh/soh/Enhancements/randomizer/item.cpp @@ -78,6 +78,12 @@ const std::string& Item::GetColor() const { } bool Item::IsAdvancement() const { + // With the shop shield/tunic gate on, a found Deku/Hylian Shield unlocks its shop copy, so it must + // be treated as progression. Tunics already are. + if (!advancement && (randomizerGet == RG_DEKU_SHIELD || randomizerGet == RG_HYLIAN_SHIELD) && + Context::GetInstance()->GetOption(RSK_SHOP_SHIELDS_AND_TUNICS_ONLY_REFILL).Is(RO_GENERIC_ON)) { + return true; + } return advancement; } @@ -478,6 +484,23 @@ bool Item::IsMajorItem() const { return IsAdvancement(); } +bool Item::IsShieldOrTunic() const { + switch (randomizerGet) { + case RG_DEKU_SHIELD: + case RG_HYLIAN_SHIELD: + case RG_MIRROR_SHIELD: + case RG_GORON_TUNIC: + case RG_ZORA_TUNIC: + case RG_BUY_DEKU_SHIELD: + case RG_BUY_HYLIAN_SHIELD: + case RG_BUY_GORON_TUNIC: + case RG_BUY_ZORA_TUNIC: + return true; + default: + return false; + } +} + RandomizerHintTextKey Item::GetHintKey() const { return hintKey; } diff --git a/soh/soh/Enhancements/randomizer/item.h b/soh/soh/Enhancements/randomizer/item.h index 3c8e9744a3..0404c46a67 100644 --- a/soh/soh/Enhancements/randomizer/item.h +++ b/soh/soh/Enhancements/randomizer/item.h @@ -60,6 +60,7 @@ class Item { bool IsPlaythrough() const; bool IsBottleItem() const; bool IsMajorItem() const; + bool IsShieldOrTunic() const; RandomizerHintTextKey GetHintKey() const; const HintText& GetHint() const; GetItemCategory GetCategory(); diff --git a/soh/soh/Enhancements/randomizer/logic.cpp b/soh/soh/Enhancements/randomizer/logic.cpp index d9b8b9e598..9aa6323d93 100644 --- a/soh/soh/Enhancements/randomizer/logic.cpp +++ b/soh/soh/Enhancements/randomizer/logic.cpp @@ -1269,6 +1269,28 @@ bool Logic::BombchusEnabled() { : HasItem(RG_BOMB_BAG); } +// With the shop shield/tunic gate enabled, a shop slot selling a shield/tunic is considered not-for-sale +// in logic until the matching item has been found in the world (which sets its RandomizerInf). Shop slots +// are randomized, so this keys off the item actually placed in the slot rather than a fixed location. +bool Logic::ShopItemNotForSale(RandomizerCheck loc) { + if (ctx->GetOption(RSK_SHOP_SHIELDS_AND_TUNICS_ONLY_REFILL).IsNot(RO_GENERIC_ON) || + StaticData::GetLocation(loc)->GetRCType() != RCTYPE_SHOP) { + return false; + } + switch (ctx->GetItemLocation(loc)->GetPlacedRandomizerGet()) { + case RG_BUY_DEKU_SHIELD: + return !CheckRandoInf(RAND_INF_HAS_FOUND_DEKU_SHIELD); + case RG_BUY_HYLIAN_SHIELD: + return !CheckRandoInf(RAND_INF_HAS_FOUND_HYLIAN_SHIELD); + case RG_BUY_GORON_TUNIC: + return !CheckRandoInf(RAND_INF_HAS_FOUND_GORON_TUNIC); + case RG_BUY_ZORA_TUNIC: + return !CheckRandoInf(RAND_INF_HAS_FOUND_ZORA_TUNIC); + default: + return false; + } +} + // TODO: Implement Ammo Drop Setting in place of bombchu drops bool Logic::BombchuRefill() { return Get(LOGIC_BUY_BOMBCHUS) || Get(LOGIC_COULD_PLAY_BOWLING) || Get(LOGIC_CARPET_MERCHANT) || @@ -2081,6 +2103,23 @@ void Logic::ApplyItemEffect(Item& item, bool state) { } break; case ITEMTYPE_EQUIP: { RandomizerGet itemRG = item.GetRandomizerGet(); + // Finding a non-shop shield/tunic unlocks its matching shop copy when that gate is enabled. + switch (itemRG) { + case RG_DEKU_SHIELD: + SetRandoInf(RAND_INF_HAS_FOUND_DEKU_SHIELD, state); + break; + case RG_HYLIAN_SHIELD: + SetRandoInf(RAND_INF_HAS_FOUND_HYLIAN_SHIELD, state); + break; + case RG_GORON_TUNIC: + SetRandoInf(RAND_INF_HAS_FOUND_GORON_TUNIC, state); + break; + case RG_ZORA_TUNIC: + SetRandoInf(RAND_INF_HAS_FOUND_ZORA_TUNIC, state); + break; + default: + break; + } if (itemRG == RG_DEKU_SHIELD || itemRG == RG_HYLIAN_SHIELD) { return; } diff --git a/soh/soh/Enhancements/randomizer/logic.h b/soh/soh/Enhancements/randomizer/logic.h index 7c8f591f75..9f5883e6a9 100644 --- a/soh/soh/Enhancements/randomizer/logic.h +++ b/soh/soh/Enhancements/randomizer/logic.h @@ -77,6 +77,7 @@ class Logic { bool CanAttack(); bool BombchusEnabled(); bool BombchuRefill(); + bool ShopItemNotForSale(RandomizerCheck loc); bool HookshotOrBoomerang(); bool ScarecrowsSong(); bool BlueFire(); diff --git a/soh/soh/Enhancements/randomizer/option_descriptions.cpp b/soh/soh/Enhancements/randomizer/option_descriptions.cpp index 6200b08f10..de0ff371fb 100644 --- a/soh/soh/Enhancements/randomizer/option_descriptions.cpp +++ b/soh/soh/Enhancements/randomizer/option_descriptions.cpp @@ -398,6 +398,10 @@ void Settings::CreateOptionDescriptions() { "After choosing a price, set it to the affordable amount based on the wallet required.\n\n" "Affordable prices per tier: starter = 1, adult = 100, giant = 201, tycoon = 501\n\n" "Use this to enable wallet tier locking, but make shop items not as expensive as they could be."; + mOptionDescriptions[RSK_SHOP_SHIELDS_AND_TUNICS_ONLY_REFILL] = + "Non-randomized shields and tunics sold in shops cannot be purchased until you have first found a shield " + "elsewhere. " + "Regions containing a shield or tunic will not be hinted foolish."; mOptionDescriptions[RSK_FISHSANITY] = "Off - Fish will not be shuffled. No changes will be made to fishing behavior.\n\n" "Shuffle only Hyrule Loach - Allows you to earn an item by catching the Hyrule Loach at the fishing pond and " diff --git a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerInf.h b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerInf.h index ddf673ac48..7f142c787b 100644 --- a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerInf.h +++ b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerInf.h @@ -2925,6 +2925,13 @@ RANDO_ENUM_ITEM(RAND_INF_GANONS_CASTLE_MQ_WATER_TRIAL_SECOND_DOOR_RED_ICE_3) RANDO_ENUM_ITEM(RAND_INF_GANONS_CASTLE_MQ_WATER_TRIAL_SECOND_DOOR_RED_ICE_4) RANDO_ENUM_ITEM(RAND_INF_GANONS_CASTLE_MQ_WATER_TRIAL_SECOND_DOOR_RED_ICE_5) +// Set when a non-shop shield/tunic is found, gating the matching shop copy behind it +// (RSK_SHOP_SHIELDS_AND_TUNICS_ONLY_REFILL). +RANDO_ENUM_ITEM(RAND_INF_HAS_FOUND_DEKU_SHIELD) +RANDO_ENUM_ITEM(RAND_INF_HAS_FOUND_HYLIAN_SHIELD) +RANDO_ENUM_ITEM(RAND_INF_HAS_FOUND_GORON_TUNIC) +RANDO_ENUM_ITEM(RAND_INF_HAS_FOUND_ZORA_TUNIC) + RANDO_ENUM_ITEM(RAND_INF_MAX) RANDO_ENUM_END(RandomizerInf) diff --git a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerSettingKey.h b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerSettingKey.h index 08d489e3db..ce738a6fa6 100644 --- a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerSettingKey.h +++ b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerSettingKey.h @@ -76,6 +76,7 @@ RANDO_ENUM_ITEM(RSK_SHOPSANITY_PRICES_ADULT_WALLET_WEIGHT) RANDO_ENUM_ITEM(RSK_SHOPSANITY_PRICES_GIANT_WALLET_WEIGHT) RANDO_ENUM_ITEM(RSK_SHOPSANITY_PRICES_TYCOON_WALLET_WEIGHT) RANDO_ENUM_ITEM(RSK_SHOPSANITY_PRICES_AFFORDABLE) +RANDO_ENUM_ITEM(RSK_SHOP_SHIELDS_AND_TUNICS_ONLY_REFILL) RANDO_ENUM_ITEM(RSK_SHUFFLE_SCRUBS) RANDO_ENUM_ITEM(RSK_SCRUBS_PRICES) RANDO_ENUM_ITEM(RSK_SCRUBS_PRICES_FIXED_PRICE) diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index 0c13f70eba..0037d4ad16 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -650,6 +650,7 @@ void Settings::CreateOptions() { OPT_U8(RSK_SHOPSANITY_PRICES_GIANT_WALLET_WEIGHT, "Shops Giant Wallet Weight", {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShopsanityGiantWalletWeight"), mOptionDescriptions[RSK_SHOPSANITY_PRICES_GIANT_WALLET_WEIGHT], WIDGET_CVAR_SLIDER_INT, 10, true, nullptr, IMFLAG_NONE); OPT_U8(RSK_SHOPSANITY_PRICES_TYCOON_WALLET_WEIGHT, "Shops Tycoon Wallet Weight", {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShopsanityTycoonWalletWeight"), mOptionDescriptions[RSK_SHOPSANITY_PRICES_TYCOON_WALLET_WEIGHT], WIDGET_CVAR_SLIDER_INT, 10, true, nullptr, IMFLAG_NONE); OPT_BOOL(RSK_SHOPSANITY_PRICES_AFFORDABLE, "Shops Affordable Prices", CVAR_RANDOMIZER_SETTING("ShopsanityPricesAffordable"), mOptionDescriptions[RSK_SHOPSANITY_PRICES_AFFORDABLE]); + OPT_BOOL(RSK_SHOP_SHIELDS_AND_TUNICS_ONLY_REFILL, "Gate Shop Shields & Tunics", CVAR_RANDOMIZER_SETTING("ShopShieldsTunicsGate"), mOptionDescriptions[RSK_SHOP_SHIELDS_AND_TUNICS_ONLY_REFILL]); OPT_U8(RSK_SHUFFLE_TOKENS, "Token Shuffle", {"Off", "Dungeons", "Overworld", "All Tokens"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleTokens"), mOptionDescriptions[RSK_SHUFFLE_TOKENS], WIDGET_CVAR_COMBOBOX, RO_TOKENSANITY_OFF); OPT_U8(RSK_SHUFFLE_SCRUBS, "Scrubs Shuffle", {"Off", "One-Time Only", "All"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleScrubs"), mOptionDescriptions[RSK_SHUFFLE_SCRUBS], WIDGET_CVAR_COMBOBOX, RO_SCRUBS_OFF); OPT_CALLBACK(RSK_SHUFFLE_SCRUBS, { @@ -1910,6 +1911,7 @@ void Settings::CreateOptions() { &mOptions[RSK_SHOPSANITY_PRICES_GIANT_WALLET_WEIGHT], &mOptions[RSK_SHOPSANITY_PRICES_TYCOON_WALLET_WEIGHT], &mOptions[RSK_SHOPSANITY_PRICES_AFFORDABLE], + &mOptions[RSK_SHOP_SHIELDS_AND_TUNICS_ONLY_REFILL], &mOptions[RSK_SHUFFLE_SCRUBS], &mOptions[RSK_SCRUBS_PRICES], &mOptions[RSK_SCRUBS_PRICES_FIXED_PRICE], @@ -2142,6 +2144,7 @@ void Settings::CreateOptions() { &mOptions[RSK_SHOPSANITY_PRICES_GIANT_WALLET_WEIGHT], &mOptions[RSK_SHOPSANITY_PRICES_TYCOON_WALLET_WEIGHT], &mOptions[RSK_SHOPSANITY_PRICES_AFFORDABLE], + &mOptions[RSK_SHOP_SHIELDS_AND_TUNICS_ONLY_REFILL], &mOptions[RSK_FISHSANITY], &mOptions[RSK_FISHSANITY_POND_COUNT], &mOptions[RSK_FISHSANITY_AGE_SPLIT], diff --git a/soh/src/overlays/actors/ovl_En_GirlA/z_en_girla.c b/soh/src/overlays/actors/ovl_En_GirlA/z_en_girla.c index 9576c7a82f..e73609aa3e 100644 --- a/soh/src/overlays/actors/ovl_En_GirlA/z_en_girla.c +++ b/soh/src/overlays/actors/ovl_En_GirlA/z_en_girla.c @@ -664,6 +664,10 @@ s32 EnGirlA_CanBuy_Longsword(PlayState* play, EnGirlA* this) { } s32 EnGirlA_CanBuy_HylianShield(PlayState* play, EnGirlA* this) { + s32 canBuy; + if (GameInteractor_Should(VB_CAN_BUY_SHOP_SHIELD_OR_TUNIC, false, &canBuy, RAND_INF_HAS_FOUND_HYLIAN_SHIELD)) { + return canBuy; + } if (CHECK_OWNED_EQUIP_ALT(EQUIP_TYPE_SHIELD, EQUIP_INV_SHIELD_HYLIAN)) { return CANBUY_RESULT_CANT_GET_NOW; } @@ -677,6 +681,10 @@ s32 EnGirlA_CanBuy_HylianShield(PlayState* play, EnGirlA* this) { } s32 EnGirlA_CanBuy_DekuShield(PlayState* play, EnGirlA* this) { + s32 canBuy; + if (GameInteractor_Should(VB_CAN_BUY_SHOP_SHIELD_OR_TUNIC, false, &canBuy, RAND_INF_HAS_FOUND_DEKU_SHIELD)) { + return canBuy; + } if (CHECK_OWNED_EQUIP_ALT(EQUIP_TYPE_SHIELD, EQUIP_INV_SHIELD_DEKU)) { return CANBUY_RESULT_CANT_GET_NOW; } @@ -690,6 +698,10 @@ s32 EnGirlA_CanBuy_DekuShield(PlayState* play, EnGirlA* this) { } s32 EnGirlA_CanBuy_GoronTunic(PlayState* play, EnGirlA* this) { + s32 canBuy; + if (GameInteractor_Should(VB_CAN_BUY_SHOP_SHIELD_OR_TUNIC, false, &canBuy, RAND_INF_HAS_FOUND_GORON_TUNIC)) { + return canBuy; + } if (LINK_AGE_IN_YEARS == YEARS_CHILD && (!IS_RANDO || Randomizer_GetSettingValue(RSK_SHOPSANITY) == RO_SHOPSANITY_OFF)) { return CANBUY_RESULT_CANT_GET_NOW; @@ -707,6 +719,10 @@ s32 EnGirlA_CanBuy_GoronTunic(PlayState* play, EnGirlA* this) { } s32 EnGirlA_CanBuy_ZoraTunic(PlayState* play, EnGirlA* this) { + s32 canBuy; + if (GameInteractor_Should(VB_CAN_BUY_SHOP_SHIELD_OR_TUNIC, false, &canBuy, RAND_INF_HAS_FOUND_ZORA_TUNIC)) { + return canBuy; + } if (LINK_AGE_IN_YEARS == YEARS_CHILD && (!IS_RANDO || Randomizer_GetSettingValue(RSK_SHOPSANITY) == RO_SHOPSANITY_OFF)) { return CANBUY_RESULT_CANT_GET_NOW;