custom flow nodes and initial custom midna choices

This commit is contained in:
gymnast86
2026-07-21 00:51:33 -07:00
parent a245876f7f
commit 21a7823045
22 changed files with 1171 additions and 220 deletions
+1
View File
@@ -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
+23
View File
@@ -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;
+7 -2
View File
@@ -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
+17
View File
@@ -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;
}
+30
View File
@@ -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<JMSMesgEntry_c*>(&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<JMSMesgEntry_c*>(&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)
+157 -40
View File
@@ -16,6 +16,7 @@
#include <cstring>
#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<mesg_flow*>(&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<u16*>(&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<u16*>(&mFlowNodeTBL[0])) == 0x0803) {
key &= 0x0000FFFF;
}
auto key = getKeyForIndex(nodeIdx);
if (randomizer_GetContext().mFlowPatches.contains(key)) {
type = reinterpret_cast<mesg_flow_node*>(&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<mesg_flow_node*>(&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<u16*>(&mFlowNodeTBL[0])) == 0x0803) {
key &= 0x0000FFFF;
}
auto key = getKeyForIndex(nodeIdx);
if (randomizer_GetContext().mFlowPatches.contains(key)) {
branch_node = reinterpret_cast<mesg_flow_node_branch*>(&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<u16*>(&mFlowNodeTBL[0])) == 0x0803) {
key &= 0x0000FFFF;
}
auto key = getKeyForIndex(nodeIdx);
if (randomizer_GetContext().mFlowPatches.contains(key)) {
event_node = reinterpret_cast<mesg_flow_node_event*>(&randomizer_GetContext().mFlowPatches[key]);
}
+26 -2
View File
@@ -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 <algorithm>
#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
@@ -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;
+16
View File
@@ -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;
+2
View File
@@ -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);
+243 -9
View File
@@ -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 <fstream>
#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<std::string> RandomizerContext::WriteToFile() {
@@ -103,6 +105,12 @@ std::optional<std::string> 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<std::string> RandomizerContext::WriteToFile() {
out["mEntranceOverrides"][key] = std::bit_cast<int>(override);
}
for (const auto& [key, override] : mReturnToPlaceOverrides) {
out["mReturnToPlaceOverrides"][key] = std::bit_cast<int>(override);
}
seedData << YAML::Dump(out);
seedData << '\n' << textData.c_str();
seedData.close();
@@ -270,6 +282,15 @@ std::optional<std::string> 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<u32>();
for (const auto& branchNode : flowNode.second) {
auto override = branchNode.as<u16>();
this->mFlowPatchesBranchOverrides[key].push_back(override);
}
}
// Text Overrides
for (const auto& languageNode: in["mTextOverrides"]) {
const auto& languageStr = languageNode.first.as<std::string>();
@@ -289,6 +310,13 @@ std::optional<std::string> 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<int>();
auto override = std::bit_cast<EntranceOverride>(entranceNode.second.as<int>());
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<u8>({
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<std::string, u16> customMessageIDs{};
std::unordered_map<std::string, u16> 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<u8>();
for (const auto& flowNode : groupNode.second) {
u16 index = flowNode["index"].as<u16>();
std::string name{};
std::list<u16> indices{};
if (flowNode["index"]) {
if (flowNode["index"].IsSequence()) {
for (const auto& indexNode : flowNode["index"]) {
indices.push_back(indexNode.as<u16>());
}
name = std::to_string(indices.front());
}
else if (flowNode["index"].IsScalar()) {
auto index = flowNode["index"].as<u16>();
indices.push_back(index);
name = std::to_string(index);
}
} else {
name = flowNode["name"].as<std::string>();
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<std::string>();
u64 value{};
if (type == "branch") {
@@ -1314,21 +1412,97 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) {
branch->query_idx = flowNode["query"].as<u16>();
branch->param = flowNode["parameters"].as<u16>();
branch->next_node_idx = flowNode["next node index"].as<u16>();
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<std::string>();
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<mesg_flow_node_event*>(&value);
event->type = 3;
event->event_idx = flowNode["event"].as<u8>();
event->next_node_idx = flowNode["next node index"].as<u16>();
// Check to see if we're setting a custom node as our next node
auto nextNodeIndexStr = flowNode["next node index"].as<std::string>();
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<u32>();
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<mesg_flow_node*>(&value);
message->type = 1;
// Check to see if we have a custom index (identified by string)
auto infIndexStr = flowNode["inf index"].as<std::string>();
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<u16>();
// Check to see if we're setting a custom node as our next node
auto nextNodeIndexStr = flowNode["next flow index"].as<std::string>();
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<std::string>();
u8 group;
u16 messageId;
if (overrideNode["Group"]) {
group = overrideNode["Group"].as<u8>();
} else {
// If no group specified, assume custom bmg group
group = CUSTOM_BMG_GROUP;
}
if (overrideNode["Message Id"]) {
messageId = overrideNode["Message Id"].as<u16>();
} 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<u8>();
u16 messageId = overrideNode["Message Id"].as<u16>();
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<std::string>();
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<u8, 20> 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<int>(original)] = override;
}
// Vanilla Return to Place Overrides. Will need to change when boss/miniboss ER is implemented
static const std::list<std::pair<std::list<int>, 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);
}
@@ -62,6 +62,8 @@ public:
std::unordered_map<u32, std::list<std::vector<u8>>> mObjectAdditions{};
// std::unordered_map<u32, std::unordered_set<u32>> mTgscDeletions{};
std::unordered_map<u32, u64> mFlowPatches{};
std::unordered_map<u32, std::vector<u16>> mFlowPatchesBranchOverrides{};
std::unordered_map<u32, std::array<u8, 20>> mAttributeOverrides{};
// struct TextOverride {
// std::array<u8, 16> mAttributes{};
@@ -80,6 +82,9 @@ public:
// keyed by stageId << 24 | pointNo << 16 | roomNo << 8 | mapLayer
std::unordered_map<int, EntranceOverride> mEntranceOverrides{};
// Overrides for returning to spawn. Keyed by stageId
std::unordered_map<int, EntranceOverride> mReturnToPlaceOverrides{};
std::optional<std::string> WriteToFile();
std::optional<std::string> 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();
/**
@@ -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
@@ -1935,37 +1935,37 @@ Partially Filled Sky Book Get Item Text:
<fast>You got a <red>Sky Character<white>!
You've collected <red>{}<white> so far.
#Midna Call As Human Two Choice:
# Standard:
# Text: |-
# <choice 1>Transform into wolf
# <choice 2>Change time
#
#Midna Call As Wolf Two Choice:
# Standard:
# Text: |-
# <choice 1>Transform into human
# <choice 2>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: |-
# <choice 1>Warp
# <choice 2>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: |-
<choice 1>Transform into wolf
<choice 2>Warp
<choice 3>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: |-
<choice 1>Transform into human
<choice 2>Warp
<choice 3>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: |-
<choice 1>100 Rupees
<choice 2>50 Rupees
<choice 3>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:
<Item Pretty Name> here! The fish are CRYING!
Keep the fishing hole clean!
Custom Midna Call Need Something Text:
Standard:
Text: |-
Need Something?<begin choice>
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?<begin choice>
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
@@ -1935,37 +1935,37 @@ Partially Filled Sky Book Get Item Text:
<fast>You got a <red>Sky Character<white>!
You've collected <red>{}<white> so far.
#Midna Call As Human Two Choice:
# Standard:
# Text: |-
# <choice 1>Transform into wolf
# <choice 2>Change time
#
#Midna Call As Wolf Two Choice:
# Standard:
# Text: |-
# <choice 1>Transform into human
# <choice 2>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: |-
# <choice 1>Warp
# <choice 2>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: |-
<choice 1>Transform into wolf
<choice 2>Warp
<choice 3>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: |-
<choice 1>Transform into human
<choice 2>Warp
<choice 3>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: |-
<choice 1>100 Rupees
<choice 2>50 Rupees
<choice 3>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:
<Item Pretty Name> here! The fish are CRYING!
Keep the fishing hole clean!
Custom Midna Call Need Something Text:
Standard:
Text: |-
Need Something?<begin choice>
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?<begin choice>
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
@@ -1935,37 +1935,37 @@ Partially Filled Sky Book Get Item Text:
<fast>You got a <red>Sky Character<white>!
You've collected <red>{}<white> so far.
#Midna Call As Human Two Choice:
# Standard:
# Text: |-
# <choice 1>Transform into wolf
# <choice 2>Change time
#
#Midna Call As Wolf Two Choice:
# Standard:
# Text: |-
# <choice 1>Transform into human
# <choice 2>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: |-
# <choice 1>Warp
# <choice 2>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: |-
<choice 1>Transform into wolf
<choice 2>Warp
<choice 3>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: |-
<choice 1>Transform into human
<choice 2>Warp
<choice 3>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: |-
<choice 1>100 Rupees
<choice 2>50 Rupees
<choice 3>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:
<Item Pretty Name> here! The fish are CRYING!
Keep the fishing hole clean!
Custom Midna Call Need Something Text:
Standard:
Text: |-
Need Something?<begin choice>
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?<begin choice>
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
@@ -1935,37 +1935,37 @@ Partially Filled Sky Book Get Item Text:
<fast>You got a <red>Sky Character<white>!
You've collected <red>{}<white> so far.
#Midna Call As Human Two Choice:
# Standard:
# Text: |-
# <choice 1>Transform into wolf
# <choice 2>Change time
#
#Midna Call As Wolf Two Choice:
# Standard:
# Text: |-
# <choice 1>Transform into human
# <choice 2>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: |-
# <choice 1>Warp
# <choice 2>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: |-
<choice 1>Transform into wolf
<choice 2>Warp
<choice 3>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: |-
<choice 1>Transform into human
<choice 2>Warp
<choice 3>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: |-
<choice 1>100 Rupees
<choice 2>50 Rupees
<choice 3>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:
<Item Pretty Name> here! The fish are CRYING!
Keep the fishing hole clean!
Custom Midna Call Need Something Text:
Standard:
Text: |-
Need Something?<begin choice>
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?<begin choice>
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
@@ -1935,37 +1935,37 @@ Partially Filled Sky Book Get Item Text:
<fast>You got a <red>Sky Character<white>!
You've collected <red>{}<white> so far.
#Midna Call As Human Two Choice:
# Standard:
# Text: |-
# <choice 1>Transform into wolf
# <choice 2>Change time
#
#Midna Call As Wolf Two Choice:
# Standard:
# Text: |-
# <choice 1>Transform into human
# <choice 2>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: |-
# <choice 1>Warp
# <choice 2>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: |-
<choice 1>Transform into wolf
<choice 2>Warp
<choice 3>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: |-
<choice 1>Transform into human
<choice 2>Warp
<choice 3>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: |-
<choice 1>100 Rupees
<choice 2>50 Rupees
<choice 3>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:
<Item Pretty Name> here! The fish are CRYING!
Keep the fishing hole clean!
Custom Midna Call Need Something Text:
Standard:
Text: |-
Need Something?<begin choice>
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?<begin choice>
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
@@ -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
@@ -2,9 +2,7 @@
#include <locale>
#include <string>
#include <map>
#include <iostream>
#include <cstdarg>
#include <charconv>
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<int> 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;
}
}
@@ -113,4 +113,6 @@ namespace randomizer::utility::str {
}
}
}
std::optional<int> toInt(std::string_view str);
}
+21 -18
View File
@@ -330,25 +330,28 @@ namespace randomizer {
using namespace std::string_view_literals;
static const std::unordered_map<std::string_view, std::string_view> messageCodes = {
{"<fast>", "\x1A\x05\x00\x00\x01"sv},
{"<slow>", "\x1A\x05\x00\x00\x02"sv},
{"<begin choice>", "\x1A\x05\x00\x00\x20"sv},
{"<male>", "\x1A\x05\x06\x00\x02"sv},
{"<female>", "\x1A\x05\x06\x00\x03"sv},
{"<choice 1>", "\x1A\x06\x00\x00\x09\x01"sv},
{"<choice 2>", "\x1A\x06\x00\x00\x09\x02"sv},
{"<choice 3>", "\x1A\x06\x00\x00\x09\x03"sv},
{"<white>", "\x1A\x06\xFF\x00\x00\x00"sv},
{"<red>", "\x1A\x06\xFF\x00\x00\x01"sv},
{"<green>", "\x1A\x06\xFF\x00\x00\x02"sv},
{"<light blue>", "\x1A\x06\xFF\x00\x00\x03"sv},
{"<yellow>", "\x1A\x06\xFF\x00\x00\x04"sv},
{"<purple>", "\x1A\x06\xFF\x00\x00\x06"sv},
{"<orange>", "\x1A\x06\xFF\x00\x00\x08"sv},
{"<player name>", "\x1A\x05\x00\x00\x00"sv},
{"<fast>", "\x1A\x05\x00\x00\x01"sv},
{"<slow>", "\x1A\x05\x00\x00\x02"sv},
{"<begin choice>", "\x1A\x05\x00\x00\x20"sv},
{"<male>", "\x1A\x05\x06\x00\x02"sv},
{"<female>", "\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},
{"<white>", "\x1A\x06\xFF\x00\x00\x00"sv},
{"<red>", "\x1A\x06\xFF\x00\x00\x01"sv},
{"<green>", "\x1A\x06\xFF\x00\x00\x02"sv},
{"<light blue>", "\x1A\x06\xFF\x00\x00\x03"sv},
{"<yellow>", "\x1A\x06\xFF\x00\x00\x04"sv},
{"<purple>", "\x1A\x06\xFF\x00\x00\x06"sv},
{"<orange>", "\x1A\x06\xFF\x00\x00\x08"sv},
// custom colors
{"<dark green>", "\x1A\x06\xFF\x00\x00\x09"sv},
{"<blue>", "\x1A\x06\xFF\x00\x00\x0A"sv},
{"<silver>", "\x1A\x06\xFF\x00\x00\x0B"sv},
{"<dark green>", "\x1A\x06\xFF\x00\x00\x09"sv},
{"<blue>", "\x1A\x06\xFF\x00\x00\x0A"sv},
{"<silver>", "\x1A\x06\xFF\x00\x00\x0B"sv},
};
void breakLines(std::string& str, int maxLineWidth) {