Files
ss/src/d/d_message.cpp
T
robojumper 35ae04fdf9 Refactoring
2025-06-04 23:47:43 +02:00

1889 lines
61 KiB
C++

#include "d/d_message.h"
#include "c/c_math.h"
#include "common.h"
#include "d/a/d_a_item.h"
#include "d/a/d_a_itembase.h"
#include "d/a/d_a_player.h"
#include "d/d_base.h"
#include "d/d_player.h"
#include "d/d_pouch.h"
#include "d/d_sc_game.h"
#include "d/d_stage.h"
#include "d/d_stage_mgr.h"
#include "d/d_tag_processor.h"
#include "d/d_textunk.h"
#include "d/flag/itemflag_manager.h"
#include "d/flag/sceneflag_manager.h"
#include "d/flag/storyflag_manager.h"
#include "d/lyt/d_lyt_control_game.h"
#include "d/lyt/d_lyt_demo_dowsing.h"
#include "d/lyt/d_lyt_map.h"
#include "d/lyt/d_lyt_mini_game.h"
#include "d/lyt/meter/d_lyt_meter.h"
#include "d/lyt/msg_window/d_lyt_msg_window.h"
#include "d/snd/d_snd_small_effect_mgr.h"
#include "egg/core/eggHeap.h"
#include "f/f_base.h"
#include "f/f_profile.h"
#include "f/f_profile_name.h"
#include "libms/flowfile.h"
#include "libms/libms.h"
#include "libms/msgfile.h"
#include "sized_string.h"
#include "toBeSorted/arc_managers/oarc_manager.h"
#include "toBeSorted/blur_and_palette_manager.h"
#include "toBeSorted/dowsing_target.h"
#include "toBeSorted/event_manager.h"
#include "toBeSorted/fi_context.h"
#include "toBeSorted/file_manager.h"
#include "toBeSorted/minigame_mgr.h"
#include "toBeSorted/music_mgrs.h"
#include "toBeSorted/unk_save_time.h"
#include "rvl/OS.h"
#include <cstring>
#include <stdio.h>
s32 dFlow_c::sExitId = -1;
dFlow_c::dFlow_c() {
field_0x14 = 0;
mpMsbf = nullptr;
mCurrentFlowIndex = -1;
field_0x0E = 0;
field_0x0F = 0;
field_0x10 = 1;
field_0x3C = 0;
field_0x40 = -1;
field_0x44 = -1;
mNextFiFlow = -1;
mDelayTimer = 0;
mFiSpeechArgument = -1;
}
dFlow_c::~dFlow_c() {}
u16 dFlow_c::findEntryPoint(u16 labelPart1, u16 labelPart2) {
char buf[8];
for (int i = 0; i < 8; i++) {
buf[i] = '\0';
}
u16 ret = 0xFFFF;
if (labelPart1 < 100) {
sprintf(buf, "%03d_%03d", labelPart1, labelPart2);
} else {
sprintf(buf, "%03d_%02d", labelPart1, labelPart2);
}
for (int i = 0; i < 80; i++) {
if (dMessage_c::getMsbfInfoForIndex(i) != nullptr) {
int entry = LMS_GetEntrypoint(dMessage_c::getMsbfInfoForIndex(i), buf);
if (entry >= 0) {
mpMsbf = dMessage_c::getMsbfInfoForIndex(i);
s32 fileNumber = dMessage_c::getMsbtIndexForMsbfIndex(i);
ret = entry;
dMessage_c::getInstance()->setCurrentTextFileNumber(fileNumber);
}
}
}
const char *arcName = dMessage_c::getArcNameByIndex(labelPart1 / 100, true);
dLytMsgWindow_c::getInstance()->setCurrentFlowFilename(arcName);
dLytMsgWindow_c::getInstance()->setCurrentEntrypointName(buf);
return ret;
}
u16 dFlow_c::findEntryPoint(const char *label) {
u16 ret = 0xFFFF;
for (int i = 0; i < 80; i++) {
if (dMessage_c::getMsbfInfoForIndex(i) != nullptr) {
int entry = LMS_GetEntrypoint(dMessage_c::getMsbfInfoForIndex(i), label);
if (entry >= 0) {
mpMsbf = dMessage_c::getMsbfInfoForIndex(i);
s32 fileNumber = dMessage_c::getMsbtIndexForMsbfIndex(i);
ret = entry;
dMessage_c::getInstance()->setCurrentTextFileNumber(fileNumber);
}
}
}
const char *arcName = dMessage_c::getArcNameByIndex(dLytMsgWindow_c::fn_800D7B40() / 10000, true);
dLytMsgWindow_c::getInstance()->setCurrentFlowFilename(arcName);
dLytMsgWindow_c::getInstance()->setCurrentEntrypointName(label);
return ret;
}
void dFlow_c::setNext(u16 next) {
if (next != 0xFFFF) {
if (mCurrentFlowIndex != next) {
mCurrentFlowIndex = next;
} else {
clear();
}
} else {
clear();
}
}
void dFlow_c::advanceFlow() {
bool keepGoing = true;
if (dLytMsgWindow_c::getInstance()->getTagProcessor()->getField_0x8FC() >= 0 &&
dLytMsgWindow_c::getInstance()->getTagProcessor()->getField_0x900() >= 0) {
triggerEntryPoint(
dLytMsgWindow_c::getInstance()->getTagProcessor()->getField_0x8FC(),
dLytMsgWindow_c::getInstance()->getTagProcessor()->getField_0x900()
);
field_0x0F = 1;
dLytMsgWindow_c::getInstance()->getTagProcessor()->setFields_0x8FC_0x900(-1, -1);
} else {
while (keepGoing && !vt_0x18() && !dLytMsgWindow_c::getInstance()->getField_0x815() && !checkField0x3C()) {
MsbFlowInfo *element = LMS_GetFlowElement(mpMsbf, mCurrentFlowIndex);
s32 prevIdx = mCurrentFlowIndex;
s32 type = element->type;
mCurrentTextLabelName = "";
switch (type) {
case FLOW_MESSAGE: keepGoing = handleMessage(); break;
case FLOW_BRANCH: keepGoing = handleBranch(); break;
case FLOW_EVENT:
handleEvent();
keepGoing = false;
break;
case FLOW_ENTRY: keepGoing = handleEntry(); break;
case FLOW_JUMP: keepGoing = handleJump(); break;
}
field_0x10 = prevIdx != mCurrentFlowIndex;
if (field_0x10) {
memcpy(&mFlowInfo, element, sizeof(MsbFlowInfo));
}
}
}
}
bool dFlow_c::advanceUntilEvent(s32 searchParam3, s32 *pOutParams1n2) {
return advanceUntil(FLOW_EVENT, searchParam3, pOutParams1n2) == true;
}
bool dFlow_c::vt_0x18() const {
bool ret = false;
if (field_0x0E && dLytMsgWindow_c::getInstance()->getField_0x815() == false) {
ret = true;
}
return ret;
}
struct FlowSoundDef {
/* 0x00 */ u32 mParams;
/* 0x04 */ u32 mSoundMgr;
/* 0x08 */ WZSound mSoundId;
};
static const FlowSoundDef sSoundDefs[] = {
{ 1, 0, FAN_ITEM_GET_MINI},
{ 2, 0, FAN_ITEM_GET},
{ 3, 0, FAN_HEART_GET},
{ 4, 1, SE_S_MSG_IMPORTANT},
{ 5, 1, SE_S_REACTION},
{ 6, 1, SE_S_MSG_GOOD},
{ 7, 1, SE_S_MSG_PRESAGE},
{ 8, 0, FAN_TRANSITION_IMPACT_01},
{ 9, 1, SE_S_READ_RIDDLE_A},
{10, 1, SE_S_READ_RIDDLE_B},
};
void dFlow_c::playSound(u32 params) {
if (params >= 1000) {
fn_80364FD0(ENEMY_SOUND_MGR, params);
return;
}
if (params >= 100) {
fn_803858D0(ENEMY_BGM_RELATED_MGR);
return;
}
s32 idx = -1;
for (int i = 0; i < 10; i++) {
if (sSoundDefs[i].mParams == params) {
idx = i;
break;
}
}
if (idx < 0) {
return;
}
switch (sSoundDefs[idx].mSoundMgr) {
case 0: AnotherSoundMgr__playSound(FANFARE_SOUND_MGR, sSoundDefs[idx].mSoundId); break;
case 1: dSndSmallEffectMgr_c::GetInstance()->playSound(sSoundDefs[idx].mSoundId); break;
}
}
bool dFlow_c::handleEventInternal(const MsbFlowInfo *element) {
u32 params1n2 = element->params1n2;
switch (element->param3) {
case EVENT_SET_STORYFLAG:
StoryflagManager::sInstance->setFlag(params1n2);
if (params1n2 == 0x52) {
dLytMeter_c::GetInstance()->setMeterField_0x13775(true);
}
if (dLytMsgWindow_c::fn_800D7B40() != 50013 && dLytMsgWindow_c::fn_800D7B40() != 20061) {
if (params1n2 == 100 || params1n2 == 64 || params1n2 == 271 || params1n2 == 81 || params1n2 == 668 ||
params1n2 == 669) {
FileManager::GetInstance()->setDowsingSlotIdx(DowsingTarget::SLOT_STORY_EVENT);
} else if (params1n2 == 106 || params1n2 == 107) {
FileManager::GetInstance()->setDowsingSlotIdx(DowsingTarget::SLOT_QUEST);
}
}
break;
case EVENT_UNSET_STORYFLAG: StoryflagManager::sInstance->unsetFlag(params1n2); break;
case EVENT_SET_SCENEFLAG:
dStageMgr_c::GetInstance()->getFlagIndex();
SceneflagManager::sInstance->setFlag(0x3F, (params1n2 >> 16) & 0xFFFF);
break;
case EVENT_UNSET_SCENEFLAG:
dStageMgr_c::GetInstance()->getFlagIndex();
SceneflagManager::sInstance->unsetFlag(0x3F, (params1n2 >> 16) & 0xFFFF);
break;
case EVENT_SET_ZONEFLAG:
dStageMgr_c::GetInstance()->getFlagIndex();
SceneflagManager::sInstance->setZoneflag_i(
dStage_c::GetInstance()->getCurrRoomId(), (params1n2 >> 16) & 0xFFFF
);
break;
case EVENT_UNSET_ZONEFLAG:
dStageMgr_c::GetInstance()->getFlagIndex();
SceneflagManager::sInstance->unsetZoneflag_i(
dStage_c::GetInstance()->getCurrRoomId(), (params1n2 >> 16) & 0xFFFF
);
break;
case EVENT_DELAY:
mDelayTimer++;
if (mDelayTimer < params1n2) {
return false;
}
mDelayTimer = 0;
break;
case EVENT_LOAD_FI_FLOW:
if (params1n2 == -1) {
s32 hi, lo;
s32 selectedOption = dLytMsgWindow_c::getInstance()->getTextOptionSelection();
mFiInfo0 = FiContext::getGlobalFiInfo0(selectedOption);
switch (mFiInfo0) {
case FiContext::KEN8_Summary: {
hi = 6;
lo = 1;
mFiSpeechArgument = FiContext::getNaviTableProgressSummary();
break;
}
case 11:
// TODO: Is this one actually used? It completely duplicates
// KEN8_Hint (case 1) down below...
hi = 6;
lo = 100;
mFiSpeechArgument = FiContext::getFiAdviceHintEntry();
break;
case FiContext::KEN8_Objective: {
hi = 6;
lo = 200;
mFiSpeechArgument = FiContext::getObjective();
break;
}
case FiContext::KEN8_Analysis: {
FiAnalysisHandle handle = FiContext::getNaviTableEquipmentCheckEntry();
if (handle.isValid()) {
hi = 6;
lo = 300;
} else {
hi = 6;
lo = 301;
}
mFiSpeechArgument = 5;
break;
}
case FiContext::KEN8_PlayTime:
hi = 6;
lo = 800;
break;
case FiContext::KEN8_Advice:
hi = 6;
lo = 802;
break;
case FiContext::KEN8_Hint: {
hi = 6;
lo = 100;
mFiSpeechArgument = FiContext::getFiAdviceHintEntry();
break;
}
case FiContext::KEN8_Rumors:
hi = 6;
lo = 900;
break;
}
FiContext::do_setAdviceOptions(FiContext::getGlobalFiInfo0(selectedOption));
mNextFiFlow = lo + hi * 1000;
} else {
triggerEntryPoint((params1n2 >> 16) & 0xFFFF, params1n2 & 0xFFFF);
field_0x0F = 1;
}
break;
case EVENT_RUPEES:
if (dMessage_c::getInstance()->getField_0x2FC() != -1) {
dMessage_c::getInstance()->setField_0x2FC(0x3C);
}
dAcItem_c::addRupees(params1n2);
if ((s32)params1n2 > 0) {
dLytMeter_c::setRupyField_0x8AD(1);
}
break;
case EVENT_SET_ITEM: {
u16 flag = params1n2 & 0xFFFF;
ItemflagManager::sInstance->setItemFlag(flag);
switch (params1n2) {
case ITEM_FARORES_COURAGE:
case ITEM_NAYRUS_WISDOM:
case ITEM_DINS_POWER:
case ITEM_SOTH: FileManager::GetInstance()->setDowsingSlotIdx(DowsingTarget::SLOT_STORY_EVENT); break;
}
break;
}
case EVENT_EXIT: {
u16 id = (params1n2 >> 16) & 0xFFFF;
sExitId = id;
if ((params1n2 & 0xFFFF) == 1) {
dScGame_c::GetInstance()->triggerExit(
dStage_c::GetInstance()->getCurrRoomId(), id, SpawnInfo::RETAIN_TOD, SpawnInfo::NO_TRIAL
);
} else {
dScGame_c::GetInstance()->triggerExit(
dStage_c::GetInstance()->getCurrRoomId(), id, SpawnInfo::RETAIN_TOD, SpawnInfo::RETAIN_TRIAL
);
}
return 0;
}
case EVENT_12:
if (params1n2 == 1) {
dMessage_c::getInstance()->setField_0x2FC(-1);
} else {
dLytMeter_c::setRupyField_0x8AC(1);
dMessage_c::getInstance()->setField_0x2FC(0);
}
break;
case EVENT_COUNTER_THRESHOLD: {
u16 counter = (params1n2 >> 16) & 0xFFFF;
u16 threshold = (params1n2 & 0xFFFF);
if (counter == 0x1F5) {
counter = dAcItem_c::getRupeeCounter();
} else if (counter == 0x1ED) {
counter = dAcItem_c::getTotalSeedCount();
} else if (counter == 0x1F2) {
counter = dAcItem_c::getTotalArrowCount();
} else if (counter == 0x1F3) {
counter = dAcItem_c::getTotalBombCount();
} else {
counter = ItemflagManager::sInstance->getItemCounterOrFlag(counter);
}
if (counter >= threshold) {
mResultFromCounterCheck = 0;
} else {
mResultFromCounterCheck = 1;
}
break;
}
case EVENT_PLAY_SOUND: playSound(params1n2); break;
case EVENT_ADD_ITEM: {
u16 flag = (params1n2 >> 16) & 0xFFFF;
s16 change = (s16)(params1n2 & 0xFFFF);
s32 value = ItemflagManager::sInstance->getItemCounterOrFlag(flag);
value += change;
if (value < 0) {
value = 0;
} else if (value > 0x8000) {
value = 0x7FFF;
}
ItemflagManager::sInstance->setItemFlagOrCounterToValue(flag, value);
break;
}
case EVENT_SET_TEMPFLAG:
dStageMgr_c::GetInstance()->getFlagIndex();
SceneflagManager::sInstance->setTempflag_i(0x3F, (params1n2 >> 16) & 0xFFFF);
break;
case EVENT_UNSET_TEMPFLAG:
dStageMgr_c::GetInstance()->getFlagIndex();
SceneflagManager::sInstance->unsetTempflag_i(0x3F, (params1n2 >> 16) & 0xFFFF);
break;
case EVENT_LIGHT_PILLAR_30: {
s8 p4 = (params1n2 >> 24) & 0xFF;
s8 p1 = params1n2 & 0xFF;
s8 p3 = (params1n2 >> 16) & 0xFF;
s8 p2 = (params1n2 >> 8) & 0xFF;
s32 val = 1;
switch (p1) {
case 1: val = 4; break;
case 2: val = 3; break;
case 3: val = 5; break;
case 5: val = 7; break;
case 6: val = 8; break;
case 7: val = 9; break;
}
dMessage_c::getInstance()->setField_0x32C(val);
dMessage_c::getInstance()->setField_0x329(true);
if (dMessage_c::getInstance()->getField_0x328() == 0) {
dMessage_c::getInstance()->setField_0x328(1);
dMessage_c::getInstance()->clearLightPillarRelatedArgs();
if (dLytControlGame_c::getInstance()->isStateNormalOrNotInEvent()) {
dLytControlGame_c::getInstance()->somehowRelatedToEnteringLightPillars(val, p3, p2);
}
} else {
dLytMap_c::getInstance()->lightPillarRelated(val, p3, p2);
}
dMessage_c::getInstance()->storeLightPillarRelatedArg(p4);
break;
}
case EVENT_LIGHT_PILLAR_34: {
// TODO what do these modes do?
if (params1n2 == 1) {
if (!dLytControlGame_c::getInstance()->isNotInStateMap()) {
dLytControlGame_c::getInstance()->fn_802CCD40(true);
}
} else if (params1n2 == 2) {
dMessage_c::getInstance()->setField_0x330(1);
if (dMessage_c::getInstance()->getField_0x32C() != 4 &&
dMessage_c::getInstance()->getField_0x32C() != 12) {
dMessage_c::getInstance()->setField_0x32A(1);
}
} else {
if (dMessage_c::getInstance()->getField_0x32C() != 4 &&
dMessage_c::getInstance()->getField_0x32C() != 12) {
dMessage_c::getInstance()->setField_0x32A(1);
}
}
dMessage_c::getInstance()->setField_0x328(0);
break;
}
case EVENT_SET_STORYFLAG_217:
StoryflagManager::sInstance->setFlagOrCounterToValue(
217, dLytMsgWindow_c::getInstance()->getTextOptionSelection()
);
break;
case EVENT_DEMO_METER_ITEM_SELECT: return dLytMeter_c::GetInstance()->itemSelectDemoRelated(params1n2);
case EVENT_CAMERA_42: {
s32 p1 = (params1n2 >> 16) & 0xFFFF;
s32 p2 = params1n2 & 0xFFFF;
dScGame_c::getCamera(0)->doFn_800918E0(p1, p2);
break;
}
case EVENT_LYT_MINI_GAME: {
if (dMessage_c::getInstance()->getField_0x340() != 0) {
// cancel something minigame related if running
clearMinigame();
}
// start something minigame related
dMessage_c::getInstance()->setMiniGameVariant(params1n2);
createLytMiniGame();
break;
}
case EVENT_LYT_MINI_GAME_END:
// cancel something minigame related
clearMinigame();
break;
case EVENT_46: dMessage_c::getInstance()->setField_0x344(params1n2); break;
case EVENT_RESET_STORYFLAG: StoryflagManager::sInstance->setFlagOrCounterToValue(params1n2 & 0xFFFF, 0); break;
case EVENT_SET_ITEMFLAG: dAcItem_c::setFlag(params1n2 & 0xFFFF); break;
case EVENT_PALETTE: {
s16 p1 = (params1n2 >> 16) & 0xFFFF;
s16 p2 = params1n2 & 0xFFFF;
if (&BlurAndPaletteManager::GetInstance() != nullptr) {
BlurAndPaletteManager::GetInstance().fn_80024240(-1, p1, p2);
}
break;
}
case EVENT_DEMO_DOWSING:
if (dLytDemoDowsing_c::GetInstance() != nullptr) {
dLytDemoDowsing_c::GetInstance()->start();
}
break;
case EVENT_DEMO_METER_DOWSING: return dLytMeter_c::GetInstance()->dowsingDemoRelated(params1n2); break;
case EVENT_DEMO_METER_MINUS_BTN: return dLytMeter_c::GetInstance()->minusBtnDemoRelated(params1n2); break;
case EVENT_SELECT_STORY_DOWSING:
FileManager::GetInstance()->setDowsingSlotIdx(DowsingTarget::SLOT_STORY_EVENT);
break;
case EVENT_DEMO_COLLECTION_SCREEN:
if (!MinigameManager::isInMinigameState(MinigameManager::INSECT_CAPTURE)) {
dLytControlGame_c::getInstance()->openCollectionScreenDemo();
}
break;
}
return true;
}
bool dFlow_c::handleEvent() {
MsbFlowInfo *element = LMS_GetFlowElement(mpMsbf, mCurrentFlowIndex);
u16 next = element->next;
if (handleEventInternal(element)) {
if (next != 0xFFFF) {
setNext(next);
if (checkField0x3C()) {
field_0x40 = mCurrentFlowIndex;
field_0x44 = dLytMsgWindow_c::fn_800D7B40();
}
} else if (field_0x0F != 0) {
field_0x0F = 0;
} else {
clear();
}
return true;
} else {
return false;
}
}
bool dFlow_c::handleMessage() {
char label[40];
MsbFlowInfo *flow = LMS_GetFlowElement(mpMsbf, mCurrentFlowIndex);
int hasLabel = LMS_GetLabelByTextIndex(
dMessage_c::getMsbtInfoForIndex(dMessage_c::getInstance()->getCurrentTextFileNumber()), flow->param4, label
);
mCurrentTextLabelName.set(label);
u16 next = flow->next;
// Does this make sense? Result is unused...
LMS_GetAttribute(dMessage_c::getMsbtInfoForIndex(dMessage_c::getInstance()->getCurrentTextFileNumber()), hasLabel);
if (mCurrentTextLabelName == "KEN0_08") {
// "After winning the race in the Wing Ceremony..."
// Current objective - use value from FiContext::getObjective?
if (mFiSpeechArgument < 0) {
mFiSpeechArgument = 8;
}
mCurrentTextLabelName.sprintf("KEN0_%02d", mFiSpeechArgument);
} else if (mCurrentTextLabelName == "KEN1_000") {
// "This is Skyloft..."
// Area analysis - use value from FiContext::???
if (mFiSpeechArgument < 0) {
mFiSpeechArgument = 0;
}
mCurrentTextLabelName.sprintf("KEN1_%03d", mFiSpeechArgument);
} else if (mCurrentTextLabelName == "KEN2_000") {
// "To search for Zelda..."
// Hint - use value from FiContext::getFiAdviceHintEntry?
if (mFiSpeechArgument < 0) {
mFiSpeechArgument = 2;
}
mCurrentTextLabelName.sprintf("KEN2_%03d", mFiSpeechArgument);
} else if (mCurrentTextLabelName == "KEN3_500") {
// "Master, your current selection of pouch items is..."
// Equipment focus
FiAnalysisHandle analysis = FiContext::getNaviTableEquipmentCheckEntry();
SizedString<16> label;
label.sprintf("KEN3_%03d", analysis.getEquipmentFocus());
dLytMsgWindow_c::getInstance()->getTagProcessor()->setStringArg(
dMessage_c::getTextMessageByLabel(label, true, nullptr, nullptr), 0
);
} else if (mCurrentTextLabelName == "KEN3_000") {
// "You are located in the Sealed Grounds..."
// Threat assessment
FiAnalysisHandle analysis = FiContext::getNaviTableEquipmentCheckEntry();
s16 value = analysis.getAreaIndexForFiAreaName();
if (value < 0) {
value = 0;
}
mCurrentTextLabelName.sprintf("KEN3_%03d", value);
} else if (mCurrentTextLabelName == "KEN3_501") {
// "Suitability to current location is..."
// Suitability analysis
FiAnalysisHandle analysis = FiContext::getNaviTableEquipmentCheckEntry();
s32 arg = analysis.getSuitabilityPercentageArg();
dLytMsgWindow_c::getInstance()->setNumericArg0(arg);
} else if (mCurrentTextLabelName == "KEN3_100") {
// "My projections indicate that equipping..."
// Equipment recommendation
FiAnalysisHandle analysis = FiContext::getNaviTableEquipmentCheckEntry();
s16 value = analysis.getEquipmentRecommendation();
if (value < 0) {
value = 0;
}
mCurrentTextLabelName.sprintf("KEN3_%03d", value);
} else if (mCurrentTextLabelName == "KEN3_200") {
// "To make matters worse, your Wooden Shield..."
// Shield danger (wooden vs fire, iron vs electrical)
FiAnalysisHandle analysis = FiContext::getNaviTableEquipmentCheckEntry();
s16 value = analysis.getShieldMessage();
if (value < 0) {
value = 0;
}
mCurrentTextLabelName.sprintf("KEN3_%03d", value);
} else if (mCurrentTextLabelName == "KEN4_000") {
// Your hearts have decreased quite dramatically...
s32 fiHelpIndex = FiContext::getHelpIndex();
if (fiHelpIndex < 0) {
fiHelpIndex = 0;
}
if (fiHelpIndex == 5) {
StoryflagManager::sInstance->setFlag(727);
}
mCurrentTextLabelName.sprintf("KEN4_%03d", fiHelpIndex);
} else if (mCurrentTextLabelName == "KEN5_000") {
// This is...
s32 targetActorId = FiContext::getTargetActorId();
if (targetActorId < 0) {
targetActorId = 0;
}
if (targetActorId <= 480) {
mCurrentTextLabelName.sprintf("KEN5_%03d", targetActorId);
} else {
// "My apologies. I have no relevant information on the subject in my sizable memory."
// FiHead
mCurrentTextLabelName.sprintf("KEN7_000");
}
} else if (mCurrentTextLabelName == "KEN6_000") {
// "Target lock: ..."
s32 targetActorId = FiContext::getTargetActorId();
if (targetActorId < 0) {
targetActorId = 0;
}
if (targetActorId <= 91) {
mCurrentTextLabelName.sprintf("KEN6_%03d", targetActorId);
} else {
mCurrentTextLabelName.sprintf("KEN7_000");
}
} else if (mCurrentTextLabelName == "KEN6_107") {
// "You have defeated <X> of this type of enemy. My analysis shows that your battle performance..."
s32 targetActorId = FiContext::getTargetActorId();
if (targetActorId < 0) {
targetActorId = 0;
}
u16 killCount = FileManager::GetInstance()->getEnemyKillCount(targetActorId);
u8 performance = FiContext::rateBattlePerformance(targetActorId);
if (performance == 0xFF) {
dLytMsgWindow_c::getInstance()->setNumericArg0(killCount);
// "You have defeated <X> of this enemy type. I am unable to analyze your battle performance..."
mCurrentTextLabelName.sprintf("KEN6_108");
} else {
dLytMsgWindow_c::getInstance()->setNumericArg0(killCount);
SizedString<16> tmpLabel;
// "very strong, ..., very weak"
tmpLabel.sprintf("KEN6_1%02d", performance);
dLytMsgWindow_c::getInstance()->getTagProcessor()->setStringArg(
dMessage_c::getTextMessageByLabel(tmpLabel, true, nullptr, nullptr), 0
);
}
} else if (mCurrentTextLabelName == "KEN7_000") {
// "My apologies. I have no relevant information on the subject in my sizable memory."
s32 targetActorId = FiContext::getTargetActorId();
if (targetActorId < 0) {
targetActorId = 0;
}
if (targetActorId <= 561) {
// object info
mCurrentTextLabelName.sprintf("KEN7_%03d", targetActorId);
} else {
mCurrentTextLabelName.sprintf("KEN7_000");
}
} else if (mCurrentTextLabelName == "KEN8_000" || mCurrentTextLabelName == "KEN2_096") {
// "Current Session Play Time: ... Total Play Time: ..."
s32 seconds = OS_TICKS_TO_SEC(SaveTimeRelated::GetInstance()->getField_0x08());
s32 minutes_ = seconds / 60;
s32 minutes = minutes_ % 60;
s32 hours = seconds / 3600;
if (hours > 99) {
hours = 99;
minutes = 59;
}
s32 seconds1 = OS_TICKS_TO_SEC(SaveTimeRelated::GetInstance()->fn_801907D0());
s32 minutes1_ = seconds1 / 60;
s32 minutes1 = minutes1_ % 60;
s32 hours1 = seconds1 / 3600;
if (hours1 > 999) {
hours1 = 999;
minutes1 = 59;
}
s32 time[4] = {hours, minutes, hours1, minutes1};
dLytMsgWindow_c::getInstance()->setNumericArgs(time, 4);
} else if (mCurrentTextLabelName == "KEN9_000") {
// Random hint
s32 v;
if (StoryflagManager::sInstance->getCounterOrFlag(530)) {
// "When you require my analysis..."
v = 200;
} else if (dStageMgr_c::GetInstance()->getSTIFbyte4() == 0) {
// Sky rumor
v = cM::rndInt(30);
} else {
// Elsewhere rumor
v = cM::rndInt(30) + 30;
}
mCurrentTextLabelName.sprintf("KEN9_%03d", v);
}
if (dLytMsgWindow_c::getInstance()->setCurrentLabelName(mCurrentTextLabelName, false) == true) {
if (!dLytMsgWindow_c::getInstance()->getField_0x80D()) {
field_0x14 = 1;
}
if (next != 0xFFFF) {
setNext(next);
} else {
clear();
}
}
return false;
}
u16 dFlow_c::getSwitchChoice(const MsbFlowInfo *element, u16 param) const {
u16 result = 0;
if (param < BRANCH_14 || param > BRANCH_16) {
result = (this->*(sBranchHandlers[param]))(element);
}
return result;
}
inline void syncFiButtonText(s32 argIdx, s32 btnIdx) {
dTagProcessor_c *p = dLytMsgWindow_c::getInstance()->getTagProcessor();
p->setStringArg(FiContext::getMessageForFiInfo(argIdx), btnIdx);
}
inline void setFiButtonTextDirectly(s32 textLabel, s32 btnIdx) {
dTagProcessor_c *p = dLytMsgWindow_c::getInstance()->getTagProcessor();
p->setStringArg(FiContext::getButtonText(textLabel), btnIdx);
}
u16 dFlow_c::branchHandler00(const MsbFlowInfo *element) const {
return dLytMsgWindow_c::getInstance()->getTextOptionSelection();
}
u16 dFlow_c::branchHandler01(const MsbFlowInfo *element) const {
return dLytMsgWindow_c::getInstance()->getTextOptionSelection();
}
u16 dFlow_c::branchHandler02(const MsbFlowInfo *element) const {
if (FiContext::getIsInFiMainMenuChecked()) {
if (dLytMsgWindow_c::getInstance()->getTextOptionSelection() == 0 &&
FiContext::getGlobalFiInfo0(0) == FiContext::KEN8_Advice) {
// HACK (?): If the user presses "Advice" on the Fi main menu,
// which is known to be button 0, update Fi's buttons since that
// menu isn't entirely integrated into the flow
FiContext::do_resetAdviceOptions();
syncFiButtonText(0, 0);
syncFiButtonText(1, 1);
syncFiButtonText(2, 2);
} else {
FiContext::setIsInFiMainMenu(false);
}
}
return dLytMsgWindow_c::getInstance()->getTextOptionSelection();
}
u16 dFlow_c::branchHandler03(const MsbFlowInfo *element) const {
return !StoryflagManager::sInstance->getCounterOrFlag(element->params1n2);
}
u16 dFlow_c::branchHandler04(const MsbFlowInfo *element) const {
return 0;
}
u16 dFlow_c::branchHandler05(const MsbFlowInfo *element) const {
return !SceneflagManager::sInstance->checkZoneflag_i(
dStage_c::GetInstance()->getCurrRoomId(), element->params1n2 & 0xFFFF
);
}
u16 dFlow_c::branchHandler06(const MsbFlowInfo *element) const {
return !SceneflagManager::sInstance->checkFlag(0x3F, (element->params1n2 & 0xFFFF));
}
u16 dFlow_c::branchHandler07(const MsbFlowInfo *element) const {
return mResultFromCounterCheck != 0;
}
u16 dFlow_c::branchHandler08(const MsbFlowInfo *element) const {
return mResultFromCounterCheck != 0;
}
u16 dFlow_c::branchHandler09(const MsbFlowInfo *element) const {
return !SceneflagManager::sInstance->checkTempflag_i(0x3F, element->params1n2 & 0xFFFF);
}
u16 dFlow_c::branchHandler10(const MsbFlowInfo *element) const {
u16 threshold = element->params1n2 & 0xFFFF;
return threshold > dAcItem_c::getRupeeCounter();
}
u16 dFlow_c::branchHandler11(const MsbFlowInfo *element) const {
return (s32)(cM::rnd() * 2.0f);
}
u16 dFlow_c::branchHandler12(const MsbFlowInfo *element) const {
return (s32)(cM::rnd() * 3.0f);
}
u16 dFlow_c::branchHandler13(const MsbFlowInfo *element) const {
return (s32)(cM::rnd() * 4.0f);
}
u16 dFlow_c::branchHandler14(const MsbFlowInfo *element) const {
return 0;
}
u16 dFlow_c::branchHandler15(const MsbFlowInfo *element) const {
return 0;
}
u16 dFlow_c::branchHandler16(const MsbFlowInfo *element) const {
return 0;
}
u16 dFlow_c::branchHandler17(const MsbFlowInfo *element) const {
bool ret = false;
if (adventurePouchFindItemSlot(ITEM_NONE) != POUCH_SLOT_NONE) {
ret = true;
}
return ret;
}
u16 dFlow_c::branchHandler18(const MsbFlowInfo *element) const {
bool ret = false;
if (!getLinkPtr()->isItemFairyFromBugnet()) {
ret = true;
}
return ret;
}
u16 dFlow_c::branchHandler19(const MsbFlowInfo *element) const {
u16 ret = 1;
switch (element->params1n2 & 0xFFFF) {
case 0:
if (dAcPy_c::getCurrentlyEquippedShieldType() != 4) {
ret = 0;
}
break;
case 1:
if (adventurePouchFindItemSlot(ITEM_BOTTLE) != POUCH_SLOT_NONE) {
ret = 0;
}
break;
case 2:
if (adventurePouchFindItemSlot(ITEM_HOT_SOUP) != POUCH_SLOT_NONE) {
ret = 0;
}
break;
case 3:
if (adventurePouchFindItemSlot(ITEM_COLD_SOUP) != POUCH_SLOT_NONE) {
ret = 0;
}
break;
case 4:
if (adventurePouchFindItemSlot(ITEM_STAMINA_POTION) != POUCH_SLOT_NONE ||
adventurePouchFindItemSlot(ITEM_STAMINA_POTION_PLUS) != POUCH_SLOT_NONE) {
ret = 0;
}
break;
case 5:
if (adventurePouchFindItemSlot(ITEM_MUSHROOM_SPORES) != POUCH_SLOT_NONE ||
adventurePouchFindItemSlot(ITEM_GLITTERING_SPORES) != POUCH_SLOT_NONE) {
ret = 0;
}
break;
case 6: {
FiAnalysisHandle handle = FiContext::getNaviTableEquipmentCheckEntry();
switch (handle.getThreatenedShield()) {
case 0:
if (dAcPy_c::getCurrentlyEquippedShieldType() == 0) {
ret = 0;
}
break;
case 1:
if (dAcPy_c::getCurrentlyEquippedShieldType() == 1) {
ret = 0;
}
break;
}
break;
}
case 7:
if (adventurePouchFindItemSlot(ITEM_SACRED_WATER) != POUCH_SLOT_NONE) {
ret = 0;
}
break;
case 8:
if (adventurePouchCountItem(ITEM_HEART_MEDAL) != 0) {
ret = 0;
}
break;
case 9:
if (hasAnyShields()) {
ret = 0;
}
break;
}
return ret;
}
u16 dFlow_c::branchHandler20(const MsbFlowInfo *element) const {
u16 ret = 0;
if (itemCheckFindItemSlot(ITEM_NONE) != ITEM_CHECK_SLOT_NONE) {
ret = 1;
}
return ret;
}
u16 dFlow_c::branchHandler21(const MsbFlowInfo *element) const {
u16 ret = 0;
if (shouldHideKillCountForActor(FiContext::getTargetActorId())) {
ret = 1;
}
return ret;
}
u16 dFlow_c::branchHandler22(const MsbFlowInfo *element) const {
u16 ret = StoryflagManager::sInstance->getCounterOrFlag(692);
if (ret > 3) {
ret = 3;
}
return ret;
}
dFlow_c::BranchHandler dFlow_c::sBranchHandlers[] = {
&dFlow_c::branchHandler00, // BRANCH_SELECTED_OPTION_0
&dFlow_c::branchHandler01, // BRANCH_SELECTED_OPTION_1
&dFlow_c::branchHandler02, // BRANCH_SELECTED_OPTION_FI
&dFlow_c::branchHandler03, // BRANCH_STORYFLAG
&dFlow_c::branchHandler04, // BRANCH_NONE
&dFlow_c::branchHandler05, // BRANCH_ZONEFLAG
&dFlow_c::branchHandler06, // BRANCH_SCENEFLAG
&dFlow_c::branchHandler07, // BRANCH_EVENT_COUNTER_THRESHOLD_1
&dFlow_c::branchHandler08, // BRANCH_EVENT_COUNTER_THRESHOLD_2
&dFlow_c::branchHandler09, // BRANCH_TEMPFLAG
&dFlow_c::branchHandler10, // BRANCH_CURRENT_RUPEES
&dFlow_c::branchHandler11, // BRANCH_RAND_2
&dFlow_c::branchHandler12, // BRANCH_RAND_3
&dFlow_c::branchHandler13, // BRANCH_RAND_4
&dFlow_c::branchHandler14, // BRANCH_14
&dFlow_c::branchHandler15, // BRANCH_15
&dFlow_c::branchHandler16, // BRANCH_16
&dFlow_c::branchHandler17, // BRANCH_FREE_SPACE_IN_POUCH
&dFlow_c::branchHandler18, // BRANCH_18
&dFlow_c::branchHandler19, // BRANCH_19
&dFlow_c::branchHandler20, // BRANCH_FREE_SPACE_IN_ITEM_CHECK
&dFlow_c::branchHandler21, // BRANCH_TARGET_ACTOR_HAS_KILL_COUNT
&dFlow_c::branchHandler22, // BRANCH_22
};
bool dFlow_c::handleBranch() {
MsbFlowInfo *info = LMS_GetFlowElement(mpMsbf, mCurrentFlowIndex);
u16 next = getSwitchChoice(info, info->param3);
u16 *branchPoints = LMS_GetBranchPoints(mpMsbf, mCurrentFlowIndex);
if (info->param5 != 0xFFFF) {
setNext(branchPoints[next]);
} else {
clear();
}
return true;
}
bool dFlow_c::handleEntry() {
MsbFlowInfo *element = LMS_GetFlowElement(mpMsbf, mCurrentFlowIndex);
setNext(element->next);
return true;
}
bool dFlow_c::handleJump() {
// unimplemented
return true;
}
void dFlow_c::triggerEntryPoint(s32 labelPart1, s32 labelPart2) {
if (labelPart1 == 6) {
// "Your hearts have decreased quite dramatically..."
// 400 -> may introduce heart dowsing
// 401 -> no heart dowsing introduction
if (labelPart2 == 400) {
FiContext::setField_0x48(true);
labelPart2 = 401;
} else if (labelPart2 == 401) {
StoryflagManager::sInstance->setFlag(808);
// Don't introduce heart dowsing in areas where you might
// not be able to dowse
if (dStageMgr_c::GetInstance()->isAreaTypeNormal()) {
labelPart2 = 400;
} else {
labelPart2 = 401;
}
} else if (labelPart2 == 402 && (dLytMsgWindow_c::getInstance()->getTagProcessor()->getField_0x8FC() < 0 ||
dLytMsgWindow_c::getInstance()->getTagProcessor()->getField_0x900() < 0)) {
// "You have elected to engage Hero Mode..."
FiContext::setField_0x4A(true);
labelPart2 = 401;
}
}
if (labelPart1 == 6 && labelPart2 == 801) {
// "You called for me, Master?"
FiContext::do_prepareFiCallOptions();
FiContext::setIsInFiMainMenu(true);
FiContext::do_fn_8016CB40();
syncFiButtonText(0, 0);
syncFiButtonText(1, 1);
syncFiButtonText(2, 2);
setFiButtonTextDirectly(FiContext::KEN8_Nevermind, 3);
} else if (labelPart1 == 6 && labelPart2 == 802) {
// Doesn't seem to exist in the files
FiContext::setIsInFiMainMenu(true);
syncFiButtonText(0, 0);
syncFiButtonText(1, 1);
syncFiButtonText(2, 2);
setFiButtonTextDirectly(FiContext::KEN8_Nevermind, 3);
}
u16 entry = findEntryPoint(labelPart1, labelPart2);
start(entry);
}
void dFlow_c::triggerEntryPoint(const char *label) {
u16 entry = findEntryPoint(label);
start(entry);
}
u16 dFlow_c::getField_0x44() const {
return field_0x44;
}
u16 dFlow_c::getNextFiFlow() const {
return mNextFiFlow;
}
bool dFlow_c::triggerEntryPointChecked(s32 labelPart1, s32 labelPart2) {
if (checkField0x3C()) {
s32 old = field_0x40;
dFlow_c::triggerEntryPoint(labelPart1, labelPart2);
setNext(old);
return true;
}
return false;
}
void dFlow_c::setField0x3C() {
field_0x3C = 1;
}
bool dFlow_c::checkField0x3C() const {
return field_0x3C == 1;
}
extern "C" dFlow_c *CURRENT_ACTOR_EVENT_FLOW_MANAGER;
void dFlow_c::start(u16 entry) {
field_0x0E = 0;
field_0x0F = 0;
field_0x3C = 0;
field_0x40 = -1;
field_0x44 = -1;
mNextFiFlow = -1;
mDelayTimer = 0;
std::memset(&mFlowInfo, 0, sizeof(MsbFlowInfo));
CURRENT_ACTOR_EVENT_FLOW_MANAGER = this;
field_0x0C = 0xFFFF;
mCurrentFlowIndex = -1;
field_0x10 = 1;
setNext(entry);
}
bool dFlow_c::advanceUntil(s32 searchType, s32 searchParam3, s32 *pOutParams1n2) {
bool keepGoing = true;
while (keepGoing && !vt_0x18()) {
MsbFlowInfo *element = LMS_GetFlowElement(mpMsbf, mCurrentFlowIndex);
s32 type = element->type;
if (searchType == type) {
if (element->type == FLOW_EVENT) {
if (element->param3 == searchParam3) {
if (pOutParams1n2 != nullptr) {
*pOutParams1n2 = element->params1n2;
}
return true;
}
} else {
return true;
}
}
switch (type) {
case FLOW_MESSAGE:
// Skip text processing, simply advance
setNext(element->next);
keepGoing = true;
break;
case FLOW_BRANCH:
if (element->param3 <= BRANCH_SELECTED_OPTION_FI) {
// Skip evaluating if next branch depends on user input
keepGoing = false;
} else {
// Otherwise
keepGoing = handleBranch();
}
break;
case FLOW_EVENT:
switch (element->param3) {
case EVENT_LOAD_FI_FLOW:
case EVENT_COUNTER_THRESHOLD:
case 27: keepGoing = handleEvent(); continue;
case EVENT_LIGHT_PILLAR_34:
if (element->params1n2 == 2) {
keepGoing = false;
continue;
}
// fall-through
default:
setNext(element->next);
keepGoing = true;
break;
}
break;
case FLOW_ENTRY: keepGoing = handleEntry(); break;
case FLOW_JUMP: keepGoing = handleJump(); break;
}
}
return 0;
}
// TODO: Used KEN6 as reference, will be set from dAcBase_c::targetFiTextId (0x114)
static const s32 sActorsWithoutKillCount[] = {
0, // Remlit (Day)
1, // Remlit (Night)
10, // Moldarach
46, // Stalmaster
47, // LD-002S Scervo
48, // Koloktos
49, // Ghirahim (G1)
50, // Ghirahim (G2)
51, // Ghirahim (G3.1)
52, // The Imprisoned
53, // The Imprisoned
54, // The Imprisoned
55, // Demise
65, // Scaldera
67, // <?>
77, // Tentalus
81, // Peahat
87, // Sentrobe Bomb
88, // LD-003D Dreadfuse
89, // Ghirahim (G3.2)
90, // Ghirahim (G3.3)
-1 // <end of array>
};
bool dFlow_c::shouldHideKillCountForActor(s32 id) const {
for (s32 i = 0; sActorsWithoutKillCount[i] >= 0; i++) {
if (sActorsWithoutKillCount[i] == id) {
return true;
}
}
return false;
}
void dFlow_c::clear() {
mCurrentFlowIndex = -1;
field_0x0E = 1;
field_0x3C = 0;
field_0x40 = -1;
field_0x44 = -1;
}
void dFlow_c::createLytMiniGame() {
if (dLytMiniGame_c::GetInstance() == nullptr) {
switch (dMessage_c::getInstance()->getMiniGameVariant()) {
case 0:
dBase_c::createBase(fProfile::LYT_MINI_GAME, dLytControlGame_c::getInstance(), 11, fBase_c::OTHER);
break;
case 1:
dBase_c::createBase(fProfile::LYT_MINI_GAME, dLytControlGame_c::getInstance(), 10, fBase_c::OTHER);
break;
case 2:
dBase_c::createBase(fProfile::LYT_MINI_GAME, dLytControlGame_c::getInstance(), 12, fBase_c::OTHER);
break;
case 3:
dBase_c::createBase(fProfile::LYT_MINI_GAME, dLytControlGame_c::getInstance(), 14, fBase_c::OTHER);
break;
case 4:
dBase_c::createBase(fProfile::LYT_MINI_GAME, dLytControlGame_c::getInstance(), 13, fBase_c::OTHER);
break;
case 5:
dBase_c::createBase(fProfile::LYT_MINI_GAME, dLytControlGame_c::getInstance(), 2, fBase_c::OTHER);
break;
case 6:
dBase_c::createBase(fProfile::LYT_MINI_GAME, dLytControlGame_c::getInstance(), 16, fBase_c::OTHER);
break;
case 7:
dBase_c::createBase(fProfile::LYT_MINI_GAME, dLytControlGame_c::getInstance(), 15, fBase_c::OTHER);
break;
}
}
}
void dFlow_c::clearMinigame() {
if (dMessage_c::getInstance()->getField_0x340()) {
switch (dMessage_c::getInstance()->getMiniGameVariant()) {
case 0:
case 1:
case 4:
case 5:
if (dLytMiniGame_c::GetInstance() != nullptr) {
dLytMiniGame_c::GetInstance()->scoreRelated();
}
break;
case 2:
case 3:
case 6:
case 7:
if (dLytMiniGame_c::GetInstance() != nullptr) {
dLytMiniGame_c::GetInstance()->timeRelated();
}
break;
}
}
dMessage_c::getInstance()->setField_0x340(0);
dMessage_c::getInstance()->setMiniGameVariant(8);
}
SPECIAL_BASE_PROFILE(MESSAGE, dMessage_c, fProfile::MESSAGE, 0x2A8, 0);
static const char *sMsbtFileNames[82] = {
"000-Test.msbt",
"001-Action.msbt",
"002-System.msbt",
"003-ItemGet.msbt",
"004-Object.msbt",
"005-Tutorial.msbt",
"006-KenseiNormal.msbt",
"006-1KenseiNormal.msbt",
"006-2KenseiNormal.msbt",
"006-3KenseiNormal.msbt",
"006-4KenseiNormal.msbt",
"006-5KenseiNormal.msbt",
"006-6KenseiNormal.msbt",
"006-7KenseiNormal.msbt",
"006-8KenseiNormal.msbt",
"006-9KenseiNormal.msbt",
"007-MapText.msbt",
"008-Hint.msbt",
"word.msbt",
"100-Town.msbt",
"101-Shop.msbt",
"102-Zelda.msbt",
"103-DaiShinkan.msbt",
"104-Rival.msbt",
"105-Terry.msbt",
"106-DrugStore.msbt",
"107-Kanban.msbt",
"108-ShinkanA.msbt",
"109-TakeGoron.msbt",
"110-DivingGame.msbt",
"111-FortuneTeller.msbt",
"112-Trustee.msbt",
"113-RemodelStore.msbt",
"114-Friend.msbt",
"115-Town2.msbt",
"116-InsectGame.msbt",
"117-Pumpkin.msbt",
"118-Town3.msbt",
"119-Captain.msbt",
"120-Nushi.msbt",
"121-AkumaKun.msbt",
"122-Town4.msbt",
"123-Town5.msbt",
"124-Town6.msbt",
"125-D3.msbt",
"150-Siren.msbt",
"198-Test.msbt",
"199-Demo.msbt",
"200-Forest.msbt",
"201-ForestD1.msbt",
"202-ForestD2.msbt",
"203-ForestF2.msbt",
"204-ForestF3.msbt",
"250-ForestSiren.msbt",
"251-Salvage.msbt",
"299-Demo.msbt",
"300-Mountain.msbt",
"301-MountainD1.msbt",
"302-Anahori.msbt",
"303-MountainF2.msbt",
"304-MountainD2.msbt",
"305-MountainF3.msbt",
"350-MountainSiren.msbt",
"351-Salvage.msbt",
"399-Demo.msbt",
"400-Desert.msbt",
"401-DesertD2.msbt",
"402-DesertF2.msbt",
"403-DesertD1.msbt",
"404-DesertF3.msbt",
"405-DesertD2Clear.msbt",
"406-TrolleyRace.msbt",
"450-DesertSiren.msbt",
"451-Salvage.msbt",
"460-RairyuMinigame.msbt",
"499-Demo.msbt",
"500-CenterField.msbt",
"501-Inpa.msbt",
"502-CenterFieldBack.msbt",
"503-Goron.msbt",
"510-Salvage.msbt",
"599-Demo.msbt",
};
static char *sMsbfFileNames[80] = {
"000-Test.msbf",
"001-Action.msbf",
"002-System.msbf",
"003-ItemGet.msbf",
"004-Object.msbf",
"005-Tutorial.msbf",
"006-KenseiNormal.msbf",
"006-1KenseiNormal.msbf",
"006-2KenseiNormal.msbf",
"006-3KenseiNormal.msbf",
"006-4KenseiNormal.msbf",
"006-5KenseiNormal.msbf",
"006-6KenseiNormal.msbf",
"006-7KenseiNormal.msbf",
"006-8KenseiNormal.msbf",
"006-9KenseiNormal.msbf",
"008-Hint.msbf",
"100-Town.msbf",
"101-Shop.msbf",
"102-Zelda.msbf",
"103-DaiShinkan.msbf",
"104-Rival.msbf",
"105-Terry.msbf",
"106-DrugStore.msbf",
"107-Kanban.msbf",
"108-ShinkanA.msbf",
"109-TakeGoron.msbf",
"110-DivingGame.msbf",
"111-FortuneTeller.msbf",
"112-Trustee.msbf",
"113-RemodelStore.msbf",
"114-Friend.msbf",
"115-Town2.msbf",
"116-InsectGame.msbf",
"117-Pumpkin.msbf",
"118-Town3.msbf",
"119-Captain.msbf",
"120-Nushi.msbf",
"121-AkumaKun.msbf",
"122-Town4.msbf",
"123-Town5.msbf",
"124-Town6.msbf",
"125-D3.msbf",
"150-Siren.msbf",
"198-Test.msbf",
"199-Demo.msbf",
"200-Forest.msbf",
"201-ForestD1.msbf",
"202-ForestD2.msbf",
"203-ForestF2.msbf",
"204-ForestF3.msbf",
"250-ForestSiren.msbf",
"251-Salvage.msbf",
"299-Demo.msbf",
"300-Mountain.msbf",
"301-MountainD1.msbf",
"302-Anahori.msbf",
"303-MountainF2.msbf",
"304-MountainD2.msbf",
"305-MountainF3.msbf",
"350-MountainSiren.msbf",
"351-Salvage.msbf",
"399-Demo.msbf",
"400-Desert.msbf",
"401-DesertD2.msbf",
"402-DesertF2.msbf",
"403-DesertD1.msbf",
"404-DesertF3.msbf",
"405-DesertD2Clear.msbf",
"406-TrolleyRace.msbf",
"450-DesertSiren.msbf",
"451-Salvage.msbf",
"460-RairyuMinigame.msbf",
"499-Demo.msbf",
"500-CenterField.msbf",
"501-Inpa.msbf",
"502-CenterFieldBack.msbf",
"503-Goron.msbf",
"510-Salvage.msbf",
"599-Demo.msbf",
};
dMessage_c *dMessage_c::sInstance;
dTagProcessor_c *dMessage_c::sTagProcessor;
static void *msbAlloc(size_t size) {
if (UnkTextThing::getInstance()->getShouldHookAllocations() == false) {
return EGG::Heap::alloc(size, 0x20, nullptr);
} else {
return UnkTextThing::getInstance()->allocUnk(size, 0x20);
}
}
static void msbFree(void *ptr) {
if (UnkTextThing::getInstance()->getShouldHookAllocations() == false) {
EGG::Heap::free(ptr, nullptr);
} else {
UnkTextThing::getInstance()->destroyUnk(ptr);
}
}
dMessage_c::dMessage_c() {
sInstance = this;
}
int dMessage_c::create() {
mLanguage = getLanguageIdentifier();
mCurrentTextFileNumber = -1;
field_0x2FC = 0;
field_0x344 = 0;
LMS_SetMemFuncs(msbAlloc, msbFree);
setZevFromMsbArc();
for (s32 i = 0; i < 82; i++) {
SizedString<0x40> fileName = getMsbtFileName(i);
s32 num = getMsbtNumberByIndex(i);
void *data = getDataFromMsbArc(num, fileName, true);
if (data == nullptr) {
mpMsgs[i] = nullptr;
} else {
mpMsgs[i] = LMS_InitMessage(data);
}
}
for (s32 i = 0; i < 80; i++) {
SizedString<0x40> fileName = getMsbfFileName(i);
s32 num = getMsbfNumberByIndex(i);
void *data = getDataFromMsbArc(num, fileName, true);
if (data == nullptr) {
mpFlows[i] = nullptr;
} else {
mpFlows[i] = LMS_InitFlow(data);
}
}
sTagProcessor = new dTagProcessor_c();
FiContext::create();
reset();
return SUCCEEDED;
}
int dMessage_c::doDelete() {
UnkTextThing::getInstance()->destroy();
if (sTagProcessor != nullptr) {
delete sTagProcessor;
sTagProcessor = nullptr;
}
for (int i = 0; i < 82; i++) {
if (mpMsgs[i] != nullptr) {
LMS_CloseMessage(mpMsgs[i]);
}
}
for (int i = 0; i < 80; i++) {
if (mpFlows[i] != nullptr) {
LMS_CloseFlow(mpFlows[i]);
}
}
return SUCCEEDED;
}
int dMessage_c::execute() {
if (field_0x2FC != 0 && !EventManager::isInEvent()) {
field_0x2FC = 0;
}
if (field_0x2FC > 0) {
field_0x2FC--;
}
executeMinigame();
sTagProcessor->execute();
return SUCCEEDED;
}
int dMessage_c::draw() {
return SUCCEEDED;
}
const wchar_t *dMessage_c::getTextMessageByLabel(const char *label, bool global, wchar_t *dstBuf, u32 maxLen) {
return sInstance->getTextMessageByLabelInternal(label, nullptr, global, dstBuf, maxLen);
}
const wchar_t *dMessage_c::getTextMessageByLabel(
const char *label, dTagProcessor_c *pTagProcessor, bool global, wchar_t *dstBuf, u32 maxLen
) {
return sInstance->getTextMessageByLabelInternal(label, pTagProcessor, global, dstBuf, maxLen);
}
const wchar_t *dMessage_c::formatText(const wchar_t *text) {
return sInstance->formatTextInternal(text);
}
const wchar_t *dMessage_c::getTextMessageByLabelInternal(
const char *label, dTagProcessor_c *pTagProcessor, bool global, wchar_t *dstBuf, u32 maxLen
) {
s32 fileIndex = mCurrentTextFileNumber;
MsbtInfo *info = nullptr;
if (global) {
fileIndex = getMsbtIndexForLabelInternal(label);
}
if (fileIndex >= 0) {
info = getMsbtInfoForIndex(fileIndex);
}
const wchar_t *text = LMS_GetTextByLabel(info, label);
if (pTagProcessor == nullptr) {
pTagProcessor = sTagProcessor;
}
u32 outLen = 0;
s32 textIdx = LMS_GetTextIndexByLabel(info, label);
MsbtAttrInfo *att = LMS_GetAttribute(info, textIdx);
pTagProcessor->setMsgWindowSubtype(att->c_0x00);
pTagProcessor->setField_0x90D(att->c_0x01);
// Strip trailing newline
wchar_t *end;
s32 i;
if (dstBuf != nullptr) {
pTagProcessor->format(nullptr, text, dstBuf, maxLen, &outLen, nullptr);
i = outLen - 1;
end = &dstBuf[i];
for (; i > 0; i--) {
if (*end != L'\n') {
break;
}
*end-- = L'\0';
}
// no return - might be intentional since dstBuf already has the result
} else {
static wchar_t sBuf[0x400] = {};
pTagProcessor->format(nullptr, text, sBuf, ARRAY_LENGTH(sBuf) - 1, &outLen, nullptr);
i = outLen - 1;
end = &sBuf[i];
for (; i > 0; i--) {
if (*end != L'\n') {
break;
}
*end = L'\0';
end--;
}
return sBuf;
}
return nullptr;
}
const wchar_t *dMessage_c::formatTextInternal(const wchar_t *text) {
u32 outLen = 0;
static wchar_t sBuf[0x200] = {};
sTagProcessor->format(nullptr, text, sBuf, ARRAY_LENGTH(sBuf), &outLen, nullptr);
s32 i = outLen - 1;
wchar_t *end = &sBuf[i];
for (; i > 0; i--) {
if (*end != L'\n') {
break;
}
*end = L'\0';
end--;
}
return sBuf;
}
bool dMessage_c::isValidTextLabel(const char *name) {
return sInstance->checkIsValidTextLabel(name);
}
bool dMessage_c::checkIsValidTextLabel(const char *name) {
if (name == nullptr) {
return false;
}
return getTextIndexForLabel(name) >= 0;
}
const char *dMessage_c::getMsbtFileName(s32 idx) {
static SizedString<128> sPath;
s32 arcIdx = getArcIndexForFile(sMsbtFileNames[idx]);
const char *arcName = getArcNameByIndex(arcIdx, true);
sPath.sprintf("%s/%s", arcName, sMsbtFileNames[idx]);
return sPath;
}
const char *dMessage_c::getMsbfFileName(s32 idx) {
static SizedString<128> sPath;
s32 arcIdx = getArcIndexForFile(sMsbfFileNames[idx]);
const char *arcName = getArcNameByIndex(arcIdx, true);
sPath.sprintf("%s/%s", arcName, sMsbfFileNames[idx]);
return sPath;
}
s32 dMessage_c::getMsbtNumberByIndex(s32 index) {
return atoi(getMsbtFileName(index));
}
s32 dMessage_c::getMsbfNumberByIndex(s32 index) {
return atoi(getMsbfFileName(index));
}
// Skipping 007-MapText.msbt and word.msbt
static const s32 sMsbfToMsbt[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81,
};
s32 dMessage_c::getMsbtIndexForMsbfIndex(s32 index) {
return sMsbfToMsbt[index];
}
s32 dMessage_c::getTextIndexForLabel(const char *label) {
s32 idx = getMsbtIndexForLabelInternal(label);
MsbtInfo *info = nullptr;
if (idx >= 0) {
info = getMsbtInfoForIndex(idx);
}
return LMS_GetTextIndexByLabel(info, label);
}
s32 dMessage_c::getMsbtIndexForLabel(const char *label) {
return sInstance->getMsbtIndexForLabelInternal(label);
}
s32 dMessage_c::getMsbtIndexForLabelInternal(const char *label) {
if (mCurrentTextFileNumber <= 81) {
MsbtInfo *info = getMsbtInfoForIndex(mCurrentTextFileNumber);
if (LMS_GetTextIndexByLabel(info, label) >= 0) {
return mCurrentTextFileNumber;
}
}
for (s32 i = 0; i < 82; i++) {
if (getMsbtInfoForIndex(i) != nullptr) {
if (LMS_GetTextIndexByLabel(getMsbtInfoForIndex(i), label) >= 0) {
return i;
}
}
}
return -1;
}
MsbtInfo *dMessage_c::getMsbtInfoForIndex(s32 index) {
return sInstance->getMsbtInfoForIndexInternal(index);
}
MsbtInfo *dMessage_c::getMsbtInfoForIndexInternal(s32 index) {
UnkTextThing *thing = UnkTextThing::getInstance();
MsbtInfo *overrideMsbt = thing->getOverrideMsbtInfo(index);
if (overrideMsbt != nullptr && thing->getShouldOverrideData() == true) {
return overrideMsbt;
}
return mpMsgs[index];
}
MsbfInfo *dMessage_c::getMsbfInfoForIndex(s32 index) {
return sInstance->getMsbfInfoForIndexInternal(index);
}
MsbfInfo *dMessage_c::getMsbfInfoForIndexInternal(s32 index) {
UnkTextThing *thing = UnkTextThing::getInstance();
MsbfInfo *overrideMsbf = thing->getOverrideMsbfInfo(index);
if (overrideMsbf != nullptr && thing->getShouldOverrideData() == true) {
return overrideMsbf;
}
return mpFlows[index];
}
u32 dMessage_c::getLightPillarRelatedArg(s32 arg) {
return field_0x300[arg];
}
void dMessage_c::storeLightPillarRelatedArg(u32 arg) {
for (s32 i = 0; i < ARRAY_LENGTH(field_0x300); i++) {
if (field_0x300[i] == 0xFFFFFFFF) {
field_0x300[i] = arg;
return;
}
}
}
void dMessage_c::clearLightPillarRelatedArgs() {
for (s32 i = 0; i < ARRAY_LENGTH(field_0x300); i++) {
field_0x300[i] = 0xFFFFFFFF;
}
}
void dMessage_c::executeMinigame() {
if (mMiniGameVariant == 8) {
return;
}
if (!dMessage_c::getInstance()->getField_0x340()) {
switch (mMiniGameVariant) {
case 0:
case 1:
case 4:
case 5:
if (dLytMiniGame_c::GetInstance() != nullptr) {
dLytMiniGame_c::GetInstance()->scoreRelatedExecute();
dLytMiniGame_c::GetInstance()->setDisplayedPoints(mMinigameResultPoints);
sInstance->field_0x340 = 1;
}
break;
case 2:
case 3:
case 6:
case 7:
if (dLytMiniGame_c::GetInstance() != nullptr) {
dLytMiniGame_c::GetInstance()->timeRelatedExecute();
dLytMiniGame_c::GetInstance()->setDisplayedTime(mMinigameTime);
sInstance->field_0x340 = 1;
}
break;
}
}
}
void dMessage_c::init() {
clearLightPillarRelatedArgs();
// Probably inlines
field_0x328 = 0;
field_0x329 = 0;
field_0x32A = 0;
sInstance->setField_0x32C(12);
field_0x330 = 0;
}
void dMessage_c::reset() {
init();
mMiniGameVariant = 8;
field_0x340 = 0;
mMinigameResultPoints = 0;
mMinigameTime = 0;
}
extern "C" u8 fn_80054F30();
static SizedString<8> sCurrentLanguage;
const char *dMessage_c::getLanguageIdentifier() {
u8 lang = fn_80054F30();
if (lang == 3) {
sCurrentLanguage = "fr_US";
} else if (lang == 4) {
sCurrentLanguage = "es_US";
} else {
sCurrentLanguage = "en_US";
}
return sCurrentLanguage;
}
const char *sLytMsbts_Unused[] = {
"basic/remoConBtn_00.msbt",
"basic/remoConBtn_01.msbt",
"basic/remoConBtn_03.msbt",
"basic/remoConBtn_04.msbt",
"basic/remoConBtn_05.msbt",
"basic/nunBtn_03.msbt",
"common/commonTitle_00.msbt",
"endroll/endScroll_00.msbt",
"fileSelect/fileSelect_00.msbt",
"information/skip_00.msbt",
"map/map_00.msbt",
"messageWindow/messageBtn_00.msbt",
"miniGame/start_00.msbt",
"miniGameScore/miniGameScore_00.msbt",
"miniGameTime/miniGameTime_00.msbt",
"pause/pauseInfo_00.msbt",
"pause/pause_00.msbt",
"shop/depositBox_00.msbt",
"shop/itemSelect_00.msbt",
"shop/materialCheck_00.msbt",
"softwareKeyboard/toolbarBtn_00.msbt",
"systemWindow/systemWindow_00.msbt",
"title/titleBG_00.msbt",
};
const char *dMessage_c::getArcNameByIndex(s32 idx, bool global) {
return getArcNameByIndexInternal(idx, global);
}
static char *sArcNames[] = {
"0-Common", "1-Town", "2-Forest", "3-Mountain", "4-Desert", "5-CenterField",
};
const char *dMessage_c::getArcNameByIndexInternal(s32 idx, bool global) {
return sArcNames[idx];
}
void *dMessage_c::getDataFromMsbArc(s32 number, const char *fileName, bool global) {
SizedString<128> path;
path.sprintf("%s/%s", getLanguageIdentifier(), getArcNameByIndex(number, global));
return OarcManager::GetInstance()->getData(path, fileName);
}
void dMessage_c::setZevFromMsbArc() {
SizedString<128> path;
path.sprintf("%s/dat/zev.dat", getArcNameByIndex(0, false));
sZev0 = getDataFromMsbArc(0, path, false);
sZevStage = nullptr;
}
void dMessage_c::setStageZevFromMsbArc() {
SizedString<32> stage = dScGame_c::currentSpawnInfo.stageName;
char buf[2];
buf[0] = stage[1];
buf[1] = '\0';
SizedString<128> path;
int i = atoi(buf) + 1;
// TODO figure out what this is
if (stage == "t_tkm24" || stage == "t_tkm26") {
i = 2;
} else if (i < 1 || 6 <= i) {
i = 0;
}
path.sprintf("%s/dat/zev.dat", getArcNameByIndex(i, false));
sZevStage = getDataFromMsbArc(i, path, false);
}
void *dMessage_c::sZev0;
void *dMessage_c::sZevStage;
void *dMessage_c::getZev0Internal() {
UnkTextThing *thing = UnkTextThing::getInstance();
void *overrideZev = thing->getOverrideZev0();
if (overrideZev != nullptr && thing->getShouldOverrideData() == true) {
return overrideZev;
}
return sZev0;
}
void *dMessage_c::getZevStageInternal() {
UnkTextThing *thing = UnkTextThing::getInstance();
void *overrideZev = thing->getOverrideStageZev();
if (overrideZev != nullptr && thing->getShouldOverrideData() == true) {
return overrideZev;
}
return sZevStage;
}
s32 dMessage_c::getArcIndexForFile(const char *fileName) {
char buf[2];
buf[0] = fileName[0];
buf[1] = '\0';
return atoi(buf);
}