Shuffle Masks (#5536)

Future improvements can build on this,
giving masks abilities,
or adding checks for trading mask to someone
This commit is contained in:
Philip Dubé
2026-01-17 05:00:03 +00:00
committed by GitHub
parent 69f151e79d
commit 44d351698b
18 changed files with 225 additions and 80 deletions

View File

@@ -385,12 +385,7 @@ static bool ShouldRenderItem(s16 fileIndex, u8 item) {
return false;
}
if (item == ITEM_MASK_KEATON && (HasItem(fileIndex, ITEM_WEIRD_EGG) || HasItem(fileIndex, ITEM_CHICKEN) ||
HasItem(fileIndex, ITEM_LETTER_ZELDA) || HasItem(fileIndex, ITEM_MASK_SKULL) ||
HasItem(fileIndex, ITEM_MASK_SPOOKY) || HasItem(fileIndex, ITEM_MASK_BUNNY) ||
HasItem(fileIndex, ITEM_MASK_GORON) || HasItem(fileIndex, ITEM_MASK_ZORA) ||
HasItem(fileIndex, ITEM_MASK_GERUDO) || HasItem(fileIndex, ITEM_MASK_TRUTH) ||
HasItem(fileIndex, ITEM_SOLD_OUT))) {
if (item == ITEM_MASK_KEATON && !HasItem(fileIndex, ITEM_MASK_KEATON)) {
return false;
}

View File

@@ -2081,6 +2081,15 @@ void StaticData::HintTable_Init_Item() {
CustomMessage("a rightward tone", /*german*/"ein rechtsseitiger Ton", /*french*/"une tonalité vers la droite")});
// /*spanish*/un tono hacia la derecha
hintTextTable[RHT_MASK_KEATON] = HintText(CustomMessage("a keaton mask", /*german*/"!!!", /*french*/"le Masque du Renard"), {CustomMessage("a mask", /*german*/"!!!", /*french*/"un masque")});
hintTextTable[RHT_MASK_SKULL] = HintText(CustomMessage("a skull mask", /*german*/"!!!", /*french*/"le Masque de Mort"), {CustomMessage("a mask", /*german*/"!!!", /*french*/"un masque")});
hintTextTable[RHT_MASK_SPOOKY] = HintText(CustomMessage("a spooky mask", /*german*/"!!!", /*french*/"le Masque d'Effroi"), {CustomMessage("a mask", /*german*/"!!!", /*french*/"un masque")});
hintTextTable[RHT_MASK_BUNNY] = HintText(CustomMessage("a bunny hood", /*german*/"!!!", /*french*/"le Masque du Lapin"), {CustomMessage("a mask", /*german*/"!!!", /*french*/"un masque")});
hintTextTable[RHT_MASK_GORON] = HintText(CustomMessage("a goron mask", /*german*/"!!!", /*french*/"le Masque de Goron"), {CustomMessage("a mask", /*german*/"!!!", /*french*/"un masque")});
hintTextTable[RHT_MASK_ZORA] = HintText(CustomMessage("a zora mask", /*german*/"!!!", /*french*/"le Masque de Zora"), {CustomMessage("a mask", /*german*/"!!!", /*french*/"un masque")});
hintTextTable[RHT_MASK_GERUDO] = HintText(CustomMessage("a gerudo mask", /*german*/"!!!", /*french*/"le Masque de Gerudo"), {CustomMessage("a mask", /*german*/"!!!", /*french*/"un masque")});
hintTextTable[RHT_MASK_TRUTH] = HintText(CustomMessage("a mask of truth", /*german*/"!!!", /*french*/"le Masque de Vérité"), {CustomMessage("a mask", /*german*/"!!!", /*french*/"un masque")});
hintTextTable[RHT_FISHING_POLE] = HintText(CustomMessage("a fishing pole", /*german*/"eine Angelrute", /*french*/"une canne à pêche"),
// /*spanish*/caña de pescar
{

View File

@@ -279,7 +279,7 @@ std::vector<std::pair<RandomizerCheck, std::function<bool()>>> conditionalAlways
std::make_pair(RC_DEKU_THEATER_MASK_OF_TRUTH,
[]() {
auto ctx = Rando::Context::GetInstance();
return !ctx->GetOption(RSK_MASK_SHOP_HINT) && !ctx->GetOption(RSK_COMPLETE_MASK_QUEST);
return !ctx->GetOption(RSK_MASK_SHOP_HINT) && !ctx->GetOption(RSK_MASK_QUEST);
}),
std::make_pair(RC_SONG_FROM_OCARINA_OF_TIME,
[]() {

View File

@@ -385,6 +385,17 @@ void GenerateItemPool() {
AddFixedItemToPool(RG_SKELETON_KEY, 1);
}
if (ctx->GetOption(RSK_MASK_QUEST).Is(RO_MASK_QUEST_SHUFFLE)) {
AddItemToPool(RG_KEATON_MASK, 2, 1, 1, 1);
AddItemToPool(RG_SKULL_MASK, 2, 1, 1, 1);
AddItemToPool(RG_SPOOKY_MASK, 2, 1, 1, 1);
AddItemToPool(RG_BUNNY_HOOD, 2, 1, 1, 1);
AddItemToPool(RG_GORON_MASK, 2, 1, 1, 1);
AddItemToPool(RG_ZORA_MASK, 2, 1, 1, 1);
AddItemToPool(RG_GERUDO_MASK, 2, 1, 1, 1);
AddItemToPool(RG_MASK_OF_TRUTH, 2, 1, 1, 1);
}
if (ctx->GetOption(RSK_ROCS_FEATHER)) {
AddItemToPool(RG_ROCS_FEATHER, 2, 1, 1, 1);
}

View File

@@ -1206,6 +1206,56 @@ void InitTrickNames() {
Text{ "Ganondorf's Key", "Ganondorf's Key", "Ganondorf's Key" },
};
trickNameTable[RG_KEATON_MASK] = {
// TODO_TRANSLATE
Text{ "Korok Mask" },
Text{ "Lynel Mask" },
Text{ "Cucco Mask" },
Text{ "Remlit Mask" },
};
trickNameTable[RG_SKULL_MASK] = {
// TODO_TRANSLATE
Text{ "Darknut Mask" },
Text{ "Stalfos Mask" },
Text{ "Captain's Hat" },
};
trickNameTable[RG_SPOOKY_MASK] = {
// TODO_TRANSLATE
Text{ "Gibdo Mask" },
Text{ "Garo's Mask" },
Text{ "Redead mask" },
};
trickNameTable[RG_BUNNY_HOOD] = {
// TODO_TRANSLATE
Text{ "Bunny Mask" },
Text{ "Bremen Mask" },
Text{ "Rabbit Hood" },
};
trickNameTable[RG_MASK_OF_TRUTH] = {
// TODO_TRANSLATE
Text{ "Feirce Diety Mask" },
Text{ "Majora's Mask" },
Text{ "Hero's Charm" },
};
trickNameTable[RG_GORON_MASK] = {
// TODO_TRANSLATE
Text{ "Stone Mask" },
Text{ "Darmani's Mask" },
Text{ "Goron Garb" },
};
trickNameTable[RG_ZORA_MASK] = {
// TODO_TRANSLATE
Text{ "Zora Costume" },
Text{ "Don Gero's Mask" },
Text{ "Mikau's Mask" },
};
trickNameTable[RG_GERUDO_MASK] = {
// TODO_TRANSLATE
Text{ "Great Fairy Mask" },
Text{ "Romani's Mask" },
Text{ "Gerudo Veil" },
};
trickNameTable[RG_GUARD_HOUSE_KEY] = {
// TODO_TRANSLATE
Text{ "Pot Room Key", "Pot Room Key", "Pot Room Key" },

View File

@@ -25,6 +25,7 @@ extern "C" {
#include "src/overlays/actors/ovl_Bg_Treemouth/z_bg_treemouth.h"
#include "src/overlays/actors/ovl_Bg_Jya_Bigmirror/z_bg_jya_bigmirror.h"
#include "src/overlays/actors/ovl_En_Si/z_en_si.h"
#include "src/overlays/actors/ovl_En_Ossan/z_en_ossan.h"
#include "src/overlays/actors/ovl_En_Shopnuts/z_en_shopnuts.h"
#include "src/overlays/actors/ovl_En_Dns/z_en_dns.h"
#include "src/overlays/actors/ovl_En_Gb/z_en_gb.h"
@@ -369,7 +370,7 @@ void RandomizerOnPlayerUpdateForRCQueueHandler() {
iceTrapScale = 0.0f;
randomizerQueuedCheck = rc;
randomizerQueuedItemEntry = getItemEntry;
SPDLOG_INFO("Queueing Item mod {} item {} from RC {}", getItemEntry.modIndex, getItemEntry.itemId,
SPDLOG_INFO("Queuing Item mod {} item {} from RC {}", getItemEntry.modIndex, getItemEntry.itemId,
static_cast<uint32_t>(rc));
if (
// Skipping ItemGet animation incompatible with checks that require closing a text box to finish
@@ -2195,6 +2196,11 @@ void RandomizerOnActorInitHandler(void* actorRef) {
}
}
if (actor->id == ACTOR_EN_OSSAN && actor->params == OSSAN_TYPE_MASK &&
RAND_GET_OPTION(RSK_MASK_QUEST) == RO_MASK_QUEST_SHUFFLE) {
Actor_Kill(actor);
}
if (actor->id == ACTOR_BG_TREEMOUTH && LINK_IS_ADULT &&
RAND_GET_OPTION(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF &&
(RAND_GET_OPTION(RSK_FOREST) == RO_CLOSED_FOREST_OFF ||

View File

@@ -337,7 +337,16 @@ void Rando::StaticData::InitItemTable() {
itemTable[RG_OCARINA_C_RIGHT_BUTTON] = Item(RG_OCARINA_C_RIGHT_BUTTON, Text{ "Ocarina C Right Button", "Touche C-Droit de l'Ocarina", "Taste C-Rechts der Okarina" }, ITEMTYPE_ITEM, GI_MAP, true, LOGIC_OCARINA_C_RIGHT_BUTTON, RHT_OCARINA_C_RIGHT_BUTTON, RG_OCARINA_C_RIGHT_BUTTON, OBJECT_GI_MAP, GID_STONE_OF_AGONY, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER, {"the ", "den ", "le "});
itemTable[RG_OCARINA_C_RIGHT_BUTTON].SetCustomDrawFunc(Randomizer_DrawOcarinaButton);
itemTable[RG_BRONZE_SCALE] = Item(RG_BRONZE_SCALE, Text{ "Bronze Scale", "Écaille de Bronze", "Bronzene Schuppe" }, ITEMTYPE_ITEM, GI_SCALE_SILVER, true, LOGIC_PROGRESSIVE_SCALE, RHT_BRONZE_SCALE, RG_BRONZE_SCALE, OBJECT_GI_SCALE, GID_SCALE_SILVER, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER, {"the ", "die ", "le "});
itemTable[RG_KEATON_MASK] = Item(RG_KEATON_MASK, Text{ "Keaton Mask", "Masque du Renard", TODO_TRANSLATE }, ITEMTYPE_ITEM, RG_KEATON_MASK, true, LOGIC_NONE, RHT_MASK_KEATON, RG_KEATON_MASK, OBJECT_GI_KI_TAN_MASK, GID_MASK_KEATON, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER);
itemTable[RG_SKULL_MASK] = Item(RG_SKULL_MASK, Text{ "Skull Mask", "Masque de Mort", TODO_TRANSLATE }, ITEMTYPE_ITEM, RG_SKULL_MASK, true, LOGIC_NONE, RHT_MASK_SKULL, RG_SKULL_MASK, OBJECT_GI_SKJ_MASK, GID_MASK_SKULL, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER);
itemTable[RG_SPOOKY_MASK] = Item(RG_SPOOKY_MASK, Text{ "Spooky Mask", "Masque d'Effroi", TODO_TRANSLATE }, ITEMTYPE_ITEM, RG_SPOOKY_MASK, true, LOGIC_NONE, RHT_MASK_SPOOKY, RG_SPOOKY_MASK, OBJECT_GI_REDEAD_MASK, GID_MASK_SPOOKY, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER);
itemTable[RG_BUNNY_HOOD] = Item(RG_BUNNY_HOOD, Text{ "Bunny Hood", "Masque du Lapin", TODO_TRANSLATE }, ITEMTYPE_ITEM, RG_BUNNY_HOOD, true, LOGIC_NONE, RHT_MASK_BUNNY, RG_BUNNY_HOOD, OBJECT_GI_RABIT_MASK, GID_MASK_BUNNY, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER);
itemTable[RG_GORON_MASK] = Item(RG_GORON_MASK, Text{ "Goron Mask", "Masque de Goron", TODO_TRANSLATE }, ITEMTYPE_ITEM, RG_GORON_MASK, true, LOGIC_NONE, RHT_MASK_GORON, RG_GORON_MASK, OBJECT_GI_GOLONMASK, GID_MASK_GORON, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER);
itemTable[RG_ZORA_MASK] = Item(RG_ZORA_MASK, Text{ "Zora Mask", "Masque de Zora", TODO_TRANSLATE }, ITEMTYPE_ITEM, RG_ZORA_MASK, true, LOGIC_NONE, RHT_MASK_ZORA, RG_ZORA_MASK, OBJECT_GI_ZORAMASK, GID_MASK_ZORA, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER);
itemTable[RG_GERUDO_MASK] = Item(RG_GERUDO_MASK, Text{ "Gerudo Mask", "Masque de Gerudo", TODO_TRANSLATE }, ITEMTYPE_ITEM, RG_GERUDO_MASK, true, LOGIC_NONE, RHT_MASK_GERUDO, RG_GERUDO_MASK, OBJECT_GI_GERUDOMASK, GID_MASK_GERUDO, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER);
itemTable[RG_MASK_OF_TRUTH] = Item(RG_MASK_OF_TRUTH, Text{ "Mask of Truth", "Masque de Vérité", TODO_TRANSLATE }, ITEMTYPE_ITEM, RG_MASK_OF_TRUTH, true, LOGIC_NONE, RHT_MASK_TRUTH, RG_MASK_OF_TRUTH, OBJECT_GI_TRUTH_MASK, GID_MASK_TRUTH, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER);
itemTable[RG_BRONZE_SCALE] = Item(RG_BRONZE_SCALE, Text{ "Bronze Scale", "Écaille de Bronze", "Bronzene Schuppe" }, ITEMTYPE_ITEM, GI_SCALE_SILVER, true, LOGIC_PROGRESSIVE_SCALE, RHT_BRONZE_SCALE, RG_BRONZE_SCALE, OBJECT_GI_SCALE, GID_SCALE_SILVER, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER, {"the ", "die ", "le "});
itemTable[RG_BRONZE_SCALE].SetCustomDrawFunc(Randomizer_DrawBronzeScale);
itemTable[RG_CRAWL] = Item(RG_CRAWL, Text{ "Crawl", "Ramper", "Kriechen" }, ITEMTYPE_ITEM, GI_SHIELD_DEKU, true, LOGIC_NONE, RHT_CRAWL, RG_CRAWL, OBJECT_GI_SHIELD_1, GID_SHIELD_DEKU, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER);

View File

@@ -1038,7 +1038,7 @@ void RegionTable_Init_SpiritTemple() {
//Exits
Entrance(RR_SPIRIT_TEMPLE_MQ_BEAMOS_PITS, []{return true;}),
//technically we only need to avoid them, but the sheer height and the moving walls makes getting to the top after only stunning them very difficult/impossible
Entrance(RR_SPIRIT_TEMPLE_MQ_BIG_WALL_UPPER, []{return /*(*/logic->CanKillEnemy(RE_KEESE)/*|| CanUse(RG_SKULL_MASK)) && CanClimbHigh()*/;}),
Entrance(RR_SPIRIT_TEMPLE_MQ_BIG_WALL_UPPER, []{return /*(*/logic->CanKillEnemy(RE_KEESE) || logic->CanUse(RG_SKULL_MASK)/*) && CanClimbHigh()*/;}),
});
areaTable[RR_SPIRIT_TEMPLE_MQ_BIG_WALL_UPPER] = Region("Spirit Temple MQ Big Wall Upper", SCENE_SPIRIT_TEMPLE, {

View File

@@ -110,8 +110,8 @@ void RegionTable_Init_LostWoods() {
areaTable[RR_DEKU_THEATER] = Region("Deku Theater", SCENE_GROTTOS, {}, {
//Locations
LOCATION(RC_DEKU_THEATER_SKULL_MASK, logic->IsChild && logic->Get(LOGIC_BORROW_SKULL_MASK)),
LOCATION(RC_DEKU_THEATER_MASK_OF_TRUTH, logic->IsChild && logic->Get(LOGIC_BORROW_RIGHT_MASKS)),
LOCATION(RC_DEKU_THEATER_SKULL_MASK, logic->CanUse(RG_SKULL_MASK)),
LOCATION(RC_DEKU_THEATER_MASK_OF_TRUTH, logic->CanUse(RG_MASK_OF_TRUTH)),
}, {
//Exits
Entrance(RR_LW_BEYOND_MIDO, []{return true;}),

View File

@@ -143,10 +143,10 @@ void RegionTable_Init_Market() {
//If it is forced on/a setting, a copy of these events should be added to root
//it also doesn't need you to open kak gate, but that might be best treated as a bug
EventAccess(LOGIC_CAN_BORROW_MASKS, []{return logic->HasItem(RG_ZELDAS_LETTER) && logic->Get(LOGIC_KAKARIKO_GATE_OPEN);}),
EventAccess(LOGIC_BORROW_SKULL_MASK, []{return ctx->GetOption(RSK_COMPLETE_MASK_QUEST) && logic->Get(LOGIC_CAN_BORROW_MASKS);}),
EventAccess(LOGIC_BORROW_SPOOKY_MASK, []{return ctx->GetOption(RSK_COMPLETE_MASK_QUEST) && logic->Get(LOGIC_CAN_BORROW_MASKS);}),
EventAccess(LOGIC_BORROW_BUNNY_HOOD, []{return ctx->GetOption(RSK_COMPLETE_MASK_QUEST) && logic->Get(LOGIC_CAN_BORROW_MASKS);}),
EventAccess(LOGIC_BORROW_RIGHT_MASKS, []{return ctx->GetOption(RSK_COMPLETE_MASK_QUEST) && logic->Get(LOGIC_CAN_BORROW_MASKS);}),
EventAccess(LOGIC_BORROW_SKULL_MASK, []{return ctx->GetOption(RSK_MASK_QUEST).Is(RO_MASK_QUEST_COMPLETED) && logic->Get(LOGIC_CAN_BORROW_MASKS);}),
EventAccess(LOGIC_BORROW_SPOOKY_MASK, []{return ctx->GetOption(RSK_MASK_QUEST).Is(RO_MASK_QUEST_COMPLETED) && logic->Get(LOGIC_CAN_BORROW_MASKS);}),
EventAccess(LOGIC_BORROW_BUNNY_HOOD, []{return ctx->GetOption(RSK_MASK_QUEST).Is(RO_MASK_QUEST_COMPLETED) && logic->Get(LOGIC_CAN_BORROW_MASKS);}),
EventAccess(LOGIC_BORROW_RIGHT_MASKS, []{return ctx->GetOption(RSK_MASK_QUEST).Is(RO_MASK_QUEST_COMPLETED) && logic->Get(LOGIC_CAN_BORROW_MASKS);}),
}, {
//Locations
LOCATION(RC_MASK_SHOP_HINT, true),

View File

@@ -120,6 +120,31 @@ bool Logic::HasItem(RandomizerGet itemName) {
return CheckQuestItem(RandoGetToQuestItem.at(itemName));
case RG_DOUBLE_DEFENSE:
return GetSaveContext()->isDoubleDefenseAcquired;
// Masks
case RG_SKULL_MASK:
switch (ctx->GetOption(RSK_MASK_QUEST).Get()) {
case RO_MASK_QUEST_VANILLA:
return Get(LOGIC_BORROW_SKULL_MASK);
case RO_MASK_QUEST_COMPLETED:
return HasItem(RG_ZELDAS_LETTER) && Get(LOGIC_KAKARIKO_GATE_OPEN);
case RO_MASK_QUEST_SHUFFLE:
return CheckRandoInf(RAND_INF_CHILD_TRADES_HAS_MASK_SKULL);
default:
assert(false);
return false;
}
case RG_MASK_OF_TRUTH:
switch (ctx->GetOption(RSK_MASK_QUEST).Get()) {
case RO_MASK_QUEST_VANILLA:
return Get(LOGIC_BORROW_RIGHT_MASKS);
case RO_MASK_QUEST_COMPLETED:
return HasItem(RG_ZELDAS_LETTER) && Get(LOGIC_KAKARIKO_GATE_OPEN);
case RO_MASK_QUEST_SHUFFLE:
return CheckRandoInf(RAND_INF_CHILD_TRADES_HAS_MASK_TRUTH);
default:
assert(false);
return false;
}
case RG_FISHING_POLE:
case RG_ZELDAS_LETTER:
case RG_WEIRD_EGG:
@@ -349,6 +374,9 @@ bool Logic::CanUse(RandomizerGet itemName) {
return IsChild;
case RG_MAGIC_BEAN:
return IsChild;
case RG_SKULL_MASK:
case RG_MASK_OF_TRUTH:
return IsChild;
// Songs
case RG_ZELDAS_LULLABY:
@@ -973,7 +1001,7 @@ bool Logic::CanAvoidEnemy(RandomizerEnemy enemy, bool grounded, uint8_t quantity
case RE_KEESE:
case RE_FIRE_KEESE:
case RE_GUAY:
return CanUse(RG_NUTS);
return CanUse(RG_NUTS) || CanUse(RG_SKULL_MASK);
case RE_BLUE_BUBBLE:
// RANDOTODO Trick to use shield hylian shield as child to stun these guys
return !grounded || CanUse(RG_NUTS) || HookshotOrBoomerang() || CanStandingShield();
@@ -1564,6 +1592,14 @@ std::map<RandomizerGet, uint32_t> Logic::RandoGetToRandInf = {
{ RG_OCARINA_C_DOWN_BUTTON, RAND_INF_HAS_OCARINA_C_DOWN },
{ RG_OCARINA_C_LEFT_BUTTON, RAND_INF_HAS_OCARINA_C_LEFT },
{ RG_OCARINA_C_RIGHT_BUTTON, RAND_INF_HAS_OCARINA_C_RIGHT },
{ RG_KEATON_MASK, RAND_INF_CHILD_TRADES_HAS_MASK_KEATON },
{ RG_SKULL_MASK, RAND_INF_CHILD_TRADES_HAS_MASK_SKULL },
{ RG_SPOOKY_MASK, RAND_INF_CHILD_TRADES_HAS_MASK_SPOOKY },
{ RG_BUNNY_HOOD, RAND_INF_CHILD_TRADES_HAS_MASK_BUNNY },
{ RG_GORON_MASK, RAND_INF_CHILD_TRADES_HAS_MASK_GORON },
{ RG_ZORA_MASK, RAND_INF_CHILD_TRADES_HAS_MASK_ZORA },
{ RG_GERUDO_MASK, RAND_INF_CHILD_TRADES_HAS_MASK_GERUDO },
{ RG_MASK_OF_TRUTH, RAND_INF_CHILD_TRADES_HAS_MASK_TRUTH },
{ RG_SKELETON_KEY, RAND_INF_HAS_SKELETON_KEY },
{ RG_GREG_RUPEE, RAND_INF_GREG_FOUND },
{ RG_FISHING_POLE, RAND_INF_FISHING_POLE_FOUND },
@@ -1946,6 +1982,14 @@ void Logic::ApplyItemEffect(Item& item, bool state) {
case RG_OCARINA_C_DOWN_BUTTON:
case RG_OCARINA_C_LEFT_BUTTON:
case RG_OCARINA_C_RIGHT_BUTTON:
case RG_KEATON_MASK:
case RG_SKULL_MASK:
case RG_SPOOKY_MASK:
case RG_BUNNY_HOOD:
case RG_GORON_MASK:
case RG_ZORA_MASK:
case RG_GERUDO_MASK:
case RG_MASK_OF_TRUTH:
case RG_GREG_RUPEE:
case RG_FISHING_POLE:
case RG_GUARD_HOUSE_KEY:

View File

@@ -626,8 +626,13 @@ void Settings::CreateOptionDescriptions() {
"Start with Zelda's Letter and the item Impa would normally give you and skip the sequence up "
"until after meeting Zelda. Disables the ability to shuffle Weird Egg.";
mOptionDescriptions[RSK_SKIP_EPONA_RACE] = "Epona can be summoned with Epona's Song without needing to race Ingo.";
mOptionDescriptions[RSK_COMPLETE_MASK_QUEST] =
"Once the Happy Mask Shop is opened, all masks will be available to be borrowed.";
mOptionDescriptions[RSK_MASK_QUEST] =
"How masks are acquired.\n"
"Vanilla - Mask trade quest.\n"
"\n"
"Completed - Once the Happy Mask Shop is opened, all masks will be available to be borrowed.\n"
"\n"
"Shuffle - Happy Mask Shop never opens, masks are shuffled with rest of items.";
mOptionDescriptions[RSK_SKIP_SCARECROWS_SONG] =
"Start with the ability to summon Pierre the Scarecrow. Pulling out an Ocarina in the usual locations will "
"automatically summon him.\n"

View File

@@ -120,46 +120,42 @@ std::unordered_map<std::string, SceneID> spoilerFileDungeonToScene = {
{ "Ganon's Castle", SCENE_INSIDE_GANONS_CASTLE }
};
std::unordered_map<s16, s16> getItemIdToItemId = {
{ GI_BOW, ITEM_BOW },
{ GI_ARROW_FIRE, ITEM_ARROW_FIRE },
{ GI_DINS_FIRE, ITEM_DINS_FIRE },
{ GI_SLINGSHOT, ITEM_SLINGSHOT },
{ GI_OCARINA_FAIRY, ITEM_OCARINA_FAIRY },
{ GI_OCARINA_OOT, ITEM_OCARINA_TIME },
{ GI_HOOKSHOT, ITEM_HOOKSHOT },
{ GI_LONGSHOT, ITEM_LONGSHOT },
{ GI_ARROW_ICE, ITEM_ARROW_ICE },
{ GI_FARORES_WIND, ITEM_FARORES_WIND },
{ GI_BOOMERANG, ITEM_BOOMERANG },
{ GI_LENS, ITEM_LENS },
{ GI_HAMMER, ITEM_HAMMER },
{ GI_ARROW_LIGHT, ITEM_ARROW_LIGHT },
{ GI_NAYRUS_LOVE, ITEM_NAYRUS_LOVE },
{ GI_BOTTLE, ITEM_BOTTLE },
{ GI_POTION_RED, ITEM_POTION_RED },
{ GI_POTION_GREEN, ITEM_POTION_GREEN },
{ GI_POTION_BLUE, ITEM_POTION_BLUE },
{ GI_FAIRY, ITEM_FAIRY },
{ GI_FISH, ITEM_FISH },
{ GI_MILK_BOTTLE, ITEM_MILK_BOTTLE },
{ GI_LETTER_RUTO, ITEM_LETTER_RUTO },
{ GI_BLUE_FIRE, ITEM_BLUE_FIRE },
{ GI_BUGS, ITEM_BUG },
{ GI_BIG_POE, ITEM_BIG_POE },
{ GI_POE, ITEM_POE },
{ GI_WEIRD_EGG, ITEM_WEIRD_EGG },
{ GI_LETTER_ZELDA, ITEM_LETTER_ZELDA },
{ GI_POCKET_EGG, ITEM_POCKET_EGG },
{ GI_COJIRO, ITEM_COJIRO },
{ GI_ODD_MUSHROOM, ITEM_ODD_MUSHROOM },
{ GI_ODD_POTION, ITEM_ODD_POTION },
{ GI_SAW, ITEM_SAW },
{ GI_SWORD_BROKEN, ITEM_SWORD_BROKEN },
{ GI_PRESCRIPTION, ITEM_PRESCRIPTION },
{ GI_FROG, ITEM_FROG },
{ GI_EYEDROPS, ITEM_EYEDROPS },
{ GI_CLAIM_CHECK, ITEM_CLAIM_CHECK },
// used for items that only set a rand inf when obtained
std::map<RandomizerGet, RandomizerInf> randomizerGetToRandInf = {
{ RG_FISHING_POLE, RAND_INF_FISHING_POLE_FOUND },
{ RG_BRONZE_SCALE, RAND_INF_CAN_SWIM },
{ RG_QUIVER_INF, RAND_INF_HAS_INFINITE_QUIVER },
{ RG_BOMB_BAG_INF, RAND_INF_HAS_INFINITE_BOMB_BAG },
{ RG_BULLET_BAG_INF, RAND_INF_HAS_INFINITE_BULLET_BAG },
{ RG_STICK_UPGRADE_INF, RAND_INF_HAS_INFINITE_STICK_UPGRADE },
{ RG_NUT_UPGRADE_INF, RAND_INF_HAS_INFINITE_NUT_UPGRADE },
{ RG_MAGIC_INF, RAND_INF_HAS_INFINITE_MAGIC_METER },
{ RG_BOMBCHU_INF, RAND_INF_HAS_INFINITE_BOMBCHUS },
{ RG_WALLET_INF, RAND_INF_HAS_INFINITE_MONEY },
{ RG_OCARINA_A_BUTTON, RAND_INF_HAS_OCARINA_A },
{ RG_OCARINA_C_UP_BUTTON, RAND_INF_HAS_OCARINA_C_UP },
{ RG_OCARINA_C_DOWN_BUTTON, RAND_INF_HAS_OCARINA_C_DOWN },
{ RG_OCARINA_C_LEFT_BUTTON, RAND_INF_HAS_OCARINA_C_LEFT },
{ RG_OCARINA_C_RIGHT_BUTTON, RAND_INF_HAS_OCARINA_C_RIGHT },
{ RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL, RAND_INF_DEATH_MOUNTAIN_CRATER_BEAN_SOUL },
{ RG_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL, RAND_INF_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL },
{ RG_DESERT_COLOSSUS_BEAN_SOUL, RAND_INF_DESERT_COLOSSUS_BEAN_SOUL },
{ RG_GERUDO_VALLEY_BEAN_SOUL, RAND_INF_GERUDO_VALLEY_BEAN_SOUL },
{ RG_GRAVEYARD_BEAN_SOUL, RAND_INF_GRAVEYARD_BEAN_SOUL },
{ RG_KOKIRI_FOREST_BEAN_SOUL, RAND_INF_KOKIRI_FOREST_BEAN_SOUL },
{ RG_LAKE_HYLIA_BEAN_SOUL, RAND_INF_LAKE_HYLIA_BEAN_SOUL },
{ RG_LOST_WOODS_BRIDGE_BEAN_SOUL, RAND_INF_LOST_WOODS_BRIDGE_BEAN_SOUL },
{ RG_LOST_WOODS_BEAN_SOUL, RAND_INF_LOST_WOODS_BEAN_SOUL },
{ RG_ZORAS_RIVER_BEAN_SOUL, RAND_INF_ZORAS_RIVER_BEAN_SOUL },
{ RG_GOHMA_SOUL, RAND_INF_GOHMA_SOUL },
{ RG_KING_DODONGO_SOUL, RAND_INF_KING_DODONGO_SOUL },
{ RG_BARINADE_SOUL, RAND_INF_BARINADE_SOUL },
{ RG_PHANTOM_GANON_SOUL, RAND_INF_PHANTOM_GANON_SOUL },
{ RG_VOLVAGIA_SOUL, RAND_INF_VOLVAGIA_SOUL },
{ RG_MORPHA_SOUL, RAND_INF_MORPHA_SOUL },
{ RG_BONGO_BONGO_SOUL, RAND_INF_BONGO_BONGO_SOUL },
{ RG_TWINROVA_SOUL, RAND_INF_TWINROVA_SOUL },
{ RG_GANON_SOUL, RAND_INF_GANON_SOUL },
};
#ifdef _MSC_VER
@@ -299,6 +295,10 @@ ItemObtainability Randomizer::GetItemObtainabilityFromRandomizerCheck(Randomizer
}
ItemObtainability Randomizer::GetItemObtainabilityFromRandomizerGet(RandomizerGet randoGet) {
if (randomizerGetToRandInf.find(randoGet) != randomizerGetToRandInf.end()) {
return Flags_GetRandomizerInf(randomizerGetToRandInf.find(randoGet)->second) ? CANT_OBTAIN_ALREADY_HAVE
: CAN_OBTAIN;
}
// This is needed since Plentiful item pool also adds a third progressive wallet
// but we should not get Tycoon's Wallet from it if it is off.
@@ -702,18 +702,6 @@ ItemObtainability Randomizer::GetItemObtainabilityFromRandomizerGet(RandomizerGe
case RG_LIGHT_MEDALLION:
return !CHECK_QUEST_ITEM(QUEST_MEDALLION_LIGHT) ? CAN_OBTAIN : CANT_OBTAIN_ALREADY_HAVE;
// Ocarina Buttons
case RG_OCARINA_A_BUTTON:
return Flags_GetRandomizerInf(RAND_INF_HAS_OCARINA_A) ? CANT_OBTAIN_ALREADY_HAVE : CAN_OBTAIN;
case RG_OCARINA_C_LEFT_BUTTON:
return Flags_GetRandomizerInf(RAND_INF_HAS_OCARINA_C_LEFT) ? CANT_OBTAIN_ALREADY_HAVE : CAN_OBTAIN;
case RG_OCARINA_C_RIGHT_BUTTON:
return Flags_GetRandomizerInf(RAND_INF_HAS_OCARINA_C_RIGHT) ? CANT_OBTAIN_ALREADY_HAVE : CAN_OBTAIN;
case RG_OCARINA_C_UP_BUTTON:
return Flags_GetRandomizerInf(RAND_INF_HAS_OCARINA_C_UP) ? CANT_OBTAIN_ALREADY_HAVE : CAN_OBTAIN;
case RG_OCARINA_C_DOWN_BUTTON:
return Flags_GetRandomizerInf(RAND_INF_HAS_OCARINA_C_DOWN) ? CANT_OBTAIN_ALREADY_HAVE : CAN_OBTAIN;
case RG_RECOVERY_HEART:
case RG_GREEN_RUPEE:
case RG_GREG_RUPEE:
@@ -3887,6 +3875,12 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) {
Flags_SetRandomizerInf(
(RandomizerInf)((int)RAND_INF_GUARD_HOUSE_UNLOCKED + ((item - RG_GUARD_HOUSE_KEY) * 2) + 1));
return Return_Item_Entry(giEntry, RG_NONE);
} else if (item >= RG_KEATON_MASK && item <= RG_MASK_OF_TRUTH) {
Flags_SetRandomizerInf((RandomizerInf)((int)RAND_INF_CHILD_TRADES_HAS_MASK_KEATON + (item - RG_KEATON_MASK)));
if (INV_CONTENT(ITEM_TRADE_CHILD) == ITEM_NONE) {
INV_CONTENT(ITEM_TRADE_CHILD) = (int)ITEM_MASK_KEATON + (item - RG_KEATON_MASK);
}
return Return_Item_Entry(giEntry, RG_NONE);
}
switch (item) {

View File

@@ -4641,6 +4641,14 @@ typedef enum {
RG_DEKU_STICK_CAPACITY_30,
RG_HOOKSHOT,
RG_LONGSHOT,
RG_KEATON_MASK,
RG_SKULL_MASK,
RG_SPOOKY_MASK,
RG_BUNNY_HOOD,
RG_GORON_MASK,
RG_ZORA_MASK,
RG_GERUDO_MASK,
RG_MASK_OF_TRUTH,
// Overworld keys
RG_GUARD_HOUSE_KEY,
@@ -5806,6 +5814,14 @@ typedef enum {
RHT_OCARINA_C_DOWN_BUTTON,
RHT_OCARINA_C_LEFT_BUTTON,
RHT_OCARINA_C_RIGHT_BUTTON,
RHT_MASK_KEATON,
RHT_MASK_SKULL,
RHT_MASK_SPOOKY,
RHT_MASK_BUNNY,
RHT_MASK_GORON,
RHT_MASK_ZORA,
RHT_MASK_GERUDO,
RHT_MASK_TRUTH,
RHT_BRONZE_SCALE,
RHT_CRAWL,
RHT_OPEN_CHEST,
@@ -6508,7 +6524,7 @@ typedef enum {
RSK_SHUFFLE_CHEST_MINIGAME,
RSK_BIG_POE_COUNT,
RSK_SKIP_EPONA_RACE,
RSK_COMPLETE_MASK_QUEST,
RSK_MASK_QUEST,
RSK_SKIP_SCARECROWS_SONG,
RSK_SKIP_PLANTING_BEANS,
RSK_SKULLS_SUNS_SONG,
@@ -6887,6 +6903,13 @@ typedef enum {
RO_STARTING_OCARINA_TIME,
} RandoOptionStartingOcarina;
// Mask Quest Settings (vanilla, completed, shuffle)
typedef enum {
RO_MASK_QUEST_VANILLA,
RO_MASK_QUEST_COMPLETED,
RO_MASK_QUEST_SHUFFLE,
} RandoOptionMaskQuest;
// Item Pool Settings
typedef enum {
RO_ITEM_POOL_PLENTIFUL,

View File

@@ -469,7 +469,7 @@ extern "C" void Randomizer_InitSaveFile() {
}
// complete mask quest
if (Randomizer_GetSettingValue(RSK_COMPLETE_MASK_QUEST)) {
if (Randomizer_GetSettingValue(RSK_MASK_QUEST) == RO_MASK_QUEST_COMPLETED) {
Flags_SetInfTable(INFTABLE_GATE_GUARD_PUT_ON_KEATON_MASK);
Flags_SetEventChkInf(EVENTCHKINF_PAID_BACK_BUNNY_HOOD_FEE);

View File

@@ -1188,7 +1188,7 @@ void Settings::CreateOptions() {
mOptions[RSK_BIG_POES_HINT].Enable();
}
});
OPT_BOOL(RSK_COMPLETE_MASK_QUEST, "Complete Mask Quest", CVAR_RANDOMIZER_SETTING("CompleteMaskQuest"), mOptionDescriptions[RSK_COMPLETE_MASK_QUEST]);
OPT_U8(RSK_MASK_QUEST, "Mask Quest", {"Vanilla", "Completed", "Shuffle"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("CompleteMaskQuest"), mOptionDescriptions[RSK_MASK_QUEST], WIDGET_CVAR_COMBOBOX, 0);
OPT_U8(RSK_GOSSIP_STONE_HINTS, "Gossip Stone Hints", {"No Hints", "Need Nothing", "Mask of Truth", "Stone of Agony"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GossipStoneHints"), mOptionDescriptions[RSK_GOSSIP_STONE_HINTS], WIDGET_CVAR_COMBOBOX, RO_GOSSIP_STONES_NEED_NOTHING, false, nullptr, IMFLAG_NONE);
OPT_CALLBACK(RSK_GOSSIP_STONE_HINTS, {
if (CVarGetInteger(CVAR_RANDOMIZER_SETTING("GossipStoneHints"), RO_GOSSIP_STONES_NEED_NOTHING) ==
@@ -2193,7 +2193,7 @@ void Settings::CreateOptions() {
&mOptions[RSK_FULL_WALLETS],
&mOptions[RSK_SLINGBOW_BREAK_BEEHIVES],
&mOptions[RSK_SKIP_CHILD_ZELDA],
&mOptions[RSK_COMPLETE_MASK_QUEST],
&mOptions[RSK_MASK_QUEST],
&mOptions[RSK_SKIP_CHILD_STEALTH],
&mOptions[RSK_SKIP_PLANTING_BEANS],
&mOptions[RSK_SKIP_EPONA_RACE],
@@ -2704,7 +2704,6 @@ void Settings::CreateOptions() {
&mOptions[RSK_SKIP_SCARECROWS_SONG],
&mOptions[RSK_SKIP_PLANTING_BEANS],
&mOptions[RSK_BIG_POE_COUNT],
&mOptions[RSK_COMPLETE_MASK_QUEST],
});
mOptionGroups[RSG_MISC] = OptionGroup("Miscellaneous Settings",
{

View File

@@ -2120,7 +2120,7 @@ u8 Item_Give(PlayState* play, u8 item) {
}
}
}
// update the adult/child equips when rando'd (accounting for equp swapped hookshot as child)
// update the adult/child equips when rando'd (accounting for equip swapped hookshot as child)
if (IS_RANDO && LINK_IS_CHILD) {
for (i = 1; i < ARRAY_COUNT(gSaveContext.adultEquips.buttonItems); i++) {
if (gSaveContext.adultEquips.buttonItems[i] == ITEM_HOOKSHOT) {

View File

@@ -324,9 +324,9 @@ void KaleidoScope_HandleItemCycleExtras(PlayState* play, u8 slot, bool canCycle,
bool CanMaskSelect() {
if (IS_RANDO) {
return CVarGetInteger(CVAR_ENHANCEMENT("MaskSelect"), 0) &&
Flags_GetRandomizerInf(
RAND_INF_ZELDAS_LETTER); /* || Randomizer_GetSettingValue(RSK_SHUFFLE_CHILD_TRADE) */
return (CVarGetInteger(CVAR_ENHANCEMENT("MaskSelect"), 0) && Flags_GetRandomizerInf(RAND_INF_ZELDAS_LETTER) &&
Flags_GetInfTable(INFTABLE_SHOWED_ZELDAS_LETTER_TO_GATE_GUARD)) ||
Randomizer_GetSettingValue(RSK_MASK_QUEST) == RO_MASK_QUEST_SHUFFLE;
}
// only allow mask select when: