From 36d31e2bb8f82356847d53dc32289d4712678cbf Mon Sep 17 00:00:00 2001 From: gymnast86 Date: Mon, 3 Aug 2026 14:15:35 -0700 Subject: [PATCH] update data for flow node usage --- .../generator/data/flow_patches.yaml | 38 ++++ mods/randomizer/generator/data/locations.yaml | 147 ++++++++++++++++ mods/randomizer/generator/logic/hints.cpp | 4 + mods/randomizer/src/randomizer_context.cpp | 165 ++++++++++++------ mods/randomizer/src/randomizer_context.hpp | 2 + 5 files changed, 298 insertions(+), 58 deletions(-) diff --git a/mods/randomizer/generator/data/flow_patches.yaml b/mods/randomizer/generator/data/flow_patches.yaml index 51d99be4ac..43eb86aff5 100644 --- a/mods/randomizer/generator/data/flow_patches.yaml +++ b/mods/randomizer/generator/data/flow_patches.yaml @@ -42,18 +42,21 @@ query: 6 parameters: 0x0064 next node index: 0x222 + # Patch Charlo to take 100 rupees from Link instead of 30 - index: 0x34E type: event event: 41 parameters: 0x00000064 next node index: 0x226 + # Patch Charlo to add 100 rupees to his counter instead of 30 - index: 0x357 type: event event: 3 parameters: 0x00000064 next node index: 0x22B + # Patch Malo Mart door person to not check for time of day. # Change from branch node that checks time of day to event node # that does nothing. @@ -63,6 +66,22 @@ parameters: 0x00000000 next node index: 0x10C + # Patch Jovani to skip checking whether we've received the first + # or second reward and skip straight to the second reward if we're + # talking to him for the first time with 60 souls + - index: 0x844 + type: message + inf index: 0x322 + next flow index: 0x851 + + # Patch Jovani to check if we've received the 20 reward yet at + # the end of the 60 reward sequence. + - index: 0x81F + type: event + event: 14 + parameters: 0x00000063 + next node index: Jovani Check 20 Reward Obtained + 5: # zel_05.bmg # Patch Yeta flow to always give the map check even if the player has obtained the Ordon Cheese - index: 0x8 @@ -86,6 +105,13 @@ parameters: 0x00000000 next node index: 0x1AC + # Patch Cave of Ordeals Great Fairy to skip over the check + # for an empty bottle the first time + - index: 0x850 + type: message + inf index: 0x25B + next flow index: 0x202 + #6: # zel_06.bmg #7: # zel_07.bmg #8: # zel_08.bmg @@ -277,6 +303,18 @@ parameters: 0x00000000 next node index: 0x116 + # Check to see if we've obtained the 20 Poe Soul Reward. + # If we haven't then branch to giving the reward + - name: Jovani Check 20 Reward Obtained + type: branch + num results: 2 + query: 1 + parameters: 0x0272 + next node index: 0xFFFF + results: + - 0xFFFF # Yes, we've obtained the reward, do nothing + - Jovani Give 20 Reward + ################################################# # HINT SIGNS # ################################################# diff --git a/mods/randomizer/generator/data/locations.yaml b/mods/randomizer/generator/data/locations.yaml index 0a6372420d..6af1a39957 100644 --- a/mods/randomizer/generator/data/locations.yaml +++ b/mods/randomizer/generator/data/locations.yaml @@ -33,6 +33,11 @@ - Ordona Province - Npc Metadata: + FLW Event: + - Group: 1 + Index: 0x18F + Next Node Index: 0x116 + Parameters: 0x0001004A Name Lookup: - Uli Cradle Delivery Event Flag: 0x0301 @@ -44,6 +49,11 @@ - Npc - Ordona Province Metadata: + FLW Event: + - Group: 1 + Index: 0x6DF + Next Node Index: 0x44A + Parameters: 0x00010065 Name Lookup: - Ordon Cat Rescue Event Flag: 0x1408 @@ -277,6 +287,11 @@ - Npc - Ordona Province Metadata: + FLW Event: + - Group: 1 + Index: 0x345 + Next Node Index: 0x216 + Parameters: 0x00010021 Name Lookup: - Herding Goats Reward Event Flag: 0x4240 @@ -493,6 +508,11 @@ - Faron Woods - ARC Metadata: + FLW Event: + - Group: 6 + Index: 0x72 + Next Node Index: 0x54 + Parameters: 0x0001009D Name Lookup: - Coro Bottle Event Flag: 0x1A08 @@ -1471,6 +1491,17 @@ - ARC - Remote Location Metadata: + FLW Event: + # Win the game without ever using the hawkeye + - Group: 2 + Index: 0x124 + Next Node Index: 0xA6 + Parameters: 0x00010021 + # Win the game, but have used hawkeye before + - Group: 2 + Index: 0xE0 + Next Node Index: 0x71 + Parameters: 0x00010021 Name Lookup: - Talo Sharpshooting Event Flag: 0x0920 @@ -1483,6 +1514,11 @@ - Kakariko Village - ARC Metadata: + FLW Event: + - Group: 2 + Index: 0x19A + Next Node Index: 0xF5 + Parameters: 0x00010080 Name Lookup: - Renados Letter Event Flag: 0x0F80 @@ -1507,6 +1543,11 @@ - Kakariko Graveyard - ARC Metadata: + FLW Event: + - Group: 2 + Index: 0x55 + Next Node Index: 0x25 + Parameters: 0x00010031 Name Lookup: - Rutelas Blessing Event Flag: 0x0804 @@ -1519,6 +1560,11 @@ - Kakariko Graveyard - ARC Metadata: + FLW Event: + - Group: 2 + Index: 0x339 + Next Node Index: 0x1ED + Parameters: 0x0001003D Name Lookup: - Gift From Ralis Event Flag: 0x3B80 @@ -1887,6 +1933,11 @@ - ARC - Remote Location Metadata: + FLW Event: + - Group: 6 + Index: 0x610 + Next Node Index: 0x439 + Parameters: 0x000100E9 Name Lookup: - Skybook From Impaz Event Flag: 0x5F80 @@ -1922,6 +1973,11 @@ - Hidden Village - ARC Metadata: + FLW Event: + - Group: 6 + Index: 0x32 + Next Node Index: 0x2A + Parameters: 0x00010083 Name Lookup: - Ilia Charm Event Flag: 0x2280 @@ -2390,6 +2446,11 @@ - Castle Town - ARC Metadata: + FLW Event: + - Group: 4 + Index: 0x276 + Next Node Index: 0x1A5 + Parameters: 0x00010055 Name Lookup: - STAR Prize 1 Event Flag: 0x2308 @@ -2402,6 +2463,11 @@ - Castle Town - ARC Metadata: + FLW Event: + - Group: 4 + Index: 0x269 + Next Node Index: 0x1A1 + Parameters: 0x00010056 Name Lookup: - STAR Prize 2 Event Flag: 0x2301 @@ -2471,6 +2537,17 @@ - Castle Town - ARC Metadata: + FLW Event: + # Flow if less than 60 souls + - Group: 4 + Index: 0x841 + Next Node Index: 0x773 + Parameters: 0x00010075 + # Custom flow if 60 souls + - Group: 9 + Index: Jovani Give 20 Reward + Next Node Index: 0x77E + Parameters: 0x00010075 Name Lookup: - Jovani 20 Poe Soul Reward Event Flag: 0x5510 @@ -2485,6 +2562,11 @@ - ARC - Remote Location Metadata: + FLW Event: + - Group: 4 + Index: 0x852 + Next Node Index: 0x781 + Parameters: 0x00010007 Name Lookup: - Jovani 60 Poe Soul Reward Event Flag: 0x3820 @@ -2518,6 +2600,11 @@ - Castle Town - ARC Metadata: + FLW Event: + - Group: 4 + Index: 0x14D + Next Node Index: 0xC6 + Parameters: 0x00010081 Name Lookup: - Telma Invoice Event Flag: 0x2180 @@ -3040,6 +3127,11 @@ - Lake Hylia - ARC Metadata: + FLW Event: + - Group: 8 + Index: 0x180 + Next Node Index: 0xF7 + Parameters: 0x00010090 Name Lookup: - Auru Gift To Fyer Event Flag: 0x2520 @@ -3564,6 +3656,11 @@ - ARC - Remote Location Metadata: + FLW Event: + - Group: 8 + Index: 0x1FC + Next Node Index: 0x152 + Parameters: 0x00010021 Name Lookup: - Plumm Fruit Balloon Minigame Event Flag: 0x2380 @@ -3653,6 +3750,11 @@ - ARC - Remote Location Metadata: + FLW Event: + - Group: 8 + Index: 0x2E4 + Next Node Index: 0x208 + Parameters: 0x00010051 Name Lookup: - Iza Helping Hand Event Flag: 0x0B01 @@ -3666,6 +3768,11 @@ - ARC - Remote Location Metadata: + FLW Event: + - Group: 8 + Index: 0x2EE + Next Node Index: 0x20F + Parameters: 0x0001004F Name Lookup: - Iza Raging Rapids Minigame Event Flag: 0x5908 @@ -3898,6 +4005,11 @@ - Zoras Domain - ARC Metadata: + FLW Event: + - Group: 7 + Index: 0x8 + Next Node Index: 0x1 + Parameters: 0x00010051 Name Lookup: - Zoras Domain Underwater Goron Event Flag: 0x3D10 @@ -3987,6 +4099,11 @@ - Snowpeak Province - ARC Metadata: + FLW Event: + - Group: 6 + Index: 0x282 + Next Node Index: 0x1C6 + Parameters: 0x00010091 Name Lookup: - Ashei Sketch Event Flag: 0x2940 @@ -4077,6 +4194,11 @@ - ARC - Remote Location Metadata: + FLW Event: + - Group: 6 + Index: 0x2F8 + Next Node Index: 0x237 + Parameters: 0x00010021 Name Lookup: - Snowboard Racing Prize Event Flag: 0x3B10 @@ -4724,6 +4846,11 @@ - ARC - Remote Location Metadata: + FLW Event: + - Group: 5 + Index: 0x20A + Next Node Index: 0x18F + Parameters: 0x00010073 Name Lookup: - Cave of Ordeals Great Fairy Reward Event Flag: 0x3E40 @@ -4992,6 +5119,11 @@ - Big Key - ARC Metadata: + FLW Event: + - Group: 5 + Index: 0x237 + Next Node Index: 0x19B + Parameters: 0x000100F9 Name Lookup: - Goron Mines Gor Amato Key Shard Event Flag: 0x3008 # late @@ -5111,6 +5243,11 @@ - Big Key - ARC Metadata: + FLW Event: + - Group: 5 + Index: 0x259 + Next Node Index: 0x1AD + Parameters: 0x000100FA Name Lookup: - Goron Mines Gor Ebizo Key Shard Event Flag: 0x3702 # late @@ -5171,6 +5308,11 @@ - Big Key - ARC Metadata: + FLW Event: + - Group: 5 + Index: 0x248 + Next Node Index: 0x1A4 + Parameters: 0x000100FD Name Lookup: - Goron Mines Gor Liggs Key Shard Event Flag: 0x3701 # late @@ -5892,6 +6034,11 @@ - Dungeon Map - ARC Metadata: + FLW Event: + - Group: 5 + Index: 0x3C8 + Next Node Index: 0x265 + Parameters: 0x00010023 Name Lookup: - Snowpeak Ruins Mansion Map Event Flag: 0x0B10 # late diff --git a/mods/randomizer/generator/logic/hints.cpp b/mods/randomizer/generator/logic/hints.cpp index a8897b70e7..5e016d1eef 100644 --- a/mods/randomizer/generator/logic/hints.cpp +++ b/mods/randomizer/generator/logic/hints.cpp @@ -635,6 +635,10 @@ namespace randomizer::logic::hints { static void AssignHintSignHints(const location::LocationPool& hintSigns, std::vector hints, world::World* world) { size_t hintsPerSign = std::ceil(static_cast(hints.size()) / static_cast(hintSigns.size())); + // Don't bother placing hints if there are none + if (hintsPerSign == 0) { + return; + } auto& worlds = world->GetRandomizer()->GetWorlds(); auto& hintSignHints = world->GetHintSignHints(); auto hintSignHintsOriginal = hintSignHints; diff --git a/mods/randomizer/src/randomizer_context.cpp b/mods/randomizer/src/randomizer_context.cpp index fe4442303f..244d16995b 100644 --- a/mods/randomizer/src/randomizer_context.cpp +++ b/mods/randomizer/src/randomizer_context.cpp @@ -873,7 +873,7 @@ void randomizer_checkAndOverrideEntranceData(const char*& stageName, s8& roomNo, } } -static void randomizer_setTempFlag(RandomizerContext::itemLocationData data) { +void randomizer_setTempFlag(RandomizerContext::itemLocationData data) { // If stage is 0xFF, then this is an event flag if (data.stage == 0xFF) { g_randomizerState.mTrackerTempEventFlag = data.flag; @@ -1113,6 +1113,50 @@ static std::array CreateAttributeData(const YAML::Node& node, const std: RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { RandomizerContext randoData{}; + // Give custom flows and messages new indices as we read them in/create them + std::unordered_map customMessageIDs{}; + std::unordered_map customFlowIDs{}; + std::unordered_set usedMessageIDs{}; + std::unordered_set usedFlowIDs{}; + u16 curCustomMessageID = BASE_CUSTOM_MSG_AND_FLOW_ID; + u16 curCustomFlowID = BASE_CUSTOM_MSG_AND_FLOW_ID; + + // Helper functions for assigning new custom flow IDs/message IDs + auto handleCustomID = [](const std::string& name, auto& customIds, auto& usedIds, u16& curCustomID) { + u16 resultIndex{}; + // Check to see if we're setting a custom index + auto resultInt = randomizer::utility::str::toInt(name); + // If we have a regular index, then use that directly + if (resultInt.has_value()) { + resultIndex = resultInt.value(); + } else { + // If we don't, assume we're setting the index as custom + if (customIds.contains(name)) { + resultIndex = customIds[name]; + } else { + while (usedIds.contains(curCustomID)) { + ++curCustomID; + } + auto newIndex = curCustomID++; + resultIndex = newIndex; + customIds[name] = newIndex; + } + } + + usedIds.insert(resultIndex); + return resultIndex; + }; + + + auto handleCustomFlowID = [&](const std::string& name) { + return handleCustomID(name, customFlowIDs, usedFlowIDs, curCustomFlowID); + }; + + + auto handleCustomMessageID = [&](const std::string& name) { + return handleCustomID(name, customMessageIDs, usedMessageIDs, curCustomMessageID); + }; + // Settings we need to check ingame for (const auto& [setting, info] : *randomizer::seedgen::settings::GetAllSettingsInfo()) { if (info->NeedInGame()) { @@ -1254,6 +1298,60 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { } } + // Items that are given by FLW events. Override the item in the existing event with the + // randomized item + if (location->HasCategories("FLW Event")) { + for (const auto& flwEventNode : metaData["FLW Event"]) { + u8 group = flwEventNode["Group"].as(); + u16 index = handleCustomFlowID(flwEventNode["Index"].as()); + mesg_flow_node_event event{}; + event.type = 3; // event type node + event.event_idx = 8; + event.next_node_idx = handleCustomFlowID(flwEventNode["Next Node Index"].as()); + auto params = flwEventNode["Parameters"].as(); + // Zero out the spot for the item id + params &= 0xFFFFFF00; + // Put in the item id + params |= location->GetCurrentItem()->GetID(); + // Set the params in the correct order + event.params[0] = (params >> 24) & 0xFF; + event.params[1] = (params >> 16) & 0xFF; + event.params[2] = (params >> 8) & 0xFF; + event.params[3] = params & 0xFF; + + // Construct another FLW node to set the associated flag in a temporary variable + // right before we receive the item. This ensures that a tracker/AP can pick up + // on the fact that we've received the item. + auto newFlwIndex = handleCustomFlowID(location->GetName() + " Flag Set Node"); + mesg_flow_node_event flagEvent{}; + flagEvent.type = 3; // event type node + flagEvent.event_idx = 46; // Set temporary randomizer flag + + u8 stage{0xFF}; + u16 flag{0xFFFF}; + if (metaData["Event Flag"]) { + flag = metaData["Event Flag"].as(); + } else if (metaData["Switch Flag"]) { + stage = metaData["Switch Flag"]["Stage"].as(); + flag = metaData["Switch Flag"]["Flag"].as(); + } + flagEvent.params[0] = 0; + flagEvent.params[1] = stage; + flagEvent.params[2] = (flag >> 8) & 0xFF; + flagEvent.params[3] = flag & 0xFF; + + // Store the modified FLW nodes. The flag event takes the place of the original + // index we're modifying and then leads into the custom index of the event that + // sets up the item id + flagEvent.next_node_idx = newFlwIndex; + u32 key = (group << 16) | index; + randoData.mFlowPatches[key] = std::bit_cast(flagEvent); + + key = (CUSTOM_BMG_GROUP << 16) | newFlwIndex; + randoData.mFlowPatches[key] = std::bit_cast(event); + } + } + // Items that we lookup just by calling their location name if (location->HasCategories("Name Lookup")) { for (const auto& locationNameNode : metaData["Name Lookup"]) { @@ -1280,7 +1378,6 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { } else if (flagNode.IsMap()) { const auto& condition = flagNode.begin()->first.as(); if (world->EvaluateSettingCondition(condition)) { - randomizer::session::LogDebug(fmt::format("Setting flags for {}", condition).c_str()); for (const auto& conditionalFlag : flagNode.begin()->second) { const auto& flag = conditionalFlag.as(); randoData.mStartEventFlags.push_back(flag); @@ -1294,7 +1391,6 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { const auto& region = regionNode.first.as(); const auto& index = regionNode.second["Index"].as(); const auto& flags = regionNode.second["Flags"]; - randomizer::session::LogDebug(fmt::format("Setting region flags for {}", region).c_str()); // This seems kinda scuffed so maybe we change it later for (const auto& flagNode : flags) { if (flagNode.IsScalar()) { @@ -1400,49 +1496,6 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { } } - // Give custom flows and messages new indices as we read them in - std::unordered_map customMessageIDs{}; - std::unordered_map customFlowIDs{}; - std::unordered_set usedMessageIDs{}; - std::unordered_set usedFlowIDs{}; - u16 curCustomMessageID = BASE_CUSTOM_MSG_AND_FLOW_ID; - u16 curCustomFlowID = BASE_CUSTOM_MSG_AND_FLOW_ID; - - // Helper functions for assigning new custom flow IDs/message IDs - auto handleCustomID = [](const YAML::Node& node, auto& customIds, auto& usedIds, u16& curCustomID) { - u16 resultIndex{}; - // Check to see if we're setting a custom index - auto resultStr = node.as(); - auto resultInt = randomizer::utility::str::toInt(resultStr); - // If we have a regular index, then use that directly - if (resultInt.has_value()) { - resultIndex = resultInt.value(); - } else { - // If we don't, assume we're setting the index as custom - if (customIds.contains(resultStr)) { - resultIndex = customIds[resultStr]; - } else { - while (usedIds.contains(curCustomID)) { - ++curCustomID; - } - auto newIndex = curCustomID++; - resultIndex = newIndex; - customIds[resultStr] = newIndex; - } - } - - usedIds.insert(resultIndex); - return resultIndex; - }; - - auto handleCustomFlowID = [&](const YAML::Node& node) { - return handleCustomID(node, customFlowIDs, usedFlowIDs, curCustomFlowID); - }; - - auto handleCustomMessageID = [&](const YAML::Node& node) { - return handleCustomID(node, customMessageIDs, usedMessageIDs, curCustomMessageID); - }; - // Flow Patches auto flowPatches = LOAD_EMBED_YAML(RANDO_DATA_PATH "flow_patches.yaml"); for (const auto& groupNode : flowPatches) { @@ -1476,7 +1529,7 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { } } else { name = flowNode["name"].as(); - indices.push_back(handleCustomFlowID(flowNode["name"])); + indices.push_back(handleCustomFlowID(flowNode["name"].as())); } const auto& type = flowNode["type"].as(); @@ -1496,7 +1549,7 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { "do not match num results. (expected: {}. size: {})", name, branch->field_0x1, results.size())); } for (const auto& resultNode : results) { - auto resultIndex = handleCustomFlowID(resultNode); + auto resultIndex = handleCustomFlowID(resultNode.as()); for (auto index : indices) { u32 key = (groupNo << 16) | index; randoData.mFlowPatchesBranchOverrides[key].push_back(resultIndex); @@ -1508,7 +1561,7 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { auto event = reinterpret_cast(&value); event->type = 3; event->event_idx = flowNode["event"].as(); - event->next_node_idx = handleCustomFlowID(flowNode["next node index"]); + event->next_node_idx = handleCustomFlowID(flowNode["next node index"].as()); u32 params = flowNode["parameters"].as(); event->params[0] = (params >> 24) & 0xFF; event->params[1] = (params >> 16) & 0xFF; @@ -1517,8 +1570,8 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { } else if (type == "message") { auto message = reinterpret_cast(&value); message->type = 1; - message->msg_index = handleCustomMessageID(flowNode["inf index"]); - message->next_node_idx = handleCustomFlowID(flowNode["next flow index"]); + message->msg_index = handleCustomMessageID(flowNode["inf index"].as()); + message->next_node_idx = handleCustomFlowID(flowNode["next flow index"].as()); // If a custom message is too long, split it up among additional flow/message nodes if (message->msg_index >= BASE_CUSTOM_MSG_AND_FLOW_ID) { @@ -1548,13 +1601,9 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { auto extraTextName = textName + std::to_string(i + 1); world->AddNewText(extraTextName) = extraText[i]; - // Kinda silly, but means we don't need to create another function - // to handle direct string names. - YAML::Node node{}; - node["name"] = extraTextName; // Create new Flow and Message Ids for the split text object - auto newCustomFlowIndex = handleCustomFlowID(node["name"]); - auto newCustomMessageIndex = handleCustomMessageID(node["name"]); + auto newCustomFlowIndex = handleCustomFlowID(extraTextName); + auto newCustomMessageIndex = handleCustomMessageID(extraTextName); // Create the new flow node. We're storing its own flow index with // itself for now, but we'll shift it back to the previous node later @@ -1629,7 +1678,7 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { messageId = overrideNode["Message Id"].as(); } else { // If no message id specified, assume a custom one - messageId = handleCustomMessageID(overrideNode["Name"]); + messageId = handleCustomMessageID(overrideNode["Name"].as()); } u32 key = (group << 16) | messageId; for (auto language : randomizer::supportedLanguages) { diff --git a/mods/randomizer/src/randomizer_context.hpp b/mods/randomizer/src/randomizer_context.hpp index 46850260b0..acce792acd 100644 --- a/mods/randomizer/src/randomizer_context.hpp +++ b/mods/randomizer/src/randomizer_context.hpp @@ -222,6 +222,8 @@ void randomizer_checkAndOverrideEntranceData(const char*& i_Name, s8& i_RoomNo, * variable. This allows the tracker/Archipelago to know a location has been checked * when the item is received instead of some indeterminate amount of time afterward. */ +void randomizer_setTempFlag(RandomizerContext::itemLocationData); + void randomizer_setTempFlagForLocation(const std::string& locationName); void randomizer_setTempFlagForFLWOverride(u32 key);