#include "Runtime/MP1/CFrontEndUI.hpp" #include #include #include #include "NESEmulator/CNESEmulator.hpp" #include "Runtime/CArchitectureMessage.hpp" #include "Runtime/CArchitectureQueue.hpp" #include "Runtime/CDependencyGroup.hpp" #include "Runtime/CDvdFile.hpp" #include "Runtime/CGameState.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Audio/CAudioGroupSet.hpp" #include "Runtime/Audio/CSfxManager.hpp" #include "Runtime/Graphics/CMoviePlayer.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" #include "Runtime/GuiSys/CGuiModel.hpp" #include "Runtime/GuiSys/CGuiSliderGroup.hpp" #include "Runtime/GuiSys/CGuiTableGroup.hpp" #include "Runtime/GuiSys/CGuiTextPane.hpp" #include "Runtime/GuiSys/CGuiWidgetDrawParms.hpp" #include "Runtime/GuiSys/CStringTable.hpp" #include "Runtime/Input/RumbleFxTable.hpp" #include "Runtime/MP1/CQuitGameScreen.hpp" #include "Runtime/MP1/CSaveGameScreen.hpp" #include "Runtime/MP1/CSlideShow.hpp" #include "Runtime/MP1/MP1.hpp" namespace urde::MP1 { namespace { #define FE_USE_SECONDS_IN_ELAPSED 1 /* Music volume constants */ constexpr float FE1_VOL = 0.7421875f; constexpr float FE2_VOL = 0.7421875f; /* L/R Stereo transition cues */ constexpr std::array, 3> FETransitionBackSFX{{ {SFXfnt_transfore_00L, SFXfnt_transfore_00R}, {SFXfnt_transfore_01L, SFXfnt_transfore_01R}, {SFXfnt_transfore_02L, SFXfnt_transfore_02R}, }}; constexpr std::array, 3> FETransitionForwardSFX{{ {SFXfnt_transback_00L, SFXfnt_transback_00R}, {SFXfnt_transback_01L, SFXfnt_transback_01R}, {SFXfnt_transback_02L, SFXfnt_transback_02R}, }}; struct FEMovie { const char* path; bool loop; }; constexpr std::array FEMovies{{ {"Video/00_first_start.thp", false}, {"Video/01_startloop.thp", true}, {"Video/02_start_fileselect_A.thp", false}, {"Video/03_fileselectloop.thp", true}, {"Video/04_fileselect_playgame_A.thp", false}, {"Video/06_fileselect_GBA.thp", false}, {"Video/07_GBAloop.thp", true}, {"Video/08_GBA_fileselect.thp", false}, {"Video/08_GBA_fileselect.thp", false}, }}; constexpr SObjectTag g_DefaultWorldTag = {FOURCC('MLVL'), 0x158efe17}; constexpr std::array AudioFadeTimeA{ 0.44f, 5.41f, 3.41f, }; constexpr std::array AudioFadeTimeB{ 4.2f, 6.1f, 6.1f, }; constexpr std::array NextLinkUI{ CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::ConnectSocket, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::PressStartAndSelect, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::BeginLink, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Linking, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Empty, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::TurnOffGBA, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Complete, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::InsertPak, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Empty, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Empty, }; constexpr std::array PrevLinkUI{ CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Cancelled, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Cancelled, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Cancelled, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Cancelled, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Empty, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Cancelled, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Empty, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Cancelled, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Empty, CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EUIType::Empty, }; } // Anonymous namespace void CFrontEndUI::PlayAdvanceSfx() { CSfxManager::SfxStart(SFXfnt_advance_L, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); CSfxManager::SfxStart(SFXfnt_advance_R, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } CFrontEndUI::SNewFileSelectFrame::SNewFileSelectFrame(CSaveGameScreen* sui, u32 rnd, CFrontEndUITouchBar& touchBar) : x0_rnd(rnd), x4_saveUI(sui), m_touchBar(touchBar) { x10_frme = g_SimplePool->GetObj("FRME_NewFileSelect"); } void CFrontEndUI::SNewFileSelectFrame::FinishedLoading() { x1c_loadedFrame->Reset(); x1c_loadedFrame->SetAspectConstraint(1.78f); x20_tablegroup_fileselect = static_cast(x1c_loadedFrame->FindWidget("tablegroup_fileselect")); x24_model_erase = static_cast(x1c_loadedFrame->FindWidget("model_erase")); xf8_model_erase_position = x24_model_erase->GetLocalPosition(); // TODO: Implement language menu auto langPair = FindTextPanePair(x1c_loadedFrame, "textpane_lang"); if (langPair.x0_panes[0] != nullptr) { langPair.x0_panes[0]->SetIsSelectable(false); langPair.x0_panes[0]->SetIsVisible(false); langPair.x0_panes[1]->SetIsSelectable(false); langPair.x0_panes[1]->SetIsVisible(false); } x28_textpane_erase = FindTextPanePair(x1c_loadedFrame, "textpane_erase"); x38_textpane_gba = FindTextPanePair(x1c_loadedFrame, "textpane_gba"); x30_textpane_cheats = FindTextPanePair(x1c_loadedFrame, "textpane_cheats"); x48_textpane_popupadvance = FindTextPanePair(x1c_loadedFrame, "textpane_popupadvance"); x50_textpane_popupcancel = FindTextPanePair(x1c_loadedFrame, "textpane_popupcancel"); x58_textpane_popupextra = FindTextPanePair(x1c_loadedFrame, "textpane_popupextra"); x40_tablegroup_popup = static_cast(x1c_loadedFrame->FindWidget("tablegroup_popup")); x44_model_dash7 = static_cast(x1c_loadedFrame->FindWidget("model_dash7")); x60_textpane_cancel = static_cast(x1c_loadedFrame->FindWidget("textpane_cancel")); FindAndSetPairText(x1c_loadedFrame, "textpane_title", g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 97 : 92)); CGuiTextPane* proceed = static_cast(x1c_loadedFrame->FindWidget("textpane_proceed")); if (proceed) proceed->TextSupport().SetText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 85 : 79)); x40_tablegroup_popup->SetIsVisible(false); x40_tablegroup_popup->SetIsActive(false); x40_tablegroup_popup->SetVertical(false); CGuiWidget* worker = x40_tablegroup_popup->GetWorkerWidget(2); worker->SetIsSelectable(false); worker->SetVisibility(false, ETraversalMode::Children); x20_tablegroup_fileselect->SetMenuAdvanceCallback([this](CGuiTableGroup* caller) { DoFileMenuAdvance(caller); }); x20_tablegroup_fileselect->SetMenuSelectionChangeCallback( [this](CGuiTableGroup* caller, int oldSel) { DoSelectionChange(caller, oldSel); }); x20_tablegroup_fileselect->SetMenuCancelCallback([this](CGuiTableGroup* caller) { DoFileMenuCancel(caller); }); x40_tablegroup_popup->SetMenuAdvanceCallback([this](CGuiTableGroup* caller) { DoPopupAdvance(caller); }); x40_tablegroup_popup->SetMenuSelectionChangeCallback( [this](CGuiTableGroup* caller, int oldSel) { DoSelectionChange(caller, oldSel); }); x40_tablegroup_popup->SetMenuCancelCallback([this](CGuiTableGroup* caller) { DoPopupCancel(caller); }); for (size_t i = 0; i < x64_fileSelections.size(); ++i) { x64_fileSelections[i] = FindFileSelectOption(x1c_loadedFrame, int(i)); } x104_rowPitch = (x64_fileSelections[1].x0_base->GetLocalPosition() - x64_fileSelections[0].x0_base->GetLocalPosition()).z(); } bool CFrontEndUI::SNewFileSelectFrame::PumpLoad() { if (x1c_loadedFrame) return true; if (x10_frme.IsLoaded()) { if (x10_frme->GetIsFinishedLoading()) { x1c_loadedFrame = x10_frme.GetObj(); FinishedLoading(); return true; } } return false; } bool CFrontEndUI::SNewFileSelectFrame::IsTextDoneAnimating() const { if (x64_fileSelections[0].x28_curField != 4) return false; if (x64_fileSelections[1].x28_curField != 4) return false; if (x64_fileSelections[2].x28_curField != 4) return false; if (!x28_textpane_erase.x0_panes[0]->GetTextSupport().IsAnimationDone()) return false; return x38_textpane_gba.x0_panes[0]->GetTextSupport().IsAnimationDone(); } void CFrontEndUI::SNewFileSelectFrame::Update(float dt) { bool saveReady = x4_saveUI->GetUIType() == CSaveGameScreen::EUIType::SaveReady; if (saveReady != x10c_saveReady) { if (saveReady) { ClearFrameContents(); } else if (x8_subMenu != ESubMenu::Root) { ResetFrame(); DeactivateEraseGamePopup(); DeactivateNewGamePopup(); x8_subMenu = ESubMenu::Root; } x10c_saveReady = saveReady; } if (x10c_saveReady) SetupFrameContents(); x1c_loadedFrame->Update(dt); } CFrontEndUI::SNewFileSelectFrame::EAction CFrontEndUI::SNewFileSelectFrame::ProcessUserInput(const CFinalInput& input, CFrontEndUITouchBar::EAction tbAction) { xc_action = EAction::None; if (x8_subMenu != ESubMenu::EraseGamePopup) x4_saveUI->ProcessUserInput(input); if (IsTextDoneAnimating()) x108_curTime = std::min(0.5f, x108_curTime + input.DeltaTime()); if (x108_curTime < 0.5f) return xc_action; if (x10c_saveReady) { x1c_loadedFrame->ProcessUserInput(input); if (x8_subMenu == ESubMenu::Root || x8_subMenu == ESubMenu::EraseGame) { if (m_touchBar.GetPhase() != CFrontEndUITouchBar::EPhase::FileSelect) HandleActiveChange(x20_tablegroup_fileselect); if (tbAction >= CFrontEndUITouchBar::EAction::FileA && tbAction <= CFrontEndUITouchBar::EAction::ImageGallery) { x20_tablegroup_fileselect->SetUserSelection(int(tbAction) - int(CFrontEndUITouchBar::EAction::FileA)); HandleActiveChange(x20_tablegroup_fileselect); DoFileMenuAdvance(x20_tablegroup_fileselect); } else if (tbAction == CFrontEndUITouchBar::EAction::Back) { DoFileMenuCancel(x20_tablegroup_fileselect); } } else if (x8_subMenu == ESubMenu::EraseGamePopup) { if (m_touchBar.GetPhase() != CFrontEndUITouchBar::EPhase::EraseBack) HandleActiveChange(x40_tablegroup_popup); if (tbAction != CFrontEndUITouchBar::EAction::None) { if (tbAction == CFrontEndUITouchBar::EAction::Confirm) x40_tablegroup_popup->SetUserSelection(1); else x40_tablegroup_popup->SetUserSelection(0); HandleActiveChange(x40_tablegroup_popup); DoPopupAdvance(x40_tablegroup_popup); } } else if (x8_subMenu == ESubMenu::NewGamePopup) { if (m_touchBar.GetPhase() != CFrontEndUITouchBar::EPhase::StartOptions) HandleActiveChange(x40_tablegroup_popup); if (tbAction == CFrontEndUITouchBar::EAction::Back) { DoPopupCancel(x40_tablegroup_popup); } else if (tbAction != CFrontEndUITouchBar::EAction::None) { if (tbAction == CFrontEndUITouchBar::EAction::Options) x40_tablegroup_popup->SetUserSelection(1); else if (tbAction == CFrontEndUITouchBar::EAction::Start || tbAction == CFrontEndUITouchBar::EAction::Hard) x40_tablegroup_popup->SetUserSelection(0); else if (tbAction == CFrontEndUITouchBar::EAction::Normal) x40_tablegroup_popup->SetUserSelection(2); HandleActiveChange(x40_tablegroup_popup); DoPopupAdvance(x40_tablegroup_popup); } } } if (x10d_needsEraseToggle) { if (x40_tablegroup_popup->GetIsActive()) DeactivateEraseGamePopup(); else ActivateEraseGamePopup(); x10d_needsEraseToggle = false; } if (x10e_needsNewToggle) { if (x40_tablegroup_popup->GetIsActive()) DeactivateNewGamePopup(); else ActivateNewGamePopup(); x10e_needsNewToggle = false; } return xc_action; } void CFrontEndUI::SNewFileSelectFrame::Draw() const { if (x1c_loadedFrame && x10c_saveReady) x1c_loadedFrame->Draw(CGuiWidgetDrawParms::Default); } void CFrontEndUI::SNewFileSelectFrame::HandleActiveChange(CGuiTableGroup* active) { if (!active) return; active->SetColors(zeus::skWhite, zeus::CColor{0.627450f, 0.627450f, 0.627450f, 0.784313f}); if (active == x20_tablegroup_fileselect) { x24_model_erase->SetLocalTransform(zeus::CTransform::Translate( zeus::CVector3f{0.f, 0.f, active->GetUserSelection() * x104_rowPitch} + xf8_model_erase_position)); /* Set Touch Bar contents here */ std::array tbDetails{}; for (size_t i = 0; i < tbDetails.size(); ++i) { if (const CGameState::GameFileStateInfo* data = x4_saveUI->GetGameData(int(i))) { tbDetails[i].state = data->x20_hardMode ? CFrontEndUITouchBar::EFileState::Hard : CFrontEndUITouchBar::EFileState::Normal; tbDetails[i].percent = data->x18_itemPercent; } } m_touchBar.SetFileSelectPhase(tbDetails.data(), x8_subMenu == ESubMenu::EraseGame, CSlideShow::SlideShowGalleryFlags()); } else if (active == x40_tablegroup_popup) { if (x8_subMenu == ESubMenu::EraseGamePopup) m_touchBar.SetPhase(CFrontEndUITouchBar::EPhase::EraseBack); else if (x8_subMenu == ESubMenu::NewGamePopup) m_touchBar.SetStartOptionsPhase(g_GameState->SystemOptions().GetPlayerBeatNormalMode()); else m_touchBar.SetPhase(CFrontEndUITouchBar::EPhase::None); } if (x8_subMenu == ESubMenu::Root || x8_subMenu == ESubMenu::NewGamePopup) x24_model_erase->SetIsVisible(false); else x24_model_erase->SetIsVisible(true); } void CFrontEndUI::SNewFileSelectFrame::DeactivateEraseGamePopup() { x40_tablegroup_popup->SetIsActive(false); x40_tablegroup_popup->SetIsVisible(false); x20_tablegroup_fileselect->SetIsActive(true); HandleActiveChange(x20_tablegroup_fileselect); x64_fileSelections[x20_tablegroup_fileselect->GetUserSelection()].x0_base->SetColor(zeus::skWhite); } void CFrontEndUI::SNewFileSelectFrame::ActivateEraseGamePopup() { x40_tablegroup_popup->SetIsActive(true); x40_tablegroup_popup->SetIsVisible(true); x40_tablegroup_popup->SetLocalTransform( zeus::CTransform::Translate(0.f, 0.f, x20_tablegroup_fileselect->GetUserSelection() * x104_rowPitch) * x40_tablegroup_popup->GetTransform()); x20_tablegroup_fileselect->SetIsActive(false); x8_subMenu = ESubMenu::EraseGamePopup; HandleActiveChange(x40_tablegroup_popup); x48_textpane_popupadvance.SetPairText( g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 95 : 89)); x50_textpane_popupcancel.SetPairText( g_MainStringTable->GetString(38)); // This string is unmodified in PAL/NTSCJ/Trilogy x64_fileSelections[x20_tablegroup_fileselect->GetUserSelection()].x0_base->SetColor(zeus::CColor{1.f, 1.f, 1.f, 0.f}); x44_model_dash7->SetVisibility(false, ETraversalMode::Children); } void CFrontEndUI::SNewFileSelectFrame::DeactivateNewGamePopup() { x40_tablegroup_popup->SetIsActive(false); x40_tablegroup_popup->SetIsVisible(false); x20_tablegroup_fileselect->SetIsActive(true); CGuiWidget* worker = x40_tablegroup_popup->GetWorkerWidget(2); worker->SetIsSelectable(false); worker->SetVisibility(false, ETraversalMode::Children); x44_model_dash7->SetVisibility(false, ETraversalMode::Children); HandleActiveChange(x20_tablegroup_fileselect); x64_fileSelections[x20_tablegroup_fileselect->GetUserSelection()].x0_base->SetColor(zeus::skWhite); x60_textpane_cancel->TextSupport().SetText(u""); } void CFrontEndUI::SNewFileSelectFrame::ActivateNewGamePopup() { x40_tablegroup_popup->SetIsActive(true); x40_tablegroup_popup->SetIsVisible(true); x40_tablegroup_popup->SetUserSelection(0); x40_tablegroup_popup->SetLocalTransform( zeus::CTransform::Translate(0.f, 0.f, x20_tablegroup_fileselect->GetUserSelection() * x104_rowPitch) * x40_tablegroup_popup->GetTransform()); x20_tablegroup_fileselect->SetIsActive(false); x8_subMenu = ESubMenu::NewGamePopup; HandleActiveChange(x40_tablegroup_popup); x64_fileSelections[x20_tablegroup_fileselect->GetUserSelection()].x0_base->SetColor(zeus::CColor{1.f, 1.f, 1.f, 0.f}); PlayAdvanceSfx(); if (g_GameState->SystemOptions().GetPlayerBeatNormalMode()) { x48_textpane_popupadvance.SetPairText( g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 102 : 96)); x50_textpane_popupcancel.SetPairText( g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 94 : 88)); x58_textpane_popupextra.SetPairText( g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 101 : 95)); CGuiWidget* worker = x40_tablegroup_popup->GetWorkerWidget(2); worker->SetIsSelectable(true); worker->SetVisibility(true, ETraversalMode::Children); x44_model_dash7->SetVisibility(true, ETraversalMode::Children); } else { x48_textpane_popupadvance.SetPairText( g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 67 : 61)); x50_textpane_popupcancel.SetPairText( g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 94 : 88)); x44_model_dash7->SetVisibility(false, ETraversalMode::Children); } x60_textpane_cancel->TextSupport().SetText( g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76)); } void CFrontEndUI::SNewFileSelectFrame::ResetFrame() { x8_subMenu = ESubMenu::Root; x38_textpane_gba.x0_panes[0]->SetIsSelectable(true); x38_textpane_gba.x0_panes[0]->TextSupport().SetFontColor(zeus::skWhite); x30_textpane_cheats.x0_panes[0]->SetIsSelectable(true); x30_textpane_cheats.x0_panes[0]->TextSupport().SetFontColor(zeus::skWhite); ClearFrameContents(); for (int i = 2; i >= 0; --i) x20_tablegroup_fileselect->GetWorkerWidget(i)->SetIsSelectable(true); x60_textpane_cancel->TextSupport().SetText(u""); } void CFrontEndUI::SNewFileSelectFrame::ActivateErase() { x8_subMenu = ESubMenu::EraseGame; x28_textpane_erase.x0_panes[0]->SetIsSelectable(false); zeus::CColor color = zeus::skGrey; color.a() = 0.5f; x28_textpane_erase.x0_panes[0]->TextSupport().SetFontColor(color); x38_textpane_gba.x0_panes[0]->TextSupport().SetFontColor(color); x30_textpane_cheats.x0_panes[0]->TextSupport().SetFontColor(color); x38_textpane_gba.x0_panes[0]->SetIsSelectable(false); x30_textpane_cheats.x0_panes[0]->SetIsSelectable(false); for (int i = 2; i >= 0; --i) { SFileMenuOption& fileOpt = x64_fileSelections[i]; if (x4_saveUI->GetGameData(i)) { fileOpt.x0_base->SetIsSelectable(true); x20_tablegroup_fileselect->SetUserSelection(i); } else { fileOpt.x0_base->SetIsSelectable(false); } } x60_textpane_cancel->TextSupport().SetText( g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76)); HandleActiveChange(x20_tablegroup_fileselect); } void CFrontEndUI::SNewFileSelectFrame::ClearFrameContents() { x108_curTime = 0.f; bool hasSave = false; for (size_t i = 0; i < x64_fileSelections.size(); ++i) { if (x4_saveUI->GetGameData(int(i))) { hasSave = true; } SFileMenuOption& option = x64_fileSelections[i]; option.x2c_chRate = SFileMenuOption::ComputeRandom(); option.x28_curField = -1; for (auto& panePair : option.x4_textpanes) { panePair.SetPairText(u""); } } StartTextAnimating(x28_textpane_erase.x0_panes[0], g_MainStringTable->GetString(38), 60.f); // This string is unmodified in PAL/NTSCJ/Trilogy StartTextAnimating(x38_textpane_gba.x0_panes[0], g_MainStringTable->GetString(37), 60.f); // This string is unmodified in PAL/NTSCJ/Trilogy StartTextAnimating(x30_textpane_cheats.x0_panes[0], g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 96 : 90), 60.f); StartTextAnimating(x28_textpane_erase.x0_panes[1], g_MainStringTable->GetString(38), 60.f); // This string is unmodified in PAL/NTSCJ/Trilogy StartTextAnimating(x38_textpane_gba.x0_panes[1], g_MainStringTable->GetString(37), 60.f); // This string is unmodified in PAL/NTSCJ/Trilogy StartTextAnimating(x30_textpane_cheats.x0_panes[1], g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 96 : 90), 60.f); if (hasSave) { x28_textpane_erase.x0_panes[0]->SetIsSelectable(true); x28_textpane_erase.x0_panes[0]->TextSupport().SetFontColor(zeus::skWhite); } else { x28_textpane_erase.x0_panes[0]->SetIsSelectable(false); zeus::CColor color = zeus::skGrey; color.a() = 0.5f; x28_textpane_erase.x0_panes[0]->TextSupport().SetFontColor(color); } x20_tablegroup_fileselect->SetUserSelection(0); CGuiTextPane* cheats = static_cast(x20_tablegroup_fileselect->GetWorkerWidget(5)); if (CSlideShow::SlideShowGalleryFlags()) { cheats->SetIsSelectable(true); x30_textpane_cheats.x0_panes[0]->TextSupport().SetFontColor(zeus::skWhite); } else { cheats->SetIsSelectable(false); zeus::CColor color = zeus::skGrey; color.a() = 0.5f; x30_textpane_cheats.x0_panes[0]->TextSupport().SetFontColor(color); } HandleActiveChange(x20_tablegroup_fileselect); } std::u16string GetTimeString(const CGameState::GameFileStateInfo* data) { if (data) { auto pt = std::div(data->x0_playTime, 3600); #if FE_USE_SECONDS_IN_ELAPSED return fmt::format(FMT_STRING(u"{:02d}:{:02d}:{:02d}"), pt.quot, pt.rem / 60, pt.rem % 60); #else return fmt::format(FMT_STRING(u"{:02d}:{:02d}"), pt.quot, pt.rem / 60); #endif } if (!g_Main->IsUSA() || g_Main->IsTrilogy()) return g_MainStringTable->GetString(53); return g_MainStringTable->GetString(52); } std::u16string GetElapsedString(const CGameState::GameFileStateInfo* data) { if (!g_Main->IsUSA() || g_Main->IsTrilogy()) return g_MainStringTable->GetString(data ? 55 : 54); return std::u16string(g_MainStringTable->GetString(data ? 54 : 53)); } void CFrontEndUI::SNewFileSelectFrame::SetupFrameContents() { for (size_t i = 0; i < x64_fileSelections.size(); ++i) { SFileMenuOption& option = x64_fileSelections[i]; if (option.x28_curField == 4) { continue; } SGuiTextPair* pair = (option.x28_curField == UINT32_MAX) ? nullptr : &option.x4_textpanes[option.x28_curField]; if (!pair || pair->x0_panes[0]->GetTextSupport().GetNumCharsPrinted() >= pair->x0_panes[0]->GetTextSupport().GetNumCharsTotal()) { if (++option.x28_curField < 4) { std::u16string str; SGuiTextPair& populatePair = option.x4_textpanes[option.x28_curField]; const CGameState::GameFileStateInfo* data = x4_saveUI->GetGameData(int(i)); switch (option.x28_curField) { case 0: // Completion percent if (data) { int strIdx = (data->x20_hardMode ? 106 : 39); if ((!g_Main->IsUSA() || g_Main->IsTrilogy())) strIdx = (strIdx == 106 ? 100 : 40); std::u16string fileStr = g_MainStringTable->GetString(strIdx + int(i)); str = fileStr + fmt::format(FMT_STRING(u" {:02d}%"), data->x18_itemPercent); break; } str = g_MainStringTable->GetString(36); break; case 1: // World name if (data) { if (g_MemoryCardSys->HasSaveWorldMemory(data->x8_mlvlId)) { const CSaveWorldMemory& wldMem = g_MemoryCardSys->GetSaveWorldMemory(data->x8_mlvlId); str = wldMem.GetFrontEndName(); } break; } str = g_MainStringTable->GetString(51 + int(!g_Main->IsUSA() || g_Main->IsTrilogy())); break; case 2: #if FE_USE_SECONDS_IN_ELAPSED if (!g_Main->IsUSA() || g_Main->IsTrilogy()) { str = GetElapsedString(data); } else { str = GetTimeString(data); } #else if (!g_Main->IsUSA() || g_Main->IsTrilogy()) { str = GetElapsedString(data); } else { str = GetTimeString(data); } #endif break; case 3: #if FE_USE_SECONDS_IN_ELAPSED if (!g_Main->IsUSA() || g_Main->IsTrilogy()) { str = GetTimeString(data); } else { str = std::u16string(u" ") + GetElapsedString(data); } #else if (!g_Main->IsUSA() || g_Main->IsTrilogy()) { str = GetTimeString(data); } else { str = std::u16string(u" ") + GetElapsedString(data); } #endif break; default: break; } StartTextAnimating(populatePair.x0_panes[0], str, option.x2c_chRate); StartTextAnimating(populatePair.x0_panes[1], str, option.x2c_chRate); } } } } void CFrontEndUI::SNewFileSelectFrame::DoPopupCancel(CGuiTableGroup* caller) { if (x8_subMenu == ESubMenu::EraseGamePopup) { CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); x8_subMenu = ESubMenu::EraseGame; x10d_needsEraseToggle = true; } else { CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); x8_subMenu = ESubMenu::Root; x10e_needsNewToggle = true; } } void CFrontEndUI::SNewFileSelectFrame::DoPopupAdvance(CGuiTableGroup* caller) { if (x8_subMenu == ESubMenu::EraseGamePopup) { if (x40_tablegroup_popup->GetUserSelection() == 1) { x4_saveUI->EraseGame(x20_tablegroup_fileselect->GetUserSelection()); ResetFrame(); } else x8_subMenu = ESubMenu::EraseGame; x10d_needsEraseToggle = true; } else { if (g_GameState->SystemOptions().GetPlayerBeatNormalMode()) { if (x40_tablegroup_popup->GetUserSelection() == 1) { PlayAdvanceSfx(); xc_action = EAction::GameOptions; return; } g_GameState->SetHardMode(!x40_tablegroup_popup->GetUserSelection()); x4_saveUI->StartGame(x20_tablegroup_fileselect->GetUserSelection()); } else { if (x40_tablegroup_popup->GetUserSelection() == 1) { PlayAdvanceSfx(); xc_action = EAction::GameOptions; return; } x4_saveUI->StartGame(x20_tablegroup_fileselect->GetUserSelection()); } } } void CFrontEndUI::SNewFileSelectFrame::DoFileMenuCancel(CGuiTableGroup* caller) { if (x8_subMenu == ESubMenu::EraseGame) { CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); ResetFrame(); } } void CFrontEndUI::SNewFileSelectFrame::DoSelectionChange(CGuiTableGroup* caller, int oldSel) { HandleActiveChange(caller); CSfxManager::SfxStart(SFXfnt_selection_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } void CFrontEndUI::SNewFileSelectFrame::DoFileMenuAdvance(CGuiTableGroup* caller) { int userSel = x20_tablegroup_fileselect->GetUserSelection(); if (userSel < 3) { if (x8_subMenu == ESubMenu::EraseGame) { if (x4_saveUI->GetGameData(userSel)) { PlayAdvanceSfx(); x10d_needsEraseToggle = true; } } else { if (x4_saveUI->GetGameData(userSel)) { m_touchBar.SetPhase(CFrontEndUITouchBar::EPhase::None); x4_saveUI->StartGame(userSel); } else x10e_needsNewToggle = true; } } else if (userSel == 3) { PlayAdvanceSfx(); ActivateErase(); } else if (userSel == 4) { xc_action = EAction::FusionBonus; } else if (userSel == 5) { xc_action = EAction::SlideShow; } } CFrontEndUI::SFileMenuOption CFrontEndUI::SNewFileSelectFrame::FindFileSelectOption(CGuiFrame* frame, int idx) { SFileMenuOption ret; ret.x0_base = frame->FindWidget(fmt::format(FMT_STRING("basewidget_file{}"), idx)); ret.x4_textpanes[0] = FindTextPanePair(frame, fmt::format(FMT_STRING("textpane_filename{}"), idx)); ret.x4_textpanes[1] = FindTextPanePair(frame, fmt::format(FMT_STRING("textpane_world{}"), idx)); ret.x4_textpanes[2] = FindTextPanePair(frame, fmt::format(FMT_STRING("textpane_playtime{}"), idx)); ret.x4_textpanes[3] = FindTextPanePair(frame, fmt::format(FMT_STRING("textpane_date{}"), idx)); return ret; } void CFrontEndUI::SNewFileSelectFrame::StartTextAnimating(CGuiTextPane* text, std::u16string_view str, float chRate) { text->TextSupport().SetText(str); text->TextSupport().SetTypeWriteEffectOptions(true, 0.1f, chRate); } CFrontEndUI::SFusionBonusFrame::SFusionBonusFrame(CFrontEndUITouchBar& touchBar) : m_touchBar(touchBar) { x4_gbaSupport = std::make_unique(); xc_gbaScreen = g_SimplePool->GetObj("FRME_GBAScreen"); x18_gbaLink = g_SimplePool->GetObj("FRME_GBALink"); } void CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::SetUIText(EUIType tp) { int instructions = -1; int yes = -1; int no = -1; bool cableVisible = false; bool circleGcVisible = false; bool circleGbaVisible = false; bool circleStartVisible = false; bool pakoutVisible = false; bool gbaScreenVisible = false; bool connectVisible = false; switch (tp) { case EUIType::InsertPak: instructions = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 73 : 67; // Insert Game Pak no = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76; yes = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 83 : 77; pakoutVisible = true; circleGbaVisible = true; break; case EUIType::ConnectSocket: instructions = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 68 : 62; // Connect socket no = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76; yes = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 83 : 77; cableVisible = true; circleGcVisible = true; circleGbaVisible = true; break; case EUIType::PressStartAndSelect: instructions = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 74 : 68; // Hold start and select no = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76; yes = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 83 : 77; cableVisible = true; circleStartVisible = true; gbaScreenVisible = true; break; case EUIType::BeginLink: instructions = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 75 : 69; // Begin link? no = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76; yes = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 83 : 77; cableVisible = true; gbaScreenVisible = true; break; case EUIType::TurnOffGBA: instructions = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 76 : 70; // Turn off GBA no = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76; yes = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 83 : 77; cableVisible = true; gbaScreenVisible = true; circleStartVisible = true; break; case EUIType::Linking: x4_gbaSupport->StartLink(); instructions = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 72 : 66; // Linking cableVisible = true; gbaScreenVisible = true; connectVisible = true; break; case EUIType::LinkFailed: instructions = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 69 : 63; // Link failed no = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76; yes = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 83 : 77; cableVisible = true; circleGcVisible = true; circleGbaVisible = true; circleStartVisible = true; gbaScreenVisible = true; break; case EUIType::LinkCompleteOrLinking: yes = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 83 : 77; instructions = x40_linkInProgress + (g_Main->IsUSA() && !g_Main->IsTrilogy() ? 71 : 65); // Complete or linking cableVisible = true; gbaScreenVisible = true; break; case EUIType::Complete: case EUIType::Cancelled: default: break; } std::u16string instructionsStr; if (instructions != -1) instructionsStr = g_MainStringTable->GetString(instructions); xc_textpane_instructions.SetPairText(instructionsStr); std::u16string yesStr; if (yes != -1) yesStr = g_MainStringTable->GetString(yes); x14_textpane_yes->TextSupport().SetText(yesStr); std::u16string noStr; if (no != -1) noStr = g_MainStringTable->GetString(no); x18_textpane_no->TextSupport().SetText(noStr); x1c_model_gc->SetVisibility(true, ETraversalMode::Children); x20_model_gba->SetVisibility(true, ETraversalMode::Children); x24_model_cable->SetVisibility(cableVisible, ETraversalMode::Children); x28_model_circlegcport->SetVisibility(circleGcVisible, ETraversalMode::Children); x2c_model_circlegbaport->SetVisibility(circleGbaVisible, ETraversalMode::Children); x30_model_circlestartselect->SetVisibility(circleStartVisible, ETraversalMode::Children); x34_model_pakout->SetVisibility(pakoutVisible, ETraversalMode::Children); x38_model_gbascreen->SetVisibility(gbaScreenVisible, ETraversalMode::Children); x3c_model_connect->SetVisibility(connectVisible, ETraversalMode::Children); x0_uiType = tp; } CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::EAction CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::ProcessUserInput(const CFinalInput& input, bool linkInProgress, CFrontEndUITouchBar::EAction tbAction) { if (linkInProgress != x40_linkInProgress) { x40_linkInProgress = linkInProgress; SetUIText(x0_uiType); } switch (x0_uiType) { case EUIType::InsertPak: case EUIType::ConnectSocket: case EUIType::PressStartAndSelect: case EUIType::BeginLink: case EUIType::LinkFailed: case EUIType::LinkCompleteOrLinking: case EUIType::TurnOffGBA: if (input.PA() || input.PSpecialKey(boo::ESpecialKey::Enter) || input.PMouseButton(boo::EMouseButton::Primary) || tbAction == CFrontEndUITouchBar::EAction::Confirm) { PlayAdvanceSfx(); SetUIText(NextLinkUI[size_t(x0_uiType)]); } else if (input.PB() || input.PSpecialKey(boo::ESpecialKey::Esc) || tbAction == CFrontEndUITouchBar::EAction::Back) { const EUIType prevUi = PrevLinkUI[size_t(x0_uiType)]; if (prevUi == EUIType::Empty) { break; } CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); SetUIText(prevUi); } break; case EUIType::Linking: if (x4_gbaSupport->GetPhase() == CGBASupport::EPhase::Complete) { if (x4_gbaSupport->IsFusionLinked()) g_GameState->SystemOptions().SetPlayerLinkedFusion(true); if (x4_gbaSupport->IsFusionBeat()) g_GameState->SystemOptions().SetPlayerBeatFusion(true); if (x4_gbaSupport->IsFusionLinked()) { PlayAdvanceSfx(); SetUIText(EUIType::LinkCompleteOrLinking); } else { CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); SetUIText(EUIType::LinkFailed); } } else if (x4_gbaSupport->GetPhase() == CGBASupport::EPhase::Failed) { CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); SetUIText(EUIType::LinkFailed); } break; case EUIType::Complete: return EAction::Complete; case EUIType::Cancelled: return EAction::Cancelled; default: break; } return EAction::None; } void CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::Update(float dt) { x4_gbaSupport->Update(dt); x8_frme->Update(dt); } void CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::FinishedLoading() { x8_frme->SetAspectConstraint(1.78f); xc_textpane_instructions = FindTextPanePair(x8_frme, "textpane_instructions"); x14_textpane_yes = static_cast(x8_frme->FindWidget("textpane_yes")); x18_textpane_no = static_cast(x8_frme->FindWidget("textpane_no")); x1c_model_gc = static_cast(x8_frme->FindWidget("model_gc")); x20_model_gba = static_cast(x8_frme->FindWidget("model_gba")); x24_model_cable = static_cast(x8_frme->FindWidget("model_cable")); x28_model_circlegcport = static_cast(x8_frme->FindWidget("model_circlegcport")); x2c_model_circlegbaport = static_cast(x8_frme->FindWidget("model_circlegbaport")); x30_model_circlestartselect = static_cast(x8_frme->FindWidget("model_circlestartselect")); x34_model_pakout = static_cast(x8_frme->FindWidget("model_pakout")); x38_model_gbascreen = static_cast(x8_frme->FindWidget("model_gbascreen")); x3c_model_connect = static_cast(x8_frme->FindWidget("model_connect")); SetUIText(EUIType::InsertPak); } void CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::Draw() { x8_frme->Draw(CGuiWidgetDrawParms::Default); } CFrontEndUI::SFusionBonusFrame::SGBALinkFrame::SGBALinkFrame(CGuiFrame* linkFrame, CGBASupport* support, bool linkInProgress) : x4_gbaSupport(support), x8_frme(linkFrame), x40_linkInProgress(linkInProgress) { support->InitializeSupport(); FinishedLoading(); } void CFrontEndUI::SFusionBonusFrame::FinishedLoading() { x24_loadedFrame->SetAspectConstraint(1.78f); x28_tablegroup_options = static_cast(x24_loadedFrame->FindWidget("tablegroup_options")); x2c_tablegroup_fusionsuit = static_cast(x24_loadedFrame->FindWidget("tablegroup_fusionsuit")); x30_textpane_instructions = FindTextPanePair(x24_loadedFrame, "textpane_instructions"); FindAndSetPairText(x24_loadedFrame, "textpane_nes", g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 66 : 60)); FindAndSetPairText(x24_loadedFrame, "textpane_fusionsuit", g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 63 : 57)); FindAndSetPairText(x24_loadedFrame, "textpane_fusionsuitno", g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 65 : 59)); FindAndSetPairText(x24_loadedFrame, "textpane_fusionsuityes", g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 64 : 58)); FindAndSetPairText(x24_loadedFrame, "textpane_title", g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 100 : 94)); static_cast(x24_loadedFrame->FindWidget("textpane_proceed")) ->TextSupport() .SetText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 85 : 79)); static_cast(x24_loadedFrame->FindWidget("textpane_cancel")) ->TextSupport() .SetText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76)); x2c_tablegroup_fusionsuit->SetIsActive(false); x2c_tablegroup_fusionsuit->SetIsVisible(false); x2c_tablegroup_fusionsuit->SetVertical(false); x2c_tablegroup_fusionsuit->SetUserSelection(g_GameState->SystemOptions().GetPlayerFusionSuitActive()); SetTableColors(x28_tablegroup_options); SetTableColors(x2c_tablegroup_fusionsuit); x28_tablegroup_options->SetMenuAdvanceCallback([this](CGuiTableGroup* caller) { DoAdvance(caller); }); x28_tablegroup_options->SetMenuSelectionChangeCallback( [this](CGuiTableGroup* caller, int oldSel) { DoSelectionChange(caller, oldSel); }); x28_tablegroup_options->SetMenuCancelCallback([this](CGuiTableGroup* caller) { DoCancel(caller); }); x2c_tablegroup_fusionsuit->SetMenuSelectionChangeCallback( [this](CGuiTableGroup* caller, int oldSel) { DoSelectionChange(caller, oldSel); }); } bool CFrontEndUI::SFusionBonusFrame::PumpLoad() { if (x24_loadedFrame) return true; if (!xc_gbaScreen.IsLoaded()) return false; if (!x18_gbaLink.IsLoaded()) return false; if (!x4_gbaSupport->IsReady()) return false; if (!xc_gbaScreen->GetIsFinishedLoading()) return false; x24_loadedFrame = xc_gbaScreen.GetObj(); FinishedLoading(); return true; } void CFrontEndUI::SFusionBonusFrame::SetTableColors(CGuiTableGroup* tbgp) const { tbgp->SetColors(zeus::skWhite, zeus::CColor{0.627450f, 0.627450f, 0.627450f, 0.784313f}); } void CFrontEndUI::SFusionBonusFrame::Update(float dt, CSaveGameScreen* saveUI) { bool doDraw = !saveUI || saveUI->GetUIType() == CSaveGameScreen::EUIType::SaveReady; if (doDraw != x38_lastDoDraw) { x38_lastDoDraw = doDraw; ResetCompletionFlags(); } if (x0_gbaLinkFrame) x0_gbaLinkFrame->Update(dt); else if (x24_loadedFrame) x24_loadedFrame->Update(dt); bool showFusionSuit = (g_GameState->SystemOptions().GetPlayerLinkedFusion() && g_GameState->SystemOptions().GetPlayerBeatNormalMode()) || m_gbaOverride; bool showFusionSuitProceed = showFusionSuit && x28_tablegroup_options->GetUserSelection() == 1; x2c_tablegroup_fusionsuit->SetIsActive(showFusionSuitProceed); x2c_tablegroup_fusionsuit->SetIsVisible(showFusionSuitProceed); x24_loadedFrame->FindWidget("textpane_proceed")->SetIsVisible(showFusionSuitProceed); std::u16string instructionStr; x30_textpane_instructions.x0_panes[0]->TextSupport().SetFontColor(zeus::skWhite); if (x28_tablegroup_options->GetUserSelection() == 1) { /* Fusion Suit */ if (x3a_mpNotComplete) instructionStr = g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 80 : 74); // MP not complete else if (!showFusionSuit) instructionStr = g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 78 : 72); // To enable fusion suit } else { /* NES Metroid */ if (x39_fusionNotComplete) instructionStr = g_MainStringTable->GetString( (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 79 : 73); // You have not completed fusion else if (!g_GameState->SystemOptions().GetPlayerBeatFusion()) instructionStr = g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 77 : 71); // To play NES Metroid } x30_textpane_instructions.SetPairText(instructionStr); } CFrontEndUI::SFusionBonusFrame::EAction CFrontEndUI::SFusionBonusFrame::ProcessUserInput(const CFinalInput& input, CSaveGameScreen* sui, CFrontEndUITouchBar::EAction tbAction) { x8_action = EAction::None; if (sui) sui->ProcessUserInput(input); if (x38_lastDoDraw) { if (x0_gbaLinkFrame) { if (m_touchBar.GetPhase() != CFrontEndUITouchBar::EPhase::ProceedBack) m_touchBar.SetPhase(CFrontEndUITouchBar::EPhase::ProceedBack); SGBALinkFrame::EAction action = x0_gbaLinkFrame->ProcessUserInput(input, sui, tbAction); if (action != SGBALinkFrame::EAction::None) { x0_gbaLinkFrame.reset(); if (action == SGBALinkFrame::EAction::Complete) { if (x28_tablegroup_options->GetUserSelection() == 0 && !g_GameState->SystemOptions().GetPlayerBeatFusion()) x39_fusionNotComplete = true; else if (sui) sui->SaveNESState(); } } } else if (x24_loadedFrame) { CFinalInput useInput = input; if (input.PZ() || input.PKey('\t')) { useInput.x2d_b28_PA = true; m_gbaOverride = true; } bool showFusionSuit = (g_GameState->SystemOptions().GetPlayerLinkedFusion() && g_GameState->SystemOptions().GetPlayerBeatNormalMode()) || m_gbaOverride; if (m_touchBar.GetPhase() != CFrontEndUITouchBar::EPhase::FusionBonus) { m_touchBar.SetFusionBonusPhase(showFusionSuit && g_GameState->SystemOptions().GetPlayerFusionSuitActive()); } x24_loadedFrame->ProcessUserInput(useInput); switch (tbAction) { case CFrontEndUITouchBar::EAction::NESMetroid: x28_tablegroup_options->SetUserSelection(0); ResetCompletionFlags(); SetTableColors(x28_tablegroup_options); DoAdvance(x28_tablegroup_options); break; case CFrontEndUITouchBar::EAction::FusionSuit: x28_tablegroup_options->SetUserSelection(1); ResetCompletionFlags(); SetTableColors(x28_tablegroup_options); if (showFusionSuit) { if (x2c_tablegroup_fusionsuit->GetUserSelection() == 1) { x2c_tablegroup_fusionsuit->SetUserSelection(0); DoSelectionChange(x2c_tablegroup_fusionsuit, 0); } else { x2c_tablegroup_fusionsuit->SetUserSelection(1); DoSelectionChange(x2c_tablegroup_fusionsuit, 1); } } else { DoAdvance(x28_tablegroup_options); } break; case CFrontEndUITouchBar::EAction::Back: DoCancel(x28_tablegroup_options); break; default: break; } } } return x8_action; } void CFrontEndUI::SFusionBonusFrame::Draw() const { if (!x38_lastDoDraw) return; if (x0_gbaLinkFrame) x0_gbaLinkFrame->Draw(); else if (x24_loadedFrame) x24_loadedFrame->Draw(CGuiWidgetDrawParms::Default); } void CFrontEndUI::SFusionBonusFrame::DoCancel(CGuiTableGroup* caller) { x8_action = EAction::GoBack; x28_tablegroup_options->SetUserSelection(0); x2c_tablegroup_fusionsuit->SetIsActive(false); x30_textpane_instructions.SetPairText(u""); SetTableColors(x28_tablegroup_options); } void CFrontEndUI::SFusionBonusFrame::DoSelectionChange(CGuiTableGroup* caller, int oldSel) { if (caller == x28_tablegroup_options) { CSfxManager::SfxStart(SFXfnt_selection_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); ResetCompletionFlags(); } else { CSfxManager::SfxStart(SFXfnt_enum_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); bool fusionActive = x2c_tablegroup_fusionsuit->GetUserSelection() == 1; g_GameState->SystemOptions().SetPlayerFusionSuitActive(fusionActive); g_GameState->GetPlayerState()->SetIsFusionEnabled(fusionActive); m_touchBar.SetFusionBonusPhase(g_GameState->SystemOptions().GetPlayerFusionSuitActive()); } SetTableColors(caller); } void CFrontEndUI::SFusionBonusFrame::DoAdvance(CGuiTableGroup* caller) { switch (x28_tablegroup_options->GetUserSelection()) { case 1: /* Fusion Suit */ if (x3a_mpNotComplete || m_gbaOverride) { x3a_mpNotComplete = false; PlayAdvanceSfx(); } else if (g_GameState->SystemOptions().GetPlayerBeatNormalMode()) { if (g_GameState->SystemOptions().GetPlayerLinkedFusion()) return; x0_gbaLinkFrame = std::make_unique(x18_gbaLink.GetObj(), x4_gbaSupport.get(), false); PlayAdvanceSfx(); } else { x3a_mpNotComplete = true; CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } break; case 0: /* NES Metroid */ if (x39_fusionNotComplete && !m_gbaOverride) { x39_fusionNotComplete = false; PlayAdvanceSfx(); } else if (g_GameState->SystemOptions().GetPlayerBeatFusion() || m_gbaOverride) { // x8_action = EAction::None; CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); x8_action = EAction::PlayNESMetroid; } else { x0_gbaLinkFrame = std::make_unique(x18_gbaLink.GetObj(), x4_gbaSupport.get(), false); PlayAdvanceSfx(); } break; default: break; } } void CFrontEndUI::SGuiTextPair::SetPairText(std::u16string_view str) { x0_panes[0]->TextSupport().SetText(str); x0_panes[1]->TextSupport().SetText(str); } CFrontEndUI::SGuiTextPair CFrontEndUI::FindTextPanePair(CGuiFrame* frame, std::string_view name) { SGuiTextPair ret; ret.x0_panes[0] = static_cast(frame->FindWidget(name)); ret.x0_panes[1] = static_cast(frame->FindWidget(fmt::format(FMT_STRING("{}b"), name))); return ret; } void CFrontEndUI::FindAndSetPairText(CGuiFrame* frame, std::string_view name, std::u16string_view str) { CGuiTextPane* w1 = static_cast(frame->FindWidget(name)); w1->TextSupport().SetText(str); CGuiTextPane* w2 = static_cast(frame->FindWidget(fmt::format(FMT_STRING("{}b"), name))); w2->TextSupport().SetText(str); } void CFrontEndUI::SFrontEndFrame::FinishedLoading() { x14_loadedFrme->SetAspectConstraint(1.78f); x18_tablegroup_mainmenu = static_cast(x14_loadedFrme->FindWidget("tablegroup_mainmenu")); //TODO: HACK: Implement language menu so this isn't necessary if (g_Main->IsUSA() && !g_Main->IsTrilogy()) { x1c_gbaPair = FindTextPanePair(x14_loadedFrme, "textpane_gba"); } else { x1c_gbaPair = FindTextPanePair(x14_loadedFrme, "textpane_lang"); } x1c_gbaPair.SetPairText(g_MainStringTable->GetString(37)); //TODO: HACK: Implement language menu so this isn't necessary if (g_Main->IsUSA() && !g_Main->IsTrilogy()) { x24_cheatPair = FindTextPanePair(x14_loadedFrme, "textpane_cheats"); } else { x24_cheatPair = FindTextPanePair(x14_loadedFrme, "textpane_options"); } x24_cheatPair.SetPairText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 96 : 90)); FindAndSetPairText(x14_loadedFrme, "textpane_start", g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 67 : 61)); //TODO: HACK: Implement language menu so this isn't necessary FindAndSetPairText(x14_loadedFrme, (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? "textpane_options" : "textpane_gba", g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 94 : 88)); FindAndSetPairText(x14_loadedFrme, "textpane_title", g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 98 : 92)); CGuiTextPane* proceed = static_cast(x14_loadedFrme->FindWidget("textpane_proceed")); if (proceed) proceed->TextSupport().SetText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 85 : 79)); x18_tablegroup_mainmenu->SetMenuAdvanceCallback([this](CGuiTableGroup* caller) { DoAdvance(caller); }); x18_tablegroup_mainmenu->SetMenuSelectionChangeCallback( [this](CGuiTableGroup* caller, int oldSel) { DoSelectionChange(caller, oldSel); }); x18_tablegroup_mainmenu->SetMenuCancelCallback([this](CGuiTableGroup* caller) { DoCancel(caller); }); HandleActiveChange(x18_tablegroup_mainmenu); } bool CFrontEndUI::SFrontEndFrame::PumpLoad() { if (x14_loadedFrme) return true; if (x8_frme.IsLoaded()) { if (CGuiFrame* frme = x8_frme.GetObj()) { if (frme->GetIsFinishedLoading()) { x14_loadedFrme = frme; FinishedLoading(); return true; } } } return false; } void CFrontEndUI::SFrontEndFrame::Update(float dt) { CGuiTextPane* imageGallery = static_cast(x18_tablegroup_mainmenu->GetWorkerWidget(3)); if (CSlideShow::SlideShowGalleryFlags()) { imageGallery->SetIsSelectable(true); x24_cheatPair.x0_panes[0]->TextSupport().SetFontColor(zeus::skWhite); } else { imageGallery->SetIsSelectable(false); zeus::CColor color = zeus::skGrey; color.a() = 0.5f; x24_cheatPair.x0_panes[0]->TextSupport().SetFontColor(color); } x14_loadedFrme->Update(dt); } CFrontEndUI::SFrontEndFrame::EAction CFrontEndUI::SFrontEndFrame::ProcessUserInput(const CFinalInput& input, CFrontEndUITouchBar::EAction tbAction) { if (m_touchBar.GetPhase() != CFrontEndUITouchBar::EPhase::NoCardSelect) m_touchBar.SetNoCardSelectPhase(CSlideShow::SlideShowGalleryFlags()); x4_action = EAction::None; x14_loadedFrme->ProcessUserInput(input); switch (tbAction) { case CFrontEndUITouchBar::EAction::Start: x18_tablegroup_mainmenu->SetUserSelection(0); HandleActiveChange(x18_tablegroup_mainmenu); DoAdvance(x18_tablegroup_mainmenu); break; case CFrontEndUITouchBar::EAction::FusionBonus: x18_tablegroup_mainmenu->SetUserSelection(1); HandleActiveChange(x18_tablegroup_mainmenu); DoAdvance(x18_tablegroup_mainmenu); break; case CFrontEndUITouchBar::EAction::Options: x18_tablegroup_mainmenu->SetUserSelection(2); HandleActiveChange(x18_tablegroup_mainmenu); DoAdvance(x18_tablegroup_mainmenu); break; case CFrontEndUITouchBar::EAction::ImageGallery: x18_tablegroup_mainmenu->SetUserSelection(3); HandleActiveChange(x18_tablegroup_mainmenu); DoAdvance(x18_tablegroup_mainmenu); break; default: break; } return x4_action; } void CFrontEndUI::SFrontEndFrame::Draw() const { x14_loadedFrme->Draw(CGuiWidgetDrawParms::Default); } void CFrontEndUI::SFrontEndFrame::HandleActiveChange(CGuiTableGroup* active) { active->SetColors(zeus::skWhite, zeus::CColor{0.627450f, 0.627450f, 0.627450f, 0.784313f}); } void CFrontEndUI::SFrontEndFrame::DoCancel(CGuiTableGroup* caller) { /* Intentionally empty */ } void CFrontEndUI::SFrontEndFrame::DoSelectionChange(CGuiTableGroup* caller, int oldSel) { CSfxManager::SfxStart(SFXfnt_selection_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); HandleActiveChange(caller); } void CFrontEndUI::SFrontEndFrame::DoAdvance(CGuiTableGroup* caller) { switch (x18_tablegroup_mainmenu->GetUserSelection()) { case 0: CSfxManager::SfxStart(FETransitionForwardSFX[x0_rnd][0], 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); CSfxManager::SfxStart(FETransitionForwardSFX[x0_rnd][1], 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); x4_action = EAction::StartGame; break; case 1: x4_action = EAction::FusionBonus; break; case 2: PlayAdvanceSfx(); x4_action = EAction::GameOptions; break; case 3: PlayAdvanceSfx(); x4_action = EAction::SlideShow; break; default: break; } } CFrontEndUI::SFrontEndFrame::SFrontEndFrame(u32 rnd, CFrontEndUITouchBar& touchBar) : x0_rnd(rnd), m_touchBar(touchBar) { x8_frme = g_SimplePool->GetObj("FRME_FrontEndPL"); } CFrontEndUI::SNesEmulatorFrame::SNesEmulatorFrame() { x4_nesEmu = std::make_unique(); const SObjectTag* deface = g_ResFactory->GetResourceIdByName("FONT_Deface14B"); CGuiTextProperties props(false, true, EJustification::Left, EVerticalJustification::Center); xc_textSupport = std::make_unique(deface->id, props, zeus::skWhite, zeus::skBlack, zeus::skWhite, 0, 0, g_SimplePool, CGuiWidget::EGuiModelDrawFlags::Alpha); xc_textSupport->SetText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 103 : 97)); xc_textSupport->AutoSetExtent(); xc_textSupport->ClearRenderBuffer(); } void CFrontEndUI::SNesEmulatorFrame::SetMode(EMode mode) { switch (mode) { case EMode::Emulator: x8_quitScreen.reset(); break; case EMode::SaveProgress: x8_quitScreen = std::make_unique(EQuitType::SaveProgress); break; case EMode::ContinuePlaying: x8_quitScreen = std::make_unique(EQuitType::ContinuePlaying); break; case EMode::QuitNESMetroid: x8_quitScreen = std::make_unique(EQuitType::QuitNESMetroid); break; default: break; } x0_mode = mode; } void CFrontEndUI::SNesEmulatorFrame::ProcessUserInput(const CFinalInput& input, CSaveGameScreen* sui) { bool processInput = true; if (sui && sui->GetUIType() != CSaveGameScreen::EUIType::SaveReady) processInput = false; if (sui) sui->ProcessUserInput(input); if (!processInput) return; switch (x0_mode) { case EMode::Emulator: x4_nesEmu->ProcessUserInput(input, 4); if ((input.ControllerIdx() == 0 && input.PL()) || input.PSpecialKey(boo::ESpecialKey::Esc)) SetMode(EMode::QuitNESMetroid); break; case EMode::SaveProgress: case EMode::ContinuePlaying: case EMode::QuitNESMetroid: x8_quitScreen->ProcessUserInput(input); break; default: break; } } bool CFrontEndUI::SNesEmulatorFrame::Update(float dt, CSaveGameScreen* saveUi) { bool doUpdate = (saveUi && saveUi->GetUIType() != CSaveGameScreen::EUIType::SaveReady) ? false : true; x10_remTime = std::max(x10_remTime - dt, 0.f); zeus::CColor geomCol(zeus::skWhite); geomCol.a() = std::min(x10_remTime, 1.f); xc_textSupport->SetGeometryColor(geomCol); if (xc_textSupport->GetIsTextSupportFinishedLoading()) { xc_textSupport->AutoSetExtent(); xc_textSupport->ClearRenderBuffer(); } if (!doUpdate) return false; switch (x0_mode) { case EMode::Emulator: { x4_nesEmu->Update(); if (!x4_nesEmu->IsGameOver()) x14_emulationSuspended = false; if (x4_nesEmu->IsGameOver() && !x14_emulationSuspended) { x14_emulationSuspended = true; if (saveUi && !saveUi->IsSavingDisabled()) { SetMode(EMode::SaveProgress); break; } SetMode(EMode::ContinuePlaying); break; } if (x4_nesEmu->GetPasswordEntryState() == CNESEmulator::EPasswordEntryState::NotEntered && saveUi) x4_nesEmu->LoadPassword(g_GameState->SystemOptions().GetNESState()); break; } case EMode::SaveProgress: { if (saveUi) { EQuitAction action = x8_quitScreen->Update(dt); if (action == EQuitAction::Yes) { memmove(g_GameState->SystemOptions().GetNESState(), x4_nesEmu->GetPassword(), 18); saveUi->SaveNESState(); SetMode(EMode::ContinuePlaying); } else if (action == EQuitAction::No) SetMode(EMode::ContinuePlaying); } else SetMode(EMode::ContinuePlaying); break; } case EMode::ContinuePlaying: { EQuitAction action = x8_quitScreen->Update(dt); if (action == EQuitAction::Yes) SetMode(EMode::Emulator); else if (action == EQuitAction::No) return true; break; } case EMode::QuitNESMetroid: { EQuitAction action = x8_quitScreen->Update(dt); if (action == EQuitAction::Yes) return true; else if (action == EQuitAction::No) SetMode(EMode::Emulator); break; } default: break; } return false; } void CFrontEndUI::SNesEmulatorFrame::Draw(CSaveGameScreen* saveUi) const { zeus::CColor mulColor = zeus::skWhite; bool blackout = saveUi && saveUi->GetUIType() != CSaveGameScreen::EUIType::SaveReady; if (blackout) mulColor = zeus::skBlack; else if (x8_quitScreen) mulColor = zeus::CColor{0.376470f, 0.376470f, 0.376470f, 1.f}; x4_nesEmu->Draw(mulColor, x15_enableFiltering); if (!blackout && x8_quitScreen) x8_quitScreen->Draw(); if (x10_remTime >= 7.5f) return; if (x10_remTime <= 0.f) return; if (xc_textSupport->GetIsTextSupportFinishedLoading()) { float aspect = g_Viewport.aspect / 1.33f; CGraphics::SetOrtho(-320.f * aspect, 320.f * aspect, 240.f, -240.f, -4096.f, 4096.f); CGraphics::SetViewPointMatrix(zeus::CTransform()); CGraphics::SetModelMatrix(zeus::CTransform::Translate(-220.f, 0.f, -200.f)); xc_textSupport->Render(); } } CFrontEndUI::SOptionsFrontEndFrame::SOptionsFrontEndFrame() : x134_24_visible(true), x134_25_exitOptions(false) { x4_frme = g_SimplePool->GetObj("FRME_OptionsFrontEnd"); x10_pauseScreen = g_SimplePool->GetObj("STRG_PauseScreen"); } void CFrontEndUI::SOptionsFrontEndFrame::DoSliderChange(CGuiSliderGroup* caller, float value) { if (x28_tablegroup_rightmenu->GetIsActive()) { int leftSel = x24_tablegroup_leftmenu->GetUserSelection(); int rightSel = x28_tablegroup_rightmenu->GetUserSelection(); const auto& optionCategory = GameOptionsRegistry[leftSel]; const SGameOption& option = optionCategory.second[rightSel]; CGameOptions::SetOption(option.option, caller->GetGurVal()); m_touchBarValueDirty = true; } } void CFrontEndUI::SOptionsFrontEndFrame::DoMenuCancel(CGuiTableGroup* caller) { if (x28_tablegroup_rightmenu == caller) { DeactivateRightMenu(); x24_tablegroup_leftmenu->SetIsActive(true); x28_tablegroup_rightmenu->SetIsActive(false); x28_tablegroup_rightmenu->SetUserSelection(0); SetTableColors(x28_tablegroup_rightmenu); CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } } void CFrontEndUI::SOptionsFrontEndFrame::DoMenuSelectionChange(CGuiTableGroup* caller, int oldSel) { SetTableColors(caller); if (x24_tablegroup_leftmenu == caller) { SetRightUIText(); CSfxManager::SfxStart(SFXfnt_selection_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } else if (x28_tablegroup_rightmenu == caller) { HandleRightSelectionChange(); CSfxManager::SfxStart(SFXfnt_selection_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } else if (x2c_tablegroup_double == caller || x30_tablegroup_triple == caller) { if (x28_tablegroup_rightmenu->GetIsActive()) { int leftSel = x24_tablegroup_leftmenu->GetUserSelection(); int rightSel = x28_tablegroup_rightmenu->GetUserSelection(); const auto& optionCategory = GameOptionsRegistry[leftSel]; const SGameOption& option = optionCategory.second[rightSel]; CGameOptions::SetOption(option.option, caller->GetUserSelection()); m_touchBarValueDirty = true; CSfxManager::SfxStart(SFXfnt_enum_change, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); if (option.option == EGameOption::Rumble && caller->GetUserSelection() > 0) { x40_rumbleGen.HardStopAll(); x40_rumbleGen.Rumble(RumbleFxTable[size_t(ERumbleFxId::PlayerBump)], 1.f, ERumblePriority::One, EIOPort::Zero); } } } } void CFrontEndUI::SOptionsFrontEndFrame::DoLeftMenuAdvance(CGuiTableGroup* caller) { if (caller == x24_tablegroup_leftmenu) { HandleRightSelectionChange(); x28_tablegroup_rightmenu->SetUserSelection(0); x24_tablegroup_leftmenu->SetIsActive(false); x28_tablegroup_rightmenu->SetIsActive(true); CSfxManager::SfxStart(SFXfnt_advance_L, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); CSfxManager::SfxStart(SFXfnt_advance_R, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } } void CFrontEndUI::SOptionsFrontEndFrame::DeactivateRightMenu() { x2c_tablegroup_double->SetIsActive(false); x30_tablegroup_triple->SetIsActive(false); x34_slidergroup_slider->SetIsActive(false); x2c_tablegroup_double->SetVisibility(false, ETraversalMode::Children); x30_tablegroup_triple->SetVisibility(false, ETraversalMode::Children); x34_slidergroup_slider->SetVisibility(false, ETraversalMode::Children); } void CFrontEndUI::SOptionsFrontEndFrame::HandleRightSelectionChange() { DeactivateRightMenu(); int leftSel = x24_tablegroup_leftmenu->GetUserSelection(); int rightSel = x28_tablegroup_rightmenu->GetUserSelection(); const auto& optionCategory = GameOptionsRegistry[leftSel]; const SGameOption& option = optionCategory.second[rightSel]; switch (option.type) { case EOptionType::Float: x34_slidergroup_slider->SetIsActive(true); x34_slidergroup_slider->SetVisibility(true, ETraversalMode::Children); x34_slidergroup_slider->SetMinVal(option.minVal); x34_slidergroup_slider->SetMaxVal(option.maxVal); x34_slidergroup_slider->SetIncrement(option.increment); x34_slidergroup_slider->SetCurVal(CGameOptions::GetOption(option.option)); x34_slidergroup_slider->SetLocalTransform(zeus::CTransform::Translate(0.f, 0.f, rightSel * x38_rowPitch) * x34_slidergroup_slider->GetTransform()); break; case EOptionType::DoubleEnum: x2c_tablegroup_double->SetUserSelection(CGameOptions::GetOption(option.option)); x2c_tablegroup_double->SetIsVisible(true); x2c_tablegroup_double->SetIsActive(true); x2c_tablegroup_double->SetLocalTransform(zeus::CTransform::Translate(0.f, 0.f, rightSel * x38_rowPitch) * x2c_tablegroup_double->GetTransform()); SetTableColors(x2c_tablegroup_double); break; case EOptionType::TripleEnum: x30_tablegroup_triple->SetUserSelection(CGameOptions::GetOption(option.option)); x30_tablegroup_triple->SetIsVisible(true); x30_tablegroup_triple->SetIsActive(true); x30_tablegroup_triple->SetLocalTransform(zeus::CTransform::Translate(0.f, 0.f, rightSel * x38_rowPitch) * x30_tablegroup_triple->GetTransform()); SetTableColors(x30_tablegroup_triple); break; default: break; } } void CFrontEndUI::SOptionsFrontEndFrame::SetRightUIText() { const int userSel = x24_tablegroup_leftmenu->GetUserSelection(); const auto& options = (g_Main->IsUSA() && !g_Main->IsTrilogy()) ? GameOptionsRegistry[userSel] : GameOptionsRegistryNew[userSel]; for (int i = 0; i < 5; ++i) { std::string name = fmt::format(FMT_STRING("textpane_right{}"), i); if (i < static_cast(options.first)) { FindTextPanePair(x1c_loadedFrame, name).SetPairText(x20_loadedPauseStrg->GetString(options.second[i].stringId)); x28_tablegroup_rightmenu->GetWorkerWidget(i)->SetIsSelectable(true); } else { FindTextPanePair(x1c_loadedFrame, name).SetPairText(u""); x28_tablegroup_rightmenu->GetWorkerWidget(i)->SetIsSelectable(false); } } } void CFrontEndUI::SOptionsFrontEndFrame::SetTableColors(CGuiTableGroup* tbgp) const { tbgp->SetColors(zeus::skWhite, zeus::CColor{0.627450f, 0.627450f, 0.627450f, 0.784313f}); } void CFrontEndUI::SOptionsFrontEndFrame::FinishedLoading() { x1c_loadedFrame->SetAspectConstraint(1.78f); x24_tablegroup_leftmenu = static_cast(x1c_loadedFrame->FindWidget("tablegroup_leftmenu")); x28_tablegroup_rightmenu = static_cast(x1c_loadedFrame->FindWidget("tablegroup_rightmenu")); x2c_tablegroup_double = static_cast(x1c_loadedFrame->FindWidget("tablegroup_double")); x30_tablegroup_triple = static_cast(x1c_loadedFrame->FindWidget("tablegroup_triple")); x34_slidergroup_slider = static_cast(x1c_loadedFrame->FindWidget("slidergroup_slider")); x24_tablegroup_leftmenu->SetMenuAdvanceCallback([this](CGuiTableGroup* caller) { DoLeftMenuAdvance(caller); }); x24_tablegroup_leftmenu->SetMenuSelectionChangeCallback( [this](CGuiTableGroup* caller, int oldSel) { DoMenuSelectionChange(caller, oldSel); }); x38_rowPitch = x24_tablegroup_leftmenu->GetWorkerWidget(1)->GetIdlePosition().z() - x24_tablegroup_leftmenu->GetWorkerWidget(0)->GetIdlePosition().z(); x28_tablegroup_rightmenu->SetMenuSelectionChangeCallback( [this](CGuiTableGroup* caller, int oldSel) { DoMenuSelectionChange(caller, oldSel); }); x28_tablegroup_rightmenu->SetMenuCancelCallback([this](CGuiTableGroup* caller) { DoMenuCancel(caller); }); x2c_tablegroup_double->SetMenuSelectionChangeCallback( [this](CGuiTableGroup* caller, int oldSel) { DoMenuSelectionChange(caller, oldSel); }); x2c_tablegroup_double->SetMenuCancelCallback([this](CGuiTableGroup* caller) { DoMenuCancel(caller); }); x30_tablegroup_triple->SetMenuSelectionChangeCallback( [this](CGuiTableGroup* caller, int oldSel) { DoMenuSelectionChange(caller, oldSel); }); x30_tablegroup_triple->SetMenuCancelCallback([this](CGuiTableGroup* caller) { DoMenuCancel(caller); }); x34_slidergroup_slider->SetSelectionChangedCallback( [this](CGuiSliderGroup* caller, float value) { DoSliderChange(caller, value); }); FindTextPanePair(x1c_loadedFrame, "textpane_double0") .SetPairText(x20_loadedPauseStrg->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 95 : 98)); // Off FindTextPanePair(x1c_loadedFrame, "textpane_double1") .SetPairText(x20_loadedPauseStrg->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 94 : 97)); // On FindTextPanePair(x1c_loadedFrame, "textpane_triple0") .SetPairText(x20_loadedPauseStrg->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 96 : 99)); // Mono FindTextPanePair(x1c_loadedFrame, "textpane_triple1") .SetPairText(x20_loadedPauseStrg->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 97 : 100)); // Stereo FindTextPanePair(x1c_loadedFrame, "textpane_triple2") .SetPairText(x20_loadedPauseStrg->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 98 : 101)); // Dolby FindTextPanePair(x1c_loadedFrame, "textpane_title") .SetPairText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 99 : 93)); // OPTIONS if (CGuiTextPane* proceed = static_cast(x1c_loadedFrame->FindWidget("textpane_proceed"))) proceed->TextSupport().SetText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 85 : 79)); if (CGuiTextPane* cancel = static_cast(x1c_loadedFrame->FindWidget("textpane_cancel"))) cancel->TextSupport().SetText(g_MainStringTable->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy()) ? 82 : 76)); // Visor, Display, Sound, Controller for (int i = 0; i < 4; ++i) { const std::string name = fmt::format(FMT_STRING("textpane_filename{}"), i); FindTextPanePair(x1c_loadedFrame, name) .SetPairText(x20_loadedPauseStrg->GetString((g_Main->IsUSA() && !g_Main->IsTrilogy() ? 16 : 18) + i)); } x2c_tablegroup_double->SetVertical(false); x30_tablegroup_triple->SetVertical(false); x24_tablegroup_leftmenu->SetIsActive(true); x28_tablegroup_rightmenu->SetIsActive(false); SetTableColors(x24_tablegroup_leftmenu); SetTableColors(x28_tablegroup_rightmenu); SetTableColors(x2c_tablegroup_double); SetTableColors(x30_tablegroup_triple); SetRightUIText(); DeactivateRightMenu(); } bool CFrontEndUI::SOptionsFrontEndFrame::PumpLoad() { if (x1c_loadedFrame) return true; if (!x4_frme.IsLoaded()) return false; if (!x10_pauseScreen.IsLoaded()) return false; if (!x4_frme->GetIsFinishedLoading()) return false; x1c_loadedFrame = x4_frme.GetObj(); x20_loadedPauseStrg = x10_pauseScreen.GetObj(); m_touchBar = NewGameOptionsTouchBar(); FinishedLoading(); return true; } bool CFrontEndUI::SOptionsFrontEndFrame::ProcessUserInput(const CFinalInput& input, CSaveGameScreen* sui) { x134_25_exitOptions = false; if (sui) sui->ProcessUserInput(input); if (x1c_loadedFrame && x134_24_visible) { if ((input.PB() || input.PSpecialKey(boo::ESpecialKey::Esc)) && x24_tablegroup_leftmenu->GetIsActive()) { x134_25_exitOptions = true; CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } else { x1c_loadedFrame->ProcessUserInput(input); int leftSel = x24_tablegroup_leftmenu->GetUserSelection(); int rightSel = x28_tablegroup_rightmenu->GetUserSelection(); CGameOptions::TryRestoreDefaults(input, leftSel, rightSel, true, false); CGameOptionsTouchBar::EAction tbAction = m_touchBar->PopAction(); if (x28_tablegroup_rightmenu->GetIsActive()) { if (tbAction == CGameOptionsTouchBar::EAction::Advance && !m_touchBarInValue) { int value; m_touchBar->GetSelection(leftSel, rightSel, value); x28_tablegroup_rightmenu->SetUserSelection(rightSel); SetTableColors(x28_tablegroup_rightmenu); HandleRightSelectionChange(); const auto& optionCategory = GameOptionsRegistry[leftSel]; const SGameOption& option = optionCategory.second[rightSel]; if (option.type != EOptionType::RestoreDefaults) { m_touchBarInValue = true; m_touchBarValueDirty = true; } else { CGameOptions::TryRestoreDefaults(input, leftSel, rightSel, true, true); } } else if (tbAction == CGameOptionsTouchBar::EAction::Back) { if (m_touchBarInValue) m_touchBarInValue = false; else DoMenuCancel(x28_tablegroup_rightmenu); } else if (tbAction == CGameOptionsTouchBar::EAction::ValueChange) { int value; m_touchBar->GetSelection(leftSel, rightSel, value); const auto& optionCategory = GameOptionsRegistry[leftSel]; const SGameOption& option = optionCategory.second[rightSel]; CGameOptions::SetOption(option.option, value); if (option.type != EOptionType::Float) m_touchBarValueDirty = true; HandleRightSelectionChange(); } else { if (m_touchBarInValue) { if (m_touchBarValueDirty) { const auto& optionCategory = GameOptionsRegistry[leftSel]; const SGameOption& option = optionCategory.second[rightSel]; int value = CGameOptions::GetOption(option.option); m_touchBar->SetSelection(leftSel, rightSel, value); m_touchBarValueDirty = false; } } else { m_touchBar->SetSelection(leftSel, -1, -1); } } } else { if (tbAction == CGameOptionsTouchBar::EAction::Advance) { int value; m_touchBar->GetSelection(leftSel, rightSel, value); x24_tablegroup_leftmenu->SetUserSelection(leftSel); SetTableColors(x24_tablegroup_leftmenu); SetRightUIText(); DoLeftMenuAdvance(x24_tablegroup_leftmenu); } if (tbAction == CGameOptionsTouchBar::EAction::Back) { x134_25_exitOptions = true; CSfxManager::SfxStart(SFXfnt_back, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } else { m_touchBar->SetSelection(-1, -1, -1); } } } } return !x134_25_exitOptions; } void CFrontEndUI::SOptionsFrontEndFrame::Update(float dt, CSaveGameScreen* sui) { x40_rumbleGen.Update(dt); x134_24_visible = !sui || sui->GetUIType() == CSaveGameScreen::EUIType::SaveReady; if (!PumpLoad()) return; x0_uiAlpha = std::min(1.f, x0_uiAlpha + dt); x1c_loadedFrame->Update(dt); bool isSliding = x34_slidergroup_slider->GetState() != CGuiSliderGroup::EState::None; if (x3c_sliderSfx.operator bool() != isSliding) { if (isSliding) { x3c_sliderSfx = CSfxManager::SfxStart(SFXui_frontend_options_slider_change_lp, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } else { CSfxManager::SfxStop(x3c_sliderSfx); x3c_sliderSfx.reset(); } } } void CFrontEndUI::SOptionsFrontEndFrame::Draw() const { if (x1c_loadedFrame && x134_24_visible) { CGuiWidgetDrawParms params(x0_uiAlpha, zeus::skZero3f); x1c_loadedFrame->Draw(params); } } CFrontEndUI::CFrontEndUI() : CIOWin("FrontEndUI") { CMain* m = static_cast(g_Main); CRandom16 r(time(nullptr)); x18_rndA = r.Range(0, 2); x1c_rndB = r.Range(0, 2); x20_depsGroup = g_SimplePool->GetObj("FrontEnd_DGRP"); x38_pressStart = g_SimplePool->GetObj("TXTR_PressStart"); x44_frontendAudioGrp = g_SimplePool->GetObj("FrontEnd_AGSC"); xdc_saveUI = std::make_unique(ESaveContext::FrontEnd, g_GameState->GetCardSerial()); m->ResetGameState(); g_GameState->SetCurrentWorldId(g_DefaultWorldTag.id); g_GameState->GameOptions().ResetToDefaults(); g_GameState->WriteBackupBuf(); for (int i = 0; CDvdFile::FileExists(GetAttractMovieFileName(i)); ++i) ++xc0_attractCount; m_touchBar = NewFrontEndUITouchBar(); m_touchBar->SetPhase(CFrontEndUITouchBar::EPhase::None); } void CFrontEndUI::StartSlideShow(CArchitectureQueue& queue) { xf4_curAudio->StopMixing(); queue.Push(MakeMsg::CreateCreateIOWin(EArchMsgTarget::IOWinManager, 12, 11, std::make_shared())); } std::string CFrontEndUI::GetAttractMovieFileName(int idx) { return fmt::format(FMT_STRING("Video/attract{}.thp"), idx); } std::string CFrontEndUI::GetNextAttractMovieFileName() { std::string ret = GetAttractMovieFileName(xbc_nextAttract); xbc_nextAttract = (xbc_nextAttract + 1) % xc0_attractCount; return ret; } void CFrontEndUI::SetCurrentMovie(EMenuMovie movie) { if (xb8_curMovie == movie) { return; } StopAttractMovie(); if (xb8_curMovie != EMenuMovie::Stopped) { xcc_curMoviePtr->SetPlayMode(CMoviePlayer::EPlayMode::Stopped); xcc_curMoviePtr->Rewind(); } xb8_curMovie = movie; if (xb8_curMovie != EMenuMovie::Stopped) { xcc_curMoviePtr = x70_menuMovies[size_t(xb8_curMovie)].get(); xcc_curMoviePtr->SetPlayMode(CMoviePlayer::EPlayMode::Playing); } else { xcc_curMoviePtr = nullptr; } } void CFrontEndUI::StopAttractMovie() { if (!xc4_attractMovie) return; xc4_attractMovie.reset(); xcc_curMoviePtr = nullptr; } void CFrontEndUI::StartAttractMovie() { if (xc4_attractMovie) return; SetCurrentMovie(EMenuMovie::Stopped); xc4_attractMovie = std::make_unique(GetNextAttractMovieFileName().c_str(), 0.f, false, true); xcc_curMoviePtr = xc4_attractMovie.get(); } void CFrontEndUI::StartStateTransition(EScreen screen) { switch (x50_curScreen) { case EScreen::Title: if (screen != EScreen::FileSelect) break; SetCurrentMovie(EMenuMovie::StartFileSelectA); SetFadeBlackTimer(xcc_curMoviePtr->GetTotalSeconds()); break; case EScreen::FileSelect: if (screen == EScreen::ToPlayGame) { SetCurrentMovie(EMenuMovie::FileSelectPlayGameA); SetFadeBlackTimer(xcc_curMoviePtr->GetTotalSeconds()); } else if (screen == EScreen::FusionBonus) { SetCurrentMovie(EMenuMovie::FileSelectGBA); SetFadeBlackTimer(xcc_curMoviePtr->GetTotalSeconds()); CSfxManager::SfxStart(SFXfnt_tofusion_L, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); CSfxManager::SfxStart(SFXfnt_tofusion_R, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } break; case EScreen::FusionBonus: if (screen == EScreen::ToPlayGame) { SetCurrentMovie(EMenuMovie::GBAFileSelectB); SetFadeBlackTimer(xcc_curMoviePtr->GetTotalSeconds()); } else if (screen == EScreen::FileSelect) { SetCurrentMovie(EMenuMovie::GBAFileSelectA); SetFadeBlackTimer(xcc_curMoviePtr->GetTotalSeconds()); CSfxManager::SfxStart(SFXfnt_fromfusion_L, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); CSfxManager::SfxStart(SFXfnt_fromfusion_R, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } break; default: break; } switch (screen) { case EScreen::OpenCredits: case EScreen::Title: SetCurrentMovie(EMenuMovie::FirstStart); SetFadeBlackTimer(xcc_curMoviePtr->GetTotalSeconds()); break; case EScreen::AttractMovie: StartAttractMovie(); SetFadeBlackWithMovie(); break; default: break; } x54_nextScreen = screen; } void CFrontEndUI::CompleteStateTransition() { EScreen oldScreen = x50_curScreen; x50_curScreen = x54_nextScreen; switch (x50_curScreen) { case EScreen::AttractMovie: x54_nextScreen = EScreen::OpenCredits; x50_curScreen = EScreen::OpenCredits; xd0_playerSkipToTitle = false; StartStateTransition(EScreen::Title); break; case EScreen::Title: SetCurrentMovie(EMenuMovie::StartLoop); SetFadeBlackTimer(30.f); break; case EScreen::FileSelect: SetCurrentMovie(EMenuMovie::FileSelectLoop); if (oldScreen == EScreen::Title) { xf4_curAudio->StopMixing(); xf4_curAudio = xd8_audio2.get(); xf4_curAudio->StartMixing(); } if (xdc_saveUI) xdc_saveUI->ResetCardDriver(); break; case EScreen::FusionBonus: SetCurrentMovie(EMenuMovie::GBALoop); break; case EScreen::ToPlayGame: x14_phase = EPhase::ExitFrontEnd; break; default: break; } } void CFrontEndUI::HandleDebugMenuReturnValue(CGameDebug::EReturnValue val, CArchitectureQueue& queue) {} void CFrontEndUI::Draw() { if (x14_phase < EPhase::DisplayFrontEnd) { return; } SCOPED_GRAPHICS_DEBUG_GROUP("CFrontEndUI::Draw", zeus::skGreen); if (xec_emuFrme) { xec_emuFrme->Draw(xdc_saveUI.get()); } else { // g_Renderer->SetDepthReadWrite(false, false); g_Renderer->SetViewportOrtho(false, -4096.f, 4096.f); /* Correct movie aspect ratio */ float hPad, vPad; if (g_Viewport.aspect >= 1.78f) { hPad = 1.78f / g_Viewport.aspect; vPad = 1.78f / 1.33f; } else { hPad = 1.f; vPad = g_Viewport.aspect / 1.33f; } if (xcc_curMoviePtr && xcc_curMoviePtr->GetIsFullyCached()) { /* Render movie */ xcc_curMoviePtr->SetFrame({-hPad, vPad, 0.f}, {-hPad, -vPad, 0.f}, {hPad, -vPad, 0.f}, {hPad, vPad, 0.f}); xcc_curMoviePtr->DrawFrame(); } if (x50_curScreen == EScreen::FileSelect && x54_nextScreen == EScreen::FileSelect) { /* Render active FileSelect UI */ if (xf0_optionsFrme) xf0_optionsFrme->Draw(); else if (xe0_frontendCardFrme) xe0_frontendCardFrme->Draw(); else xe8_frontendNoCardFrme->Draw(); } else if (x50_curScreen == EScreen::FusionBonus && x54_nextScreen == EScreen::FusionBonus) { /* Render Fusion bonus UI */ xe4_fusionBonusFrme->Draw(); } if (x64_pressStartAlpha > 0.f && x38_pressStart.IsLoaded() && m_pressStartQuad) { /* Render "Press Start" */ const zeus::CRectangle rect(0.5f - x38_pressStart->GetWidth() / 2.f / 640.f * hPad, 0.5f + (x38_pressStart->GetHeight() / 2.f - 240.f + 72.f) / 480.f * vPad, x38_pressStart->GetWidth() / 640.f * hPad, x38_pressStart->GetHeight() / 480.f * vPad); zeus::CColor color = zeus::skWhite; color.a() = x64_pressStartAlpha; m_pressStartQuad->draw(color, 1.f, rect); } if (xc0_attractCount > 0) { /* Render fade-to-black into attract movie */ if (((x50_curScreen == EScreen::Title && x54_nextScreen == EScreen::Title) || x54_nextScreen == EScreen::AttractMovie) && x58_fadeBlackTimer < 1.f) { /* To black */ zeus::CColor color = zeus::skBlack; color.a() = 1.f - x58_fadeBlackTimer; m_fadeToBlack.draw(color); } } if (xd0_playerSkipToTitle) { /* Render fade-through-black into title if player skips */ if (x50_curScreen == EScreen::OpenCredits && x54_nextScreen == EScreen::Title) { /* To black */ zeus::CColor color = zeus::skBlack; color.a() = zeus::clamp(0.f, 1.f - x58_fadeBlackTimer, 1.f); m_fadeToBlack.draw(color); } else if (x50_curScreen == EScreen::Title && x54_nextScreen == EScreen::Title) { /* From black with 30-sec skip to title */ zeus::CColor color = zeus::skBlack; color.a() = 1.f - zeus::clamp(0.f, 30.f - x58_fadeBlackTimer, 1.f); m_fadeToBlack.draw(color); } } if (xdc_saveUI) { /* Render memory card feedback strings */ if ((CanShowSaveUI() && !xdc_saveUI->IsHiddenFromFrontEnd()) || ((x50_curScreen == EScreen::FileSelect && x54_nextScreen == EScreen::FileSelect) || (x50_curScreen == EScreen::FusionBonus && x54_nextScreen == EScreen::FusionBonus))) xdc_saveUI->Draw(); } } } void CFrontEndUI::UpdateMovies(float dt) { if (xcc_curMoviePtr && x5c_fadeBlackWithMovie) { /* Set fade-to-black timer to match attract movie */ x5c_fadeBlackWithMovie = false; x58_fadeBlackTimer = xcc_curMoviePtr->GetTotalSeconds(); } /* Advance playing menu movies */ for (auto& movie : x70_menuMovies) if (movie) movie->Update(dt); /* Advance attract movie */ if (xc4_attractMovie) xc4_attractMovie->Update(dt); } void CFrontEndUI::FinishedLoadingDepsGroup() { /* Transfer DGRP tokens into FrontEnd and lock */ const CDependencyGroup* dgrp = x20_depsGroup.GetObj(); x2c_deps.reserve(dgrp->GetObjectTagVector().size()); for (const SObjectTag& tag : dgrp->GetObjectTagVector()) { x2c_deps.push_back(g_SimplePool->GetObj(tag)); x2c_deps.back().Lock(); } x44_frontendAudioGrp.Lock(); } bool CFrontEndUI::PumpLoad() { /* Poll all tokens for load completion */ for (CToken& tok : x2c_deps) if (!tok.IsLoaded()) return false; if (!x44_frontendAudioGrp.IsLoaded()) return false; /* Ready to construct texture quads */ m_pressStartQuad.emplace(EFilterType::Add, x38_pressStart); return true; } bool CFrontEndUI::PumpMovieLoad() { /* Prepare all FrontEnd movies and pause each */ if (xd1_moviesLoaded) { return true; } for (size_t i = 0; i < x70_menuMovies.size(); ++i) { if (x70_menuMovies[i] != nullptr) { continue; } const FEMovie& movie = FEMovies[i]; std::string path = movie.path; if (i == size_t(EMenuMovie::StartFileSelectA)) { const auto pos = path.find("A.thp"); if (pos != std::string::npos) { path[pos] = 'A' + x18_rndA; } } else if (i == size_t(EMenuMovie::FileSelectPlayGameA)) { const auto pos = path.find("A.thp"); if (pos != std::string::npos) { path[pos] = 'A' + x1c_rndB; } } x70_menuMovies[i] = std::make_unique(path.c_str(), 0.05f, movie.loop, false); x70_menuMovies[i]->SetPlayMode(CMoviePlayer::EPlayMode::Stopped); return false; } xd1_moviesLoaded = true; return true; } void CFrontEndUI::ProcessUserInput(const CFinalInput& input, CArchitectureQueue& queue) { if (static_cast(g_Main)->GetCardBusy()) return; if (input.ControllerIdx() > 1) return; if (xec_emuFrme) { /* NES emulator pre-empts user input if active */ xec_emuFrme->ProcessUserInput(input, xdc_saveUI.get()); return; } /* Controllers other than first shall not pass */ if (x14_phase != EPhase::DisplayFrontEnd || input.ControllerIdx() != 0) return; /* Pop most recent action from Touch Bar */ CFrontEndUITouchBar::EAction touchBarAction = m_touchBar->PopAction(); if (x50_curScreen != x54_nextScreen) { if (x54_nextScreen == EScreen::AttractMovie && (input.PStart() || input.PA() || input.PSpecialKey(boo::ESpecialKey::Esc) || input.PSpecialKey(boo::ESpecialKey::Enter) || input.PMouseButton(boo::EMouseButton::Primary) || touchBarAction == CFrontEndUITouchBar::EAction::Start)) { /* Player wants to return to opening credits from attract movie */ SetFadeBlackTimer(std::min(1.f, x58_fadeBlackTimer)); PlayAdvanceSfx(); return; } if (input.PA() || input.PStart() || input.PSpecialKey(boo::ESpecialKey::Esc) || input.PSpecialKey(boo::ESpecialKey::Enter) || input.PMouseButton(boo::EMouseButton::Primary) || touchBarAction == CFrontEndUITouchBar::EAction::Start) { if (x50_curScreen == EScreen::OpenCredits && x54_nextScreen == EScreen::Title && x58_fadeBlackTimer > 1.f) { /* Player is too impatient to view opening credits */ xd0_playerSkipToTitle = true; SetFadeBlackTimer(1.f); return; } } } else { if (x50_curScreen == EScreen::Title) { if (input.PStart() || input.PA() || input.PSpecialKey(boo::ESpecialKey::Esc) || input.PSpecialKey(boo::ESpecialKey::Enter) || input.PMouseButton(boo::EMouseButton::Primary) || touchBarAction == CFrontEndUITouchBar::EAction::Start) { if (x58_fadeBlackTimer < 30.f - g_tweakGame->GetPressStartDelay()) { /* Proceed to file select UI */ CSfxManager::SfxStart(FETransitionBackSFX[x18_rndA][0], 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); CSfxManager::SfxStart(FETransitionBackSFX[x18_rndA][1], 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); m_touchBar->SetPhase(CFrontEndUITouchBar::EPhase::None); StartStateTransition(EScreen::FileSelect); return; } } } else if (x50_curScreen == EScreen::FileSelect && x54_nextScreen == EScreen::FileSelect) { if (xf0_optionsFrme) { /* Control options UI */ if (xf0_optionsFrme->ProcessUserInput(input, xdc_saveUI.get())) return; /* Exit options UI */ m_touchBar->SetPhase(CFrontEndUITouchBar::EPhase::None); xf0_optionsFrme.reset(); return; } else if (xe0_frontendCardFrme) { /* Control FrontEnd with memory card */ switch (xe0_frontendCardFrme->ProcessUserInput(input, touchBarAction)) { case SNewFileSelectFrame::EAction::FusionBonus: m_touchBar->SetPhase(CFrontEndUITouchBar::EPhase::None); StartStateTransition(EScreen::FusionBonus); return; case SNewFileSelectFrame::EAction::GameOptions: m_touchBar->SetPhase(CFrontEndUITouchBar::EPhase::None); xf0_optionsFrme = std::make_unique(); return; case SNewFileSelectFrame::EAction::SlideShow: m_touchBar->SetPhase(CFrontEndUITouchBar::EPhase::None); xd2_deferSlideShow = true; StartSlideShow(queue); return; default: return; } } else { /* Control FrontEnd without memory card */ switch (xe8_frontendNoCardFrme->ProcessUserInput(input, touchBarAction)) { case SFrontEndFrame::EAction::FusionBonus: m_touchBar->SetPhase(CFrontEndUITouchBar::EPhase::None); StartStateTransition(EScreen::FusionBonus); return; case SFrontEndFrame::EAction::GameOptions: m_touchBar->SetPhase(CFrontEndUITouchBar::EPhase::None); xf0_optionsFrme = std::make_unique(); return; case SFrontEndFrame::EAction::StartGame: TransitionToGame(); return; case SFrontEndFrame::EAction::SlideShow: m_touchBar->SetPhase(CFrontEndUITouchBar::EPhase::None); xd2_deferSlideShow = true; StartSlideShow(queue); return; default: return; } } } else if (x50_curScreen == EScreen::FusionBonus && x54_nextScreen == EScreen::FusionBonus) { /* Control Fusion bonus UI */ switch (xe4_fusionBonusFrme->ProcessUserInput(input, xdc_saveUI.get(), touchBarAction)) { case SFusionBonusFrame::EAction::GoBack: m_touchBar->SetPhase(CFrontEndUITouchBar::EPhase::None); StartStateTransition(EScreen::FileSelect); return; case SFusionBonusFrame::EAction::PlayNESMetroid: m_touchBar->SetPhase(CFrontEndUITouchBar::EPhase::None); xf4_curAudio->StopMixing(); xec_emuFrme = std::make_unique(); if (xdc_saveUI) xdc_saveUI->SetInGame(true); return; default: return; } } } } void CFrontEndUI::TransitionToGame() { if (x14_phase >= EPhase::ToPlayGame) { return; } const auto sfx = FETransitionForwardSFX[x1c_rndB]; CSfxManager::SfxStart(sfx[0], 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); CSfxManager::SfxStart(sfx[1], 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); x14_phase = EPhase::ToPlayGame; m_touchBar->SetPhase(CFrontEndUITouchBar::EPhase::None); StartStateTransition(EScreen::ToPlayGame); } void CFrontEndUI::UpdateMusicVolume() { float volMul = (xf4_curAudio == xd4_audio1.get()) ? FE1_VOL : FE2_VOL; if (xf4_curAudio) { float vol = volMul * x68_musicVol * (g_GameState->GameOptions().GetMusicVolume() / 127.f); xf4_curAudio->SetVolume(vol); } } CIOWin::EMessageReturn CFrontEndUI::Update(float dt, CArchitectureQueue& queue) { if (xdc_saveUI && x50_curScreen >= EScreen::FileSelect) { switch (xdc_saveUI->Update(dt)) { case EMessageReturn::Exit: /* Memory card operation complete, transition to game */ TransitionToGame(); break; case EMessageReturn::RemoveIOWinAndExit: case EMessageReturn::RemoveIOWin: /* No memory card available, fallback to non-save UI */ xe0_frontendCardFrme.reset(); xdc_saveUI.reset(); break; default: break; } } /* Set music fade volume */ UpdateMusicVolume(); switch (x14_phase) { case EPhase::LoadDepsGroup: /* Poll DGRP load */ if (!x20_depsGroup.IsLoaded()) return EMessageReturn::Exit; FinishedLoadingDepsGroup(); x20_depsGroup.Unlock(); x14_phase = EPhase::LoadDeps; [[fallthrough]]; case EPhase::LoadDeps: /* Poll loading DGRP resources */ if (PumpLoad()) { xe0_frontendCardFrme = std::make_unique(xdc_saveUI.get(), x1c_rndB, *m_touchBar); xe4_fusionBonusFrme = std::make_unique(*m_touchBar); xe8_frontendNoCardFrme = std::make_unique(x1c_rndB, *m_touchBar); x38_pressStart.GetObj(); CAudioSys::AddAudioGroup(x44_frontendAudioGrp->GetAudioGroupData()); xd4_audio1 = std::make_unique("Audio/frontend_1.rsf", 416480, 1973664); xd8_audio2 = std::make_unique("Audio/frontend_2.rsf", 273556, 1636980); x14_phase = EPhase::LoadFrames; } if (x14_phase == EPhase::LoadDeps) return EMessageReturn::Exit; [[fallthrough]]; case EPhase::LoadFrames: /* Poll loading music and FRME resources */ if (!xd4_audio1->IsReady() || !xd8_audio2->IsReady() || !xe0_frontendCardFrme->PumpLoad() || !xe4_fusionBonusFrme->PumpLoad() || !xe8_frontendNoCardFrme->PumpLoad() || !xdc_saveUI->PumpLoad()) return EMessageReturn::Exit; xf4_curAudio = xd4_audio1.get(); xf4_curAudio->StartMixing(); x14_phase = EPhase::LoadMovies; [[fallthrough]]; case EPhase::LoadMovies: { /* Poll loading movies */ bool moviesReady = true; if (PumpMovieLoad()) { /* Prime first frame of movies */ UpdateMovies(dt); moviesReady = std::all_of(x70_menuMovies.cbegin(), x70_menuMovies.cend(), [](const auto& movie) { return movie->GetIsFullyCached(); }); } else { moviesReady = false; } if (moviesReady) { /* Ready to display FrontEnd */ x14_phase = EPhase::DisplayFrontEnd; m_touchBar->SetPhase(CFrontEndUITouchBar::EPhase::PressStart); StartStateTransition(EScreen::Title); } else { return EMessageReturn::Exit; } [[fallthrough]]; } case EPhase::DisplayFrontEnd: case EPhase::ToPlayGame: /* Displaying frontend to user */ if (xec_emuFrme) { /* Update just the emulator if active */ if (xec_emuFrme->Update(dt, xdc_saveUI.get())) { /* Exit emulator */ xec_emuFrme.reset(); if (xdc_saveUI) xdc_saveUI->SetInGame(false); xf4_curAudio->StartMixing(); } break; } if (xd2_deferSlideShow) { /* Start mixing slideshow music */ xd2_deferSlideShow = false; xf4_curAudio->StartMixing(); if (xdc_saveUI) xdc_saveUI->ResetCardDriver(); } if (x50_curScreen == EScreen::FileSelect && x54_nextScreen == EScreen::FileSelect) { /* Main FrontEnd UI tree active */ if (xf0_optionsFrme) { bool optionsActive = true; if (xdc_saveUI && xdc_saveUI->GetUIType() != CSaveGameScreen::EUIType::SaveReady) optionsActive = false; if (optionsActive) { /* Update options UI */ xf0_optionsFrme->Update(dt, xdc_saveUI.get()); } else { /* Save triggered; exit options UI here */ xf0_optionsFrme.reset(); } } else if (xe0_frontendCardFrme) { /* Update FrontEnd with memory card UI */ xe0_frontendCardFrme->Update(dt); } else { /* Update FrontEnd without memory card UI */ xe8_frontendNoCardFrme->Update(dt); } } else if (x50_curScreen == EScreen::FusionBonus && x54_nextScreen == EScreen::FusionBonus) { /* Update Fusion bonus UI */ xe4_fusionBonusFrme->Update(dt, xdc_saveUI.get()); } if (x50_curScreen != x54_nextScreen && xcc_curMoviePtr && (xcc_curMoviePtr->GetIsMovieFinishedPlaying() || xcc_curMoviePtr->IsLooping())) { /* Movie-based transition complete */ CompleteStateTransition(); } if (x58_fadeBlackTimer > 0.f && !x5c_fadeBlackWithMovie) { SetFadeBlackTimer(std::max(0.f, x58_fadeBlackTimer - dt)); if (x58_fadeBlackTimer == 0.f) { if (x50_curScreen == EScreen::Title && x54_nextScreen == EScreen::Title) { if (xc0_attractCount > 0) { /* Screen black, start attract movie */ StartStateTransition(EScreen::AttractMovie); } } else if (x54_nextScreen == EScreen::AttractMovie) { /* Attract movie done, play open credits again */ CompleteStateTransition(); } else if (x50_curScreen != x54_nextScreen) { /* Fade-based transition complete */ CompleteStateTransition(); } } } /* Advance active movies */ UpdateMovies(dt); if (x50_curScreen == EScreen::Title && x54_nextScreen == EScreen::Title) { /* Update press-start pulsing */ if (x58_fadeBlackTimer < 30.f - g_tweakGame->GetPressStartDelay()) { x60_pressStartTime = std::fmod(x60_pressStartTime + dt, 1.f); if (x60_pressStartTime < 0.5f) x64_pressStartAlpha = x60_pressStartTime / 0.5f; else x64_pressStartAlpha = (1.f - x60_pressStartTime) / 0.5f; } } else { /* Clear press-start pulsing */ x60_pressStartTime = 0.f; x64_pressStartAlpha = 0.f; } if (x50_curScreen == EScreen::Title && x54_nextScreen == EScreen::FileSelect) { /* Fade out title music */ x68_musicVol = 1.f - zeus::clamp(0.f, (xcc_curMoviePtr->GetPlayedSeconds() - AudioFadeTimeA[x18_rndA]) / 2.5f, 1.f); } else if (x54_nextScreen == EScreen::ToPlayGame) { /* Fade out menu music */ float delay = AudioFadeTimeB[x1c_rndB]; x68_musicVol = 1.f - zeus::clamp(0.f, (xcc_curMoviePtr->GetPlayedSeconds() - delay) / (xcc_curMoviePtr->GetTotalSeconds() - delay), 1.f); } else { /* Full music volume */ x68_musicVol = 1.f; } return EMessageReturn::Exit; case EPhase::ExitFrontEnd: /* Remove FrontEnd IOWin and begin updating next IOWin */ return EMessageReturn::RemoveIOWin; default: break; } return EMessageReturn::Exit; } CIOWin::EMessageReturn CFrontEndUI::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) { switch (msg.GetType()) { case EArchMsgType::UserInput: { /* Forward user events */ const CArchMsgParmUserInput& input = MakeMsg::GetParmUserInput(msg); ProcessUserInput(input.x4_parm, queue); break; } case EArchMsgType::TimerTick: { /* Forward frame events */ float dt = MakeMsg::GetParmTimerTick(msg).x4_parm; return Update(dt, queue); } case EArchMsgType::QuitGameplay: { /* Immediately exit FrontEnd */ x14_phase = EPhase::ExitFrontEnd; break; } default: break; } return EMessageReturn::Normal; } } // namespace urde::MP1