#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 #include 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 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 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 // }; 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); }