diff --git a/files.cmake b/files.cmake index f4d9721a83..12152e35d7 100644 --- a/files.cmake +++ b/files.cmake @@ -1632,6 +1632,7 @@ set(DUSK_FILES src/dusk/cosmetics/texture_utils.hpp src/dusk/cosmetics/texture_utils.cpp # Randomizer files + src/dusk/randomizer/game/custom_flow_ids.hpp src/dusk/randomizer/game/flags.cpp src/dusk/randomizer/game/flags.h src/dusk/randomizer/game/messages.cpp diff --git a/include/d/d_msg_class.h b/include/d/d_msg_class.h index 5292eed5d3..3172f8a01a 100644 --- a/include/d/d_msg_class.h +++ b/include/d/d_msg_class.h @@ -37,6 +37,26 @@ public: /* 0x12 */ BE(u16) unk_0x12; }; +#if TARGET_PC +// Default attributes for custom text ids +static JMSMesgEntry_c defaultEntry{ + .string_offset = 0, + .message_id = 0, + .event_label_id = 0, + .se_speaker = 0x15, + .fuki_kind = 0x0D, + .output_type = 0x01, + .fuki_pos_type = 0x00, + .unk_0xc = 0xFF, + .unk_0xd = 0x00, + .se_mood = 0x0D, + .camera_id = 0x0, + .base_anm_id = 0x1F, + .face_anm_id = 0x1F, + .unk_0x12 = 0x400, +}; +#endif + class JMSMesgInfo_c { public: /* 0x00 */ bmg_section_t header; // section header @@ -44,6 +64,9 @@ public: /* 0x0A */ BE(u16) entry_size; // size of an entry /* 0x0C */ u8 padding[4]; // padding /* 0x10 */ JMSMesgEntry_c entries[0]; +#if TARGET_PC + JMSMesgEntry_c& getEntry(u16 index); +#endif }; class COutFont_c; diff --git a/include/d/d_msg_flow.h b/include/d/d_msg_flow.h index 98bfb029d8..87c0d2aa32 100644 --- a/include/d/d_msg_flow.h +++ b/include/d/d_msg_flow.h @@ -133,6 +133,8 @@ public: u16 query053(mesg_flow_node_branch*, fopAc_ac_c*, int); #if TARGET_PC u16 query054(mesg_flow_node_branch*, fopAc_ac_c*, int); + u16 query055(mesg_flow_node_branch*, fopAc_ac_c*, int); + u16 query056(mesg_flow_node_branch*, fopAc_ac_c*, int); #endif int event000(mesg_flow_node_event*, fopAc_ac_c*); int event001(mesg_flow_node_event*, fopAc_ac_c*); @@ -181,6 +183,7 @@ public: // events for rando int event043(mesg_flow_node_event*, fopAc_ac_c*); int event044(mesg_flow_node_event*, fopAc_ac_c*); + int event045(mesg_flow_node_event*, fopAc_ac_c*); #endif void initWord(fopAc_ac_c*, const char*, u8, int, fopAc_ac_c**); @@ -196,12 +199,14 @@ public: void setMsg(u32 msg) { mMsg = msg; } bool checkEndFlow() { return (u32)field_0x26 == 1; } - static DUSK_GAME_DATA queryFunc mQueryList[DUSK_IF_ELSE(54, 53)]; - static DUSK_GAME_DATA eventFunc mEventList[DUSK_IF_ELSE(45, 43)]; + static DUSK_GAME_DATA queryFunc mQueryList[DUSK_IF_ELSE(56, 53)]; + static DUSK_GAME_DATA eventFunc mEventList[DUSK_IF_ELSE(46, 43)]; #if TARGET_PC // patch funcs for rando + u32 getKeyForIndex(u16 nodeIdx); void randoPatchNodeType(u8& type, u16 nodeIdx); + void randoPatchMessageNode(mesg_flow_node*& message_node, u16 nodeIdx); void randoPatchBranchNode(mesg_flow_node_branch*& branch_node, u16 nodeIdx); void randoPatchEventNode(mesg_flow_node_event*& event_node, u16 nodeIdx); #endif diff --git a/libs/JSystem/src/JMessage/control.cpp b/libs/JSystem/src/JMessage/control.cpp index 68a5ffd1cd..cbf1fbb20d 100644 --- a/libs/JSystem/src/JMessage/control.cpp +++ b/libs/JSystem/src/JMessage/control.cpp @@ -8,6 +8,7 @@ #include "JSystem/JMessage/control.h" #if TARGET_PC +#include "dusk/randomizer/game/custom_flow_ids.hpp" #include "dusk/randomizer/game/messages.hpp" #endif @@ -75,6 +76,12 @@ int JMessage::TControl::setMessageID(u32 uMsgID, u32 param_1, bool* pbValid) { u32 uCode = pProcessor->toMessageCode_messageID(uMsgID, param_1, pbValid); if (uCode == 0xFFFFFFFF) { +#if TARGET_PC + // If this is a custom text id + if (uMsgID >= BASE_CUSTOM_MSG_AND_FLOW_ID) + uCode = CUSTOM_BMG_GROUP << 16 | uMsgID; + else // return 0 +#endif return 0; } @@ -84,6 +91,16 @@ int JMessage::TControl::setMessageID(u32 uMsgID, u32 param_1, bool* pbValid) { bool JMessage::TControl::setMessageCode_inSequence_(JMessage::TProcessor const* pProcessor, u16 u16GroupID, u16 u16Index) { pEntry_ = pProcessor->getMessageEntry_messageCode(u16GroupID, u16Index); if (pEntry_ == NULL) { +#if TARGET_PC + if (HandleCustomText(this, u16Index)) { + uMessageGroupID_ = u16GroupID; + uMessageID_ = u16Index; + pResourceCache_ = pProcessor->getResourceCache(); + pMessageText_current_ = pMessageText_begin_; + oStack_renderingProcessor_.clear(); + return true; + } else +#endif return false; } diff --git a/src/d/d_msg_class.cpp b/src/d/d_msg_class.cpp index 4f0db99bc5..6ebaf5c320 100644 --- a/src/d/d_msg_class.cpp +++ b/src/d/d_msg_class.cpp @@ -15,6 +15,8 @@ #if TARGET_PC #include "dusk/menu_pointer.h" #include "dusk/scope_guard.hpp" +#include "dusk/randomizer/game/randomizer_context.hpp" +#include "dusk/randomizer/game/custom_flow_ids.hpp" #endif #if REGION_JPN @@ -37,6 +39,34 @@ #define CHAR_CODE_THIN_DOWN_ARROW 0xBE #endif +#if TARGET_PC +// Custom function to return custom entries if applicable +JMSMesgEntry_c& JMSMesgInfo_c::getEntry(u16 index) { + u32 key{}; + auto& attributeOverrides = randomizer_GetContext().mAttributeOverrides; + if (index < BASE_CUSTOM_MSG_AND_FLOW_ID) { + // Regular entry + u8 group = dComIfGp_getMsgObjectClass()->getMessageGroup(index); + key = group << 16 | index; + // Override attributes if we modified them for this index + if (attributeOverrides.contains(key)) { + return *reinterpret_cast(&attributeOverrides[key]); + } + return entries[index]; + } + + // We have a custom index, use the custom index group + key = CUSTOM_BMG_GROUP << 16 | index; + if (attributeOverrides.contains(key)) { + return *reinterpret_cast(&attributeOverrides[key]); + } + + // If we didn't specify attributes for the custom index, use the default + defaultEntry.message_id = index; + return defaultEntry; +} +#endif + static bool checkCharInfoCharactor(int c) { if (c != 0x8140 && c != 0x8141 && c != 0x8142 && c != 0x0020 && c != 0x0022 && c != 0x0027 && c != 0x002C && c != 0x002E) diff --git a/src/d/d_msg_flow.cpp b/src/d/d_msg_flow.cpp index 387b9f6a2c..cdfa7a9e2f 100644 --- a/src/d/d_msg_flow.cpp +++ b/src/d/d_msg_flow.cpp @@ -16,6 +16,7 @@ #include #if TARGET_PC +#include "dusk/randomizer/game/custom_flow_ids.hpp" #include "dusk/randomizer/game/flags.h" #include "dusk/randomizer/game/tools.h" #include "dusk/randomizer/game/stages.h" @@ -23,6 +24,21 @@ #include "dusk/randomizer/game/verify_item_functions.h" #endif +// Macros for somewhat gracefully handling custom flow ids +// The game likes to directly index into the flow node table a lot, so this macro keeps +// reletively the same syntax in all the places that happens +#if TARGET_PC +#define M_FLOW_NODE_TBL(index) \ + (*(index >= BASE_CUSTOM_MSG_AND_FLOW_ID && index != 0xFFFF && randomizer_IsActive() ? \ + reinterpret_cast(&randomizer_GetContext().mFlowPatches.at((CUSTOM_BMG_GROUP << 16) | (index))) : \ + &mFlowNodeTBL[index])) +// If we're indexing into the flow index table, just return the custom index directly +#define M_FLOW_IDX_TBL(index) index >= BASE_CUSTOM_MSG_AND_FLOW_ID && randomizer_IsActive() ? index : mFlowIdxTBL[index] +#else +#define M_FLOW_NODE_TBL(index) mFlowNodeTBL[index] +#define M_FLOW_IDX_TBL(index) mFlowIdxTBL[index] +#endif + dMsgFlow_c::dMsgFlow_c() { mNonStopJunpFlowFlag = 0; setInitValue(1); @@ -115,7 +131,7 @@ int dMsgFlow_c::checkOpenDoor(fopAc_ac_c* i_speaker_p, int* param_2) { mesg_flow_node_event* event_node = NULL; while ((nodeIdx != 0xFFFF && !var_r27) && !var_r25) { - u8 type = mFlowNodeTBL[nodeIdx].message.type; + u8 type = M_FLOW_NODE_TBL(nodeIdx).message.type; #if TARGET_PC randoPatchNodeType(type, nodeIdx); @@ -123,13 +139,13 @@ int dMsgFlow_c::checkOpenDoor(fopAc_ac_c* i_speaker_p, int* param_2) { switch(type) { case NODETYPE_MESSAGE_e: { - msg_node = &mFlowNodeTBL[nodeIdx].message; + msg_node = &M_FLOW_NODE_TBL(nodeIdx).message; nodeIdx = msg_node->next_node_idx; var_r26++; break; } case NODETYPE_BRANCH_e: { - branch_node = (mesg_flow_node_branch*)&mFlowNodeTBL[nodeIdx].branch; + branch_node = (mesg_flow_node_branch*)&M_FLOW_NODE_TBL(nodeIdx).branch; #if TARGET_PC randoPatchBranchNode(branch_node, nodeIdx); @@ -149,10 +165,20 @@ int dMsgFlow_c::checkOpenDoor(fopAc_ac_c* i_speaker_p, int* param_2) { u16 query_ret = (this->*mQueryList[branch_node->query_idx])(branch_node, i_speaker_p, 0); u16 spE = branch_node->next_node_idx + query_ret; nodeIdx = mFlowIdxTBL[spE]; +#if TARGET_PC + // Overwrite the node we branch to if applicable + if (randomizer_IsActive()) { + auto& branchOverrides = randomizer_GetContext().mFlowPatchesBranchOverrides; + auto key = getKeyForIndex(nodeIdx); + if (branchOverrides.contains(key)) { + nodeIdx = branchOverrides[key][query_ret]; + } + } +#endif break; } case NODETYPE_EVENT_e: { - event_node = &mFlowNodeTBL[nodeIdx].event; + event_node = &M_FLOW_NODE_TBL(nodeIdx).event; #if TARGET_PC randoPatchEventNode(event_node, nodeIdx); @@ -171,7 +197,7 @@ int dMsgFlow_c::checkOpenDoor(fopAc_ac_c* i_speaker_p, int* param_2) { var_r25 = TRUE; break; default: - nodeIdx = mFlowIdxTBL[event_node->next_node_idx]; + nodeIdx = M_FLOW_IDX_TBL(event_node->next_node_idx); break; } break; @@ -413,7 +439,7 @@ void dMsgFlow_c::setNodeIndex(u16 i_nodeIdx, fopAc_ac_c** i_talkPartners) { dMsgObject_endFlowGroup(); field_0x26 = 1; } else { - switch (mFlowNodeTBL[i_nodeIdx].message.type) { + switch (M_FLOW_NODE_TBL(i_nodeIdx).message.type) { case 0: break; case NODETYPE_MESSAGE_e: @@ -423,7 +449,11 @@ void dMsgFlow_c::setNodeIndex(u16 i_nodeIdx, fopAc_ac_c** i_talkPartners) { break; case NODETYPE_EVENT_e: mesg_flow_node_event* node = NULL; - node = &mFlowNodeTBL[i_nodeIdx].event; + node = &M_FLOW_NODE_TBL(i_nodeIdx).event; + +#if TARGET_PC + randoPatchEventNode(node, i_nodeIdx); +#endif if (node->event_idx == 21 || node->event_idx == 32 || node->event_idx == 33) { if (node->event_idx == 21) { @@ -476,9 +506,21 @@ int dMsgFlow_c::setSelectMsg(mesg_flow_node* i_flowNode_p, mesg_flow_node* param inf_p = (BE(u16)*)getMsgDataBlock("INF1"); var_r29 = param_2; +#if TARGET_PC + // Don't index our msg index if it's a custom one (the custom one is already the correct value) + if (randomizer_IsActive() && var_r29->msg_index >= BASE_CUSTOM_MSG_AND_FLOW_ID) { + temp_r25 = var_r29->msg_index; + } else +#endif temp_r25 = ((inf_p + (var_r29->msg_index) * 10))[10]; var_r29 = i_flowNode_p; +#if TARGET_PC + // Same as above + if (randomizer_IsActive() && var_r29->msg_index >= BASE_CUSTOM_MSG_AND_FLOW_ID) { + msg_no = var_r29->msg_index; + } else +#endif msg_no = ((inf_p + (var_r29->msg_index) * 10))[10]; // "Message Set (Select)" @@ -523,6 +565,12 @@ int dMsgFlow_c::setNormalMsg(mesg_flow_node* i_flowNode_p, fopAc_ac_c* i_speaker var_r29 = i_flowNode_p; inf_p = (BE(u16)*)getMsgDataBlock("INF1"); +#if TARGET_PC + // Don't index our msg index if it's a custom one (the custom one is already the correct value) + if (randomizer_IsActive() && var_r29->msg_index >= BASE_CUSTOM_MSG_AND_FLOW_ID) { + msg_no = var_r29->msg_index; + } else +#endif msg_no = (inf_p + (var_r29->msg_index) * 10)[10]; #if TARGET_PC @@ -581,31 +629,42 @@ int dMsgFlow_c::setNormalMsg(mesg_flow_node* i_flowNode_p, fopAc_ac_c* i_speaker int dMsgFlow_c::messageNodeProc(fopAc_ac_c* i_speaker_p, fopAc_ac_c** i_talkPartners) { mesg_flow_node* flowNode_p = NULL; - flowNode_p = &mFlowNodeTBL[mNodeIdx].message; + flowNode_p = &M_FLOW_NODE_TBL(mNodeIdx).message; + +#if TARGET_PC + randoPatchMessageNode(flowNode_p, mNodeIdx); +#endif if (field_0x25 != 0) { if (mSelType != SELTYPE_NONE_e) { u16 aNextNodeIndex = flowNode_p->next_node_idx; JUT_ASSERT(1051, 0xFFFF != aNextNodeIndex); - if (mSelType == SELTYPE_VERTICAL_e && mFlowNodeTBL[aNextNodeIndex].message.type == NODETYPE_MESSAGE_e) { - JUT_ASSERT(1056, NODETYPE_MESSAGE_e == mFlowNodeTBL[aNextNodeIndex].message.type); - if (setSelectMsg(&mFlowNodeTBL[mNodeIdx].message, &mFlowNodeTBL[aNextNodeIndex].message, i_speaker_p)) { +#if TARGET_PC + mesg_flow_node* nextFlowNode_p = &M_FLOW_NODE_TBL(aNextNodeIndex).message; + randoPatchMessageNode(nextFlowNode_p, aNextNodeIndex); + // The code really likes directly indexing over and over again here, so we have to put + // in some ugly DUSK_IF_ELSEs +#endif + + if (mSelType == SELTYPE_VERTICAL_e && DUSK_IF_ELSE(nextFlowNode_p->, mFlowNodeTBL[aNextNodeIndex].message.)type == NODETYPE_MESSAGE_e) { + JUT_ASSERT(1056, NODETYPE_MESSAGE_e == DUSK_IF_ELSE(nextFlowNode_p->, mFlowNodeTBL[aNextNodeIndex].message.)type); + if (setSelectMsg(DUSK_IF_ELSE(flowNode_p, &mFlowNodeTBL[mNodeIdx].message), DUSK_IF_ELSE(nextFlowNode_p, &mFlowNodeTBL[aNextNodeIndex].message), i_speaker_p)) { mNodeIdx = aNextNodeIndex; mSelType = SELTYPE_NONE_e; field_0x25 = 0; } - } else if (mSelType == SELTYPE_HORIZONTAL_e && mFlowNodeTBL[aNextNodeIndex].message.type == NODETYPE_BRANCH_e) { - if (setNormalMsg(&mFlowNodeTBL[mNodeIdx].message, i_speaker_p)) { + } else if (mSelType == SELTYPE_HORIZONTAL_e && DUSK_IF_ELSE(nextFlowNode_p->, mFlowNodeTBL[aNextNodeIndex].message.)type == NODETYPE_BRANCH_e) { + if (setNormalMsg(DUSK_IF_ELSE(flowNode_p, &mFlowNodeTBL[mNodeIdx].message), i_speaker_p)) { mSelType = SELTYPE_NONE_e; field_0x25 = 0; } } else { - OS_REPORT("★sel select mesg ===> %d, %d, %d\n", mSelType, aNextNodeIndex, mFlowNodeTBL[aNextNodeIndex].message.type); + OS_REPORT("★sel select mesg ===> %d, %d, %d\n", mSelType, aNextNodeIndex, DUSK_IF_ELSE(nextFlowNode_p->, mFlowNodeTBL[aNextNodeIndex].message.)type); JUT_ASSERT(1077, FALSE); } } else { - if (setNormalMsg(&mFlowNodeTBL[mNodeIdx].message, i_speaker_p)) { + if (setNormalMsg(DUSK_IF_ELSE(flowNode_p, &mFlowNodeTBL[mNodeIdx].message), i_speaker_p)) { field_0x25 = 0; } } @@ -654,7 +713,7 @@ int dMsgFlow_c::messageNodeProc(fopAc_ac_c* i_speaker_p, fopAc_ac_c** i_talkPart case 18: setNodeIndex(flowNode_p->next_node_idx, i_talkPartners); - mesg_flow_node* var_r26 = &mFlowNodeTBL[flowNode_p->next_node_idx].message; + mesg_flow_node* var_r26 = &M_FLOW_NODE_TBL(flowNode_p->next_node_idx).message; if (var_r26->field_0x1 == 0x15 || var_r26->field_0x1 == 0x20 || var_r26->field_0x1 == 0x21) { return 0; } @@ -668,7 +727,7 @@ int dMsgFlow_c::messageNodeProc(fopAc_ac_c* i_speaker_p, fopAc_ac_c** i_talkPart int dMsgFlow_c::branchNodeProc(fopAc_ac_c* i_speaker_p, fopAc_ac_c** i_talkPartners) { mesg_flow_node_branch* node = NULL; - node = &mFlowNodeTBL[mNodeIdx].branch; + node = &M_FLOW_NODE_TBL(mNodeIdx).branch; #if TARGET_PC randoPatchBranchNode(node, mNodeIdx); @@ -677,13 +736,25 @@ int dMsgFlow_c::branchNodeProc(fopAc_ac_c* i_speaker_p, fopAc_ac_c** i_talkPartn u16 proc_status = (this->*mQueryList[node->query_idx])(node, i_speaker_p, 1); u16 var_r28 = node->next_node_idx + proc_status; +#if TARGET_PC + // Overwrite the node we branch to if applicable + if (randomizer_IsActive()) { + auto& branchOverrides = randomizer_GetContext().mFlowPatchesBranchOverrides; + auto key = getKeyForIndex(mNodeIdx); + u16 nextIndex = mFlowIdxTBL[var_r28]; + if (branchOverrides.contains(key)) { + nextIndex = branchOverrides[key][proc_status]; + } + setNodeIndex(nextIndex, i_talkPartners); + } else +#endif setNodeIndex(mFlowIdxTBL[var_r28], i_talkPartners); return 1; } int dMsgFlow_c::eventNodeProc(fopAc_ac_c* i_speaker_p, fopAc_ac_c** i_talkPartners) { mesg_flow_node_event* node = NULL; - node = &mFlowNodeTBL[mNodeIdx].event; + node = &M_FLOW_NODE_TBL(mNodeIdx).event; #if TARGET_PC randoPatchEventNode(node, mNodeIdx); #endif @@ -692,7 +763,7 @@ int dMsgFlow_c::eventNodeProc(fopAc_ac_c* i_speaker_p, fopAc_ac_c** i_talkPartne switch (node->event_idx) { case 8: { getParam(&mEventId, &field_0x30, node->params); - setNodeIndex(mFlowIdxTBL[node->next_node_idx], i_talkPartners); + setNodeIndex(M_FLOW_IDX_TBL(node->next_node_idx), i_talkPartners); if (field_0x26 != 0) { break; @@ -737,7 +808,7 @@ int dMsgFlow_c::eventNodeProc(fopAc_ac_c* i_speaker_p, fopAc_ac_c** i_talkPartne return 0; } default: - setNodeIndex(mFlowIdxTBL[node->next_node_idx], i_talkPartners); + setNodeIndex(M_FLOW_IDX_TBL(node->next_node_idx), i_talkPartners); } return 1; @@ -758,7 +829,7 @@ int dMsgFlow_c::nodeProc(fopAc_ac_c* i_speaker_p, fopAc_ac_c** i_talkPartners) { aSpeaker_p = i_talkPartners[field_0x38]; } - u8 type = mFlowNodeTBL[mNodeIdx].message.type; + u8 type = M_FLOW_NODE_TBL(mNodeIdx).message.type; #if TARGET_PC randoPatchNodeType(type, mNodeIdx); #endif @@ -801,7 +872,7 @@ int dMsgFlow_c::getParam(u8* params) { return *(BE(int)*)params; } -DUSK_GAME_DATA queryFunc dMsgFlow_c::mQueryList[DUSK_IF_ELSE(54, 53)] = { +DUSK_GAME_DATA queryFunc dMsgFlow_c::mQueryList[DUSK_IF_ELSE(56, 53)] = { &dMsgFlow_c::query005, &dMsgFlow_c::query001, &dMsgFlow_c::query002, &dMsgFlow_c::query003, &dMsgFlow_c::query006, &dMsgFlow_c::query007, &dMsgFlow_c::query004, &dMsgFlow_c::query008, &dMsgFlow_c::query009, &dMsgFlow_c::query010, &dMsgFlow_c::query011, &dMsgFlow_c::query012, @@ -817,7 +888,7 @@ DUSK_GAME_DATA queryFunc dMsgFlow_c::mQueryList[DUSK_IF_ELSE(54, 53)] = { &dMsgFlow_c::query049, &dMsgFlow_c::query050, &dMsgFlow_c::query051, &dMsgFlow_c::query052, &dMsgFlow_c::query053, #if TARGET_PC - &dMsgFlow_c::query054, + &dMsgFlow_c::query054, &dMsgFlow_c::query055, &dMsgFlow_c::query056 #endif }; @@ -1836,6 +1907,7 @@ u16 dMsgFlow_c::query053(mesg_flow_node_branch* i_flowNode_p, fopAc_ac_c* i_spea return ret; } +#if TARGET_PC // Like query 1, but instead returns 1 if true, and 0 if false u16 dMsgFlow_c::query054(mesg_flow_node_branch* i_flowNode_p, fopAc_ac_c* i_speaker_p, int param_2) { const u16 prm0 = i_flowNode_p->param; @@ -1850,7 +1922,33 @@ u16 dMsgFlow_c::query054(mesg_flow_node_branch* i_flowNode_p, fopAc_ac_c* i_spea return ret; } -DUSK_GAME_DATA eventFunc dMsgFlow_c::mEventList[DUSK_IF_ELSE(45, 43)] = { +// Return 0 if can change ToD, else 1 if cannot. +u16 dMsgFlow_c::query055(mesg_flow_node_branch* i_flowNode_p, fopAc_ac_c* i_speaker_p, int param_2) { + // This function is based on query044 which is used to determine whether to show the Midna menu which includes + // "Warp" or not. We also add a check to ensure the current environment is not twilight. "R_SP161" is STAR tent. + if (!dKy_darkworld_check() && !daAlink_c::checkForestOldCentury() && + (daAlink_c::checkField() || daAlink_c::checkCastleTown()) && + !daAlink_c::checkStageName("R_SP161")) + { + return 0; + } + return 1; +} + +// Return 0 if can return to dungeon entrance, 1 if in dungeon but can only return to spawn, or 2 if not in dungeon. +u16 dMsgFlow_c::query056(mesg_flow_node_branch* i_flowNode_p, fopAc_ac_c* i_speaker_p, int param_2) { + // Darkhammer is highest numbered dungeon map stage id + if (getStageID() <= Darkhammer) { + if (randomizer_GetContext().mReturnToPlaceOverrides.contains(getStageID())) { + return 0; + } + return 1; + } + return 2; +} +#endif + +DUSK_GAME_DATA eventFunc dMsgFlow_c::mEventList[DUSK_IF_ELSE(46, 43)] = { &dMsgFlow_c::event000, &dMsgFlow_c::event001, &dMsgFlow_c::event002, &dMsgFlow_c::event003, &dMsgFlow_c::event004, &dMsgFlow_c::event005, &dMsgFlow_c::event006, &dMsgFlow_c::event007, &dMsgFlow_c::event008, &dMsgFlow_c::event009, &dMsgFlow_c::event010, &dMsgFlow_c::event011, @@ -1863,7 +1961,7 @@ DUSK_GAME_DATA eventFunc dMsgFlow_c::mEventList[DUSK_IF_ELSE(45, 43)] = { &dMsgFlow_c::event036, &dMsgFlow_c::event037, &dMsgFlow_c::event038, &dMsgFlow_c::event039, &dMsgFlow_c::event040, &dMsgFlow_c::event041, &dMsgFlow_c::event042, #if TARGET_PC - &dMsgFlow_c::event043, &dMsgFlow_c::event044, + &dMsgFlow_c::event043, &dMsgFlow_c::event044, &dMsgFlow_c::event045 #endif }; @@ -2733,29 +2831,52 @@ int dMsgFlow_c::event044(mesg_flow_node_event* i_flowNode_p, fopAc_ac_c* i_speak return 1; } +int dMsgFlow_c::event045(mesg_flow_node_event* i_flowNode_p, fopAc_ac_c* i_speaker_p) { + int prm0 = getParam(i_flowNode_p->params); + randomizer_returnToSpawn(prm0); + return 1; +} + // Rando patch funcs + +u32 dMsgFlow_c::getKeyForIndex(u16 nodeIdx) { + u32 key = (dMsgObject_getGroupID() << 16) | nodeIdx; + // Change group to custom group if custom index + if (nodeIdx >= BASE_CUSTOM_MSG_AND_FLOW_ID) { + key = (CUSTOM_BMG_GROUP << 16) | nodeIdx; + } + // Dirty check to see if this node is part of bmg 0 + else if (*(reinterpret_cast(&mFlowNodeTBL[0])) == 0x0803) { + key &= 0x0000FFFF; + } + + return key; +} + void dMsgFlow_c::randoPatchNodeType(u8& type, u16 nodeIdx) { // Overwrite the type if we have a patch for this index if (randomizer_IsActive()) { - u32 key = (dMsgObject_getGroupID() << 16) | nodeIdx; - // Dirty check to see if this node is part of bmg 0 - if (*(reinterpret_cast(&mFlowNodeTBL[0])) == 0x0803) { - key &= 0x0000FFFF; - } + auto key = getKeyForIndex(nodeIdx); if (randomizer_GetContext().mFlowPatches.contains(key)) { type = reinterpret_cast(&randomizer_GetContext().mFlowPatches[key])->type; } } } +void dMsgFlow_c::randoPatchMessageNode(mesg_flow_node*& message_node, u16 nodeIdx) { + // Overwrite this node if we have a patch for it + if (randomizer_IsActive()) { + auto key = getKeyForIndex(nodeIdx); + if (randomizer_GetContext().mFlowPatches.contains(key)) { + message_node = reinterpret_cast(&randomizer_GetContext().mFlowPatches[key]); + } + } +} + void dMsgFlow_c::randoPatchBranchNode(mesg_flow_node_branch*& branch_node, u16 nodeIdx) { // Overwrite this node if we have a patch for it if (randomizer_IsActive()) { - u32 key = (dMsgObject_getGroupID() << 16) | nodeIdx; - // Dirty check to see if this node is part of bmg 0 - if (*(reinterpret_cast(&mFlowNodeTBL[0])) == 0x0803) { - key &= 0x0000FFFF; - } + auto key = getKeyForIndex(nodeIdx); if (randomizer_GetContext().mFlowPatches.contains(key)) { branch_node = reinterpret_cast(&randomizer_GetContext().mFlowPatches[key]); } @@ -2765,11 +2886,7 @@ void dMsgFlow_c::randoPatchBranchNode(mesg_flow_node_branch*& branch_node, u16 n void dMsgFlow_c::randoPatchEventNode(mesg_flow_node_event*& event_node, u16 nodeIdx) { // Overwrite this node if we have a patch for it if (randomizer_IsActive()) { - u32 key = (dMsgObject_getGroupID() << 16) | nodeIdx; - // Dirty check to see if this node is part of bmg 0 - if (*(reinterpret_cast(&mFlowNodeTBL[0])) == 0x0803) { - key &= 0x0000FFFF; - } + auto key = getKeyForIndex(nodeIdx); if (randomizer_GetContext().mFlowPatches.contains(key)) { event_node = reinterpret_cast(&randomizer_GetContext().mFlowPatches[key]); } diff --git a/src/d/d_msg_object.cpp b/src/d/d_msg_object.cpp index e66245a33d..10882c3dfd 100644 --- a/src/d/d_msg_object.cpp +++ b/src/d/d_msg_object.cpp @@ -31,6 +31,7 @@ #if TARGET_PC #include "d/d_item.h" +#include "dusk/randomizer/game/custom_flow_ids.hpp" #include "dusk/randomizer/game/messages.hpp" #include "dusk/randomizer/game/randomizer_context.hpp" #include "dusk/randomizer/game/stages.h" @@ -45,6 +46,13 @@ #include #endif +// Macro to call our custom function for getting attributes +#if TARGET_PC +#define ENTRIES(i) getEntry(i) +#else +#define ENTRIES(i) entries[i] +#endif + static void dMsgObject_addFundRaising(s16 param_0); static void dMsgObject_addTotalPayment(s16 param_0); @@ -701,7 +709,7 @@ void dMsgObject_c::setMessageIndex(u32 revoIndex, u32 param_2, bool param_3) { JMSMesgInfo_c* pMsg = (JMSMesgInfo_c*)((char*)mpMsgDt + 0x20); u8* iVar2 = (u8*)pMsg + pMsg->header.size; u32 msg_id = getMessageIndex(revoIndex); - dComIfGp_setMesgCameraAttrInfo(pMsg->entries[msg_id].camera_id); + dComIfGp_setMesgCameraAttrInfo(pMsg->ENTRIES(msg_id).camera_id); if (field_0x15c == 1000) { mpRefer->setSelMsgPtr(NULL); } else { @@ -714,6 +722,10 @@ void dMsgObject_c::setMessageIndex(u32 revoIndex, u32 param_2, bool param_3) { // This is where the game sets the pointer to the string for message choices. // If any of our custom messages are for message choices, override them here if (randomizer_IsActive()) { + // Change to custom group if we have a custom message index + if (msgIndex >= BASE_CUSTOM_MSG_AND_FLOW_ID) { + groupID = CUSTOM_BMG_GROUP; + } auto override = GetTextOverride(groupID, field_0x15c); if (override != NULL) { my_ptr = override; @@ -750,7 +762,7 @@ void dMsgObject_c::setMessageIndexDemo(u32 revoMsgIndex, bool param_2) { JMSMesgInfo_c* info_header_p = (JMSMesgInfo_c*)((char*)mpMsgDt + 0x20); JMSMesgInfo_c* reg_25 = (JMSMesgInfo_c*)((char*) info_header_p + info_header_p->header.size); int ind = getMessageIndex(revoMsgIndex); - dComIfGp_setMesgCameraAttrInfo(info_header_p->entries[ind].camera_id); + dComIfGp_setMesgCameraAttrInfo(info_header_p->ENTRIES(ind).camera_id); mpRefer->setSelMsgPtr(NULL); if (param_2) { mpCtrl->setMessageID(mMessageID, 0, NULL); @@ -758,6 +770,13 @@ void dMsgObject_c::setMessageIndexDemo(u32 revoMsgIndex, bool param_2) { } u32 dMsgObject_c::getMessageIndex(u32 param_0) { +#if TARGET_PC + // Directly return our index if it's above the custom id base + if (randomizer_IsActive() && param_0 >= BASE_CUSTOM_MSG_AND_FLOW_ID) { + return param_0; + } +#endif + u32 i = 0; JMSMesgInfo_c* pMsg = (JMSMesgInfo_c*)((char*)mpMsgDt + 0x20); u32 msgIndexCount = *((BE(u16)*)((char*)mpMsgDt + 0x28)); @@ -778,6 +797,11 @@ u32 dMsgObject_c::getMessageIndex(u32 param_0) { u32 dMsgObject_c::getRevoMessageIndex(u32 param_1) { #if TARGET_PC if (randomizer_IsActive()) { + // Directly return our index if it's custom + if (param_1 >= BASE_CUSTOM_MSG_AND_FLOW_ID) { + return param_1; + } + // Special case for Ilia Memory Reward Text (param_1 is msgId) // If we're in the sanctuary cutscene where we get the reward, override the text. // Otherwise, the regular item text for the horse call would be overridden if we find it diff --git a/src/dusk/randomizer/game/custom_flow_ids.hpp b/src/dusk/randomizer/game/custom_flow_ids.hpp new file mode 100644 index 0000000000..faa355d899 --- /dev/null +++ b/src/dusk/randomizer/game/custom_flow_ids.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include "dolphin/types.h" + +inline constexpr u16 BASE_CUSTOM_MSG_AND_FLOW_ID = 21000; +inline constexpr u16 CUSTOM_BMG_GROUP = 9; diff --git a/src/dusk/randomizer/game/messages.cpp b/src/dusk/randomizer/game/messages.cpp index fc41bdc471..bb82fa8f75 100644 --- a/src/dusk/randomizer/game/messages.cpp +++ b/src/dusk/randomizer/game/messages.cpp @@ -3,6 +3,7 @@ #include "JSystem/JMessage/control.h" #include "d/d_msg_class.h" #include "randomizer_context.hpp" +#include "custom_flow_ids.hpp" #include "dusk/version.hpp" @@ -78,6 +79,21 @@ void HandleTextOverrides(JMessage::TControl* control, JMessage::TProcessor const } } +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]); + defaultEntry.message_id = msgId; + control->pEntry_ = &defaultEntry; + return true; + } + } + return false; +} + // Used in special cases char* GetTextOverride(s16 groupID, u32 messageId) { u32 key = (groupID << 16) | messageId; diff --git a/src/dusk/randomizer/game/messages.hpp b/src/dusk/randomizer/game/messages.hpp index 3dc871c8b1..0b66e29bba 100644 --- a/src/dusk/randomizer/game/messages.hpp +++ b/src/dusk/randomizer/game/messages.hpp @@ -8,4 +8,6 @@ struct TControl; void HandleTextOverrides(JMessage::TControl* control, JMessage::TProcessor const* pProcessor, int groupID, int index); +bool HandleCustomText(JMessage::TControl* control, u16 msgId); + char* GetTextOverride(s16 groupID, u32 messageId); \ No newline at end of file diff --git a/src/dusk/randomizer/game/randomizer_context.cpp b/src/dusk/randomizer/game/randomizer_context.cpp index e03ae7072e..18334b4201 100644 --- a/src/dusk/randomizer/game/randomizer_context.cpp +++ b/src/dusk/randomizer/game/randomizer_context.cpp @@ -2,7 +2,6 @@ #include "dusk/logging.h" #include "dusk/main.h" -#include "dusk/data.hpp" #include "dusk/ui/rando_config.hpp" #include "dusk/randomizer/game/flags.h" #include "dusk/randomizer/game/tools.h" @@ -13,14 +12,17 @@ #include "dusk/randomizer/generator/utility/yaml.hpp" #include "dusk/randomizer/generator/randomizer.hpp" #include "dusk/randomizer/generator/utility/text.hpp" +#include "dusk/randomizer/generator/utility/string.hpp" #include +#include "custom_flow_ids.hpp" #include "d/actor/d_a_alink.h" #include "d/d_com_inf_game.h" #include "d/d_meter2.h" #include "d/d_meter2_draw.h" #include "d/d_meter2_info.h" +#include "d/d_msg_class.h" #include "d/d_msg_flow.h" std::optional RandomizerContext::WriteToFile() { @@ -103,6 +105,12 @@ std::optional RandomizerContext::WriteToFile() { out["mFlowPatches"] = this->mFlowPatches; + for (const auto& [key, branchOverrides]: this->mFlowPatchesBranchOverrides) { + for (auto override : branchOverrides) { + out["mFlowPatchesBranchOverrides"][key].push_back(override); + } + } + // Dump text overrides as binary to avoid losing intentional null characters YAML::Emitter textData; textData << YAML::BeginMap; @@ -125,6 +133,10 @@ std::optional RandomizerContext::WriteToFile() { out["mEntranceOverrides"][key] = std::bit_cast(override); } + for (const auto& [key, override] : mReturnToPlaceOverrides) { + out["mReturnToPlaceOverrides"][key] = std::bit_cast(override); + } + seedData << YAML::Dump(out); seedData << '\n' << textData.c_str(); seedData.close(); @@ -270,6 +282,15 @@ std::optional RandomizerContext::LoadFromHash(const std::string& ha this->mFlowPatches[key] = value; } + // Flow Patch Branch Overrides + for (const auto& flowNode : in["mFlowPatchesBranchOverrides"]) { + auto key = flowNode.first.as(); + for (const auto& branchNode : flowNode.second) { + auto override = branchNode.as(); + this->mFlowPatchesBranchOverrides[key].push_back(override); + } + } + // Text Overrides for (const auto& languageNode: in["mTextOverrides"]) { const auto& languageStr = languageNode.first.as(); @@ -289,6 +310,13 @@ std::optional RandomizerContext::LoadFromHash(const std::string& ha this->mEntranceOverrides[key] = override; } + // Return to Place Overrides + for (const auto& entranceNode : in["mReturnToPlaceOverrides"]) { + auto key = entranceNode.first.as(); + auto override = std::bit_cast(entranceNode.second.as()); + this->mReturnToPlaceOverrides[key] = override; + } + dusk::ui::push_toast(dusk::ui::Toast{ .title = "Randomizer", .content = fmt::format("Loaded Randomizer Seed {}", this->mHash), @@ -896,6 +924,46 @@ bool randomizer_mirrorChamberWallShouldExist() { (mirrorChamberAccess == RandomizerContext::BARRIER && !dComIfGs_isStageBossEnemy(0x13)); } +void randomizer_returnToSpawn(bool tryOverride) { + + auto& placeOverrides = randomizer_GetContext().mReturnToPlaceOverrides; + auto stageId = getStageID(); + + // If we're trying to override the default return to spawn + if (tryOverride && placeOverrides.contains(stageId)) { + auto entrance = placeOverrides[stageId]; + + // If in lakebed temple, spawn on land if shadow crystal is obtained like vanilla + if (entrance.stageId == Lakebed_Temple && dComIfGs_isEventBit(TRANSFORMING_UNLOCKED)) { + entrance.pointNo = 2; + } + + dComIfGp_setNextStage(allStages[entrance.stageId], entrance.pointNo, entrance.roomNo, entrance.mapLayer); + return; + } + + // If a player hasn't completed a twilight/MDH, we want to unset the transform flag so they aren't forced to be wolf + // unnecessarily. + for (int32_t i = 0; i < 4; i++) { + if (!dComIfGs_isDarkClearLV(i)) { + dComIfGs_offTransformLV(i); + } + } + + // If Midna's Desperate Hour is not complete, unset the flags that trigger it incase the player + // used return to spawn while MDH was active + if (!dComIfGs_isEventBit(MIDNAS_DESPERATE_HOUR_COMPLETED)) { + dComIfGs_offStageSwitch(4, 0xE); + dComIfGs_offEventBit(MIDNAS_DESPERATE_HOUR_STARTED); + } + + // Turn the player back into Link if they are currently wolf + dComIfGs_setTransformStatus(TF_STATUS_HUMAN); + + // Return to spawn. If the spawn has been randomized, that's taken care of within the function + dComIfGp_setNextStage("F_SP103", 1, 1, -1); +} + u8 randomizer_getRandomFoolishItemModelID() { static constexpr auto foolishItemModels = std::to_array({ dItemNo_Randomizer_ARMOR_e, @@ -1299,12 +1367,42 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { } } + // Give custom flows and message new indices as we read them in + std::unordered_map customMessageIDs{}; + std::unordered_map customFlowIDs{}; + u16 curCustomMessageID = BASE_CUSTOM_MSG_AND_FLOW_ID; + u16 curCustomFlowID = BASE_CUSTOM_MSG_AND_FLOW_ID; + // Flow Patches auto flowPatches = LOAD_EMBED_YAML(RANDO_DATA_PATH "flow_patches.yaml"); for (const auto& groupNode : flowPatches) { u8 groupNo = groupNode.first.as(); for (const auto& flowNode : groupNode.second) { - u16 index = flowNode["index"].as(); + std::string name{}; + std::list indices{}; + if (flowNode["index"]) { + if (flowNode["index"].IsSequence()) { + for (const auto& indexNode : flowNode["index"]) { + indices.push_back(indexNode.as()); + } + name = std::to_string(indices.front()); + } + else if (flowNode["index"].IsScalar()) { + auto index = flowNode["index"].as(); + indices.push_back(index); + name = std::to_string(index); + } + } else { + name = flowNode["name"].as(); + if (customFlowIDs.contains(name)) { + indices.push_back(customFlowIDs[name]); + } else { + auto newFlowIndex = curCustomFlowID++; + indices.push_back(newFlowIndex); + customFlowIDs[name] = newFlowIndex; + } + } + const auto& type = flowNode["type"].as(); u64 value{}; if (type == "branch") { @@ -1314,21 +1412,97 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { branch->query_idx = flowNode["query"].as(); branch->param = flowNode["parameters"].as(); branch->next_node_idx = flowNode["next node index"].as(); + if (flowNode["results"]) { + if (flowNode["results"].size() != branch->field_0x1) { + throw std::runtime_error(fmt::format("Flow results size for {} " + "do not match num results. (expected: {}. size: {})", name, branch->field_0x1, flowNode["results"].size())); + } + for (const auto& resultNode : flowNode["results"]) { + auto resultStr = resultNode.as(); + auto resultInt = randomizer::utility::str::toInt(resultStr); + u16 resultIndex{}; + if (resultInt.has_value()) { + resultIndex = resultInt.value(); + } else { + if (customFlowIDs.contains(resultStr)) { + resultIndex = customFlowIDs[resultStr]; + } else { + auto newFlowIndex = curCustomFlowID++; + resultIndex = newFlowIndex; + customFlowIDs[resultStr] = newFlowIndex; + } + } + for (auto index : indices) { + u32 key = (groupNo << 16) | index; + randoData.mFlowPatchesBranchOverrides[key].push_back(resultIndex); + } + } + } } else if (type == "event") { auto event = reinterpret_cast(&value); event->type = 3; event->event_idx = flowNode["event"].as(); - event->next_node_idx = flowNode["next node index"].as(); + // Check to see if we're setting a custom node as our next node + auto nextNodeIndexStr = flowNode["next node index"].as(); + auto nextNodeIndex = randomizer::utility::str::toInt(nextNodeIndexStr); + // If we have a regular index, then assume we're using an existing node index + if (nextNodeIndex.has_value()) { + event->next_node_idx = nextNodeIndex.value(); + // If we don't, assume we're setting the next node to be a custom flow node + } else { + if (customFlowIDs.contains(nextNodeIndexStr)) { + event->next_node_idx = customFlowIDs.at(nextNodeIndexStr); + } else { + auto newFlowIndex = curCustomFlowID++; + event->next_node_idx = newFlowIndex; + customFlowIDs[nextNodeIndexStr] = newFlowIndex; + } + } u32 params = flowNode["parameters"].as(); event->params[0] = (params >> 24) & 0xFF; event->params[1] = (params >> 16) & 0xFF; event->params[2] = (params >> 8) & 0xFF; event->params[3] = params & 0xFF; + } else if (type == "message") { + auto message = reinterpret_cast(&value); + message->type = 1; + // Check to see if we have a custom index (identified by string) + auto infIndexStr = flowNode["inf index"].as(); + auto infIndex = randomizer::utility::str::toInt(infIndexStr); + if (infIndex.has_value()) { + message->msg_index = infIndex.value(); + } else { + if (customMessageIDs.contains(infIndexStr)) { + message->msg_index = customMessageIDs.at(infIndexStr); + } else { + auto newMsgIndex = curCustomMessageID++; + message->msg_index = newMsgIndex; + customMessageIDs[infIndexStr] = newMsgIndex; + } + } + // message->next_node_idx = flowNode["next flow index"].as(); + // Check to see if we're setting a custom node as our next node + auto nextNodeIndexStr = flowNode["next flow index"].as(); + auto nextNodeIndex = randomizer::utility::str::toInt(nextNodeIndexStr); + // If we have a regular index, then assume we're using an existing node index + if (nextNodeIndex.has_value()) { + message->next_node_idx = nextNodeIndex.value(); + // If we don't, assume we're setting the next node to be a custom flow node + } else { + if (customFlowIDs.contains(nextNodeIndexStr)) { + message->next_node_idx = customFlowIDs.at(nextNodeIndexStr); + } else { + auto newFlowIndex = curCustomFlowID++; + message->next_node_idx = newFlowIndex; + customFlowIDs[nextNodeIndexStr] = newFlowIndex; + } + } + } + for (auto index : indices) { + u32 key = (groupNo << 16) | index; + randoData.mFlowPatches[key] = value; } - - u32 key = (groupNo << 16) | index; - randoData.mFlowPatches[key] = value; } } @@ -1336,6 +1510,27 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { auto textOverrides = LOAD_EMBED_YAML(RANDO_DATA_PATH "text/text_overrides.yaml"); for (const auto& overrideNode : textOverrides) { const auto& name = overrideNode["Name"].as(); + u8 group; + u16 messageId; + if (overrideNode["Group"]) { + group = overrideNode["Group"].as(); + } else { + // If no group specified, assume custom bmg group + group = CUSTOM_BMG_GROUP; + } + if (overrideNode["Message Id"]) { + messageId = overrideNode["Message Id"].as(); + } else { + // If no message id specified, assign custom message id based on name + if (customMessageIDs.contains(name)) { + messageId = customMessageIDs[name]; + } else { + auto newMessageId = curCustomMessageID++; + messageId = newMessageId; + customMessageIDs[name] = newMessageId; + } + } + u32 key = (group << 16) | messageId; for (auto language : randomizer::supportedLanguages) { std::string text; if (world->GetTextDatabase().contains(name)) { @@ -1343,12 +1538,30 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { } else { text = randomizer::getTextStr(name, randomizer::Text::STANDARD, language); } - u8 group = overrideNode["Group"].as(); - u16 messageId = overrideNode["Message Id"].as(); - u32 key = (group << 16) | messageId; + randomizer::applyMessageCodes(text); randoData.mTextOverrides[language][key] = text; } + + // If we have custom attributes + if (overrideNode["Attributes"]) { + auto attributesStr = overrideNode["Attributes"].as(); + auto attributesVec = HexToBytes(attributesStr); + if (attributesVec.size() != 16) { + throw std::runtime_error(fmt::format("Attributes for Text Override {} " + "are the wrong length. (Expected: 16, Actual: {}", name, attributesVec.size())); + } + + std::array attributes{}; + for (size_t i = 0; i < attributesVec.size(); ++i) { + attributes[i + 4] = attributesVec[i]; + } + + attributes[4] = messageId >> 8; + attributes[5] = messageId & 0xFF; + + randoData.mAttributeOverrides[key] = attributes; + } } // Entrance Overrides @@ -1372,6 +1585,27 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { randoData.mEntranceOverrides[std::bit_cast(original)] = override; } + // Vanilla Return to Place Overrides. Will need to change when boss/miniboss ER is implemented + static const std::list, RandomizerContext::EntranceOverride>> defaultPlaceOverrides{ + {{Forest_Temple, Ook, Diababa}, {Forest_Temple, 22, 0, -1}}, + {{Goron_Mines, Dangoro, Fyrus}, {Goron_Mines, 1, 0, -1}}, + {{Lakebed_Temple, Deku_Toad, Morpheel}, {Lakebed_Temple, 0, 0, -1}}, + {{Arbiters_Grounds, Death_Sword, Stallord}, {Arbiters_Grounds, 0, 0, -1}}, + {{Snowpeak_Ruins, Darkhammer, Blizzeta}, {Snowpeak_Ruins, 0, 0, -1}}, + {{Temple_of_Time, Darknut, Armogohma}, {Temple_of_Time, 0, 0, -1}}, + {{City_in_the_Sky, Aeralfos, Argorok}, {City_in_the_Sky, 0, 3, -1}}, + {{Palace_of_Twilight, Phantom_Zant_1, + Phantom_Zant_2, Zant_Main_Room, Zant_Fight}, {Palace_of_Twilight, 0, 0, -1}}, + {{Hyrule_Castle, Ganondorf_Castle, Ganondorf_Field}, {Hyrule_Castle, 11, 0, -1}}, + }; + + // Return to Place Overrides + for (const auto& [stages, returnPlace] : defaultPlaceOverrides) { + for (auto stage : stages) { + randoData.mReturnToPlaceOverrides[stage] = returnPlace; + } + } + return std::move(randoData); } diff --git a/src/dusk/randomizer/game/randomizer_context.hpp b/src/dusk/randomizer/game/randomizer_context.hpp index 28c142149f..10acac1dcf 100644 --- a/src/dusk/randomizer/game/randomizer_context.hpp +++ b/src/dusk/randomizer/game/randomizer_context.hpp @@ -62,6 +62,8 @@ public: std::unordered_map>> mObjectAdditions{}; // std::unordered_map> mTgscDeletions{}; std::unordered_map mFlowPatches{}; + std::unordered_map> mFlowPatchesBranchOverrides{}; + std::unordered_map> mAttributeOverrides{}; // struct TextOverride { // std::array mAttributes{}; @@ -80,6 +82,9 @@ public: // keyed by stageId << 24 | pointNo << 16 | roomNo << 8 | mapLayer std::unordered_map mEntranceOverrides{}; + // Overrides for returning to spawn. Keyed by stageId + std::unordered_map mReturnToPlaceOverrides{}; + std::optional WriteToFile(); std::optional LoadFromHash(const std::string& hash); std::filesystem::path GetSeedDataPath() const; @@ -227,6 +232,8 @@ bool randomizer_checkTempleOfTimeRequirement(); bool randomizer_mirrorChamberWallShouldExist(); +void randomizer_returnToSpawn(bool tryDungeon); + u8 randomizer_getRandomFoolishItemModelID(); /** diff --git a/src/dusk/randomizer/generator/data/flow_patches.yaml b/src/dusk/randomizer/generator/data/flow_patches.yaml index 3b84bcaf40..735da544a9 100644 --- a/src/dusk/randomizer/generator/data/flow_patches.yaml +++ b/src/dusk/randomizer/generator/data/flow_patches.yaml @@ -3,19 +3,24 @@ # NOTE: All data is expressed in little endian 0: # zel_00.bmg - # Change Talk to Midna options to use custom event 44 - # which changes the time of day - - index: 0x18F + # Change the following indices of Midna's flows to jump to our custom choices + # instead. These are all the flow jumps that normally determine which "Talk to Midna" + # text is displayed. + - index: + - 0x1 + - 0x9 + - 0xC + - 0x8F + - 0x192 + - 0x1A4 + - 0x18F + - 0x197 + - 0x1BC + - 0x1C9 type: event - event: 44 + event: 42 parameters: 0x00000000 - next node index: 0x116 - - - index: 0x192 - type: event - event: 44 - parameters: 0x00000000 - next node index: 0x110 + next node index: Custom Midna Call Begin #1: # zel_01.bmg 2: # zel_02.bmg @@ -84,3 +89,190 @@ #6: # zel_06.bmg #7: # zel_07.bmg #8: # zel_08.bmg + +# Custom Flow Nodes +9: + # Start of custom midna call tree. Begin by checking if we can change time + # of day to see if we show that choice + - name: Custom Midna Call Begin + type: branch + num results: 2 + query: 54 + parameters: 0x0000 + next node index: 0xFFFF + results: + - Custom Midna Call 3 Choice Select Setup # Can change time of day + - Custom Midna Call 2 Choice Select Setup # Can not change time of day + + ################################################# + # CUSTOM MIDNA CALL 2 CHOICE FLOW # + ################################################# + + - name: Custom Midna Call 2 Choice Select Setup + type: event + event: 13 + parameters: 0x00000003 + next node index: Custom Midna Call 2 Choice Intro + + - name: Custom Midna Call 2 Choice Intro + type: message + inf index: Custom Midna Call Need Something Text + next flow index: Custom Midna Call 2 Choice + + - name: Custom Midna Call 2 Choice + type: message + inf index: Custom Midna Call 2 Choice Text + next flow index: Custom Midna Call 2 Choice Branch + + - name: Custom Midna Call 2 Choice Branch + type: branch + num results: 3 + query: 35 + parameters: 0x0000 + next node index: 0xFFFF + results: + - Custom Midna Call Hints # Hints + - Custom Midna Call Return to Spawn Branch # Return to Spawn + - 0xFFFF # (B button pressed) + + ################################################# + # CUSTOM MIDNA CALL 3 CHOICE FLOW # + ################################################# + + - name: Custom Midna Call 3 Choice Select Setup + type: event + event: 13 + parameters: 0x00000004 + next node index: Custom Midna Call 3 Choice Intro + + - name: Custom Midna Call 3 Choice Intro + type: message + inf index: Custom Midna Call Need Something Text + next flow index: Custom Midna Call 3 Choice + + - name: Custom Midna Call 3 Choice + type: message + inf index: Custom Midna Call 3 Choice Text + next flow index: Custom Midna Call 3 Choice Branch + + - name: Custom Midna Call 3 Choice Branch + type: branch + num results: 4 + query: 36 + parameters: 0x0000 + next node index: 0xFFFF + results: + - Custom Midna Call Hints # Hints + - Custom Midna Call Change Time # Change time of day + - Custom Midna Call Return to Spawn Branch # Return to Spawn + - 0xFFFF # (B button pressed) + + ################################################# + # CUSTOM MIDNA CALL CHOICE OPTIONS # + ################################################# + + - name: Custom Midna Call Hints + type: message + inf index: Custom Midna Call Hints Text + next flow index: 0xFFFF + + - name: Custom Midna Call Change Time + type: event + event: 44 + parameters: 0x00000000 + next node index: 0x116 + + - name: Custom Midna Call Return to Spawn Branch + type: branch + num results: 3 + query: 55 + parameters: 0x0000 + next node index: 0xFFFF + results: + - Return to Spawn Dungeon Choice Select Setup + - Return to Spawn Dungeon No Choice Select Setup + - Return to Base Spawn + + ################################################# + # RETURN TO SPAWN DUNGEON CHOICE FLOW # + ################################################# + + - name: Return to Spawn Dungeon Choice Select Setup + type: event + event: 13 + parameters: 0x00000004 + next node index: Return to Spawn Dungeon Choice Intro + + - name: Return to Spawn Dungeon Choice Intro + type: message + inf index: Return to Spawn Dungeon Intro Text + next flow index: Return to Spawn Dungeon Choice + + - name: Return to Spawn Dungeon Choice + type: message + inf index: Return to Spawn Dungeon Choice Text + next flow index: Return to Spawn Dungeon Choice Branch + + - name: Return to Spawn Dungeon Choice Branch + type: branch + num results: 4 + query: 36 + parameters: 0x0000 + next node index: 0xFFFF + results: + - Return to Dungeon Spawn # Dungeon entrance + - 0xFFFF # Nevermind + - Return to Base Spawn # Spawn + - 0xFFFF # (B button pressed) + + ################################################# + # RETURN TO SPAWN DUNGEON NO CHOICE FLOW # + ################################################# + + - name: Return to Spawn Dungeon No Choice Select Setup + type: event + event: 13 + parameters: 0x00000003 + next node index: Return to Spawn Dungeon No Choice Intro + + - name: Return to Spawn Dungeon No Choice Intro + type: message + inf index: Return to Spawn Dungeon Intro Text + next flow index: Return to Spawn No Dungeon Choice + + - name: Return to Spawn Dungeon No Choice + type: message + inf index: Return to Spawn Dungeon No Choice Text + next flow index: Return to Spawn Dungeon No Choice Branch + + - name: Return to Spawn Dungeon No Choice Branch + type: branch + num results: 3 + query: 35 + parameters: 0x0000 + next node index: 0xFFFF + results: + - 0xFFFF # Nevermind + - Return to Base Spawn # Spawn + - 0xFFFF # (B button pressed) + + ################################################# + # RETURN TO SPAWN EVENTS # + ################################################# + + # Use custom event index 45 to setup returning to spawn. A non-zero + # parameter value indicates to try and use a place override if one + # exists for the current stage + - name: Return to Dungeon Spawn + type: event + event: 45 + parameters: 0x00000001 + next node index: 0x116 + + # Use custom event index 45 to setup returning to spawn. A parameter + # value of zero indicates to always return to the starting spawn + - name: Return to Base Spawn + type: event + event: 45 + parameters: 0x00000000 + next node index: 0x116 \ No newline at end of file diff --git a/src/dusk/randomizer/generator/data/text/languages/english.yaml b/src/dusk/randomizer/generator/data/text/languages/english.yaml index a8f2b091ad..0524cf00a9 100644 --- a/src/dusk/randomizer/generator/data/text/languages/english.yaml +++ b/src/dusk/randomizer/generator/data/text/languages/english.yaml @@ -1935,37 +1935,37 @@ Partially Filled Sky Book Get Item Text: You got a Sky Character! You've collected {} so far. -#Midna Call As Human Two Choice: -# Standard: -# Text: |- -# Transform into wolf -# Change time -# -#Midna Call As Wolf Two Choice: -# Standard: -# Text: |- -# Transform into human -# Change time +Midna Call As Human Two Choice: + Standard: + Text: |- + <2 way choice 1>Transform into wolf + <2 way choice 2>Something else -#Midna Call As Wolf No Shadow Crystal Two Choice: -# Standard: -# Text: |- -# Warp -# Change time +Midna Call As Wolf Two Choice: + Standard: + Text: |- + <2 way choice 1>Transform into human + <2 way choice 2>Something else + +Midna Call As Wolf No Shadow Crystal Two Choice: + Standard: + Text: |- + <2 way choice 1>Warp + <2 way choice 2>Something else Midna Call As Human Three Choice: Standard: Text: |- - Transform into wolf - Warp - Change time of day + <3 way choice 1>Transform into wolf + <3 way choice 2>Warp + <3 way choice 3>Something else Midna Call As Wolf Three Choice: Standard: Text: |- - Transform into human - Warp - Change time of day + <3 way choice 1>Transform into human + <3 way choice 2>Warp + <3 way choice 3>Something else Slingshot Shop Text Template: Standard: @@ -2117,9 +2117,9 @@ Castle Town Malo Mart Magic Armor Sold Out Text Template: Charlo Donation Choice Text: Standard: Text: |- - 100 Rupees - 50 Rupees - Sorry... + <3 way choice 1>100 Rupees + <3 way choice 2>50 Rupees + <3 way choice 3>Sorry... Charlo Donation Ask Text Template: Standard: @@ -2159,3 +2159,45 @@ Fishing Hole Sign Text Template: here! The fish are CRYING! Keep the fishing hole clean! + +Custom Midna Call Need Something Text: + Standard: + Text: |- + Need Something? + +Custom Midna Call 3 Choice Text: + Standard: + Text: |- + <3 way choice 1>Hints + <3 way choice 2>Change time of day + <3 way choice 3>Return to spawn + +Custom Midna Call 2 Choice Text: + Standard: + Text: |- + <2 way choice 1>Hints + <2 way choice 2>Return to spawn + +Custom Midna Call Hints Text: + Standard: + Text: |- + I have no hints to give. + +Return to Spawn Dungeon Intro Text: + Standard: + Text: |- + I'll get you out of here. + Where do you want to go? + +Return to Spawn Dungeon Choice Text: + Standard: + Text: |- + <3 way choice 1>Dungeon entrance + <3 way choice 2>Nevermind + <3 way choice 3>Spawn + +Return to Spawn Dungeon No Choice Text: + Standard: + Text: |- + <2 way choice 1>Nevermind + <2 way choice 2>Spawn \ No newline at end of file diff --git a/src/dusk/randomizer/generator/data/text/languages/french.yaml b/src/dusk/randomizer/generator/data/text/languages/french.yaml index 9cb17c52e5..c1c908e484 100644 --- a/src/dusk/randomizer/generator/data/text/languages/french.yaml +++ b/src/dusk/randomizer/generator/data/text/languages/french.yaml @@ -1935,37 +1935,37 @@ Partially Filled Sky Book Get Item Text: You got a Sky Character! You've collected {} so far. -#Midna Call As Human Two Choice: -# Standard: -# Text: |- -# Transform into wolf -# Change time -# -#Midna Call As Wolf Two Choice: -# Standard: -# Text: |- -# Transform into human -# Change time +Midna Call As Human Two Choice: + Standard: + Text: |- + <2 way choice 1>Transform into wolf + <2 way choice 2>Something else -#Midna Call As Wolf No Shadow Crystal Two Choice: -# Standard: -# Text: |- -# Warp -# Change time +Midna Call As Wolf Two Choice: + Standard: + Text: |- + <2 way choice 1>Transform into human + <2 way choice 2>Something else + +Midna Call As Wolf No Shadow Crystal Two Choice: + Standard: + Text: |- + <2 way choice 1>Warp + <2 way choice 2>Something else Midna Call As Human Three Choice: Standard: Text: |- - Transform into wolf - Warp - Change time of day + <3 way choice 1>Transform into wolf + <3 way choice 2>Warp + <3 way choice 3>Something else Midna Call As Wolf Three Choice: Standard: Text: |- - Transform into human - Warp - Change time of day + <3 way choice 1>Transform into human + <3 way choice 2>Warp + <3 way choice 3>Something else Slingshot Shop Text Template: Standard: @@ -2117,9 +2117,9 @@ Castle Town Malo Mart Magic Armor Sold Out Text Template: Charlo Donation Choice Text: Standard: Text: |- - 100 Rupees - 50 Rupees - Sorry... + <3 way choice 1>100 Rupees + <3 way choice 2>50 Rupees + <3 way choice 3>Sorry... Charlo Donation Ask Text Template: Standard: @@ -2159,3 +2159,45 @@ Fishing Hole Sign Text Template: here! The fish are CRYING! Keep the fishing hole clean! + +Custom Midna Call Need Something Text: + Standard: + Text: |- + Need Something? + +Custom Midna Call 3 Choice Text: + Standard: + Text: |- + <3 way choice 1>Hints + <3 way choice 2>Change time of day + <3 way choice 3>Return to spawn + +Custom Midna Call 2 Choice Text: + Standard: + Text: |- + <2 way choice 1>Hints + <2 way choice 2>Return to spawn + +Custom Midna Call Hints Text: + Standard: + Text: |- + I have no hints to give. + +Return to Spawn Dungeon Intro Text: + Standard: + Text: |- + I'll get you out of here. + Where do you want to go? + +Return to Spawn Dungeon Choice Text: + Standard: + Text: |- + <3 way choice 1>Dungeon entrance + <3 way choice 2>Nevermind + <3 way choice 3>Spawn + +Return to Spawn Dungeon No Choice Text: + Standard: + Text: |- + <2 way choice 1>Nevermind + <2 way choice 2>Spawn \ No newline at end of file diff --git a/src/dusk/randomizer/generator/data/text/languages/german.yaml b/src/dusk/randomizer/generator/data/text/languages/german.yaml index f6a2292d80..579ee74d1f 100644 --- a/src/dusk/randomizer/generator/data/text/languages/german.yaml +++ b/src/dusk/randomizer/generator/data/text/languages/german.yaml @@ -1935,37 +1935,37 @@ Partially Filled Sky Book Get Item Text: You got a Sky Character! You've collected {} so far. -#Midna Call As Human Two Choice: -# Standard: -# Text: |- -# Transform into wolf -# Change time -# -#Midna Call As Wolf Two Choice: -# Standard: -# Text: |- -# Transform into human -# Change time +Midna Call As Human Two Choice: + Standard: + Text: |- + <2 way choice 1>Transform into wolf + <2 way choice 2>Something else -#Midna Call As Wolf No Shadow Crystal Two Choice: -# Standard: -# Text: |- -# Warp -# Change time +Midna Call As Wolf Two Choice: + Standard: + Text: |- + <2 way choice 1>Transform into human + <2 way choice 2>Something else + +Midna Call As Wolf No Shadow Crystal Two Choice: + Standard: + Text: |- + <2 way choice 1>Warp + <2 way choice 2>Something else Midna Call As Human Three Choice: Standard: Text: |- - Transform into wolf - Warp - Change time of day + <3 way choice 1>Transform into wolf + <3 way choice 2>Warp + <3 way choice 3>Something else Midna Call As Wolf Three Choice: Standard: Text: |- - Transform into human - Warp - Change time of day + <3 way choice 1>Transform into human + <3 way choice 2>Warp + <3 way choice 3>Something else Slingshot Shop Text Template: Standard: @@ -2117,9 +2117,9 @@ Castle Town Malo Mart Magic Armor Sold Out Text Template: Charlo Donation Choice Text: Standard: Text: |- - 100 Rupees - 50 Rupees - Sorry... + <3 way choice 1>100 Rupees + <3 way choice 2>50 Rupees + <3 way choice 3>Sorry... Charlo Donation Ask Text Template: Standard: @@ -2159,3 +2159,45 @@ Fishing Hole Sign Text Template: here! The fish are CRYING! Keep the fishing hole clean! + +Custom Midna Call Need Something Text: + Standard: + Text: |- + Need Something? + +Custom Midna Call 3 Choice Text: + Standard: + Text: |- + <3 way choice 1>Hints + <3 way choice 2>Change time of day + <3 way choice 3>Return to spawn + +Custom Midna Call 2 Choice Text: + Standard: + Text: |- + <2 way choice 1>Hints + <2 way choice 2>Return to spawn + +Custom Midna Call Hints Text: + Standard: + Text: |- + I have no hints to give. + +Return to Spawn Dungeon Intro Text: + Standard: + Text: |- + I'll get you out of here. + Where do you want to go? + +Return to Spawn Dungeon Choice Text: + Standard: + Text: |- + <3 way choice 1>Dungeon entrance + <3 way choice 2>Nevermind + <3 way choice 3>Spawn + +Return to Spawn Dungeon No Choice Text: + Standard: + Text: |- + <2 way choice 1>Nevermind + <2 way choice 2>Spawn \ No newline at end of file diff --git a/src/dusk/randomizer/generator/data/text/languages/italian.yaml b/src/dusk/randomizer/generator/data/text/languages/italian.yaml index e372fd5380..8fbe283883 100644 --- a/src/dusk/randomizer/generator/data/text/languages/italian.yaml +++ b/src/dusk/randomizer/generator/data/text/languages/italian.yaml @@ -1935,37 +1935,37 @@ Partially Filled Sky Book Get Item Text: You got a Sky Character! You've collected {} so far. -#Midna Call As Human Two Choice: -# Standard: -# Text: |- -# Transform into wolf -# Change time -# -#Midna Call As Wolf Two Choice: -# Standard: -# Text: |- -# Transform into human -# Change time +Midna Call As Human Two Choice: + Standard: + Text: |- + <2 way choice 1>Transform into wolf + <2 way choice 2>Something else -#Midna Call As Wolf No Shadow Crystal Two Choice: -# Standard: -# Text: |- -# Warp -# Change time +Midna Call As Wolf Two Choice: + Standard: + Text: |- + <2 way choice 1>Transform into human + <2 way choice 2>Something else + +Midna Call As Wolf No Shadow Crystal Two Choice: + Standard: + Text: |- + <2 way choice 1>Warp + <2 way choice 2>Something else Midna Call As Human Three Choice: Standard: Text: |- - Transform into wolf - Warp - Change time of day + <3 way choice 1>Transform into wolf + <3 way choice 2>Warp + <3 way choice 3>Something else Midna Call As Wolf Three Choice: Standard: Text: |- - Transform into human - Warp - Change time of day + <3 way choice 1>Transform into human + <3 way choice 2>Warp + <3 way choice 3>Something else Slingshot Shop Text Template: Standard: @@ -2117,9 +2117,9 @@ Castle Town Malo Mart Magic Armor Sold Out Text Template: Charlo Donation Choice Text: Standard: Text: |- - 100 Rupees - 50 Rupees - Sorry... + <3 way choice 1>100 Rupees + <3 way choice 2>50 Rupees + <3 way choice 3>Sorry... Charlo Donation Ask Text Template: Standard: @@ -2159,3 +2159,45 @@ Fishing Hole Sign Text Template: here! The fish are CRYING! Keep the fishing hole clean! + +Custom Midna Call Need Something Text: + Standard: + Text: |- + Need Something? + +Custom Midna Call 3 Choice Text: + Standard: + Text: |- + <3 way choice 1>Hints + <3 way choice 2>Change time of day + <3 way choice 3>Return to spawn + +Custom Midna Call 2 Choice Text: + Standard: + Text: |- + <2 way choice 1>Hints + <2 way choice 2>Return to spawn + +Custom Midna Call Hints Text: + Standard: + Text: |- + I have no hints to give. + +Return to Spawn Dungeon Intro Text: + Standard: + Text: |- + I'll get you out of here. + Where do you want to go? + +Return to Spawn Dungeon Choice Text: + Standard: + Text: |- + <3 way choice 1>Dungeon entrance + <3 way choice 2>Nevermind + <3 way choice 3>Spawn + +Return to Spawn Dungeon No Choice Text: + Standard: + Text: |- + <2 way choice 1>Nevermind + <2 way choice 2>Spawn \ No newline at end of file diff --git a/src/dusk/randomizer/generator/data/text/languages/spanish.yaml b/src/dusk/randomizer/generator/data/text/languages/spanish.yaml index 1ac06fc5a4..9629f70a08 100644 --- a/src/dusk/randomizer/generator/data/text/languages/spanish.yaml +++ b/src/dusk/randomizer/generator/data/text/languages/spanish.yaml @@ -1935,37 +1935,37 @@ Partially Filled Sky Book Get Item Text: You got a Sky Character! You've collected {} so far. -#Midna Call As Human Two Choice: -# Standard: -# Text: |- -# Transform into wolf -# Change time -# -#Midna Call As Wolf Two Choice: -# Standard: -# Text: |- -# Transform into human -# Change time +Midna Call As Human Two Choice: + Standard: + Text: |- + <2 way choice 1>Transform into wolf + <2 way choice 2>Something else -#Midna Call As Wolf No Shadow Crystal Two Choice: -# Standard: -# Text: |- -# Warp -# Change time +Midna Call As Wolf Two Choice: + Standard: + Text: |- + <2 way choice 1>Transform into human + <2 way choice 2>Something else + +Midna Call As Wolf No Shadow Crystal Two Choice: + Standard: + Text: |- + <2 way choice 1>Warp + <2 way choice 2>Something else Midna Call As Human Three Choice: Standard: Text: |- - Transform into wolf - Warp - Change time of day + <3 way choice 1>Transform into wolf + <3 way choice 2>Warp + <3 way choice 3>Something else Midna Call As Wolf Three Choice: Standard: Text: |- - Transform into human - Warp - Change time of day + <3 way choice 1>Transform into human + <3 way choice 2>Warp + <3 way choice 3>Something else Slingshot Shop Text Template: Standard: @@ -2117,9 +2117,9 @@ Castle Town Malo Mart Magic Armor Sold Out Text Template: Charlo Donation Choice Text: Standard: Text: |- - 100 Rupees - 50 Rupees - Sorry... + <3 way choice 1>100 Rupees + <3 way choice 2>50 Rupees + <3 way choice 3>Sorry... Charlo Donation Ask Text Template: Standard: @@ -2159,3 +2159,45 @@ Fishing Hole Sign Text Template: here! The fish are CRYING! Keep the fishing hole clean! + +Custom Midna Call Need Something Text: + Standard: + Text: |- + Need Something? + +Custom Midna Call 3 Choice Text: + Standard: + Text: |- + <3 way choice 1>Hints + <3 way choice 2>Change time of day + <3 way choice 3>Return to spawn + +Custom Midna Call 2 Choice Text: + Standard: + Text: |- + <2 way choice 1>Hints + <2 way choice 2>Return to spawn + +Custom Midna Call Hints Text: + Standard: + Text: |- + I have no hints to give. + +Return to Spawn Dungeon Intro Text: + Standard: + Text: |- + I'll get you out of here. + Where do you want to go? + +Return to Spawn Dungeon Choice Text: + Standard: + Text: |- + <3 way choice 1>Dungeon entrance + <3 way choice 2>Nevermind + <3 way choice 3>Spawn + +Return to Spawn Dungeon No Choice Text: + Standard: + Text: |- + <2 way choice 1>Nevermind + <2 way choice 2>Spawn \ No newline at end of file diff --git a/src/dusk/randomizer/generator/data/text/text_overrides.yaml b/src/dusk/randomizer/generator/data/text/text_overrides.yaml index 3ab5245d8a..b95cbd6dd2 100644 --- a/src/dusk/randomizer/generator/data/text/text_overrides.yaml +++ b/src/dusk/randomizer/generator/data/text/text_overrides.yaml @@ -223,17 +223,17 @@ Group: 0 Message Id: 335 -#- Name: Midna Call As Human Two Choice -# Group: 0 -# Message Id: 2004 -# -#- Name: Midna Call As Wolf Two Choice -# Group: 0 -# Message Id: 2005 +- Name: Midna Call As Human Two Choice + Group: 0 + Message Id: 2004 -#- Name: Midna Call As Wolf No Shadow Crystal Two Choice -# Group: 0 -# Message Id: 2039 +- Name: Midna Call As Wolf Two Choice + Group: 0 + Message Id: 2005 + +- Name: Midna Call As Wolf No Shadow Crystal Two Choice + Group: 0 + Message Id: 2039 - Name: Midna Call As Human Three Choice Group: 0 @@ -374,3 +374,25 @@ - Name: Fishing Hole Sign Text Group: 7 Message Id: 9502 + +# Completely Custom Text IDs +- Name: Custom Midna Call Need Something Text + Attributes: 0x07F60000150D0000FF00000000000400 + +- Name: Custom Midna Call 3 Choice Text + Attributes: 0x07F8000015000000FF00000000000400 + +- Name: Custom Midna Call 2 Choice Text + Attributes: 0x07F8000015000000FF00000000000400 + +- Name: Custom Midna Call Hints Text + Attributes: 0x07F60000150D0000FF00000000000400 + +- Name: Return to Spawn Dungeon Intro Text + Attributes: 0x07F60000150D0000FF00000000000400 + +- Name: Return to Spawn Dungeon Choice Text + Attributes: 0x07F60000150D0000FF00000000000400 + +- Name: Return to Spawn Dungeon No Choice Text + Attributes: 0x07F60000150D0000FF00000000000400 \ No newline at end of file diff --git a/src/dusk/randomizer/generator/utility/string.cpp b/src/dusk/randomizer/generator/utility/string.cpp index d995578bd1..76bc5717a7 100644 --- a/src/dusk/randomizer/generator/utility/string.cpp +++ b/src/dusk/randomizer/generator/utility/string.cpp @@ -2,9 +2,7 @@ #include #include -#include -#include -#include +#include namespace randomizer::utility::str { //can't use codecvt on Wii U, deprecated in c++17 and g++ hates it @@ -49,4 +47,46 @@ namespace randomizer::utility::str { return ret; } + + // Takes in a string and returns an optional integer if the string + // could be turned into one + std::optional toInt(std::string_view str) { + // Trim leading/trailing whitespace + auto start = str.find_first_not_of(" \t\r\n"); + if (start == std::string_view::npos) { + return std::nullopt; + } + str.remove_prefix(start); + + auto end = str.find_last_not_of(" \t\r\n"); + if (end != std::string_view::npos) { + str = str.substr(0, end + 1); + } + + if (str.empty()) { + return std::nullopt; + } + + // Identify base (only decimal/hexadecimal handled) + int base = 10; + if (str.size() > 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { + base = 16; + str.remove_prefix(2); + } + + if (str.empty()) { + return std::nullopt; + } + + // Parse the remaining absolute string + int value = 0; + auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), value, base); + + // Ensure conversion succeeded and consumed the entire trimmed string + if (ec == std::errc{} && ptr == str.data() + str.size()) { + return value; + } + + return std::nullopt; + } } diff --git a/src/dusk/randomizer/generator/utility/string.hpp b/src/dusk/randomizer/generator/utility/string.hpp index 34cd850ab0..3fadf47904 100644 --- a/src/dusk/randomizer/generator/utility/string.hpp +++ b/src/dusk/randomizer/generator/utility/string.hpp @@ -113,4 +113,6 @@ namespace randomizer::utility::str { } } } + + std::optional toInt(std::string_view str); } diff --git a/src/dusk/randomizer/generator/utility/text.cpp b/src/dusk/randomizer/generator/utility/text.cpp index 95279ab212..cc89856758 100644 --- a/src/dusk/randomizer/generator/utility/text.cpp +++ b/src/dusk/randomizer/generator/utility/text.cpp @@ -330,25 +330,28 @@ namespace randomizer { using namespace std::string_view_literals; static const std::unordered_map messageCodes = { - {"", "\x1A\x05\x00\x00\x01"sv}, - {"", "\x1A\x05\x00\x00\x02"sv}, - {"", "\x1A\x05\x00\x00\x20"sv}, - {"", "\x1A\x05\x06\x00\x02"sv}, - {"", "\x1A\x05\x06\x00\x03"sv}, - {"", "\x1A\x06\x00\x00\x09\x01"sv}, - {"", "\x1A\x06\x00\x00\x09\x02"sv}, - {"", "\x1A\x06\x00\x00\x09\x03"sv}, - {"", "\x1A\x06\xFF\x00\x00\x00"sv}, - {"", "\x1A\x06\xFF\x00\x00\x01"sv}, - {"", "\x1A\x06\xFF\x00\x00\x02"sv}, - {"", "\x1A\x06\xFF\x00\x00\x03"sv}, - {"", "\x1A\x06\xFF\x00\x00\x04"sv}, - {"", "\x1A\x06\xFF\x00\x00\x06"sv}, - {"", "\x1A\x06\xFF\x00\x00\x08"sv}, + {"", "\x1A\x05\x00\x00\x00"sv}, + {"", "\x1A\x05\x00\x00\x01"sv}, + {"", "\x1A\x05\x00\x00\x02"sv}, + {"", "\x1A\x05\x00\x00\x20"sv}, + {"", "\x1A\x05\x06\x00\x02"sv}, + {"", "\x1A\x05\x06\x00\x03"sv}, + {"<2 way choice 1>", "\x1A\x06\x00\x00\x08\x01"sv}, + {"<2 way choice 2>", "\x1A\x06\x00\x00\x08\x02"sv}, + {"<3 way choice 1>", "\x1A\x06\x00\x00\x09\x01"sv}, + {"<3 way choice 2>", "\x1A\x06\x00\x00\x09\x02"sv}, + {"<3 way choice 3>", "\x1A\x06\x00\x00\x09\x03"sv}, + {"", "\x1A\x06\xFF\x00\x00\x00"sv}, + {"", "\x1A\x06\xFF\x00\x00\x01"sv}, + {"", "\x1A\x06\xFF\x00\x00\x02"sv}, + {"", "\x1A\x06\xFF\x00\x00\x03"sv}, + {"", "\x1A\x06\xFF\x00\x00\x04"sv}, + {"", "\x1A\x06\xFF\x00\x00\x06"sv}, + {"", "\x1A\x06\xFF\x00\x00\x08"sv}, // custom colors - {"", "\x1A\x06\xFF\x00\x00\x09"sv}, - {"", "\x1A\x06\xFF\x00\x00\x0A"sv}, - {"", "\x1A\x06\xFF\x00\x00\x0B"sv}, + {"", "\x1A\x06\xFF\x00\x00\x09"sv}, + {"", "\x1A\x06\xFF\x00\x00\x0A"sv}, + {"", "\x1A\x06\xFF\x00\x00\x0B"sv}, }; void breakLines(std::string& str, int maxLineWidth) {