From 75ae2767e96ac20fa088264cd520decd362531c7 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 21 Aug 2026 00:54:00 -0600 Subject: [PATCH] Rework to use updated ItemService --- .../generator/data/flow_patches.yaml | 8 +- mods/randomizer/generator/data/locations.yaml | 233 +----- mods/randomizer/src/hooks.cpp | 83 +-- mods/randomizer/src/item.cpp | 8 +- mods/randomizer/src/messages.cpp | 692 +++++++++++++++--- mods/randomizer/src/messages.hpp | 16 +- mods/randomizer/src/randomizer_context.cpp | 92 +-- mods/randomizer/src/randomizer_context.hpp | 13 +- mods/randomizer/src/session.cpp | 73 +- mods/randomizer/src/tools.cpp | 94 +-- 10 files changed, 748 insertions(+), 564 deletions(-) diff --git a/mods/randomizer/generator/data/flow_patches.yaml b/mods/randomizer/generator/data/flow_patches.yaml index d062f43528..d8cb5d83df 100644 --- a/mods/randomizer/generator/data/flow_patches.yaml +++ b/mods/randomizer/generator/data/flow_patches.yaml @@ -405,6 +405,12 @@ - 0xFFFF # Yes, we've obtained the reward, do nothing - Jovani Give 20 Reward + - name: Jovani Give 20 Reward + type: event + event: 8 + parameters: 0x00010075 + next node index: 0x77E + ################################################# # HINT SIGNS # ################################################# @@ -611,4 +617,4 @@ index: 21133 type: message inf index: Cave of Ordeals Hint Sign Text - next flow index: 0xFFFF \ No newline at end of file + next flow index: 0xFFFF diff --git a/mods/randomizer/generator/data/locations.yaml b/mods/randomizer/generator/data/locations.yaml index 7dede794c8..b771c7bc77 100644 --- a/mods/randomizer/generator/data/locations.yaml +++ b/mods/randomizer/generator/data/locations.yaml @@ -33,11 +33,6 @@ - Ordona Province - Npc Metadata: - FLW Event: - - Group: 1 - Index: 0x18F - Next Node Index: 0x116 - Parameters: 0x0001004A Name Lookup: - Uli Cradle Delivery Event Flag: 0x0301 @@ -49,11 +44,6 @@ - Npc - Ordona Province Metadata: - FLW Event: - - Group: 1 - Index: 0x6DF - Next Node Index: 0x44A - Parameters: 0x00010065 Name Lookup: - Ordon Cat Rescue Event Flag: 0x1408 @@ -64,14 +54,11 @@ - Overworld - Ordona Province Metadata: - FLW Event: - - Group: 1 - Index: 0x4D2 - Next Node Index: 0x2FA - Parameters: 0x0001004B Shop: - Stage: 65 Item: 0x4B + Name Lookup: + - Sera Shop Slingshot Event Flag: 0x4902 - Name: Ordon Shield @@ -292,11 +279,6 @@ - Npc - Ordona Province Metadata: - FLW Event: - - Group: 1 - Index: 0x345 - Next Node Index: 0x216 - Parameters: 0x00010021 Name Lookup: - Herding Goats Reward Event Flag: 0x4240 @@ -513,11 +495,6 @@ - Faron Woods - ARC Metadata: - FLW Event: - - Group: 6 - Index: 0x72 - Next Node Index: 0x54 - Parameters: 0x0001009D Name Lookup: - Coro Bottle Event Flag: 0x1A08 @@ -1347,14 +1324,11 @@ - Kakariko Village - ARC Metadata: - FLW Event: - - Group: 2 - Index: 0x439 - Next Node Index: 0x2E8 - Parameters: 0x0001003E Shop: - Stage: 68 Item: 0x3E + Name Lookup: + - Kakariko Village Malo Mart Hawkeye Switch Flag: Stage: 68 Flag: 0x33 @@ -1366,14 +1340,11 @@ - Kakariko Village - ARC Metadata: - FLW Event: - - Group: 2 - Index: 0x427 - Next Node Index: 0x2DB - Parameters: 0x0001002C Shop: - Stage: 68 Item: 0x2C + Name Lookup: + - Kakariko Village Malo Mart Hylian Shield Switch Flag: Stage: 68 Flag: 0x39 @@ -1385,14 +1356,11 @@ - Kakariko Village - ARC Metadata: - FLW Event: - - Group: 2 - Index: 0x449 - Next Node Index: 0x2F3 - Parameters: 0x00010061 Shop: - Stage: 68 Item: 0x61 + Name Lookup: + - Kakariko Village Malo Mart Red Potion Switch Flag: Stage: 68 Flag: 0x4 @@ -1404,14 +1372,11 @@ - Kakariko Village - ARC Metadata: - FLW Event: - - Group: 2 - Index: 0x41C - Next Node Index: 0x2D3 - Parameters: 0x0001002B Shop: - Stage: 68 Item: 0x2B + Name Lookup: + - Kakariko Village Malo Mart Wooden Shield Switch Flag: Stage: 68 Flag: 0x5 @@ -1516,17 +1481,6 @@ - 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 @@ -1539,11 +1493,6 @@ - Kakariko Village - ARC Metadata: - FLW Event: - - Group: 2 - Index: 0x19A - Next Node Index: 0xF5 - Parameters: 0x00010080 Name Lookup: - Renados Letter Event Flag: 0x0F80 @@ -1568,11 +1517,6 @@ - Kakariko Graveyard - ARC Metadata: - FLW Event: - - Group: 2 - Index: 0x55 - Next Node Index: 0x25 - Parameters: 0x00010031 Name Lookup: - Rutelas Blessing Event Flag: 0x0804 @@ -1585,11 +1529,6 @@ - Kakariko Graveyard - ARC Metadata: - FLW Event: - - Group: 2 - Index: 0x339 - Next Node Index: 0x1ED - Parameters: 0x0001003D Name Lookup: - Gift From Ralis Event Flag: 0x3B80 @@ -1958,11 +1897,6 @@ - ARC - Remote Location Metadata: - FLW Event: - - Group: 6 - Index: 0x610 - Next Node Index: 0x439 - Parameters: 0x000100E9 Name Lookup: - Skybook From Impaz Event Flag: 0x5F80 @@ -1998,11 +1932,6 @@ - Hidden Village - ARC Metadata: - FLW Event: - - Group: 6 - Index: 0x32 - Next Node Index: 0x2A - Parameters: 0x00010083 Name Lookup: - Ilia Charm Event Flag: 0x2280 @@ -2471,11 +2400,6 @@ - Castle Town - ARC Metadata: - FLW Event: - - Group: 4 - Index: 0x276 - Next Node Index: 0x1A5 - Parameters: 0x00010055 Name Lookup: - STAR Prize 1 Event Flag: 0x2308 @@ -2488,11 +2412,6 @@ - Castle Town - ARC Metadata: - FLW Event: - - Group: 4 - Index: 0x269 - Next Node Index: 0x1A1 - Parameters: 0x00010056 Name Lookup: - STAR Prize 2 Event Flag: 0x2301 @@ -2504,14 +2423,11 @@ - Castle Town - ARC Metadata: - FLW Event: - - Group: 4 - Index: 0x610 - Next Node Index: 0x5AE - Parameters: 0x00010030 Shop: - Stage: 73 Item: 0x30 + Name Lookup: + - Castle Town Malo Mart Magic Armor Switch Flag: Stage: 73 Flag: 0x2B @@ -2567,17 +2483,6 @@ - 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 @@ -2592,11 +2497,6 @@ - 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 @@ -2630,11 +2530,6 @@ - Castle Town - ARC Metadata: - FLW Event: - - Group: 4 - Index: 0x14D - Next Node Index: 0xC6 - Parameters: 0x00010081 Name Lookup: - Telma Invoice Event Flag: 0x2180 @@ -3157,11 +3052,6 @@ - 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 @@ -3686,11 +3576,6 @@ - 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 @@ -3780,11 +3665,6 @@ - ARC - Remote Location Metadata: - FLW Event: - - Group: 8 - Index: 0x2E4 - Next Node Index: 0x208 - Parameters: 0x00010051 Name Lookup: - Iza Helping Hand Event Flag: 0x0B01 @@ -3798,11 +3678,6 @@ - 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 @@ -3815,9 +3690,8 @@ - ARC - REL Metadata: - FLW Message: - - Group: 0 - Message Id: 1822 + Name Lookup: + - Fishing Hole Bottle Event Flag: 0x3908 - Name: Fishing Hole Heart Piece @@ -3833,11 +3707,8 @@ - Stage: 62 Flag: 0x80 # Or picked up with the fishing rod during fishing - FLW Message: - - Group: 7 - Message Id: 7564 - - Group: 7 - Message Id: 7578 + Name Lookup: + - Fishing Hole Heart Piece - Name: Zoras Domain Warp Portal @@ -4035,11 +3906,6 @@ - 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 @@ -4129,11 +3995,6 @@ - Snowpeak Province - ARC Metadata: - FLW Event: - - Group: 6 - Index: 0x282 - Next Node Index: 0x1C6 - Parameters: 0x00010091 Name Lookup: - Ashei Sketch Event Flag: 0x2940 @@ -4224,11 +4085,6 @@ - ARC - Remote Location Metadata: - FLW Event: - - Group: 6 - Index: 0x2F8 - Next Node Index: 0x237 - Parameters: 0x00010021 Name Lookup: - Snowboard Racing Prize Event Flag: 0x3B10 @@ -4876,11 +4732,6 @@ - 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 @@ -5107,9 +4958,8 @@ - ARC Goal Name: Diababa Metadata: - FLW Message: - - Group: 5 - Message Id: 5001 + Name Lookup: + - Forest Temple Dungeon Reward Item Flag: Stage: 7 Flag: 0x9E @@ -5149,11 +4999,6 @@ - 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 @@ -5273,11 +5118,6 @@ - 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 @@ -5338,11 +5178,6 @@ - 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 @@ -5391,9 +5226,8 @@ - ARC Goal Name: Fyrus Metadata: - FLW Message: - - Group: 5 - Message Id: 6011 + Name Lookup: + - Goron Mines Dungeon Reward Event Flag: 0x0701 # LAKEBED TEMPLE @@ -5716,9 +5550,8 @@ - ARC Goal Name: Morpheel Metadata: - FLW Message: - - Group: 5 - Message Id: 7001 + Name Lookup: + - Lakebed Temple Dungeon Reward Event Flag: 0x0904 # late # ARBITERS GROUNDS @@ -6064,11 +5897,6 @@ - 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 @@ -6285,9 +6113,8 @@ - ARC Goal Name: Blizzeta Metadata: - FLW Message: - - Group: 5 - Message Id: 9301 + Name Lookup: + - Snowpeak Ruins Dungeon Reward Event Flag: 0x2008 - Name: Temple of Time Lobby Lantern Chest @@ -6520,9 +6347,8 @@ - ARC Goal Name: Armogohma Metadata: - FLW Message: - - Group: 5 - Message Id: 9401 + Name Lookup: + - Temple of Time Dungeon Reward Item Flag: Stage: 10 Flag: 0x9E @@ -6847,9 +6673,8 @@ - ARC Goal Name: Argorok Metadata: - FLW Message: - - Group: 5 - Message Id: 11001 + Name Lookup: + - City in the Sky Dungeon Reward Event Flag: 0x2002 # PALACE OF TWILIGHT @@ -7667,4 +7492,4 @@ - Overworld - Non-Item Location Metadata: - - None \ No newline at end of file + - None diff --git a/mods/randomizer/src/hooks.cpp b/mods/randomizer/src/hooks.cpp index 182b71c8b3..bcd48ce1be 100644 --- a/mods/randomizer/src/hooks.cpp +++ b/mods/randomizer/src/hooks.cpp @@ -31,7 +31,6 @@ #include "d/actor/d_a_npc_zrc.h" #include "d/actor/d_a_npc_zrz.h" #include "d/actor/d_a_obj_bosswarp.h" -#include "d/actor/d_a_obj_master_sword.h" #include "d/actor/d_a_obj_swBallC.h" #include "d/actor/d_a_obj_zra_rock.h" #include "d/actor/d_a_tag_kmsg.h" @@ -145,9 +144,6 @@ DEFINE_HOOK(&daNpc_ykW_c::isDelete, daNpc_ykW_c__isDelete); DEFINE_HOOK_SYMBOL("daE_MD_Create", int(fopAc_ac_c*), daE_MD_c__create); -DEFINE_HOOK(&fopAcM_orderMapToolEvent, orderMapToolEvent); -DEFINE_HOOK_SYMBOL("daObjMasterSword_Execute", int(daObjMasterSword_c*), daObjMasterSword_c__execute); - DEFINE_HOOK(&dScnName_c::changeGameScene, dScnName_c__changeGameScene); DEFINE_HOOK(&daNpc_zrZ_c::isDelete, daNpc_zrZ_c__isDelete); @@ -178,13 +174,6 @@ DEFINE_HOOK(&daNpc_zrC_c::isDelete, daNpc_zrC_c__isDelete); DEFINE_HOOK(&daObjZraRock_c::create, daObjZraRock_c__create); -#ifdef _MSVC_LANG -#define phase_1_dScnPly_sig "?phase_1@@YAHPAUdScnPly_c@@@Z" // sig is wrong? -#else -#define phase_1_dScnPly_sig "_Z7phase_1P9dScnPly_c" -#endif -// DEFINE_HOOK_SYMBOL(phase_1_dScnPly_sig, int(dScnPly_c*), phase_1__dScnPly_c); - DEFINE_HOOK(&dMenu_Ring_c::textScaleHIO, dMenu_Ring_c__textScaleHIO); #ifdef _MSVC_LANG @@ -1806,7 +1795,8 @@ HookAction hookPreProcCoGetItem(ModContext*, void* args, void*, void*) { } // Don't show special text in rando - if (dComIfGs_getPohSpiritNum() == 20) { + const u8 nextPoeCount = dComIfGs_getPohSpiritNum() + 1; + if (nextPoeCount == 20 || nextPoeCount == 60) { i_this->field_0x32cc = i_this->mProcVar2.field_0x300c + 0x65; } @@ -2226,38 +2216,6 @@ void hookPostEMdCreate(ModContext*, void* args, void*, void*) { hookEMdCreate_prevSkipInfo = 0; } -HookAction hookPreOrderMapToolEvent(ModContext*, void* args, void*, void*) { - auto eventId = mods::arg(args, 1); - - // skip starting the master sword cutscene so we can handle the checks manually - if (daAlink_c::checkStageName("F_SP117") && eventId == 2) { - // Set the necessary flags to de-spawn the MS and set the save file event flag. - dComIfGs_onTmpBit(0x820); - dComIfGs_onEventBit(0x2120); - return HOOK_SKIP_ORIGINAL; - } - - return HOOK_CONTINUE; -} - -HookAction hookPreMasterSwordExecute(ModContext*, void* args, void* retval, void*) { - auto* i_this = mods::arg(args, 0); - - (i_this->*i_this->mActionFunc[1])(); - dComIfG_Ccsp()->Set(&i_this->mCyl); - - i_this->mBtk.play(); - i_this->mBrk.play(); - - if (dComIfGs_isTmpBit(dSv_event_tmp_flag_c::tempBitLabels[73])) { - // Don't automatically give the master sword in randomizer - fopAcM_delete(i_this); - } - - *static_cast(retval) = 1; - return HOOK_SKIP_ORIGINAL; -} - void hookPost_dScnName_c__changeGameScene(ModContext* ctx, void* args, void* retval, void* userdata) { if (!mDoRst::isReset() && !fopOvlpM_IsPeek()) { randomizer::session::registerStartingLocation(); @@ -2579,32 +2537,6 @@ void hookPostObjZraRockCreate(ModContext*, void* args, void* retval, void*) { } } -bool g_horseCallFirstBit = false; -u8 g_slot21Item = 0; -HookAction hookPreScnPlyPhase1(ModContext*, void*, void*, void*) { - if (!strcmp(dComIfGp_getStartStageName(), "F_SP104") && dComIfGp_getStartStageRoomNo() == 1 && - dComIfGp_getStartStagePoint() == 23 && dComIfGp_getStartStageLayer() == 12) - { - // Save item info for rando to restore after function runs - g_horseCallFirstBit = dComIfGs_isItemFirstBit(dItemNo_HORSE_FLUTE_e); - g_slot21Item = dComIfGs_getItem(SLOT_21, false); - } - - return HOOK_CONTINUE; -} - -void hookPostScnPlyPhase1(ModContext*, void*, void*, void*) { - if (!strcmp(dComIfGp_getStartStageName(), "F_SP104") && dComIfGp_getStartStageRoomNo() == 1 && - dComIfGp_getStartStagePoint() == 23 && dComIfGp_getStartStageLayer() == 12) - { - // Restore item info - if (!g_horseCallFirstBit) { - dComIfGs_offItemFirstBit(dItemNo_HORSE_FLUTE_e); - } - dComIfGs_setItem(SLOT_21, g_slot21Item); - } -} - J2DPicture* g_dpadIcon{}; void hookPostMenuRingTextScaleHIO(ModContext*, void* args, void*, void*) { auto menuRing = mods::arg(args, 0); @@ -3230,9 +3162,6 @@ ModResult initialize() { ADD_HOOK_PRE(daE_MD_c__create, hookPreEMdCreate); ADD_HOOK_POST(daE_MD_c__create, hookPostEMdCreate); - ADD_HOOK_PRE(orderMapToolEvent, hookPreOrderMapToolEvent); - ADD_HOOK_PRE(daObjMasterSword_c__execute, hookPreMasterSwordExecute); - ADD_HOOK_POST(dScnName_c__changeGameScene, hookPost_dScnName_c__changeGameScene); ADD_HOOK_POST(daNpc_zrZ_c__isDelete, hookPostNpcZrzIsDelete); @@ -3263,9 +3192,6 @@ ModResult initialize() { ADD_HOOK_POST(daObjZraRock_c__create, hookPostObjZraRockCreate); - // ADD_HOOK_PRE(phase_1__dScnPly_c, hookPreScnPlyPhase1); - // ADD_HOOK_POST(phase_1__dScnPly_c, hookPostScnPlyPhase1); - ADD_HOOK_POST(dMenu_Ring_c__textScaleHIO, hookPostMenuRingTextScaleHIO); ADD_HOOK_POST(dMenu_Ring_c__destructor, hookPostMenuRingDestructor); ADD_HOOK_POST(dMenu_Ring_c__setActiveCursor, hookPostMenuRingSetActiveCursor); @@ -3373,9 +3299,6 @@ ModResult uninstall() { mods::hook::uninstall(svc_hook); - mods::hook::uninstall(svc_hook); - mods::hook::uninstall(svc_hook); - mods::hook::uninstall(); mods::hook::uninstall(svc_hook); @@ -3400,8 +3323,6 @@ ModResult uninstall() { mods::hook::uninstall(svc_hook); - //mods::hook::uninstall(); - mods::hook::uninstall(svc_hook); mods::hook::uninstall(svc_hook); mods::hook::uninstall(svc_hook); diff --git a/mods/randomizer/src/item.cpp b/mods/randomizer/src/item.cpp index 6dd33f92f6..a7cf671d0c 100644 --- a/mods/randomizer/src/item.cpp +++ b/mods/randomizer/src/item.cpp @@ -485,10 +485,6 @@ void randomizer_item_func_MIRROR_PIECE_4() { dComIfGs_onCollectMirror(3); } -void randomizer_item_func_POU_SPIRIT() { - dComIfGs_addPohSpiritNum(); -} - void randomizer_item_func_AIR_LETTER() { u8 letterCount = getAncientDocumentNum(); setAncientDocumentNum(letterCount + 1); @@ -764,7 +760,7 @@ static void (*item_func_ptr_randomizer[256])() = { /* 0xDD */ item_func_noentry, /* 0xDE */ item_func_noentry, /* 0xDF */ item_func_noentry, - /* 0xE0 */ randomizer_item_func_POU_SPIRIT, + /* 0xE0 */ item_func_POU_SPIRIT, /* 0xE1 */ item_func_ENDING_BLOW, /* 0xE2 */ item_func_SHIELD_ATTACK, /* 0xE3 */ item_func_BACK_SLICE, @@ -1768,4 +1764,4 @@ void restore_item_data_tables() { s_applied = false; } -} // namespace randomizer::item \ No newline at end of file +} // namespace randomizer::item diff --git a/mods/randomizer/src/messages.cpp b/mods/randomizer/src/messages.cpp index 5907a026cc..febf217b0c 100644 --- a/mods/randomizer/src/messages.cpp +++ b/mods/randomizer/src/messages.cpp @@ -1,134 +1,614 @@ #include "messages.hpp" -#include "JSystem/JMessage/control.h" -#include "d/d_msg_class.h" -#include "d/d_save.h" -#include "d/d_com_inf_game.h" -#include "randomizer_context.hpp" #include "custom_flow_ids.hpp" +#include "randomizer_context.hpp" +#include "stages.h" #include "tools.h" +#include "d/actor/d_a_alink.h" +#include "d/d_com_inf_game.h" +#include "d/d_item.h" +#include "d/d_kankyo.h" +#include "d/d_save.h" +#include "d/d_stage.h" + +#include +#include +#include + #include -static JMSMesgEntry_c defaultJMSMesgEntry{ - .string_offset = 0, - .message_id = 0, - .event_label_id = 0, - .speaker = 0x24, - .box_kind = 0x00, - .draw_type = 0x00, - .box_position = 0x00, - .item_no = 0xFF, - .line_alignment = 0x00, - .speaker_mood = 0x00, - .camera_attr = 0x00, - .talk_anim = 0x02, - .face_anim = 0x03, - .lines_per_page = 0x04, -}; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -// Format certain messages that need to have dynamic info in them -char* GetFormatedTextOverride(u32 key, std::string& text) { - // Store formatted message in static buffer so it never goes away. - // This is fine as long as we only ever need to format messages - // for textboxes, but will cause issues if we need to use it for - // other UI elements - static std::array buf; - u32 value{}; - char* outIt; +namespace randomizer::messages { +namespace { + +constexpr uint16_t kMessageGroupCount = 9; +constexpr uint16_t kLegacyQueryEventFlag = 53; +constexpr uint16_t kLegacyQueryChangeTime = 54; +constexpr uint16_t kLegacyQueryReturnToSpawn = 55; +constexpr uint8_t kLegacyEventNoOp = 43; +constexpr uint8_t kLegacyEventChangeTime = 44; +constexpr uint8_t kLegacyEventReturnToSpawn = 45; +constexpr uint8_t kLegacyEventSetTrackerFlag = 46; +constexpr uint8_t kLegacyEventRemoveTradeItem = 47; + +using LegacyIdMap = std::unordered_map; +using MessageIdMaps = std::array; + +mods::flow::Query s_eventFlagQuery; +mods::flow::Query s_changeTimeQuery; +mods::flow::Query s_returnToSpawnQuery; +mods::flow::Event s_changeTimeEvent; +mods::flow::Event s_returnToSpawnEvent; +mods::flow::Event s_removeTradeItemEvent; +std::vector s_graphs; +std::vector s_messages; +std::vector s_overrides; + +uint32_t read_parameter(const uint8_t parameters[4]) { + return static_cast(parameters[0]) << 24 | static_cast(parameters[1]) << 16 | + static_cast(parameters[2]) << 8 | parameters[3]; +} + +uint16_t query_event_flag(ModContext*, const FlowQueryContext* query, void*) { + if (query == nullptr || query->parameter >= std::size(dSv_event_flag_c::saveBitLabels)) { + return 0; + } + return dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[query->parameter]) ? 1 : 0; +} + +uint16_t query_change_time(ModContext*, const FlowQueryContext*, void*) { + if (!dKy_darkworld_check() && !daAlink_c::checkForestOldCentury() && + (daAlink_c::checkField() || daAlink_c::checkCastleTown()) && + !daAlink_c::checkStageName("R_SP161")) + { + return 0; + } + return 1; +} + +uint16_t query_return_to_spawn(ModContext*, const FlowQueryContext*, void*) { + const int stageId = getStageID(); + if (stageId <= Darkhammer) { + return randomizer_GetContext().mReturnToPlaceOverrides.contains(stageId) ? 0 : 1; + } + return 2; +} + +void event_change_time(ModContext*, const FlowEventContext*, void*) { + if (daPy_py_c::checkNowWolf()) { + g_randomizerState.setHasPendingToDChange(true); + } else { + g_randomizerState.handleTimeOfDayChange(); + } +} + +void event_return_to_spawn(ModContext*, const FlowEventContext* event, void*) { + if (event != nullptr) { + randomizer_returnToSpawn(read_parameter(event->parameters) != 0); + } +} + +void event_remove_trade_item(ModContext*, const FlowEventContext* event, void*) { + if (event == nullptr) { + return; + } + const uint8_t item = static_cast(read_parameter(event->parameters)); + if (item == dItemNo_LETTER_e || item == dItemNo_BILL_e || item == dItemNo_WOOD_STATUE_e || + item == dItemNo_IRIAS_PENDANT_e) + { + offWarashibeItem(item); + } +} + +std::vector encoded_text(const std::string& text) { + std::vector result{text.begin(), text.end()}; + if (result.empty() || result.back() != 0) { + result.push_back(0); + } + return result; +} + +MessageEntryData default_message_entry() { + MessageEntryData entry{}; + entry.bytes[8] = 0x24; + entry.bytes[12] = 0xff; + entry.bytes[16] = 0x02; + entry.bytes[17] = 0x03; + entry.bytes[18] = 0x04; + return entry; +} + +MessageEntryData message_entry(const RandomizerContext& context, uint32_t legacyKey) { + MessageEntryData entry = default_message_entry(); + const auto found = context.mAttributeOverrides.find(legacyKey); + if (found != context.mAttributeOverrides.end()) { + std::memcpy(entry.bytes, found->second.data(), sizeof(entry.bytes)); + std::fill_n(entry.bytes, 6, uint8_t{}); + } + return entry; +} + +std::vector text_keys(const RandomizerContext& context, uint16_t group) { + std::unordered_set unique; + for (const auto& [language, overrides] : context.mTextOverrides) { + for (const auto& [key, text] : overrides) { + if (key >> 16 == group) { + unique.insert(key); + } + } + } + std::vector result{unique.begin(), unique.end()}; + std::ranges::sort(result); + return result; +} + +ModResult register_custom_messages(const RandomizerContext& context, MessageIdMaps& messageIds) { + const auto keys = text_keys(context, CUSTOM_BMG_GROUP); + for (uint16_t group = 0; group < kMessageGroupCount; ++group) { + for (const uint32_t key : keys) { + std::vector variants; + for (const auto& [language, overrides] : context.mTextOverrides) { + const auto found = overrides.find(key); + if (found == overrides.end() || language < MESSAGE_LANGUAGE_ENGLISH || + language > MESSAGE_LANGUAGE_ITALIAN) + { + continue; + } + variants.emplace_back(static_cast(language), + message_entry(context, key), encoded_text(found->second)); + } + if (variants.empty()) { + return MOD_INVALID_ARGUMENT; + } + auto message = mods::flow::register_message(group, variants); + if (!message) { + return message.result(); + } + messageIds[group].emplace(static_cast(key), message.id()); + s_messages.push_back(std::move(message)); + } + } + return MOD_OK; +} + +bool formatted_override( + ModContext*, const MessageOverrideContext* message, MessageTextData* outText, void*) { + if (message == nullptr || outText == nullptr || !randomizer_IsActive()) { + return false; + } + const uint32_t key = static_cast(message->group) << 16 | message->message_id; + const auto language = static_cast(message->language); + const auto languageIt = randomizer_GetContext().mTextOverrides.find(language); + if (languageIt == randomizer_GetContext().mTextOverrides.end()) { + return false; + } + const auto textIt = languageIt->second.find(key); + if (textIt == languageIt->second.end()) { + return false; + } + + uint32_t value = 0; // For item counts, execItemGet hasn't run yet, so add one to the count switch (key) { - case (0 << 16) | 325: // Group 0, id 325 + case 325: // Group 0, id 325 // Poe Soul get item text value = dComIfGs_getPohSpiritNum() + 1; - outIt = fmt::vformat_to(buf.data(), text, fmt::make_format_args(value)); break; - case (0 << 16) | 335: // Group 0, id 335 + case 335: // Group 0, id 335 // Sky book characters get item text value = getAncientDocumentNum() + 1; - outIt = fmt::vformat_to(buf.data(), text, fmt::make_format_args(value)); break; default: - // No override, return original text - return text.data(); + return false; } - // Null-terminate - size_t len = std::distance(buf.data(), outIt); - buf[len] = '\0'; - - // Return overriden text - return buf.data(); + thread_local std::vector formatted; + formatted = encoded_text(fmt::format(fmt::runtime(textIt->second), value)); + outText->text = formatted.data(); + outText->text_size = formatted.size(); + return true; } -u8 getLanguageForOverride() { - u8 language = dSv_player_config_c::LANGUAGE_ENGLISH; - - // TODO: add service or something to check game language - /*if (dusk::version::isRegionPal()) { - language = dComIfGs_getPalLanguage(); - }*//* else if (dusk::version::isRegionJpn()) { - language = randomizer::Text::JAPANESE; - }*/ - - return language; -} - -void HandleTextOverrides(JMessage::TControl* control, JMessage::TProcessor const* pProcessor, int groupID, int index) { - if (randomizer_IsActive()) { - // Get the entry for this message - auto entry = static_cast(pProcessor->getMessageEntry_messageCode(groupID, index)); - if (!entry) { - return; - } - - // If the message id is >= 5000 then it's part of the stage file's message group - // Otherwise it's part of group 0 - auto msgId = entry->message_id.host(); - u16 group = 0; - if (msgId >= 5000) { - group = dComIfGp_getStageStagInfo()->mMsgGroup; - } - - u32 key = (group << 16) | msgId; - auto& textOverrides = randomizer_GetContext().mTextOverrides; - u8 language = getLanguageForOverride(); - if (textOverrides.at(language).contains(key)) { - control->pMessageText_begin_ = GetFormatedTextOverride(key, textOverrides[language][key]); - } - } -} - -bool HandleCustomText(JMessage::TControl* control, u16 msgId) { - if (randomizer_IsActive()) { - u32 key = (CUSTOM_BMG_GROUP << 16) | msgId; - auto& textOverrides = randomizer_GetContext().mTextOverrides; - u8 language = getLanguageForOverride(); - if (textOverrides.at(language).contains(key)) { - control->pMessageText_begin_ = GetFormatedTextOverride(key, textOverrides[language][key]); - - // Get the attributes for this text-box if they were specified - auto& attributeOverrides = randomizer_GetContext().mAttributeOverrides; - if (attributeOverrides.contains(key)) { - control->pEntry_ = reinterpret_cast(&attributeOverrides[key]); - // Otherwise, use the default entry - } else { - defaultJMSMesgEntry.message_id = msgId; - control->pEntry_ = &defaultJMSMesgEntry; +ModResult register_native_overrides(const RandomizerContext& context) { + for (uint16_t group = 0; group < kMessageGroupCount; ++group) { + for (const uint32_t key : text_keys(context, group)) { + for (const auto& [language, overrides] : context.mTextOverrides) { + const auto found = overrides.find(key); + if (found == overrides.end() || language < MESSAGE_LANGUAGE_ENGLISH || + language > MESSAGE_LANGUAGE_ITALIAN) + { + continue; + } + mods::flow::MessageOverride message; + if (key == 325 || key == 335) { + message = mods::flow::override_message_fn(group, static_cast(key), + static_cast(language), formatted_override); + } else { + const auto text = encoded_text(found->second); + message = mods::flow::override_message(group, static_cast(key), + static_cast(language), std::span{text}); + } + if (!message) { + return message.result(); + } + s_overrides.push_back(std::move(message)); } - return true; } } - return false; + return MOD_OK; } -// Used in special cases -char* GetTextOverride(s16 groupID, u32 messageId) { - u32 key = (groupID << 16) | messageId; - auto& textOverrides = randomizer_GetContext().mTextOverrides; - u8 language = getLanguageForOverride(); - if (textOverrides.at(language).contains(key)) { - return GetFormatedTextOverride(key, textOverrides[language][key]); +std::vector custom_node_ids(const RandomizerContext& context) { + std::vector result; + for (const auto& [key, node] : context.mFlowPatches) { + if (key >> 16 == CUSTOM_BMG_GROUP) { + result.push_back(static_cast(key)); + } } - return NULL; + std::ranges::sort(result); + return result; } + +ModResult allocate_shared_node_ids(const std::array& handles, + size_t count, std::array, kMessageGroupCount>& allocated, + LegacyIdMap& nodeIds, const std::vector& legacyIds) { + if (count == 0) { + return MOD_OK; + } + + std::array, kMessageGroupCount> allocatedSets; + std::vector common; + while (common.size() < count) { + for (size_t group = 0; group < handles.size(); ++group) { + uint16_t id = 0; + const ModResult result = svc_flow->allocate_node(mod_ctx, handles[group], &id); + if (result != MOD_OK) { + return result; + } + allocated[group].push_back(id); + allocatedSets[group].insert(id); + } + + common.clear(); + for (const uint16_t candidate : allocatedSets.front()) { + const bool present = std::ranges::all_of( + allocatedSets, [candidate](const auto& ids) { return ids.contains(candidate); }); + if (present) { + common.push_back(candidate); + } + } + } + + std::ranges::sort(common); + for (size_t i = 0; i < legacyIds.size(); ++i) { + nodeIds.emplace(legacyIds[i], common[i]); + } + return MOD_OK; +} + +ModResult remap_target(uint16_t legacy, const LegacyIdMap& nodeIds, uint16_t& out) { + if (legacy == mods::flow::kEnd || legacy < BASE_CUSTOM_MSG_AND_FLOW_ID) { + out = legacy; + return MOD_OK; + } + const auto found = nodeIds.find(legacy); + if (found == nodeIds.end()) { + return MOD_INVALID_ARGUMENT; + } + out = found->second; + return MOD_OK; +} + +ModResult remap_query(FlowNodeData& node) { + const uint16_t legacy = mods::read_bits(node.bytes + 2); + FlowQueryId query = legacy; + switch (legacy) { + case kLegacyQueryEventFlag: + query = s_eventFlagQuery.id(); + break; + case kLegacyQueryChangeTime: + query = s_changeTimeQuery.id(); + break; + case kLegacyQueryReturnToSpawn: + query = s_returnToSpawnQuery.id(); + break; + default: + if (legacy >= FLOW_QUERY_BUILTIN_COUNT) { + return MOD_INVALID_ARGUMENT; + } + break; + } + mods::write_bits(node.bytes + 2, query); + return MOD_OK; +} + +ModResult remap_event(FlowNodeData& node) { + switch (node.bytes[1]) { + case kLegacyEventNoOp: + node.bytes[1] = FLOW_EVENT_UNUSED_42; + break; + case kLegacyEventChangeTime: + node.bytes[1] = s_changeTimeEvent.id(); + break; + case kLegacyEventReturnToSpawn: + node.bytes[1] = s_returnToSpawnEvent.id(); + break; + case kLegacyEventSetTrackerFlag: + // Older seed files may contain the retired pre-grant tracker event. + node.bytes[1] = FLOW_EVENT_UNUSED_42; + break; + case kLegacyEventRemoveTradeItem: + node.bytes[1] = s_removeTradeItemEvent.id(); + break; + default: + if (node.bytes[1] >= FLOW_EVENT_BUILTIN_COUNT) { + return MOD_INVALID_ARGUMENT; + } + break; + } + return MOD_OK; +} + +FlowNodeData raw_node(uint64_t value) { + FlowNodeData node{}; + std::memcpy(node.bytes, &value, sizeof(node.bytes)); + return node; +} + +ModResult remap_node(const RandomizerContext& context, uint32_t legacyKey, uint16_t group, + FlowGraphHandle handle, const LegacyIdMap& nodeIds, const MessageIdMaps& messageIds, + FlowNodeData& node) { + switch (node.bytes[0]) { + case 1: { + const uint16_t legacyMessage = mods::read_bits(node.bytes + 2); + if (legacyMessage >= BASE_CUSTOM_MSG_AND_FLOW_ID) { + const auto found = messageIds[group].find(legacyMessage); + if (found == messageIds[group].end()) { + return MOD_INVALID_ARGUMENT; + } + mods::write_bits(node.bytes + 2, found->second); + } + uint16_t target = 0; + const ModResult result = + remap_target(mods::read_bits(node.bytes + 4), nodeIds, target); + if (result != MOD_OK) { + return result; + } + mods::write_bits(node.bytes + 4, target); + return MOD_OK; + } + case 2: { + ModResult result = remap_query(node); + if (result != MOD_OK) { + return result; + } + const auto overrides = context.mFlowPatchesBranchOverrides.find(legacyKey); + if (overrides == context.mFlowPatchesBranchOverrides.end()) { + return MOD_OK; + } + if (overrides->second.empty() || overrides->second.size() != node.bytes[1] || + overrides->second.size() > std::numeric_limits::max()) + { + return MOD_INVALID_ARGUMENT; + } + std::vector targets; + targets.reserve(overrides->second.size()); + for (const uint16_t legacyTarget : overrides->second) { + uint16_t target = 0; + result = remap_target(legacyTarget, nodeIds, target); + if (result != MOD_OK) { + return result; + } + targets.push_back(target); + } + uint16_t firstEdge = 0; + result = svc_flow->add_edges( + mod_ctx, handle, targets.data(), static_cast(targets.size()), &firstEdge); + if (result == MOD_OK) { + mods::write_bits(node.bytes + 6, firstEdge); + } + return result; + } + case 3: { + ModResult result = remap_event(node); + if (result != MOD_OK || node.bytes[1] == FLOW_EVENT_JUMP_FLOW) { + return result; + } + const uint16_t legacyEdge = mods::read_bits(node.bytes + 2); + if (legacyEdge < BASE_CUSTOM_MSG_AND_FLOW_ID) { + return MOD_OK; + } + uint16_t target = 0; + result = remap_target(legacyEdge, nodeIds, target); + if (result != MOD_OK) { + return result; + } + uint16_t edge = 0; + result = svc_flow->add_edges(mod_ctx, handle, &target, 1, &edge); + if (result == MOD_OK) { + mods::write_bits(node.bytes + 2, edge); + } + return result; + } + default: + return MOD_INVALID_ARGUMENT; + } +} + +void remap_actor_record(std::vector& bytes, const LegacyIdMap& nodeIds) { + if (bytes.size() < sizeof(stage_actor_data_class)) { + return; + } + stage_actor_data_class actor{}; + std::memcpy(&actor, bytes.data(), sizeof(actor)); + if (std::memcmp(actor.name, "Obj_kn2", 7) != 0) { + return; + } + const uint16_t legacy = static_cast(actor.base.angle.x); + const auto found = nodeIds.find(legacy); + if (found == nodeIds.end()) { + return; + } + actor.base.angle.x = static_cast(found->second); + std::memcpy(bytes.data(), &actor, sizeof(actor)); +} + +void remap_actor_flows(RandomizerContext& context, const LegacyIdMap& nodeIds) { + for (auto& [stage, patches] : context.mObjectPatches) { + for (auto& [crc, bytes] : patches) { + remap_actor_record(bytes, nodeIds); + } + } + for (auto& [stage, additions] : context.mObjectAdditions) { + for (auto& bytes : additions) { + remap_actor_record(bytes, nodeIds); + } + } +} + +ModResult register_graphs(RandomizerContext& context, const MessageIdMaps& messageIds) { + const auto legacyIds = custom_node_ids(context); + std::array handles{}; + std::array guards; + for (uint16_t group = 0; group < kMessageGroupCount; ++group) { + ModResult result = svc_flow->begin_graph(mod_ctx, group, &handles[group]); + if (result != MOD_OK) { + return result; + } + guards[group] = mods::flow::Graph{handles[group], MOD_OK}; + } + + std::array, kMessageGroupCount> allocated; + LegacyIdMap nodeIds; + ModResult result = + allocate_shared_node_ids(handles, legacyIds.size(), allocated, nodeIds, legacyIds); + if (result != MOD_OK) { + return result; + } + + std::unordered_map dynamicToLegacy; + for (const auto& [legacy, dynamic] : nodeIds) { + dynamicToLegacy.emplace(dynamic, legacy); + } + + const FlowNodeData unused = mods::flow::event(FLOW_EVENT_UNUSED_42, 0, {}); + for (uint16_t group = 0; group < kMessageGroupCount; ++group) { + for (const uint16_t dynamicId : allocated[group]) { + FlowNodeData node = unused; + const auto legacy = dynamicToLegacy.find(dynamicId); + if (legacy != dynamicToLegacy.end()) { + const uint32_t key = static_cast(CUSTOM_BMG_GROUP) << 16 | legacy->second; + const auto raw = context.mFlowPatches.find(key); + if (raw == context.mFlowPatches.end()) { + return MOD_INVALID_ARGUMENT; + } + node = raw_node(raw->second); + result = remap_node(context, key, group, handles[group], nodeIds, messageIds, node); + if (result != MOD_OK) { + return result; + } + } + result = svc_flow->fill_node(mod_ctx, handles[group], dynamicId, &node); + if (result != MOD_OK) { + return result; + } + } + + for (const auto& [key, value] : context.mFlowPatches) { + if (key >> 16 != group) { + continue; + } + FlowNodeData node = raw_node(value); + result = remap_node(context, key, group, handles[group], nodeIds, messageIds, node); + if (result != MOD_OK) { + return result; + } + result = + svc_flow->patch_node(mod_ctx, handles[group], static_cast(key), &node); + if (result != MOD_OK) { + return result; + } + } + } + + for (const FlowGraphHandle handle : handles) { + result = svc_flow->commit_graph(mod_ctx, handle); + if (result != MOD_OK) { + return result; + } + } + for (auto& graph : guards) { + s_graphs.push_back(std::move(graph)); + } + remap_actor_flows(context, nodeIds); + return MOD_OK; +} + +} // namespace + +ModResult initialize() { + s_eventFlagQuery = mods::flow::register_query("randomizer event flag", query_event_flag); + s_changeTimeQuery = mods::flow::register_query("randomizer change time", query_change_time); + s_returnToSpawnQuery = + mods::flow::register_query("randomizer return to spawn", query_return_to_spawn); + s_changeTimeEvent = mods::flow::register_event("randomizer change time", event_change_time); + s_returnToSpawnEvent = + mods::flow::register_event("randomizer return to spawn", event_return_to_spawn); + s_removeTradeItemEvent = + mods::flow::register_event("randomizer remove trade item", event_remove_trade_item); + + if (!s_eventFlagQuery) { + return s_eventFlagQuery.result(); + } + if (!s_changeTimeQuery) { + return s_changeTimeQuery.result(); + } + if (!s_returnToSpawnQuery) { + return s_returnToSpawnQuery.result(); + } + if (!s_changeTimeEvent) { + return s_changeTimeEvent.result(); + } + if (!s_returnToSpawnEvent) { + return s_returnToSpawnEvent.result(); + } + return s_removeTradeItemEvent ? MOD_OK : s_removeTradeItemEvent.result(); +} + +ModResult activate(RandomizerContext& context) { + deactivate(); + + MessageIdMaps messageIds; + ModResult result = register_custom_messages(context, messageIds); + if (result == MOD_OK) { + result = register_native_overrides(context); + } + if (result == MOD_OK) { + result = register_graphs(context, messageIds); + } + if (result != MOD_OK) { + mods::log::error("failed to register randomizer flow data: {}", static_cast(result)); + deactivate(); + } + return result; +} + +void deactivate() { + s_graphs.clear(); + s_overrides.clear(); + s_messages.clear(); +} + +} // namespace randomizer::messages diff --git a/mods/randomizer/src/messages.hpp b/mods/randomizer/src/messages.hpp index 17bb414802..58875ab372 100644 --- a/mods/randomizer/src/messages.hpp +++ b/mods/randomizer/src/messages.hpp @@ -1,15 +1,13 @@ #pragma once -#include +#include -// Forward declaration -namespace JMessage { -struct TProcessor; -struct TControl; -} +class RandomizerContext; -void HandleTextOverrides(JMessage::TControl* control, JMessage::TProcessor const* pProcessor, int groupID, int index); +namespace randomizer::messages { -bool HandleCustomText(JMessage::TControl* control, u16 msgId); +ModResult initialize(); +ModResult activate(RandomizerContext& context); +void deactivate(); -char* GetTextOverride(s16 groupID, u32 messageId); \ No newline at end of file +} // namespace randomizer::messages diff --git a/mods/randomizer/src/randomizer_context.cpp b/mods/randomizer/src/randomizer_context.cpp index 7d5e435b04..e49705fff4 100644 --- a/mods/randomizer/src/randomizer_context.cpp +++ b/mods/randomizer/src/randomizer_context.cpp @@ -76,13 +76,6 @@ std::optional RandomizerContext::WriteToFile() { out["mTwilitInsectOverrides"] = mTwilitInsectOverrides; - for (const auto& [key, data] : this->mFlowItemMessageOverrides) { - auto node = out["mFlowItemMessageOverrides"][key]; - node["itemId"] = data.itemId; - node["stage"] = data.stage; - node["flag"] = data.flag; - } - for (const auto& [name, data] : this->mItemLocations) { auto node = out["mItemLocations"][name]; node["itemId"] = data.itemId; @@ -248,12 +241,6 @@ std::optional RandomizerContext::LoadFromHash(const std::string& ha itemData.flag = node["flag"].as(); }; - // FLW Override items - for (const auto& flwNode : in["mFlowItemMessageOverrides"]) { - u32 key = flwNode.first.as(); - retrieveItemData(this->mFlowItemMessageOverrides[key], flwNode.second); - } - // Items we call by location name for (const auto& locationNode : in["mItemLocations"]) { const auto& locationName = nameLookupOverride(locationNode.first.as()); @@ -792,7 +779,11 @@ void randomizer_checkAndOverrideEntranceData(const char*& stageName, s8& roomNo, } } -void randomizer_setTempFlag(RandomizerContext::itemLocationData data) { +void randomizer_setTempFlag(const RandomizerContext::itemLocationData& data) { + if (data.flag == 0xFFFF) { + return; + } + // If stage is 0xFF, then this is an event flag if (data.stage == 0xFF) { g_randomizerState.mTrackerTempEventFlag = data.flag; @@ -809,14 +800,6 @@ void randomizer_setTempFlag(RandomizerContext::itemLocationData data) { } } -void randomizer_setTempFlagForLocation(const std::string& locationName) { - randomizer_setTempFlag(randomizer_GetContext().mItemLocations[nameLookupOverride(locationName)]); -} - -void randomizer_setTempFlagForFLWOverride(u32 key) { - randomizer_setTempFlag(randomizer_GetContext().mFlowItemMessageOverrides[key]); -} - bool randomizer_checkTempleOfTimeRequirement() { auto swordRequirement = randomizer_GetContext().mSettings[RandomizerContext::TEMPLE_OF_TIME_SWORD_REQUIREMENT]; u8 roomNo = dComIfGp_getStartStageRoomNo(); @@ -1206,71 +1189,6 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { } }; - // Items that we determine the text of and then give during a FLW message - if (location->HasCategories("FLW Message")) { - for (const auto& flwMessageNode : metaData["FLW Message"]) { - u8 group = flwMessageNode["Group"].as(); - u16 messageId = flwMessageNode["Message Id"].as(); - u32 key = (group << 16) | messageId; - randoData.mFlowItemMessageOverrides[key].itemId = location->GetCurrentItem()->GetID(); - getNodeFlags(randoData.mFlowItemMessageOverrides[key], metaData); - } - } - - // 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"]) { diff --git a/mods/randomizer/src/randomizer_context.hpp b/mods/randomizer/src/randomizer_context.hpp index 3e5a239735..1e6e80ee89 100644 --- a/mods/randomizer/src/randomizer_context.hpp +++ b/mods/randomizer/src/randomizer_context.hpp @@ -51,7 +51,6 @@ public: std::unordered_map mGoldenWolfOverrides{}; std::unordered_map mShopOverrides{}; std::unordered_map mTwilitInsectOverrides{}; // Just used in tracker for now - std::unordered_map mFlowItemMessageOverrides{}; std::unordered_map mItemLocations{}; u8 mStartHour{0}; @@ -181,12 +180,6 @@ public: u8 mTimeChange{}; bool mRoomReloadingState{false}; - // Used to store an item id for a flow message override so that we can give the item - // once the textbox is closed instead of when the message appears. This lines up - // more naturally with the timing of when the game normally gives items and affects - // things like the sound of the rupee counter going up. - u8 mFlowMessageItemId{0}; - int mFoolishItemCount{0}; bool mUpdateTracker{false}; bool mShowTracker{false}; @@ -219,11 +212,7 @@ void randomizer_checkAndOverrideEntranceData( * 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); +void randomizer_setTempFlag(const RandomizerContext::itemLocationData&); bool randomizer_checkTempleOfTimeRequirement(); diff --git a/mods/randomizer/src/session.cpp b/mods/randomizer/src/session.cpp index b4089b9444..edd6c47ff4 100644 --- a/mods/randomizer/src/session.cpp +++ b/mods/randomizer/src/session.cpp @@ -10,12 +10,20 @@ #include "tools.h" #include "stages.h" #include "item.hpp" +#include "messages.hpp" #include "verify_item_functions.h" #include "d/d_com_inf_game.h" #include "d/d_item.h" #include "d/d_meter2_info.h" +#include + +#include +#include +#include +#include + namespace randomizer::session { ServiceManager svc_mng; std::string g_pending_seed_hash{}; @@ -32,6 +40,18 @@ struct DerivedKey { u16 key; }; +std::optional parse_stage_check(const char* name, std::string_view prefix) { + if (std::strncmp(name, prefix.data(), prefix.size()) != 0) { + return std::nullopt; + } + const char* stage = name + prefix.size(); + if (*stage == '\0' || std::strchr(stage, ':') != nullptr) { + return std::nullopt; + } + const int stageId = getStageID(stage); + return stageId >= 0 ? std::optional{stageId} : std::nullopt; +} + std::optional parse_derived(const char* name, std::string_view prefix) { if (std::strncmp(name, prefix.data(), prefix.size()) != 0) { return std::nullopt; @@ -64,14 +84,14 @@ bool resolve_check(ModContext*, const ItemCheckInfo* info, uint8_t* out_item, vo auto& ctx = randomizer_GetContext(); if (auto it = ctx.mItemLocations.find(info->name); it != ctx.mItemLocations.end()) { - *out_item = static_cast(it->second.itemId); + *out_item = static_cast(verifyProgressiveItem(it->second.itemId)); return true; } - if (auto key = parse_derived(info->name, "chest:")) { + if (auto key = parse_derived(info->name, ITEM_CHECK_CHEST_PREFIX)) { return lookup_override(ctx.mTreasureChestOverrides, key->key, out_item, false); } - if (auto key = parse_derived(info->name, "freestanding:")) { + if (auto key = parse_derived(info->name, ITEM_CHECK_FREESTANDING_PREFIX)) { if (key->stage_id == Ook) { if (auto it = ctx.mItemLocations.find("Forest Temple Gale Boomerang"); it != ctx.mItemLocations.end()) { @@ -82,18 +102,23 @@ bool resolve_check(ModContext*, const ItemCheckInfo* info, uint8_t* out_item, vo } return lookup_override(ctx.mFreestandingItemOverrides, key->key, out_item, true); } - if (auto key = parse_derived(info->name, "poe:")) { - return lookup_override(ctx.mPoeOverrides, key->key, out_item, false); + if (auto key = parse_derived(info->name, ITEM_CHECK_POE_PREFIX)) { + return lookup_override(ctx.mPoeOverrides, key->key, out_item, true); } - if (auto key = parse_derived(info->name, "shop:")) { + if (auto stageId = parse_stage_check(info->name, ITEM_CHECK_BOSS_PREFIX)) { + const u16 key = static_cast((*stageId << 8) | 0x9F); + return lookup_override(ctx.mFreestandingItemOverrides, key, out_item, true); + } + if (auto key = parse_derived(info->name, ITEM_CHECK_SHOP_PREFIX)) { return lookup_override(ctx.mShopOverrides, key->key, out_item, true); } - if (auto key = parse_derived(info->name, "sky:")) { + if (auto key = parse_derived(info->name, ITEM_CHECK_SKY_PREFIX)) { return lookup_override(ctx.mSkyCharacterOverrides, key->key, out_item, true); } - if (std::strncmp(info->name, "bug:", 4) == 0) { - const u8 insect = static_cast(std::atoi(info->name + 4)); + constexpr std::string_view bugPrefix{ITEM_CHECK_BUG_PREFIX}; + if (std::strncmp(info->name, bugPrefix.data(), bugPrefix.size()) == 0) { + const u8 insect = static_cast(std::atoi(info->name + bugPrefix.size())); if (auto it = ctx.mBugRewardOverrides.find(insect); it != ctx.mBugRewardOverrides.end()) { *out_item = static_cast(verifyProgressiveItem(it->second)); return true; @@ -110,17 +135,20 @@ void observe_give(ModContext*, const ItemGiveInfo* info, void*) { } auto& ctx = randomizer_GetContext(); - if (ctx.mItemLocations.contains(info->check_name)) { - randomizer_setTempFlagForLocation(info->check_name); + if (const auto it = ctx.mItemLocations.find(info->check_name); + it != ctx.mItemLocations.end()) + { + randomizer_setTempFlag(it->second); } - if (std::strcmp(info->check_name, "dungeon_reward:D_MN10") == 0) { - dComIfGs_onItem(0x9E, -1); - } else if (auto key = parse_derived(info->check_name, "freestanding:"); - key && key->stage_id == Ook) + if (auto key = parse_derived(info->check_name, ITEM_CHECK_FREESTANDING_PREFIX); + key && key->stage_id == Ook) { - dComIfGs_onItem(0x9D, -1); - randomizer_setTempFlagForLocation("Forest Temple Gale Boomerang"); + if (const auto it = ctx.mItemLocations.find("Forest Temple Gale Boomerang"); + it != ctx.mItemLocations.end()) + { + randomizer_setTempFlag(it->second); + } } } @@ -132,6 +160,11 @@ bool activateSeed(const char* hash) { return false; } + if (messages::activate(ctx) != MOD_OK) { + ctx = RandomizerContext{}; + return false; + } + item::apply_item_data_tables(); svc_mng.item->set_check_resolver(mod_ctx, nullptr, resolve_check, nullptr, &s_check_resolver); @@ -158,6 +191,7 @@ void deactivateSeed() { } s_stage_edits.clear(); + messages::deactivate(); item::restore_item_data_tables(); randomizer_GetContext() = RandomizerContext{}; g_randomizerState = RandomizerState{}; @@ -438,6 +472,11 @@ ModResult onGameModeUpdate(void*, ModError* error) { ModResult initialize(const ServiceManager& services) { svc_mng = services; + const ModResult messageResult = messages::initialize(); + if (messageResult != MOD_OK) { + return messageResult; + } + const GameModeDesc gameModeDesc = { .struct_size = sizeof(GameModeDesc), .game_mode_id = "randomizer", diff --git a/mods/randomizer/src/tools.cpp b/mods/randomizer/src/tools.cpp index a76c0b8934..174b70dd61 100644 --- a/mods/randomizer/src/tools.cpp +++ b/mods/randomizer/src/tools.cpp @@ -12,6 +12,7 @@ #include "stages.h" #include "verify_item_functions.h" +#include #include bool playerIsInRoomStage(s32 room, const char* stage) @@ -505,40 +506,57 @@ randomizer::logic::item_pool::ItemPool getSaveItemPool(randomizer::logic::world: std::string nameLookupOverride(const std::string& locationName) { // [generator name], [service name] static std::unordered_map nameLookup = { - {"Arbiters Grounds Dungeon Reward", "dungeon_reward:D_MN10"}, - {"Ashei Sketch", "ashei_sketch"}, - {"Auru Gift To Fyer", "auru_memo"}, - {"Cave of Ordeals Great Fairy Reward", "fairy_reward:D_SB01"}, - {"Charlo Donation Blessing", "prayer_reward"}, - {"Coro Bottle", "coro_bottle"}, - {"Gift From Ralis", "coral_earring"}, - {"Goron Mines Gor Amato Key Shard", "key_shard_1:D_MN04"}, - {"Goron Mines Gor Ebizo Key Shard", "key_shard_2:D_MN04"}, - {"Goron Mines Gor Liggs Key Shard", "key_shard_3:D_MN04"}, - {"Herding Goats Reward", "goats_reward"}, - {"Hyrule Castle King Bulblin Key", "bulblin_key:D_MN09"}, - {"Ilia Charm", "ilia_charm"}, - {"Iza Helping Hand", "iza_reward_1"}, - {"Iza Raging Rapids Minigame", "iza_reward_2"}, - {"Jovani 20 Poe Soul Reward", "jovani_reward_1"}, - {"Jovani 60 Poe Soul Reward", "jovani_reward_2"}, - {"Ordon Cat Rescue", "sera_reward"}, - {"Ordon Shield", "ordon_shield"}, - {"Ordon Sword", "ordon_sword"}, - {"Plumm Fruit Balloon Minigame", "plumm_minigame_reward"}, - {"Renados Letter", "renado_letter"}, - {"Rutelas Blessing", "zora_armor"}, - {"Skybook From Impaz", "skybook"}, - {"Snowboard Racing Prize", "snowboard_race_reward"}, - {"Snowpeak Ruins Ball and Chain", "ball_and_chain:D_MN11"}, - {"Snowpeak Ruins Mansion Map", "dungeon_map:D_MN11"}, - {"STAR Prize 1", "star_reward_1"}, - {"STAR Prize 2", "star_reward_2"}, - {"Talo Sharpshooting", "archery_reward:F_SP109"}, - {"Telma Invoice", "telma_invoice"}, - {"Uli Cradle Delivery", "uli_cradle_reward"}, - {"Wooden Statue", "wood_statue"}, - {"Zoras Domain Underwater Goron", "goron_reward:F_SP113"}, + {"Arbiters Grounds Dungeon Reward", ITEM_CHECK_DUNGEON_REWARD_ARBITERS}, + {"Ashei Sketch", ITEM_CHECK_ASHEI_SKETCH}, + {"Auru Gift To Fyer", ITEM_CHECK_AURU_MEMO}, + {"Cave of Ordeals Great Fairy Reward", ITEM_CHECK_FAIRY_REWARD}, + {"Charlo Donation Blessing", ITEM_CHECK_PRAYER_REWARD}, + {"Coro Bottle", ITEM_CHECK_CORO_BOTTLE}, + {"Fishing Hole Bottle", ITEM_CHECK_FISHING_BOTTLE}, + {"Fishing Hole Heart Piece", ITEM_CHECK_FISHING_HEART_PIECE}, + {"Forest Temple Dungeon Reward", ITEM_CHECK_DUNGEON_REWARD_FOREST}, + {"Gift From Ralis", ITEM_CHECK_CORAL_EARRING}, + {"Goron Mines Dungeon Reward", ITEM_CHECK_DUNGEON_REWARD_GORON}, + {"Goron Mines Gor Amato Key Shard", ITEM_CHECK_KEY_SHARD_1}, + {"Goron Mines Gor Ebizo Key Shard", ITEM_CHECK_KEY_SHARD_2}, + {"Goron Mines Gor Liggs Key Shard", ITEM_CHECK_KEY_SHARD_3}, + {"Herding Goats Reward", ITEM_CHECK_GOATS_REWARD}, + {"Hyrule Castle King Bulblin Key", ITEM_CHECK_BULBLIN_KEY}, + {"Ilia Charm", ITEM_CHECK_ILIA_CHARM}, + {"Ilia Memory Reward", ITEM_CHECK_ILIA_MEMORY}, + {"Iza Helping Hand", ITEM_CHECK_IZA_REWARD_1}, + {"Iza Raging Rapids Minigame", ITEM_CHECK_IZA_REWARD_2}, + {"Jovani 20 Poe Soul Reward", ITEM_CHECK_JOVANI_REWARD_1}, + {"Jovani 60 Poe Soul Reward", ITEM_CHECK_JOVANI_REWARD_2}, + {"Kakariko Village Malo Mart Hawkeye", "shop:R_SP109:62"}, + {"Kakariko Village Malo Mart Hylian Shield", "shop:R_SP109:44"}, + {"Kakariko Village Malo Mart Red Potion", "shop:R_SP109:97"}, + {"Kakariko Village Malo Mart Wooden Shield", "shop:R_SP109:43"}, + {"Castle Town Malo Mart Magic Armor", "shop:R_SP160:48"}, + {"Lakebed Temple Dungeon Reward", ITEM_CHECK_DUNGEON_REWARD_LAKEBED}, + {"Ordon Cat Rescue", ITEM_CHECK_SERA_REWARD}, + {"Ordon Shield", ITEM_CHECK_ORDON_SHIELD}, + {"Ordon Sword", ITEM_CHECK_ORDON_SWORD}, + {"Plumm Fruit Balloon Minigame", ITEM_CHECK_PLUMM_REWARD}, + {"Renados Letter", ITEM_CHECK_RENADO_LETTER}, + {"Rutelas Blessing", ITEM_CHECK_ZORA_ARMOR}, + {"Sacred Grove Pedestal Master Sword", ITEM_CHECK_MASTER_SWORD}, + {"Sacred Grove Pedestal Shadow Crystal", ITEM_CHECK_SHADOW_CRYSTAL}, + {"Sera Shop Slingshot", "shop:R_SP01:75"}, + {"Skybook From Impaz", ITEM_CHECK_SKYBOOK}, + {"Snowboard Racing Prize", ITEM_CHECK_SNOWBOARD_REWARD}, + {"Snowpeak Ruins Dungeon Reward", ITEM_CHECK_DUNGEON_REWARD_SNOWPEAK}, + {"Snowpeak Ruins Ball and Chain", ITEM_CHECK_BALL_AND_CHAIN}, + {"Snowpeak Ruins Mansion Map", ITEM_CHECK_DUNGEON_MAP_SNOWPEAK}, + {"STAR Prize 1", ITEM_CHECK_STAR_REWARD_1}, + {"STAR Prize 2", ITEM_CHECK_STAR_REWARD_2}, + {"Talo Sharpshooting", ITEM_CHECK_ARCHERY_REWARD}, + {"Telma Invoice", ITEM_CHECK_TELMA_INVOICE}, + {"Temple of Time Dungeon Reward", ITEM_CHECK_DUNGEON_REWARD_TIME}, + {"Uli Cradle Delivery", ITEM_CHECK_ULI_CRADLE_REWARD}, + {"Wooden Statue", ITEM_CHECK_WOOD_STATUE}, + {"City in the Sky Dungeon Reward", ITEM_CHECK_DUNGEON_REWARD_CITY}, + {"Zoras Domain Underwater Goron", ITEM_CHECK_GORON_REWARD}, }; for (auto& [oldName, newName] : nameLookup) { @@ -646,12 +664,6 @@ int getLocationItem(randomizer::logic::location::Location* location) { auto key = (stage << 8) | flag; return context.mTwilitInsectOverrides[key]; } - if (auto& flwNode = locationMeta["FLW Message"]) { - auto group = flwNode[0]["Group"].as(); - auto messageId = flwNode[0]["Message Id"].as(); - u32 key = (group << 16) | messageId; - return context.mFlowItemMessageOverrides[key].itemId; - } if (auto& nameNode = locationMeta["Name Lookup"]) { auto name = nameLookupOverride(nameNode[0].as()); return context.mItemLocations[name].itemId; @@ -848,4 +860,4 @@ u8 getAreaKeyNum(int i_stageNo) { } return dComIfGs_getSaveData()->getSave(i_stageNo).getBit().getKeyNum(); -} \ No newline at end of file +}