mirror of
https://github.com/HarbourMasters/Shipwright
synced 2026-06-10 05:03:29 -04:00
add option gating shields / tunics in shop behind finding one first (#6700)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<RandomizerArea> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DnsItemEntry> 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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ class Logic {
|
||||
bool CanAttack();
|
||||
bool BombchusEnabled();
|
||||
bool BombchuRefill();
|
||||
bool ShopItemNotForSale(RandomizerCheck loc);
|
||||
bool HookshotOrBoomerang();
|
||||
bool ScarecrowsSong();
|
||||
bool BlueFire();
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user