From 27ee349d7d5886c00c0917427b4a169029835549 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Wed, 5 Nov 2025 12:55:10 +0000 Subject: [PATCH 01/29] Implemented achievements I/O, overlay and error handling --- MarathonRecomp/gpu/video.cpp | 28 +-- MarathonRecomp/locale/locale.cpp | 69 ++++-- .../patches/SaveDataTask_patches.cpp | 110 +++++++++ MarathonRecomp/patches/TitleTask_patches.cpp | 2 +- MarathonRecomp/patches/misc_patches.cpp | 3 +- MarathonRecomp/ui/achievement_overlay.cpp | 215 +++++------------- MarathonRecomp/ui/achievement_overlay.h | 1 - MarathonRecomp/ui/imgui_utils.cpp | 128 ++++++++++- MarathonRecomp/ui/imgui_utils.h | 1 + MarathonRecomp/ui/message_window.cpp | 114 +--------- MarathonRecomp/user/achievement_data.cpp | 2 +- MarathonRecomp/user/achievement_data.h | 29 +-- MarathonRecomp/user/achievement_manager.cpp | 51 ++--- MarathonRecomp/user/achievement_manager.h | 8 +- MarathonRecompLib/config/Marathon.toml | 5 + 15 files changed, 410 insertions(+), 356 deletions(-) diff --git a/MarathonRecomp/gpu/video.cpp b/MarathonRecomp/gpu/video.cpp index 08c8c74e..92960523 100644 --- a/MarathonRecomp/gpu/video.cpp +++ b/MarathonRecomp/gpu/video.cpp @@ -1577,7 +1577,6 @@ static void CreateImGuiBackend() InitImGuiUtils(); AchievementMenu::Init(); - AchievementOverlay::Init(); OptionsMenu::Init(); InstallerWizard::Init(); @@ -2311,17 +2310,17 @@ static uint32_t getSetAddress(uint32_t base, int index) { static uint32_t CreateDevice(uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5, be* a6) { LOGF_WARNING("{:p} {:p} {:p} {:p} {:p} {:p}\n", reinterpret_cast(a1), reinterpret_cast(a2), reinterpret_cast(a3), reinterpret_cast(a4), reinterpret_cast(a5), reinterpret_cast(a6)); - g_xdbfTextureCache = std::unordered_map(); + + g_xdbfTextureCache = std::unordered_map(); - // for (auto &achievement : g_xdbfWrapper.GetAchievements(XDBF_LANGUAGE_ENGLISH)) - // { - // // huh? - // if (!achievement.pImageBuffer || !achievement.ImageBufferSize) - // continue; + for (auto &achievement : g_xdbfWrapper.GetAchievements(XDBF_LANGUAGE_ENGLISH)) + { + if (!achievement.pImageBuffer || !achievement.ImageBufferSize) + continue; - // g_xdbfTextureCache[achievement.ID] = - // LoadTexture((uint8_t *)achievement.pImageBuffer, achievement.ImageBufferSize).release(); - // } + g_xdbfTextureCache[achievement.ID] = + LoadTexture((uint8_t *)achievement.pImageBuffer, achievement.ImageBufferSize).release(); + } // Move backbuffer to guest memory. assert(!g_memory.IsInMemoryRange(g_backBuffer) && g_backBufferHolder != nullptr); @@ -4458,10 +4457,13 @@ static void SanitizePipelineState(PipelineState& pipelineState) pipelineState.blendOpAlpha = RenderBlendOperation::ADD; } - for (size_t i = 0; i < 16; i++) + if (pipelineState.vertexDeclaration) { - if (!pipelineState.vertexDeclaration->vertexStreams[i]) - pipelineState.vertexStrides[i] = 0; + for (size_t i = 0; i < 16; i++) + { + if (!pipelineState.vertexDeclaration->vertexStreams[i]) + pipelineState.vertexStrides[i] = 0; + } } uint32_t specConstantsMask = 0; diff --git a/MarathonRecomp/locale/locale.cpp b/MarathonRecomp/locale/locale.cpp index 7412caa9..dd0c8d46 100644 --- a/MarathonRecomp/locale/locale.cpp +++ b/MarathonRecomp/locale/locale.cpp @@ -635,29 +635,42 @@ std::unordered_map> } }, { - // Notes: message appears when ACH-DATA is corrupted (mismatching file size, bad signature, incorrect version or invalid checksum) upon pressing start at the title screen. + // Notes: message appears when SonicNextAchievementData.bin is corrupted (mismatching file size, bad signature, incorrect version or invalid checksum) upon loading save data. // To make this occur, open the file in any editor and just remove a large chunk of data. - "Title_Message_AchievementDataCorrupt", + "Title_Message_LoadAchievementDataCorrupt", { - { ELanguage::English, "The achievement data appears to be\ncorrupted and cannot be loaded.\n\nProceeding from this point will\nclear your achievement data." }, - { ELanguage::Japanese, "実績データが破損しているため\n読み込むことができません\n\nこの先に進むと実績データが\n消去されます" }, - { ELanguage::German, "Die Erfolgsdaten sind möglicherweise\nfehlerhaft und können nicht\ngeladen werden.\n\nDurch das Fortfahren werden\ndeine bisherigen Erfolgsdaten gelöscht." }, - { ELanguage::French, "Les données des succès semblent être\nendommagées et ne peuvent être\nchargées.\n\nSi vous continuez, vos données\nseront écrasées." }, - { ELanguage::Spanish, "Los datos de logros parecen estar\ncorruptos y no pueden cargarse.\n\nContinuar a partir de este punto\neliminará los datos de logros." }, - { ELanguage::Italian, "I file degli obiettivi sembrano danneggiati\ne non possono essere caricati.\n\nSe prosegui da questo punto\ni tuoi obiettivi verranno cancellati." } + { ELanguage::English, "Load failed. Achievement data is corrupted.\nIf you continue your achievement progress will\nbe lost." }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } } }, { - // Notes: message appears when ACH-DATA cannot be loaded upon pressing start at the title screen. - // To make this occur, lock the ACH-DATA file using an external program so that it cannot be accessed by the game. - "Title_Message_AchievementDataIOError", + // Notes: message appears when SonicNextAchievementData.bin cannot be loaded upon loading save data. + // To make this occur, lock the SonicNextAchievementData.bin file using an external program so that it cannot be accessed by the game. + "Title_Message_LoadAchievementDataIOError", { - { ELanguage::English, "The achievement data could not be loaded.\nYour achievements will not be saved." }, - { ELanguage::Japanese, "実績データを読み込めませんでした\n実績は保存されません" }, - { ELanguage::German, "Die Erfolgsdaten konnten nicht geladen werden.\nDeine Erfolge werden nicht gespeichert." }, - { ELanguage::French, "Les données des succès ne\npeuvent être chargées.\nVos succès ne seront pas\nsauvegardés." }, - { ELanguage::Spanish, "Los datos de logros no pueden cargarse.\nTus logros no serán guardados." }, - { ELanguage::Italian, "I file degli obiettivi non possono essere caricati.\nI tuoi obiettivi non verranno salvati." } + { ELanguage::English, "Load failed. Achievement data cannot be loaded.\nIf you continue you will not be able to save\nyour achievement progress." }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } + } + }, + { + // Notes: message appears when SonicNextAchievementData.bin cannot be saved upon saving save data. + // To make this occur, lock the SonicNextAchievementData.bin file using an external program so that it cannot be accessed by the game. + "Title_Message_SaveAchievementDataIOError", + { + { ELanguage::English, "Save failed. Achievement data cannot be saved.\nIf you continue you will not be able to save\nyour achievement progress." }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } } }, { @@ -858,6 +871,28 @@ std::unordered_map> { ELanguage::Italian, "Cambia" } } }, + { + "Common_Retry", + { + { ELanguage::English, "Retry" }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } + } + }, + { + "Common_ContinueWithoutSaving", + { + { ELanguage::English, "Continue without saving." }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } + } + }, { "Button_Cancel", { diff --git a/MarathonRecomp/patches/SaveDataTask_patches.cpp b/MarathonRecomp/patches/SaveDataTask_patches.cpp index 0e9c5cff..3257691e 100644 --- a/MarathonRecomp/patches/SaveDataTask_patches.cpp +++ b/MarathonRecomp/patches/SaveDataTask_patches.cpp @@ -1,7 +1,20 @@ #include +#include +#include #include +#include + +enum +{ + ACH_ERROR_SUCCESS, + ACH_ERROR_READ, + ACH_ERROR_WRITE +}; + static Sonicteam::SaveDataTaskXENON::SaveDataOperation g_currentAlert{}; +static int g_achError{ ACH_ERROR_SUCCESS }; +static int g_achErrorIgnored{ ACH_ERROR_SUCCESS }; // Sonicteam::SaveDataTaskXENON::RunOperation (speculatory) PPC_FUNC_IMPL(__imp__sub_8238CB18); @@ -35,9 +48,106 @@ PPC_FUNC(sub_8238CB18) } } + // Attempt to load achievement data on save read. + if (ctx.r4.u32 == Sonicteam::SaveDataTaskXENON::SaveDataOperation_ReadSaveData) + { + if (!AchievementManager::LoadBinary()) + { + // This condition will always be met if the achievement data is corrupted (!= IOError), + // or as long as "Continue without saving." has not been chosen on load in this session. + if (AchievementManager::BinStatus != EAchBinStatus::IOError || g_achErrorIgnored != ACH_ERROR_READ) + { + g_achError = ACH_ERROR_READ; + return; + } + } + } + + // Attempt to save achievement data on save write. + if (ctx.r4.u32 == Sonicteam::SaveDataTaskXENON::SaveDataOperation_WriteSaveData) + { + // This condition will be met if the achievement data failed to save (any error status), + // and as long as "Continue without saving." has not been chosen on save in this session. + if (!AchievementManager::SaveBinary() && g_achErrorIgnored != ACH_ERROR_WRITE) + { + g_achError = ACH_ERROR_WRITE; + return; + } + } + __imp__sub_8238CB18(ctx, base); } +// Sonicteam::SaveDataTaskXENON::Update +PPC_FUNC_IMPL(__imp__sub_8238D5C8); +PPC_FUNC(sub_8238D5C8) +{ + auto pSaveDataTaskXENON = (Sonicteam::SaveDataTaskXENON*)(base + ctx.r3.u32); + + if (g_achError != ACH_ERROR_SUCCESS) + { + static auto s_achErrorMessageResult = -1; + + auto& message = Localise("Title_Message_LoadAchievementDataIOError"); + std::vector options = { Localise("Common_Retry"), Localise("Common_ContinueWithoutSaving") }; + + switch (g_achError) + { + case ACH_ERROR_READ: + { + // Achievement data is unrecoverable, force user to overwrite it. + if (AchievementManager::BinStatus != EAchBinStatus::IOError) + { + message = Localise("Title_Message_LoadAchievementDataCorrupt"); + options[0] = Localise("Common_OK"); + options.pop_back(); + } + + break; + } + + case ACH_ERROR_WRITE: + message = Localise("Title_Message_SaveAchievementDataIOError"); + break; + } + + if (MessageWindow::Open(message, &s_achErrorMessageResult, options, 0) == MSG_CLOSED) + { + auto operation = g_achError == ACH_ERROR_READ + ? Sonicteam::SaveDataTaskXENON::SaveDataOperation_ReadSaveData + : Sonicteam::SaveDataTaskXENON::SaveDataOperation_WriteSaveData; + + switch (s_achErrorMessageResult) + { + // Retry / OK + case 0: + { + // Create new achievement data file. + AchievementManager::SaveBinary(true); + + // Display any future errors for new files. + g_achErrorIgnored = ACH_ERROR_SUCCESS; + + break; + } + + // Continue without saving. + // Ignore any future errors unrelated to corruption for this file. + case 1: + g_achErrorIgnored = g_achError; + break; + } + + g_achError = ACH_ERROR_SUCCESS; + s_achErrorMessageResult = -1; + + GuestToHostFunction(sub_8238CB18, pSaveDataTaskXENON, operation); + } + } + + __imp__sub_8238D5C8(ctx, base); +} + void SaveAlertThreeOptionRemoveDeviceSelect(PPCRegister& r5) { auto options = (uint64_t*)g_memory.Translate(r5.u32 + 8); diff --git a/MarathonRecomp/patches/TitleTask_patches.cpp b/MarathonRecomp/patches/TitleTask_patches.cpp index 70e114de..740638a3 100644 --- a/MarathonRecomp/patches/TitleTask_patches.cpp +++ b/MarathonRecomp/patches/TitleTask_patches.cpp @@ -32,7 +32,7 @@ enum bool ProcessQuitMessage(Sonicteam::TitleTask* pTitleTask) { - static int s_quitMessageResult = -1; + static auto s_quitMessageResult = -1; static std::atomic s_faderBegun = false; if (!g_quitMessageOpen) diff --git a/MarathonRecomp/patches/misc_patches.cpp b/MarathonRecomp/patches/misc_patches.cpp index a8a8e320..cc9c4731 100644 --- a/MarathonRecomp/patches/misc_patches.cpp +++ b/MarathonRecomp/patches/misc_patches.cpp @@ -5,8 +5,7 @@ #include #include -// TODO (Hyper): implement achievements menu. -void AchievementManagerUnlockMidAsmHook(PPCRegister& id) +void UnlockAchievement(PPCRegister& id) { AchievementManager::Unlock(id.u32); } diff --git a/MarathonRecomp/ui/achievement_overlay.cpp b/MarathonRecomp/ui/achievement_overlay.cpp index 039b2ed3..8939997b 100644 --- a/MarathonRecomp/ui/achievement_overlay.cpp +++ b/MarathonRecomp/ui/achievement_overlay.cpp @@ -1,6 +1,4 @@ #include "achievement_overlay.h" -#include -#include #include #include #include @@ -9,95 +7,20 @@ #include #include #include -#include -constexpr double OVERLAY_CONTAINER_COMMON_MOTION_START = 0; -constexpr double OVERLAY_CONTAINER_COMMON_MOTION_END = 11; -constexpr double OVERLAY_CONTAINER_INTRO_FADE_START = 5; -constexpr double OVERLAY_CONTAINER_INTRO_FADE_END = 9; -constexpr double OVERLAY_CONTAINER_OUTRO_FADE_START = 0; -constexpr double OVERLAY_CONTAINER_OUTRO_FADE_END = 4; +constexpr double OVERLAY_DURATION = 5.0; -constexpr double OVERLAY_DURATION = 3; +static bool g_isClosing{}; +static double g_appearTime{}; -static bool g_isClosing = false; - -static double g_appearTime = 0; - -static Achievement g_achievement; - -static ImFont* g_rodinFont; - -static bool DrawContainer(ImVec2 min, ImVec2 max, float cornerRadius = 25) -{ - auto drawList = ImGui::GetBackgroundDrawList(); - - // Expand/retract animation. - auto containerMotion = ComputeMotion(g_appearTime, OVERLAY_CONTAINER_COMMON_MOTION_START, OVERLAY_CONTAINER_COMMON_MOTION_END); - - auto centreX = (min.x + max.x) / 2; - auto centreY = (min.y + max.y) / 2; - - if (g_isClosing) - { - min.x = Hermite(min.x, centreX, containerMotion); - max.x = Hermite(max.x, centreX, containerMotion); - min.y = Hermite(min.y, centreY, containerMotion); - max.y = Hermite(max.y, centreY, containerMotion); - } - else - { - min.x = Hermite(centreX, min.x, containerMotion); - max.x = Hermite(centreX, max.x, containerMotion); - min.y = Hermite(centreY, min.y, containerMotion); - max.y = Hermite(centreY, max.y, containerMotion); - } - - // Transparency fade animation. - auto colourMotion = g_isClosing - ? ComputeMotion(g_appearTime, OVERLAY_CONTAINER_OUTRO_FADE_START, OVERLAY_CONTAINER_OUTRO_FADE_END) - : ComputeMotion(g_appearTime, OVERLAY_CONTAINER_INTRO_FADE_START, OVERLAY_CONTAINER_INTRO_FADE_END); - - auto alpha = g_isClosing - ? Hermite(1, 0, colourMotion) - : Hermite(0, 1, colourMotion); - - // DrawPauseContainer(min, max, alpha); - - if (containerMotion >= 1.0f) - { - drawList->PushClipRect(min, max); - return true; - } - - return false; -} - -void AchievementOverlay::Init() -{ - auto& io = ImGui::GetIO(); - - g_rodinFont = ImFontAtlasSnapshot::GetFont("FOT-RodinPro-DB.otf"); -} - -// Dequeue achievements only when we can actually play sounds. -// Loading thread does not update this object. -static bool g_soundAdministratorUpdated; - -// PPC_FUNC_IMPL(__imp__sub_82B43480); -// PPC_FUNC(sub_82B43480) -// { -// g_soundAdministratorUpdated = true; -// __imp__sub_82B43480(ctx, base); -// } +static Achievement g_achievement{}; // Dequeue achievements only in the main thread. This is also extra thread safety. static std::thread::id g_mainThreadId = std::this_thread::get_id(); static bool CanDequeueAchievement() { - // TODO - return false; + return std::this_thread::get_id() == g_mainThreadId && !AchievementOverlay::s_queue.empty(); } void AchievementOverlay::Draw() @@ -109,25 +32,16 @@ void AchievementOverlay::Draw() g_appearTime = ImGui::GetTime(); g_achievement = g_xdbfWrapper.GetAchievement((EXDBFLanguage)Config::Language.Value, s_queue.front()); s_queue.pop(); - - if (Config::Language == ELanguage::English) - g_achievement.Name = xdbf::FixInvalidSequences(g_achievement.Name); - - Game_PlaySound("obj_navi_appear"); - } + Game_PlaySound("deside"); + } + if (!s_isVisible) - { - g_soundAdministratorUpdated = false; return; - } if (ImGui::GetTime() - g_appearTime >= OVERLAY_DURATION) AchievementOverlay::Close(); - // Close function can use this bool so reset it after. - g_soundAdministratorUpdated = false; - auto drawList = ImGui::GetBackgroundDrawList(); auto& res = ImGui::GetIO().DisplaySize; @@ -135,80 +49,73 @@ void AchievementOverlay::Draw() auto strAchievementName = g_achievement.Name.c_str(); // Calculate text sizes. - auto fontSize = Scale(24); - auto headerSize = g_rodinFont->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementUnlocked); - auto bodySize = g_rodinFont->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementName); - auto maxSize = std::max(headerSize.x, bodySize.x) + Scale(5); + auto fontSize = Scale(Config::Language == ELanguage::Japanese ? 28 : 27, true); + auto headerSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementUnlocked); + auto nameSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementName); + auto maxSize = std::max(headerSize.x, nameSize.x) + Scale(5); // Calculate image margins. auto imageMarginX = Scale(25); - auto imageMarginY = Scale(22.5f); - auto imageSize = Scale(60); + auto imageMarginY = Scale(20); + auto imageSize = Scale(64); // Calculate text margins. auto textMarginX = imageMarginX * 2 + imageSize - Scale(5); - auto textMarginY = imageMarginY + Scale(2); + auto textMarginY = imageMarginY + Scale(1); auto containerWidth = imageMarginX + textMarginX + maxSize; ImVec2 min = { (res.x / 2) - (containerWidth / 2), Scale(55) }; ImVec2 max = { min.x + containerWidth, min.y + Scale(105) }; - if (DrawContainer(min, max)) + auto windowMotion = DrawWindow(min, max, true, g_appearTime, g_isClosing); + + if (windowMotion >= 1.0) { - if (!g_isClosing) - { - // Draw achievement icon. - drawList->AddImage - ( - g_xdbfTextureCache[g_achievement.ID], // user_texture_id - { /* X */ min.x + imageMarginX, /* Y */ min.y + imageMarginY }, // p_min - { /* X */ min.x + imageMarginX + imageSize, /* Y */ min.y + imageMarginY + imageSize }, // p_max - { 0, 0 }, // uv_min - { 1, 1 }, // uv_max - IM_COL32(255, 255, 255, 255) // col - ); - - // Use low quality text. - SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); - - // Draw header text. - DrawTextWithShadow - ( - g_rodinFont, // font - fontSize, // fontSize - { /* X */ min.x + textMarginX + (maxSize - headerSize.x) / 2, /* Y */ min.y + textMarginY }, // pos - IM_COL32(252, 243, 5, 255), // colour - strAchievementUnlocked, // text - 2, // offset - 1.0f, // radius - IM_COL32(0, 0, 0, 255) // shadowColour - ); - - // Draw achievement name. - DrawTextWithShadow - ( - g_rodinFont, // font - fontSize, // fontSize - { /* X */ min.x + textMarginX + (maxSize - bodySize.x) / 2, /* Y */ min.y + textMarginY + bodySize.y + Scale(6) }, // pos - IM_COL32(255, 255, 255, 255), // colour - strAchievementName, // text - 2, // offset - 1.0f, // radius - IM_COL32(0, 0, 0, 255) // shadowColour - ); - - // Reset low quality text shader modifier. - SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); - } - else - { - s_isVisible = false; - } - - // Pop clip rect from DrawContainer. - drawList->PopClipRect(); + // Draw achievement icon. + drawList->AddImage + ( + g_xdbfTextureCache[g_achievement.ID], + { /* X */ min.x + imageMarginX, /* Y */ min.y + imageMarginY }, + { /* X */ min.x + imageMarginX + imageSize, /* Y */ min.y + imageMarginY + imageSize }, + { 0, 0 }, + { 1, 1 }, + IM_COL32(255, 255, 255, 255) + ); + + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); + + auto colourMotion = BREATHE_MOTION(1.0f, 0.0f, g_appearTime, 0.9f); + auto headerColour = ColourLerp(IM_COL32_WHITE, IM_COL32(255, 153, 0, 255), colourMotion); + + // Draw header text. + drawList->AddText + ( + g_pFntRodin, + fontSize, + { /* X */ min.x + textMarginX + (maxSize - headerSize.x) / 2, /* Y */ min.y + textMarginY }, + headerColour, + strAchievementUnlocked + ); + + // Draw achievement name. + drawList->AddText + ( + g_pFntRodin, + fontSize, + { /* X */ min.x + textMarginX + (maxSize - nameSize.x) / 2, /* Y */ min.y + textMarginY + nameSize.y + Scale(6) }, + IM_COL32(255, 255, 255, 255), + strAchievementName + ); + + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); } + else if (windowMotion <= 0.0 && g_isClosing) + { + s_isVisible = false; + } + + drawList->PopClipRect(); } void AchievementOverlay::Open(int id) diff --git a/MarathonRecomp/ui/achievement_overlay.h b/MarathonRecomp/ui/achievement_overlay.h index 8543dd78..509f285f 100644 --- a/MarathonRecomp/ui/achievement_overlay.h +++ b/MarathonRecomp/ui/achievement_overlay.h @@ -9,7 +9,6 @@ class AchievementOverlay static inline std::queue s_queue{}; - static void Init(); static void Draw(); static void Open(int id); static void Close(); diff --git a/MarathonRecomp/ui/imgui_utils.cpp b/MarathonRecomp/ui/imgui_utils.cpp index d1433aba..d617d17c 100644 --- a/MarathonRecomp/ui/imgui_utils.cpp +++ b/MarathonRecomp/ui/imgui_utils.cpp @@ -234,7 +234,7 @@ double ComputeLinearMotion(double time, double offset, double total, bool revers { auto result = std::clamp((ImGui::GetTime() - time - offset / 60.0) / total * 60.0, 0.0, 1.0); - return reverse ? 1.0f - result : result; + return reverse ? 1.0 - result : result; } double ComputeMotion(double time, double offset, double total, bool reverse) @@ -726,6 +726,132 @@ void DrawVersionString(const ImU32 colour) drawList->AddText(g_pFntNewRodin, fontSize, { res.x - textSize.x - textMargin, textY }, colour, g_versionString); } +static void DrawWindowArrow(const ImVec2 pos, float scale, float rotation, uint32_t colour) +{ + auto arrowRadius = Scale(63.0f * scale, true); + + std::array vertices = + { + pos, // Top Left + { pos.x + arrowRadius, pos.y }, // Top Right + { pos.x + arrowRadius, pos.y + arrowRadius }, // Bottom Right + { pos.x, pos.y + arrowRadius } // Bottom Left + }; + + // Adjust base rotation, since the texture + // points the arrow to the bottom left. + auto adjRotation = rotation + 90.0f; + + auto radians = adjRotation * (IM_PI / 180.0f); + auto c = cosf(radians); + auto s = sinf(radians); + + auto& pivot = vertices[3]; + + // Rotate around bottom left. + for (auto& v : vertices) + { + float dx = v.x - pivot.x; + float dy = v.y - pivot.y; + + v.x = pivot.x + dx * c - dy * s; + v.y = pivot.y + dx * s + dy * c; + } + + // Adjust height to pivot. + for (auto& v : vertices) + v.y -= arrowRadius; + + auto drawList = ImGui::GetBackgroundDrawList(); + auto arrowUVs = PIXELS_TO_UV_COORDS(128, 128, 65, 0, 63, 63); + + auto& uvMin = std::get<0>(arrowUVs); + auto& uvMax = std::get<1>(arrowUVs); + + drawList->AddImageQuad(g_upTexWindow.get(), vertices[0], vertices[1], vertices[2], vertices[3], uvMin, { uvMax.x, uvMin.y }, { uvMax.x, uvMax.y }, { uvMin.x, uvMax.y }, colour); +} + +double DrawWindow(const ImVec2 min, const ImVec2 max, bool isAnimated, double time, bool isClosing) +{ + auto drawList = ImGui::GetBackgroundDrawList(); + auto motionTime = 1.0; + + auto _min = min; + auto _max = max; + + if (isAnimated) + { + motionTime = ComputeLinearMotion(time, 0, 8, isClosing); + + auto centre = ImVec2{ min.x + ((max.x - min.x) / 2), min.y + ((max.y - min.y) / 2) }; + + _min = Lerp(centre, min, motionTime); + _max = Lerp(centre, max, motionTime); + } + + constexpr auto containerTopColour = IM_COL32(20, 56, 130, 200); + constexpr auto containerBottomColour = IM_COL32(8, 22, 51, 200); + + drawList->AddRectFilledMultiColor(_min, _max, containerTopColour, containerTopColour, containerBottomColour, containerBottomColour); + + auto lineHorzUVs = PIXELS_TO_UV_COORDS(128, 128, 2, 0, 60, 5); + auto lineVertUVs = PIXELS_TO_UV_COORDS(128, 128, 0, 66, 5, 60); + + auto lineScale = Scale(1, true); + auto lineOffsetRight = Scale(3, true); + + // Top + drawList->AddImage(g_upTexWindow.get(), _min, { _max.x, _min.y + lineScale }, GET_UV_COORDS(lineHorzUVs)); + + // Bottom + drawList->AddImage(g_upTexWindow.get(), { _min.x, _max.y - lineOffsetRight }, { _max.x, (_max.y - lineOffsetRight) + lineScale }, GET_UV_COORDS(lineHorzUVs)); + + // Left + drawList->AddImage(g_upTexWindow.get(), _min, { _min.x + lineScale, _max.y }, GET_UV_COORDS(lineVertUVs)); + + // Right + drawList->AddImage(g_upTexWindow.get(), { _max.x - lineOffsetRight, _min.y }, { (_max.x - lineOffsetRight) + lineScale, _max.y }, GET_UV_COORDS(lineVertUVs)); + + SetAdditive(true); + + constexpr auto arrowPixelRadius = 63.0f; + constexpr auto arrowInnerScale = 0.16f; + constexpr auto arrowOuterScale = 0.225f; + constexpr auto arrowOuterColour = IM_COL32(255, 255, 255, 45); + + auto arrowOuterOffset = Scale(arrowPixelRadius * arrowOuterScale, true) / 2; + + // Top Left (Inner) + DrawWindowArrow(_min, arrowInnerScale, 0.0f, containerTopColour); + + // Top Right (Inner) + DrawWindowArrow({ _max.x, _min.y }, arrowInnerScale, 90.0f, containerTopColour); + + // Bottom Right (Inner) + DrawWindowArrow(_max, arrowInnerScale, 180.0f, containerTopColour); + + // Bottom Left (Inner) + DrawWindowArrow({ _min.x, _max.y }, arrowInnerScale, 270.0f, containerTopColour); + + // Top Left (Outer) + DrawWindowArrow({ _min.x - arrowOuterOffset, _min.y - arrowOuterOffset }, arrowOuterScale, 0.0f, arrowOuterColour); + + // Top Right (Outer) + DrawWindowArrow({ _max.x + arrowOuterOffset, _min.y - arrowOuterOffset }, arrowOuterScale, 90.0f, arrowOuterColour); + + // Bottom Right (Outer) + DrawWindowArrow({ _max.x + arrowOuterOffset, _max.y + arrowOuterOffset }, arrowOuterScale, 180.0f, arrowOuterColour); + + // Bottom Left (Outer) + DrawWindowArrow({ _min.x - arrowOuterOffset, _max.y + arrowOuterOffset }, arrowOuterScale, 270.0f, arrowOuterColour); + + ResetAdditive(); + + drawList->PushClipRect(_min, _max); + + return motionTime; +} + // Taken from ImGui because we need to modify to break for '\u200B\ too // Simple word-wrapping for English, not full-featured. Please submit failing cases! // This will return the next location to wrap from. If no wrapping if necessary, this will fast-forward to e.g. text_end. diff --git a/MarathonRecomp/ui/imgui_utils.h b/MarathonRecomp/ui/imgui_utils.h index 1af397b8..795fd729 100644 --- a/MarathonRecomp/ui/imgui_utils.h +++ b/MarathonRecomp/ui/imgui_utils.h @@ -102,6 +102,7 @@ float Hermite(float a, float b, float t); ImVec2 Lerp(const ImVec2& a, const ImVec2& b, float t); ImU32 ColourLerp(ImU32 c0, ImU32 c1, float t); void DrawVersionString(const ImU32 colour = IM_COL32(255, 255, 255, 70)); +double DrawWindow(const ImVec2 min, const ImVec2 max, bool isAnimated = false, double time = 0.0, bool isClosing = false); const char* CalcWordWrapPositionA(const ImFont* font, float scale, const char* text, const char* text_end, float wrap_width); ImVec2 MeasureInterpolatedText(const ImFont* pFont, float fontSize, const char* pText, ImGuiTextInterpData* pInterpData = nullptr); void DrawInterpolatedText(const ImFont* pFont, float fontSize, const ImVec2& pos, ImU32 colour, const char* pText, ImGuiTextInterpData* pInterpData = nullptr); diff --git a/MarathonRecomp/ui/message_window.cpp b/MarathonRecomp/ui/message_window.cpp index 96023d4d..2b7c93f2 100644 --- a/MarathonRecomp/ui/message_window.cpp +++ b/MarathonRecomp/ui/message_window.cpp @@ -135,116 +135,6 @@ class SDLEventListenerForMessageWindow : public SDLEventListener } g_sdlEventListenerForMessageWindow; -void DrawContainerArrow(const ImVec2 pos, float scale, float rotation, uint32_t colour) -{ - auto arrowRadius = Scale(63.0f * scale, true); - - std::array vertices = - { - pos, // Top Left - { pos.x + arrowRadius, pos.y }, // Top Right - { pos.x + arrowRadius, pos.y + arrowRadius }, // Bottom Right - { pos.x, pos.y + arrowRadius } // Bottom Left - }; - - // Adjust base rotation, since the texture - // points the arrow to the bottom left. - auto adjRotation = rotation + 90.0f; - - auto radians = adjRotation * (IM_PI / 180.0f); - auto c = cosf(radians); - auto s = sinf(radians); - - auto& pivot = vertices[3]; - - // Rotate around bottom left. - for (auto& v : vertices) - { - float dx = v.x - pivot.x; - float dy = v.y - pivot.y; - - v.x = pivot.x + dx * c - dy * s; - v.y = pivot.y + dx * s + dy * c; - } - - // Adjust height to pivot. - for (auto& v : vertices) - v.y -= arrowRadius; - - auto drawList = ImGui::GetBackgroundDrawList(); - auto arrowUVs = PIXELS_TO_UV_COORDS(128, 128, 65, 0, 63, 63); - - auto& uvMin = std::get<0>(arrowUVs); - auto& uvMax = std::get<1>(arrowUVs); - - drawList->AddImageQuad(g_upTexWindow.get(), vertices[0], vertices[1], vertices[2], vertices[3], uvMin, { uvMax.x, uvMin.y }, { uvMax.x, uvMax.y }, { uvMin.x, uvMax.y }, colour); -} - -void DrawContainer(const ImVec2 min, const ImVec2 max) -{ - auto drawList = ImGui::GetBackgroundDrawList(); - - constexpr auto containerTopColour = IM_COL32(20, 56, 130, 200); - constexpr auto containerBottomColour = IM_COL32(8, 22, 51, 200); - - drawList->AddRectFilledMultiColor(min, max, containerTopColour, containerTopColour, containerBottomColour, containerBottomColour); - - auto lineHorzUVs = PIXELS_TO_UV_COORDS(128, 128, 2, 0, 60, 5); - auto lineVertUVs = PIXELS_TO_UV_COORDS(128, 128, 0, 66, 5, 60); - - auto lineScale = Scale(1, true); - auto lineOffsetRight = Scale(3, true); - - // Top - drawList->AddImage(g_upTexWindow.get(), min, { max.x, min.y + lineScale }, GET_UV_COORDS(lineHorzUVs)); - - // Bottom - drawList->AddImage(g_upTexWindow.get(), { min.x, max.y - lineOffsetRight }, { max.x, (max.y - lineOffsetRight) + lineScale }, GET_UV_COORDS(lineHorzUVs)); - - // Left - drawList->AddImage(g_upTexWindow.get(), min, { min.x + lineScale, max.y }, GET_UV_COORDS(lineVertUVs)); - - // Right - drawList->AddImage(g_upTexWindow.get(), { max.x - lineOffsetRight, min.y }, { (max.x - lineOffsetRight) + lineScale, max.y }, GET_UV_COORDS(lineVertUVs)); - - SetAdditive(true); - - constexpr auto arrowPixelRadius = 63.0f; - constexpr auto arrowInnerScale = 0.16f; - constexpr auto arrowOuterScale = 0.225f; - constexpr auto arrowOuterColour = IM_COL32(255, 255, 255, 45); - - auto arrowOuterOffset = Scale(arrowPixelRadius * arrowOuterScale, true) / 2; - - // Top Left (Inner) - DrawContainerArrow(min, arrowInnerScale, 0.0f, containerTopColour); - - // Top Right (Inner) - DrawContainerArrow({ max.x, min.y }, arrowInnerScale, 90.0f, containerTopColour); - - // Bottom Right (Inner) - DrawContainerArrow(max, arrowInnerScale, 180.0f, containerTopColour); - - // Bottom Left (Inner) - DrawContainerArrow({ min.x, max.y }, arrowInnerScale, 270.0f, containerTopColour); - - // Top Left (Outer) - DrawContainerArrow({ min.x - arrowOuterOffset, min.y - arrowOuterOffset }, arrowOuterScale, 0.0f, arrowOuterColour); - - // Top Right (Outer) - DrawContainerArrow({ max.x + arrowOuterOffset, min.y - arrowOuterOffset }, arrowOuterScale, 90.0f, arrowOuterColour); - - // Bottom Right (Outer) - DrawContainerArrow({ max.x + arrowOuterOffset, max.y + arrowOuterOffset }, arrowOuterScale, 180.0f, arrowOuterColour); - - // Bottom Left (Outer) - DrawContainerArrow({ min.x - arrowOuterOffset, max.y + arrowOuterOffset }, arrowOuterScale, 270.0f, arrowOuterColour); - - ResetAdditive(); - - drawList->PushClipRect(min, max); -} - void DrawButton(int rowIndex, float yOffset, float yPadding, float width, float height, std::string& text) { auto drawList = ImGui::GetBackgroundDrawList(); @@ -349,7 +239,7 @@ void MessageWindow::Draw() ImVec2 msgMax = { msgMin.x + Scale(1088, true), msgMin.y + Scale(384, true) }; ImVec2 msgCentre = { (msgMin.x / 2) + (msgMax.x / 2), (msgMin.y / 2) + (msgMax.y / 2) }; - DrawContainer(msgMin, msgMax); + DrawWindow(msgMin, msgMax); // Use low quality text when the game is booted to not clash with existing UI. if (App::s_isInit) @@ -369,7 +259,7 @@ void MessageWindow::Draw() ImVec2 selMin = { msgMin.x, msgMax.y + ((msgMin.y - g_vertCentre) / 2) }; ImVec2 selMax = { msgMax.x, selMin.y + Scale(128, true) }; - DrawContainer(selMin, selMax); + DrawWindow(selMin, selMax); auto rowCount = 0; auto windowMarginX = Scale(36, true); diff --git a/MarathonRecomp/user/achievement_data.cpp b/MarathonRecomp/user/achievement_data.cpp index 2369b0df..dd734659 100644 --- a/MarathonRecomp/user/achievement_data.cpp +++ b/MarathonRecomp/user/achievement_data.cpp @@ -11,7 +11,7 @@ bool AchievementData::VerifySignature() const bool AchievementData::VerifyVersion() const { - return Version == AchVersion ACH_VERSION; + return Version <= ACH_VERSION; } bool AchievementData::VerifyChecksum() diff --git a/MarathonRecomp/user/achievement_data.h b/MarathonRecomp/user/achievement_data.h index bd38d219..f5a33cb3 100644 --- a/MarathonRecomp/user/achievement_data.h +++ b/MarathonRecomp/user/achievement_data.h @@ -2,29 +2,14 @@ #include -#define ACH_FILENAME "ACH-DATA" +#define ACH_FILENAME "SonicNextAchievementData.bin" #define ACH_SIGNATURE { 'A', 'C', 'H', ' ' } -#define ACH_VERSION { 1, 0, 0 } -#define ACH_RECORDS 50 +#define ACH_VERSION 1 +#define ACH_RECORDS 23 class AchievementData { public: - struct AchVersion - { - uint8_t Major; - uint8_t Minor; - uint8_t Revision; - uint8_t Reserved; - - bool operator==(const AchVersion& other) const - { - return Major == other.Major && - Minor == other.Minor && - Revision == other.Revision; - } - }; - #pragma pack(push, 1) struct AchRecord { @@ -35,10 +20,10 @@ class AchievementData #pragma pack(pop) char Signature[4] ACH_SIGNATURE; - AchVersion Version ACH_VERSION; - uint32_t Checksum; - uint32_t Reserved; - AchRecord Records[ACH_RECORDS]; + uint32_t Version{ ACH_VERSION }; + uint32_t Checksum{}; + uint32_t Reserved{}; + AchRecord Records[ACH_RECORDS]{}; bool VerifySignature() const; bool VerifyVersion() const; diff --git a/MarathonRecomp/user/achievement_manager.cpp b/MarathonRecomp/user/achievement_manager.cpp index 90759765..c6061dc1 100644 --- a/MarathonRecomp/user/achievement_manager.cpp +++ b/MarathonRecomp/user/achievement_manager.cpp @@ -69,16 +69,8 @@ void AchievementManager::Unlock(uint16_t id) void AchievementManager::UnlockAll() { - for (uint16_t i = 24; i <= 83; i++) - { - if (i == 30) - i = 31; - - if (i == 55) - i = 64; - + for (uint16_t i = 1; i <= 23; i++) AchievementManager::Unlock(i); - } } void AchievementManager::Reset() @@ -86,11 +78,11 @@ void AchievementManager::Reset() Data = {}; } -void AchievementManager::Load() +bool AchievementManager::LoadBinary() { AchievementManager::Reset(); - Status = EAchStatus::Success; + BinStatus = EAchBinStatus::Success; auto dataPath = GetDataPath(true); @@ -100,7 +92,7 @@ void AchievementManager::Load() dataPath = GetDataPath(false); if (!std::filesystem::exists(dataPath)) - return; + return true; } std::error_code ec; @@ -109,16 +101,16 @@ void AchievementManager::Load() if (fileSize != dataSize) { - Status = EAchStatus::BadFileSize; - return; + BinStatus = EAchBinStatus::BadFileSize; + return false; } std::ifstream file(dataPath, std::ios::binary); if (!file) { - Status = EAchStatus::IOError; - return; + BinStatus = EAchBinStatus::IOError; + return false; } AchievementData data{}; @@ -127,19 +119,18 @@ void AchievementManager::Load() if (!data.VerifySignature()) { - Status = EAchStatus::BadSignature; + BinStatus = EAchBinStatus::BadSignature; file.close(); - return; + return false; } file.read((char*)&data.Version, sizeof(data.Version)); - // TODO: upgrade in future if the version changes. if (!data.VerifyVersion()) { - Status = EAchStatus::BadVersion; + BinStatus = EAchBinStatus::BadVersion; file.close(); - return; + return false; } file.seekg(0); @@ -147,22 +138,24 @@ void AchievementManager::Load() if (!data.VerifyChecksum()) { - Status = EAchStatus::BadChecksum; + BinStatus = EAchBinStatus::BadChecksum; file.close(); - return; + return false; } file.close(); memcpy(&Data, &data, dataSize); + + return true; } -void AchievementManager::Save(bool ignoreStatus) +bool AchievementManager::SaveBinary(bool ignoreStatus) { - if (!ignoreStatus && Status != EAchStatus::Success) + if (!ignoreStatus && BinStatus != EAchBinStatus::Success) { LOGN_WARNING("Achievement data will not be saved in this session!"); - return; + return false; } LOGN("Saving achievements..."); @@ -172,7 +165,7 @@ void AchievementManager::Save(bool ignoreStatus) if (!file) { LOGN_ERROR("Failed to write achievement data."); - return; + return false; } Data.Checksum = Data.CalculateChecksum(); @@ -180,5 +173,7 @@ void AchievementManager::Save(bool ignoreStatus) file.write((const char*)&Data, sizeof(AchievementData)); file.close(); - Status = EAchStatus::Success; + BinStatus = EAchBinStatus::Success; + + return true; } diff --git a/MarathonRecomp/user/achievement_manager.h b/MarathonRecomp/user/achievement_manager.h index eab9c829..4bff7577 100644 --- a/MarathonRecomp/user/achievement_manager.h +++ b/MarathonRecomp/user/achievement_manager.h @@ -2,7 +2,7 @@ #include -enum class EAchStatus +enum class EAchBinStatus { Success, IOError, @@ -16,7 +16,7 @@ class AchievementManager { public: static inline AchievementData Data{}; - static inline EAchStatus Status{}; + static inline EAchBinStatus BinStatus{ EAchBinStatus::Success }; static std::filesystem::path GetDataPath(bool checkForMods) { @@ -29,6 +29,6 @@ class AchievementManager static void Unlock(uint16_t id); static void UnlockAll(); static void Reset(); - static void Load(); - static void Save(bool ignoreStatus = false); + static bool LoadBinary(); + static bool SaveBinary(bool ignoreStatus = false); }; diff --git a/MarathonRecompLib/config/Marathon.toml b/MarathonRecompLib/config/Marathon.toml index fb3a8321..93197774 100644 --- a/MarathonRecompLib/config/Marathon.toml +++ b/MarathonRecompLib/config/Marathon.toml @@ -731,3 +731,8 @@ jump_address_on_true = 0x8226AB70 name = "InfiniteLives" address = 0x821857B0 jump_address_on_true = 0x821857B4 + +[[midasm_hook]] +name = "UnlockAchievement" +address = 0x825B0960 +registers = ["r29"] From 98f0d40207653677244ef9b78ab21ea8e8392b98 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Sat, 8 Nov 2025 14:13:19 +0000 Subject: [PATCH 02/29] Implemented achievements menu --- MarathonRecomp/CMakeLists.txt | 3 +- MarathonRecomp/api/Marathon.h | 20 +- MarathonRecomp/api/Sonicteam/HUDGoldMedal.h | 12 + MarathonRecomp/api/Sonicteam/HUDMainMenu.h | 5 +- MarathonRecomp/api/Sonicteam/MainMenuTask.h | 21 +- .../Message/Camera/Cameraman/MsgChangeMode.h | 19 + .../HUDButtonWindow/MsgChangeButtons.h | 16 + .../Message/HUDGoldMedal/MsgChangeState.h | 24 + .../Message/HUDMainMenu/MsgChangeState.h | 14 + .../Message/HUDMainMenu/MsgSetCursor.h | 27 + .../Message/HUDMainMenu/MsgTransition.h | 27 + .../Message/Mission/MsgGetGlobalFlag.h | 17 + .../Message/MsgCameramanChangeMode.h | 19 - .../Message/MsgHUDButtonWindowChangeButtons.h | 16 - .../Message/MsgHUDMainMenuChangeState.h | 14 - .../Message/MsgHUDMainMenuSetCursor.h | 27 - .../Message/MsgHUDMainMenuTransition.h | 27 - .../Message/MsgMissionGetGlobalFlag.h | 17 - .../Message/MsgObjJump123GetNextPoint.h | 17 - .../Sonicteam/Message/MsgPauseAdapterText.h | 18 - .../Message/ObjJump123/MsgGetNextPoint.h | 17 + .../Message/PauseAdapter/MsgGetText.h | 17 + .../Message/{ => Player}/MsgSuckPlayer.h | 4 +- .../api/Sonicteam/SoX/Input/Manager.h | 8 +- MarathonRecomp/gpu/video.cpp | 12 +- MarathonRecomp/kernel/xdbf.h | 4 +- MarathonRecomp/locale/locale.cpp | 79 +- .../patches/MainMenuTask_patches.cpp | 92 +- MarathonRecomp/patches/MainMenuTask_patches.h | 1 + .../patches/SaveDataTask_patches.cpp | 2 - MarathonRecomp/patches/TitleTask_patches.cpp | 30 +- .../patches/aspect_ratio_patches.cpp | 2 +- MarathonRecomp/patches/fps_patches.cpp | 4 +- MarathonRecomp/patches/misc_patches.cpp | 6 +- MarathonRecomp/patches/pause_patches.cpp | 4 +- MarathonRecomp/patches/player_patches.cpp | 22 +- MarathonRecomp/patches/text_patches.cpp | 20 + MarathonRecomp/patches/text_patches.h | 8 +- MarathonRecomp/ui/achievement_menu.cpp | 984 +++++++----------- MarathonRecomp/ui/achievement_menu.h | 41 +- MarathonRecomp/ui/common_menu.cpp | 219 ++-- MarathonRecomp/ui/common_menu.h | 1 + MarathonRecomp/ui/imgui_utils.cpp | 41 + MarathonRecomp/ui/imgui_utils.h | 1 + MarathonRecomp/ui/message_window.cpp | 4 +- MarathonRecomp/ui/options_menu.cpp | 58 +- MarathonRecompLib/config/Marathon.toml | 5 + MarathonRecompResources | 2 +- 48 files changed, 997 insertions(+), 1051 deletions(-) create mode 100644 MarathonRecomp/api/Sonicteam/HUDGoldMedal.h create mode 100644 MarathonRecomp/api/Sonicteam/Message/Camera/Cameraman/MsgChangeMode.h create mode 100644 MarathonRecomp/api/Sonicteam/Message/HUDButtonWindow/MsgChangeButtons.h create mode 100644 MarathonRecomp/api/Sonicteam/Message/HUDGoldMedal/MsgChangeState.h create mode 100644 MarathonRecomp/api/Sonicteam/Message/HUDMainMenu/MsgChangeState.h create mode 100644 MarathonRecomp/api/Sonicteam/Message/HUDMainMenu/MsgSetCursor.h create mode 100644 MarathonRecomp/api/Sonicteam/Message/HUDMainMenu/MsgTransition.h create mode 100644 MarathonRecomp/api/Sonicteam/Message/Mission/MsgGetGlobalFlag.h delete mode 100644 MarathonRecomp/api/Sonicteam/Message/MsgCameramanChangeMode.h delete mode 100644 MarathonRecomp/api/Sonicteam/Message/MsgHUDButtonWindowChangeButtons.h delete mode 100644 MarathonRecomp/api/Sonicteam/Message/MsgHUDMainMenuChangeState.h delete mode 100644 MarathonRecomp/api/Sonicteam/Message/MsgHUDMainMenuSetCursor.h delete mode 100644 MarathonRecomp/api/Sonicteam/Message/MsgHUDMainMenuTransition.h delete mode 100644 MarathonRecomp/api/Sonicteam/Message/MsgMissionGetGlobalFlag.h delete mode 100644 MarathonRecomp/api/Sonicteam/Message/MsgObjJump123GetNextPoint.h delete mode 100644 MarathonRecomp/api/Sonicteam/Message/MsgPauseAdapterText.h create mode 100644 MarathonRecomp/api/Sonicteam/Message/ObjJump123/MsgGetNextPoint.h create mode 100644 MarathonRecomp/api/Sonicteam/Message/PauseAdapter/MsgGetText.h rename MarathonRecomp/api/Sonicteam/Message/{ => Player}/MsgSuckPlayer.h (76%) diff --git a/MarathonRecomp/CMakeLists.txt b/MarathonRecomp/CMakeLists.txt index 93c350c2..c02239e1 100644 --- a/MarathonRecomp/CMakeLists.txt +++ b/MarathonRecomp/CMakeLists.txt @@ -538,7 +538,6 @@ set(RESOURCES_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/res") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/bc_diff/button_bc_diff.bin" DEST_FILE "${RESOURCES_OUTPUT_PATH}/bc_diff/button_bc_diff.bin" ARRAY_NAME "g_button_bc_diff" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.bin" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.bin" ARRAY_NAME "g_im_font_atlas" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.dds" ARRAY_NAME "g_im_font_atlas_texture" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/achievements_menu/trophy.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/achievements_menu/trophy.dds" ARRAY_NAME "g_trophy" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/button_window.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/button_window.dds" ARRAY_NAME "g_button_window" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/controller.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/controller.dds" ARRAY_NAME "g_controller" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/sonicnext-dev.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/sonicnext-dev.dds" ARRAY_NAME "g_sonicnextdev" COMPRESSION_TYPE "zstd") @@ -549,7 +548,7 @@ BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/com BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/main_menu9.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/main_menu9.dds" ARRAY_NAME "g_main_menu9" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/arrow.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/arrow.dds" ARRAY_NAME "g_arrow" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/window.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/window.dds" ARRAY_NAME "g_window" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/select_arrow.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/select_arrow.dds" ARRAY_NAME "g_select_arrow" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/select_arrow.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/select_arrow.dds" ARRAY_NAME "g_select_arrow" COMPRESSION_TYPE "zstd") ## Installer ## BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_001.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_001.dds" ARRAY_NAME "g_install_001" COMPRESSION_TYPE "zstd") diff --git a/MarathonRecomp/api/Marathon.h b/MarathonRecomp/api/Marathon.h index 811d11fd..ff87211d 100644 --- a/MarathonRecomp/api/Marathon.h +++ b/MarathonRecomp/api/Marathon.h @@ -43,6 +43,7 @@ #include "Sonicteam/Globals.h" #include "Sonicteam/HUDButtonWindow.h" #include "Sonicteam/HUDCALLBACK.h" +#include "Sonicteam/HUDGoldMedal.h" #include "Sonicteam/HUDLimitTime.h" #include "Sonicteam/HUDLoading.h" #include "Sonicteam/HUDMainDisplay.h" @@ -58,15 +59,16 @@ #include "Sonicteam/MainDisplayTask.h" #include "Sonicteam/MainMenuTask.h" #include "Sonicteam/MainMode.h" -#include "Sonicteam/Message/MsgCameramanChangeMode.h" -#include "Sonicteam/Message/MsgHUDButtonWindowChangeButtons.h" -#include "Sonicteam/Message/MsgHUDMainMenuChangeState.h" -#include "Sonicteam/Message/MsgHUDMainMenuSetCursor.h" -#include "Sonicteam/Message/MsgHUDMainMenuTransition.h" -#include "Sonicteam/Message/MsgMissionGetGlobalFlag.h" -#include "Sonicteam/Message/MsgObjJump123GetNextPoint.h" -#include "Sonicteam/Message/MsgPauseAdapterText.h" -#include "Sonicteam/Message/MsgSuckPlayer.h" +#include "Sonicteam/Message/Camera/Cameraman/MsgChangeMode.h" +#include "Sonicteam/Message/HUDButtonWindow/MsgChangeButtons.h" +#include "Sonicteam/Message/HUDGoldMedal/MsgChangeState.h" +#include "Sonicteam/Message/HUDMainMenu/MsgChangeState.h" +#include "Sonicteam/Message/HUDMainMenu/MsgSetCursor.h" +#include "Sonicteam/Message/HUDMainMenu/MsgTransition.h" +#include "Sonicteam/Message/Mission/MsgGetGlobalFlag.h" +#include "Sonicteam/Message/ObjJump123/MsgGetNextPoint.h" +#include "Sonicteam/Message/PauseAdapter/MsgGetText.h" +#include "Sonicteam/Message/Player/MsgSuckPlayer.h" #include "Sonicteam/MessageWindowTask.h" #include "Sonicteam/Mission/Core.h" #include "Sonicteam/MovieObject.h" diff --git a/MarathonRecomp/api/Sonicteam/HUDGoldMedal.h b/MarathonRecomp/api/Sonicteam/HUDGoldMedal.h new file mode 100644 index 00000000..340ce873 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/HUDGoldMedal.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace Sonicteam +{ + class HUDGoldMedal : public SoX::RefCountObject, public SoX::Engine::Task + { + public: + xpointer m_pCsdObject; + }; +} diff --git a/MarathonRecomp/api/Sonicteam/HUDMainMenu.h b/MarathonRecomp/api/Sonicteam/HUDMainMenu.h index ea005c17..b4663aab 100644 --- a/MarathonRecomp/api/Sonicteam/HUDMainMenu.h +++ b/MarathonRecomp/api/Sonicteam/HUDMainMenu.h @@ -12,9 +12,12 @@ namespace Sonicteam enum HUDMainMenuState : uint32_t { HUDMainMenuState_OptionsOutro = 5, + HUDMainMenuState_SinglePlayerTextOutro = 8, + HUDMainMenuState_SinglePlayerCursorIntro = 86, HUDMainMenuState_OptionsIntro = 90, HUDMainMenuState_MainCursorIntro = 98, - HUDMainMenuState_MainCursorOutro = 99 + HUDMainMenuState_MainCursorOutro = 99, + HUDMainMenuState_GoldMedalResultsCursorOutro = 101 }; MARATHON_INSERT_PADDING(0x20); diff --git a/MarathonRecomp/api/Sonicteam/MainMenuTask.h b/MarathonRecomp/api/Sonicteam/MainMenuTask.h index 8752f17d..cd69ba86 100644 --- a/MarathonRecomp/api/Sonicteam/MainMenuTask.h +++ b/MarathonRecomp/api/Sonicteam/MainMenuTask.h @@ -26,7 +26,8 @@ namespace Sonicteam MainMenuState_Tag = 0x1E, MainMenuState_Tag1PSelect = 0x1F, MainMenuState_Battle = 0x22, - MainMenuState_GoldMedalResults = 0x26, + MainMenuState_GoldMedalResultsOpen = 0x26, + MainMenuState_GoldMedalResults = 0x27, MainMenuState_AudioRoom = 0x2F, MainMenuState_TheaterRoom = 0x31, MainMenuState_Options = 0x33, @@ -37,11 +38,18 @@ namespace Sonicteam be m_State; MARATHON_INSERT_PADDING(0x24); xpointer m_pHUDMainMenu; - MARATHON_INSERT_PADDING(0x20); + MARATHON_INSERT_PADDING(8); + xpointer m_pHUDGoldMedal; + MARATHON_INSERT_PADDING(0x14); xpointer m_pButtonWindowTask; - MARATHON_INSERT_PADDING(4); + xpointer m_pMainMenuExpositionTask; be m_MainMenuSelectedIndex; - MARATHON_INSERT_PADDING(0x1D8); + be m_SinglePlayerSelectedIndex; + MARATHON_INSERT_PADDING(0x78); + be m_GoldMedalEpisodeIndex; + MARATHON_INSERT_PADDING(0x14C); + be m_IsChangingState; + MARATHON_INSERT_PADDING(8); be m_PressedButtons; MARATHON_INSERT_PADDING(0x18); xpointer m_Field298; @@ -51,8 +59,13 @@ namespace Sonicteam MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_State, 0x4C); MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_pHUDMainMenu, 0x74); + MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_pHUDGoldMedal, 0x80); MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_pButtonWindowTask, 0x98); + MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_pMainMenuExpositionTask, 0x9C); MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_MainMenuSelectedIndex, 0xA0); + MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_SinglePlayerSelectedIndex, 0xA4); + MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_GoldMedalEpisodeIndex, 0x120); + MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_IsChangingState, 0x270); MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_PressedButtons, 0x27C); MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_Field298, 0x298); MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_apSelectCharacters, 0x29C); diff --git a/MarathonRecomp/api/Sonicteam/Message/Camera/Cameraman/MsgChangeMode.h b/MarathonRecomp/api/Sonicteam/Message/Camera/Cameraman/MsgChangeMode.h new file mode 100644 index 00000000..94875ff6 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/Message/Camera/Cameraman/MsgChangeMode.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +namespace Sonicteam::Message::Camera::Cameraman +{ + struct MsgChangeMode : SoX::Message<0x14007> + { + be PadID{}; + be TargetActorID{}; + bool IsDemoCamera{}; + }; + + MARATHON_ASSERT_OFFSETOF(MsgChangeMode, PadID, 0x04); + MARATHON_ASSERT_OFFSETOF(MsgChangeMode, TargetActorID, 0x08); + MARATHON_ASSERT_OFFSETOF(MsgChangeMode, IsDemoCamera, 0x0C); +} diff --git a/MarathonRecomp/api/Sonicteam/Message/HUDButtonWindow/MsgChangeButtons.h b/MarathonRecomp/api/Sonicteam/Message/HUDButtonWindow/MsgChangeButtons.h new file mode 100644 index 00000000..3be9ee24 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/Message/HUDButtonWindow/MsgChangeButtons.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +namespace Sonicteam::Message::HUDButtonWindow +{ + struct MsgChangeButtons : SoX::Message<0x1B05B> + { + be ButtonType{}; + + MsgChangeButtons(uint32_t buttonType = 0) : ButtonType(buttonType) {} + }; + + MARATHON_ASSERT_OFFSETOF(MsgChangeButtons, ButtonType, 0x04); +} diff --git a/MarathonRecomp/api/Sonicteam/Message/HUDGoldMedal/MsgChangeState.h b/MarathonRecomp/api/Sonicteam/Message/HUDGoldMedal/MsgChangeState.h new file mode 100644 index 00000000..25696600 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/Message/HUDGoldMedal/MsgChangeState.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +namespace Sonicteam::Message::HUDGoldMedal +{ + struct MsgChangeState : SoX::Message<0x1B058> + { + be State{}; + be Field08{}; + uint8_t Field0C{}; + be EpisodeIndex{}; + + MsgChangeState() {} + + MsgChangeState(uint32_t state, uint32_t episodeIndex = 0) : State(state), EpisodeIndex(episodeIndex) {} + }; + + MARATHON_ASSERT_OFFSETOF(MsgChangeState, State, 0x04); + MARATHON_ASSERT_OFFSETOF(MsgChangeState, Field08, 0x08); + MARATHON_ASSERT_OFFSETOF(MsgChangeState, Field0C, 0x0C); + MARATHON_ASSERT_OFFSETOF(MsgChangeState, EpisodeIndex, 0x10); +} diff --git a/MarathonRecomp/api/Sonicteam/Message/HUDMainMenu/MsgChangeState.h b/MarathonRecomp/api/Sonicteam/Message/HUDMainMenu/MsgChangeState.h new file mode 100644 index 00000000..34222d8f --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/Message/HUDMainMenu/MsgChangeState.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +namespace Sonicteam::Message::HUDMainMenu +{ + struct MsgChangeState : SoX::Message<0x1B053> + { + be State{}; + }; + + MARATHON_ASSERT_OFFSETOF(MsgChangeState, State, 0x04); +} diff --git a/MarathonRecomp/api/Sonicteam/Message/HUDMainMenu/MsgSetCursor.h b/MarathonRecomp/api/Sonicteam/Message/HUDMainMenu/MsgSetCursor.h new file mode 100644 index 00000000..10986606 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/Message/HUDMainMenu/MsgSetCursor.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace Sonicteam::Message::HUDMainMenu +{ + struct MsgSetCursor : MsgChangeState + { + be CursorIndex{}; + + MsgSetCursor() {} + + MsgSetCursor(uint32_t state) + { + State = state; + } + + MsgSetCursor(uint32_t state, uint32_t cursorIndex) + { + State = state; + CursorIndex = cursorIndex; + } + }; + + MARATHON_ASSERT_OFFSETOF(MsgSetCursor, CursorIndex, 0x08); +} diff --git a/MarathonRecomp/api/Sonicteam/Message/HUDMainMenu/MsgTransition.h b/MarathonRecomp/api/Sonicteam/Message/HUDMainMenu/MsgTransition.h new file mode 100644 index 00000000..c3ea4a99 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/Message/HUDMainMenu/MsgTransition.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace Sonicteam::Message::HUDMainMenu +{ + struct MsgTransition : MsgChangeState + { + be Flags{}; + + MsgTransition() {} + + MsgTransition(uint32_t state) + { + State = state; + } + + MsgTransition(uint32_t state, uint32_t flags) + { + State = state; + Flags = flags; + } + }; + + MARATHON_ASSERT_OFFSETOF(MsgTransition, Flags, 0x08); +} diff --git a/MarathonRecomp/api/Sonicteam/Message/Mission/MsgGetGlobalFlag.h b/MarathonRecomp/api/Sonicteam/Message/Mission/MsgGetGlobalFlag.h new file mode 100644 index 00000000..27d20177 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/Message/Mission/MsgGetGlobalFlag.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace Sonicteam::Message::Mission +{ + struct MsgGetGlobalFlag : SoX::Message<0x1E004> + { + be FlagID{}; + be FlagValue{}; + + MsgGetGlobalFlag(uint32_t flagId) : FlagID(flagId) {} + }; + + MARATHON_ASSERT_OFFSETOF(MsgGetGlobalFlag, FlagID, 0x04); + MARATHON_ASSERT_OFFSETOF(MsgGetGlobalFlag, FlagValue, 0x08); +} diff --git a/MarathonRecomp/api/Sonicteam/Message/MsgCameramanChangeMode.h b/MarathonRecomp/api/Sonicteam/Message/MsgCameramanChangeMode.h deleted file mode 100644 index 710a8d28..00000000 --- a/MarathonRecomp/api/Sonicteam/Message/MsgCameramanChangeMode.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace Sonicteam::Message -{ - struct MsgCameramanChangeMode : SoX::Message<0x14007> - { - be PadID; - be TargetActorID; - bool IsDemoCamera; - }; - - MARATHON_ASSERT_OFFSETOF(MsgCameramanChangeMode, PadID, 0x04); - MARATHON_ASSERT_OFFSETOF(MsgCameramanChangeMode, TargetActorID, 0x08); - MARATHON_ASSERT_OFFSETOF(MsgCameramanChangeMode, IsDemoCamera, 0x0C); -} diff --git a/MarathonRecomp/api/Sonicteam/Message/MsgHUDButtonWindowChangeButtons.h b/MarathonRecomp/api/Sonicteam/Message/MsgHUDButtonWindowChangeButtons.h deleted file mode 100644 index 6cf77e25..00000000 --- a/MarathonRecomp/api/Sonicteam/Message/MsgHUDButtonWindowChangeButtons.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include - -namespace Sonicteam::Message -{ - struct MsgHUDButtonWindowChangeButtons : SoX::Message<0x1B05B> - { - be ButtonType; - - MsgHUDButtonWindowChangeButtons(uint32_t buttonType = 0) : ButtonType(buttonType) {} - }; - - MARATHON_ASSERT_OFFSETOF(MsgHUDButtonWindowChangeButtons, ButtonType, 0x04); -} diff --git a/MarathonRecomp/api/Sonicteam/Message/MsgHUDMainMenuChangeState.h b/MarathonRecomp/api/Sonicteam/Message/MsgHUDMainMenuChangeState.h deleted file mode 100644 index 21490e77..00000000 --- a/MarathonRecomp/api/Sonicteam/Message/MsgHUDMainMenuChangeState.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include -#include - -namespace Sonicteam::Message -{ - struct MsgHUDMainMenuChangeState : SoX::Message<0x1B053> - { - be State; - }; - - MARATHON_ASSERT_OFFSETOF(MsgHUDMainMenuChangeState, State, 0x04); -} diff --git a/MarathonRecomp/api/Sonicteam/Message/MsgHUDMainMenuSetCursor.h b/MarathonRecomp/api/Sonicteam/Message/MsgHUDMainMenuSetCursor.h deleted file mode 100644 index a7fc568d..00000000 --- a/MarathonRecomp/api/Sonicteam/Message/MsgHUDMainMenuSetCursor.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include -#include - -namespace Sonicteam::Message -{ - struct MsgHUDMainMenuSetCursor : MsgHUDMainMenuChangeState - { - be CursorIndex; - - MsgHUDMainMenuSetCursor() {} - - MsgHUDMainMenuSetCursor(uint32_t state) - { - State = state; - } - - MsgHUDMainMenuSetCursor(uint32_t state, uint32_t cursorIndex) - { - State = state; - CursorIndex = cursorIndex; - } - }; - - MARATHON_ASSERT_OFFSETOF(MsgHUDMainMenuSetCursor, CursorIndex, 0x08); -} diff --git a/MarathonRecomp/api/Sonicteam/Message/MsgHUDMainMenuTransition.h b/MarathonRecomp/api/Sonicteam/Message/MsgHUDMainMenuTransition.h deleted file mode 100644 index 3708cfee..00000000 --- a/MarathonRecomp/api/Sonicteam/Message/MsgHUDMainMenuTransition.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include -#include - -namespace Sonicteam::Message -{ - struct MsgHUDMainMenuTransition : MsgHUDMainMenuChangeState - { - be Flags; - - MsgHUDMainMenuTransition() {} - - MsgHUDMainMenuTransition(uint32_t state) - { - State = state; - } - - MsgHUDMainMenuTransition(uint32_t state, uint32_t flags) - { - State = state; - Flags = flags; - } - }; - - MARATHON_ASSERT_OFFSETOF(MsgHUDMainMenuTransition, Flags, 0x08); -} diff --git a/MarathonRecomp/api/Sonicteam/Message/MsgMissionGetGlobalFlag.h b/MarathonRecomp/api/Sonicteam/Message/MsgMissionGetGlobalFlag.h deleted file mode 100644 index 06efc946..00000000 --- a/MarathonRecomp/api/Sonicteam/Message/MsgMissionGetGlobalFlag.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -namespace Sonicteam::Message -{ - struct MsgMissionGetGlobalFlag : SoX::Message<0x1E004> - { - be FlagID; - be FlagValue; - - MsgMissionGetGlobalFlag(uint32_t flagId) : FlagID(flagId) {} - }; - - MARATHON_ASSERT_OFFSETOF(MsgMissionGetGlobalFlag, FlagID, 0x04); - MARATHON_ASSERT_OFFSETOF(MsgMissionGetGlobalFlag, FlagValue, 0x08); -} diff --git a/MarathonRecomp/api/Sonicteam/Message/MsgObjJump123GetNextPoint.h b/MarathonRecomp/api/Sonicteam/Message/MsgObjJump123GetNextPoint.h deleted file mode 100644 index b778db84..00000000 --- a/MarathonRecomp/api/Sonicteam/Message/MsgObjJump123GetNextPoint.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace Sonicteam::Message -{ - struct MsgObjJump123GetNextPoint : SoX::Message<0x10007> - { - SoX::Math::Quaternion Rotation; - SoX::Math::Vector Position; - }; - - MARATHON_ASSERT_OFFSETOF(MsgObjJump123GetNextPoint, Rotation, 0x10); - MARATHON_ASSERT_OFFSETOF(MsgObjJump123GetNextPoint, Position, 0x20); -} diff --git a/MarathonRecomp/api/Sonicteam/Message/MsgPauseAdapterText.h b/MarathonRecomp/api/Sonicteam/Message/MsgPauseAdapterText.h deleted file mode 100644 index afe8644e..00000000 --- a/MarathonRecomp/api/Sonicteam/Message/MsgPauseAdapterText.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace Sonicteam::Message -{ - struct MsgPauseAdapterText : SoX::Message<0x1C003> - { - stdx::string PauseName; - stdx::string SelectedName; - }; - - MARATHON_ASSERT_OFFSETOF(MsgPauseAdapterText, PauseName, 0x04); - MARATHON_ASSERT_OFFSETOF(MsgPauseAdapterText, SelectedName, 0x20); -} diff --git a/MarathonRecomp/api/Sonicteam/Message/ObjJump123/MsgGetNextPoint.h b/MarathonRecomp/api/Sonicteam/Message/ObjJump123/MsgGetNextPoint.h new file mode 100644 index 00000000..1a61a372 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/Message/ObjJump123/MsgGetNextPoint.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +namespace Sonicteam::Message::ObjJump123 +{ + struct MsgGetNextPoint : SoX::Message<0x10007> + { + SoX::Math::Quaternion Rotation{}; + SoX::Math::Vector Position{}; + }; + + MARATHON_ASSERT_OFFSETOF(MsgGetNextPoint, Rotation, 0x10); + MARATHON_ASSERT_OFFSETOF(MsgGetNextPoint, Position, 0x20); +} diff --git a/MarathonRecomp/api/Sonicteam/Message/PauseAdapter/MsgGetText.h b/MarathonRecomp/api/Sonicteam/Message/PauseAdapter/MsgGetText.h new file mode 100644 index 00000000..1b18c491 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/Message/PauseAdapter/MsgGetText.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +namespace Sonicteam::Message::PauseAdapter +{ + struct MsgGetText : SoX::Message<0x1C003> + { + stdx::string PauseName{}; + stdx::string SelectedName{}; + }; + + MARATHON_ASSERT_OFFSETOF(MsgGetText, PauseName, 0x04); + MARATHON_ASSERT_OFFSETOF(MsgGetText, SelectedName, 0x20); +} diff --git a/MarathonRecomp/api/Sonicteam/Message/MsgSuckPlayer.h b/MarathonRecomp/api/Sonicteam/Message/Player/MsgSuckPlayer.h similarity index 76% rename from MarathonRecomp/api/Sonicteam/Message/MsgSuckPlayer.h rename to MarathonRecomp/api/Sonicteam/Message/Player/MsgSuckPlayer.h index 76fb0f70..50d7706b 100644 --- a/MarathonRecomp/api/Sonicteam/Message/MsgSuckPlayer.h +++ b/MarathonRecomp/api/Sonicteam/Message/Player/MsgSuckPlayer.h @@ -4,11 +4,11 @@ #include #include -namespace Sonicteam::Message +namespace Sonicteam::Message::Player { struct MsgSuckPlayer : SoX::Message<0x11034A> { - SoX::Math::Vector Point; + SoX::Math::Vector Point{}; }; MARATHON_ASSERT_OFFSETOF(MsgSuckPlayer, Point, 0x10); diff --git a/MarathonRecomp/api/Sonicteam/SoX/Input/Manager.h b/MarathonRecomp/api/Sonicteam/SoX/Input/Manager.h index fc0430fd..dcae134d 100644 --- a/MarathonRecomp/api/Sonicteam/SoX/Input/Manager.h +++ b/MarathonRecomp/api/Sonicteam/SoX/Input/Manager.h @@ -6,10 +6,10 @@ namespace Sonicteam::SoX::Input { enum KeyState { - KeyState_DpadUp = 0x40, - KeyState_DpadDown = 0x80, - KeyState_DpadLeft = 0x100, - KeyState_DpadRight = 0x200, + KeyState_DPadUp = 0x40, + KeyState_DPadDown = 0x80, + KeyState_DPadLeft = 0x100, + KeyState_DPadRight = 0x200, KeyState_Start = 0x400, KeyState_Select = 0x800, KeyState_LeftStick = 0x10000, diff --git a/MarathonRecomp/gpu/video.cpp b/MarathonRecomp/gpu/video.cpp index 92960523..0524cdf1 100644 --- a/MarathonRecomp/gpu/video.cpp +++ b/MarathonRecomp/gpu/video.cpp @@ -1576,7 +1576,6 @@ static void CreateImGuiBackend() #endif InitImGuiUtils(); - AchievementMenu::Init(); OptionsMenu::Init(); InstallerWizard::Init(); @@ -2914,10 +2913,10 @@ static void DrawImGui() AchievementMenu::Draw(); OptionsMenu::Draw(); - AchievementOverlay::Draw(); InstallerWizard::Draw(); ButtonWindow::Draw(); MessageWindow::Draw(); + AchievementOverlay::Draw(); Fader::Draw(); BlackBar::Draw(); @@ -4457,13 +4456,10 @@ static void SanitizePipelineState(PipelineState& pipelineState) pipelineState.blendOpAlpha = RenderBlendOperation::ADD; } - if (pipelineState.vertexDeclaration) + for (size_t i = 0; i < 16; i++) { - for (size_t i = 0; i < 16; i++) - { - if (!pipelineState.vertexDeclaration->vertexStreams[i]) - pipelineState.vertexStrides[i] = 0; - } + if (!pipelineState.vertexDeclaration->vertexStreams[i]) + pipelineState.vertexStrides[i] = 0; } uint32_t specConstantsMask = 0; diff --git a/MarathonRecomp/kernel/xdbf.h b/MarathonRecomp/kernel/xdbf.h index 249f8c82..7a32631e 100644 --- a/MarathonRecomp/kernel/xdbf.h +++ b/MarathonRecomp/kernel/xdbf.h @@ -12,12 +12,12 @@ namespace xdbf { static std::array invalidSequences = { - "\xE2\x80\x99" + "\n" }; static std::array replaceSequences = { - "'" + " " }; for (int i = 0; i < invalidSequences.size(); i++) diff --git a/MarathonRecomp/locale/locale.cpp b/MarathonRecomp/locale/locale.cpp index dd0c8d46..66bee63a 100644 --- a/MarathonRecomp/locale/locale.cpp +++ b/MarathonRecomp/locale/locale.cpp @@ -294,8 +294,29 @@ std::unordered_map> } }, { - // Notes: used for the button guide at the pause menu. - "Achievements_Name", + "MainMenu_GoldMedalResults_Name", + { + { ELanguage::English, "RESULTS" }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } + } + }, + { + "MainMenu_GoldMedalResults_Description", + { + { ELanguage::English, "Results: Displays lists of Gold Medals and Achievements" }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } + } + }, + { + "Achievements_Title", { { ELanguage::English, "Achievements" }, { ELanguage::Japanese, "実績" }, @@ -306,8 +327,7 @@ std::unordered_map> } }, { - // Notes: used for the header in the achievements menu. - "Achievements_Name_Uppercase", + "Achievements_Title_Uppercase", { { ELanguage::English, "ACHIEVEMENTS" }, { ELanguage::Japanese, "実績" }, @@ -317,6 +337,28 @@ std::unordered_map> { ELanguage::Italian, "OBIETTIVI" } } }, + { + "Achievements_GoldMedals", + { + { ELanguage::English, "Gold Medals" }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } + } + }, + { + "Achievements_GoldMedals_Uppercase", + { + { ELanguage::English, "GOLD MEDALS" }, + { ELanguage::Japanese, "DUMMY" }, + { ELanguage::German, "DUMMY" }, + { ELanguage::French, "DUMMY" }, + { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Italian, "DUMMY" } + } + }, { "Achievements_Unlock", { @@ -328,6 +370,17 @@ std::unordered_map> { ELanguage::Italian, "Obiettivo sbloccato!" } } }, + { + "Achievements_Progress", + { + { ELanguage::English, "PROGRESS %d%%" }, + { ELanguage::Japanese, "PROGRESS %d%%" }, + { ELanguage::German, "FORTSCHRITT %d%%" }, + { ELanguage::French, "PROGRESSION %d%%" }, + { ELanguage::Spanish, "PROGRESO %d%%" }, + { ELanguage::Italian, "PROGRESSI %d%%" } + } + }, { "Installer_Header_Installer", { @@ -938,14 +991,32 @@ std::unordered_map> { ELanguage::Spanish, "${picture(button_x)} ${locale(Common_Reset)} ${picture(button_a)} ${locale(Common_Select)} ${picture(button_b)} ${locale(Common_Back)}" }, } }, + { + "Button_GoldMedalsBack", + { + { ELanguage::English, "${picture(button_y)}${locale(Achievements_GoldMedals)} ${picture(button_b)}${locale(Common_Back)}" }, + { ELanguage::German, "${picture(button_y)} ${locale(Achievements_GoldMedals)}  ${picture(button_b)} ${locale(Common_Back)}" }, + { ELanguage::Spanish, "${picture(button_y)} ${locale(Achievements_GoldMedals)} ${picture(button_b)} ${locale(Common_Back)}" } + } + }, + { + "Button_AchievementsBack", + { + { ELanguage::English, "${picture(button_y)}${locale(Achievements_Title)} ${picture(button_b)}${locale(Common_Back)}" }, + { ELanguage::German, "${picture(button_y)} ${locale(Achievements_Title)}  ${picture(button_b)} ${locale(Common_Back)}" }, + { ELanguage::Spanish, "${picture(button_y)} ${locale(Achievements_Title)} ${picture(button_b)} ${locale(Common_Back)}" } + } + } }; std::string& Localise(const std::string_view& key) { auto localeFindResult = g_locale.find(key); + if (localeFindResult != g_locale.end()) { auto languageFindResult = localeFindResult->second.find(Config::Language); + if (languageFindResult == localeFindResult->second.end()) languageFindResult = localeFindResult->second.find(ELanguage::English); diff --git a/MarathonRecomp/patches/MainMenuTask_patches.cpp b/MarathonRecomp/patches/MainMenuTask_patches.cpp index f672c1a4..09b2495f 100644 --- a/MarathonRecomp/patches/MainMenuTask_patches.cpp +++ b/MarathonRecomp/patches/MainMenuTask_patches.cpp @@ -1,4 +1,6 @@ #include "MainMenuTask_patches.h" +#include +#include #include #include @@ -10,6 +12,13 @@ PPC_FUNC(sub_824FFCF8) auto pHUDMainMenu = pMainMenuTask->m_pHUDMainMenu; #ifdef MARATHON_RECOMP_OPTIONS_MENU + if (pHUDMainMenu) + { + // Hide original "OPTIONS" title. + if (auto pOptionsSelect = pHUDMainMenu->m_pHudTextRoot->Find("options_select", "options")) + pOptionsSelect->m_Priority = -1.0f; + } + if (pMainMenuTask->m_State == Sonicteam::MainMenuTask::MainMenuState_MainMenu && pMainMenuTask->m_MainMenuSelectedIndex == 3) { if (!OptionsMenu::s_isVisible && (pMainMenuTask->m_PressedButtons.get() & 0x10) != 0) @@ -22,67 +31,36 @@ PPC_FUNC(sub_824FFCF8) pMainMenuTask->m_State = Sonicteam::MainMenuTask::MainMenuState_MainMenu; pMainMenuTask->m_PressedButtons = 0; - guest_stack_var msgHUDMainMenuSetCursor + guest_stack_var msgSetCursor ( Sonicteam::HUDMainMenu::HUDMainMenuState_MainCursorOutro, pMainMenuTask->m_MainMenuSelectedIndex ); // Play cursor outro animation. - pHUDMainMenu->ProcessMessage(msgHUDMainMenuSetCursor.get()); + pHUDMainMenu->ProcessMessage(msgSetCursor.get()); - guest_stack_var msgHUDMainMenuTransition + guest_stack_var msgTransition ( Sonicteam::HUDMainMenu::HUDMainMenuState_OptionsOutro, 3 ); // Play main menu -> options transition. - pHUDMainMenu->ProcessMessage(msgHUDMainMenuTransition.get()); + pHUDMainMenu->ProcessMessage(msgTransition.get()); } } static bool s_isReturningFromOptionsMenu{}; - static float s_buttonWindowTextOffsetY{}; - - auto& rButtonWindowTextOffsetY = pMainMenuTask->m_pButtonWindowTask->m_pHUDButtonWindow->m_pHudTextParts->m_OffsetY; - - if (pHUDMainMenu) - { - // Hide original "OPTIONS" title. - if (auto pOptionsSelect = pHUDMainMenu->m_pHudTextRoot->Find("options_select", "options")) - pOptionsSelect->m_Priority = -1.0f; - } if (OptionsMenu::s_isVisible) { pMainMenuTask->m_PressedButtons = 0; - switch (OptionsMenu::s_state) - { - case OptionsMenuState::Opening: - { - static constexpr double HIDE_TEXT_OFFSET = -100000.0f; - - if (rButtonWindowTextOffsetY == HIDE_TEXT_OFFSET) - break; - - // Move original button window text very far off screen to hide it. - s_buttonWindowTextOffsetY = rButtonWindowTextOffsetY; - rButtonWindowTextOffsetY = HIDE_TEXT_OFFSET; - - break; - } - - case OptionsMenuState::Closing: - s_isReturningFromOptionsMenu = true; - break; - } + if (OptionsMenu::s_state == OptionsMenuState::Closing) + s_isReturningFromOptionsMenu = true; } else if (s_isReturningFromOptionsMenu) { - // Restore original button window text offset. - rButtonWindowTextOffsetY = s_buttonWindowTextOffsetY; - if ((pHUDMainMenu->m_CursorFlags.get() & 2) != 0) { // Prevent inputs leaking from the @@ -96,6 +74,41 @@ PPC_FUNC(sub_824FFCF8) } #endif +#ifdef MARATHON_RECOMP_ACHIEVEMENT_MENU + if (pHUDMainMenu) + { + // Hide original "GOLD MEDAL RESULTS" title. + if (auto pGoldMedalResultSelect = pHUDMainMenu->m_pHudTextRoot->Find("goldmedalresult_select", "goldmedalresult")) + pGoldMedalResultSelect->m_Priority = -1.0f; + } + + if (pMainMenuTask->m_State == Sonicteam::MainMenuTask::MainMenuState_GoldMedalResultsOpen) + { + AchievementMenu::s_pMainMenuTask = pMainMenuTask; + AchievementMenu::Open(); + } +#endif + + static float s_buttonWindowTextOffsetY{}; + auto& rButtonWindowTextOffsetY = pMainMenuTask->m_pButtonWindowTask->m_pHUDButtonWindow->m_pHudTextParts->m_OffsetY; + + if (MainMenuTaskPatches::HideButtonWindow) + { + static constexpr double HIDE_TEXT_OFFSET = -100000.0f; + + if (rButtonWindowTextOffsetY != HIDE_TEXT_OFFSET) + { + // Move original button window text very far off screen to hide it. + s_buttonWindowTextOffsetY = rButtonWindowTextOffsetY; + rButtonWindowTextOffsetY = HIDE_TEXT_OFFSET; + } + } + else + { + // Restore original button window text offset. + rButtonWindowTextOffsetY = s_buttonWindowTextOffsetY; + } + MainMenuTaskPatches::State = (Sonicteam::MainMenuTask::MainMenuState)pMainMenuTask->m_State.get(); for (auto& event : MainMenuTaskPatches::Events) @@ -103,3 +116,8 @@ PPC_FUNC(sub_824FFCF8) __imp__sub_824FFCF8(ctx, base); } + +bool MainMenuTask_GoldMedalResults_SkipOutro() +{ + return AchievementMenu::s_state == AchievementMenuState::ClosingAchievements; +} diff --git a/MarathonRecomp/patches/MainMenuTask_patches.h b/MarathonRecomp/patches/MainMenuTask_patches.h index 6fd67751..a1764427 100644 --- a/MarathonRecomp/patches/MainMenuTask_patches.h +++ b/MarathonRecomp/patches/MainMenuTask_patches.h @@ -6,6 +6,7 @@ class MainMenuTaskPatches { public: + static inline bool HideButtonWindow{}; static inline Sonicteam::MainMenuTask::MainMenuState State{}; static inline std::vector*> Events{}; }; diff --git a/MarathonRecomp/patches/SaveDataTask_patches.cpp b/MarathonRecomp/patches/SaveDataTask_patches.cpp index 3257691e..1ae734f3 100644 --- a/MarathonRecomp/patches/SaveDataTask_patches.cpp +++ b/MarathonRecomp/patches/SaveDataTask_patches.cpp @@ -3,8 +3,6 @@ #include #include -#include - enum { ACH_ERROR_SUCCESS, diff --git a/MarathonRecomp/patches/TitleTask_patches.cpp b/MarathonRecomp/patches/TitleTask_patches.cpp index 740638a3..a5ba8514 100644 --- a/MarathonRecomp/patches/TitleTask_patches.cpp +++ b/MarathonRecomp/patches/TitleTask_patches.cpp @@ -1,7 +1,9 @@ #include +#include #include #include #include +#include #include #include #include @@ -114,16 +116,16 @@ PPC_FUNC(sub_825126A0) { auto& rPadState = spInputManager->m_PadState; - if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DpadUp)) + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DPadUp)) g_secretFlags = SECRET_UP; - if ((g_secretFlags & SECRET_UP) != 0 && rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DpadDown)) + if ((g_secretFlags & SECRET_UP) != 0 && rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DPadDown)) g_secretFlags |= SECRET_DOWN; - if ((g_secretFlags & SECRET_DOWN) != 0 && rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DpadLeft)) + if ((g_secretFlags & SECRET_DOWN) != 0 && rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DPadLeft)) g_secretFlags |= SECRET_LEFT; - if ((g_secretFlags & SECRET_LEFT) != 0 && rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DpadRight)) + if ((g_secretFlags & SECRET_LEFT) != 0 && rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DPadRight)) g_secretFlags |= SECRET_RIGHT; } @@ -155,14 +157,22 @@ PPC_FUNC(sub_825126A0) case Sonicteam::TitleTask::TitleState_OptionsProceed: { - if (Config::DisableTitleInputDelay) - break; + // Reset achievements on new game. + if (pTitleTask->m_SelectedIndex == 0) + { + LOGN("Resetting achievements..."); - g_titleProceedOutroTime += deltaTime; + AchievementManager::Reset(); + } - // Wait for outro animation to complete before entering menu. - if (g_titleProceedOutroTime > TITLE_OPTION_OUTRO_TOTAL_FRAMES) - GuestToHostFunction(sub_82511CA0, pTitleTask, 9); + if (!Config::DisableTitleInputDelay) + { + g_titleProceedOutroTime += deltaTime; + + // Wait for outro animation to complete before entering menu. + if (g_titleProceedOutroTime > TITLE_OPTION_OUTRO_TOTAL_FRAMES) + GuestToHostFunction(sub_82511CA0, pTitleTask, 9); + } break; } diff --git a/MarathonRecomp/patches/aspect_ratio_patches.cpp b/MarathonRecomp/patches/aspect_ratio_patches.cpp index 8691b520..3b088494 100644 --- a/MarathonRecomp/patches/aspect_ratio_patches.cpp +++ b/MarathonRecomp/patches/aspect_ratio_patches.cpp @@ -411,7 +411,7 @@ void Draw(PPCContext& ctx, uint8_t* base, PPCFunc* original, uint32_t stride) } // Hide original button window whilst the options menu is visible. - if ((modifier.Flags & CSD_BUTTON_WINDOW) != 0 && OptionsMenu::s_isVisible) + if ((modifier.Flags & CSD_BUTTON_WINDOW) != 0 && MainMenuTaskPatches::HideButtonWindow) return; // Remove all flags if the aspect ratio is above 16:9. diff --git a/MarathonRecomp/patches/fps_patches.cpp b/MarathonRecomp/patches/fps_patches.cpp index 08ee5664..d4af1cbd 100644 --- a/MarathonRecomp/patches/fps_patches.cpp +++ b/MarathonRecomp/patches/fps_patches.cpp @@ -115,7 +115,7 @@ void ObjEspSwing_DecayRateFix(PPCRegister& f0, PPCRegister& f13, PPCRegister& de f0.f64 = float(f13.f64 * pow(pow(f0.f64, 60.0), deltaTime.f64)); } -struct MsgSuckPlayerEx : public Sonicteam::Message::MsgSuckPlayer +struct MsgSuckPlayerEx : public Sonicteam::Message::Player::MsgSuckPlayer { be DeltaTime; }; @@ -123,7 +123,7 @@ struct MsgSuckPlayerEx : public Sonicteam::Message::MsgSuckPlayer void ObjectInputWarp_ExtendMsgSuckPlayer(PPCRegister& phantom, PPCRegister& message, PPCRegister& deltaTime) { auto pPhantom = (Sonicteam::SoX::Physics::Phantom*)g_memory.Translate(phantom.u32); - auto pMessage = (Sonicteam::Message::MsgSuckPlayer*)g_memory.Translate(message.u32); + auto pMessage = (Sonicteam::Message::Player::MsgSuckPlayer*)g_memory.Translate(message.u32); auto pMsgSuckPlayerEx = (MsgSuckPlayerEx*)g_userHeap.Alloc(sizeof(MsgSuckPlayerEx)); pMsgSuckPlayerEx->ID = pMessage->ID; diff --git a/MarathonRecomp/patches/misc_patches.cpp b/MarathonRecomp/patches/misc_patches.cpp index cc9c4731..8ecfcfe7 100644 --- a/MarathonRecomp/patches/misc_patches.cpp +++ b/MarathonRecomp/patches/misc_patches.cpp @@ -121,7 +121,7 @@ PPC_FUNC(sub_822CE930) if (!Config::ControlTutorial || Config::SlidingAttack != ESlidingAttack::X) { // Get global flag for Sonic's Antigravity being unlocked to remove "hint_all03_h26_so". - guest_stack_var msgGetSonicAntigravityFlag(6001); + guest_stack_var msgGetSonicAntigravityFlag(6001); pGame->m_pMissionCore->ProcessMessage(msgGetSonicAntigravityFlag.get()); if (msgGetSonicAntigravityFlag->FlagValue != 0 && strcmp(rMessageName, "hint_twn01_e02_tl") == 0) @@ -134,11 +134,11 @@ PPC_FUNC(sub_822CE930) if (!Config::ControlTutorial || Config::LightDash != ELightDash::X) { // Get global flag for Sonic's Light Dash being unlocked to remove "hint_twn01_e01_tl". - guest_stack_var msgGetSonicLightDashFlag(6000); + guest_stack_var msgGetSonicLightDashFlag(6000); pGame->m_pMissionCore->ProcessMessage(msgGetSonicLightDashFlag.get()); // Get global flag for Shadow's Light Dash being unlocked to remove "hint_twn01_e44_rg". - guest_stack_var msgGetShadowLightDashFlag(6012); + guest_stack_var msgGetShadowLightDashFlag(6012); pGame->m_pMissionCore->ProcessMessage(msgGetShadowLightDashFlag.get()); auto isSonicLightDashHint = msgGetSonicLightDashFlag->FlagValue != 0 && strcmp(rMessageName, "hint_twn01_e00_tl") == 0; diff --git a/MarathonRecomp/patches/pause_patches.cpp b/MarathonRecomp/patches/pause_patches.cpp index d1a10509..e8103981 100644 --- a/MarathonRecomp/patches/pause_patches.cpp +++ b/MarathonRecomp/patches/pause_patches.cpp @@ -39,12 +39,12 @@ PPC_FUNC_IMPL(__imp__sub_8216DA08); PPC_FUNC(sub_8216DA08) { auto pPauseAdapter = (Sonicteam::PauseAdapter*)(base + ctx.r3.u32); - auto pMsgPauseAdapterText = (Sonicteam::Message::MsgPauseAdapterText*)(base + ctx.r4.u32); + auto pMsgGetText = (Sonicteam::Message::PauseAdapter::MsgGetText*)(base + ctx.r4.u32); __imp__sub_8216DA08(ctx, base); // Set selected ID to unused slot. - if (pMsgPauseAdapterText->SelectedName == "options") + if (pMsgGetText->SelectedName == "options") pPauseAdapter->m_SelectedID = 6; } diff --git a/MarathonRecomp/patches/player_patches.cpp b/MarathonRecomp/patches/player_patches.cpp index 1c69458d..1d564778 100644 --- a/MarathonRecomp/patches/player_patches.cpp +++ b/MarathonRecomp/patches/player_patches.cpp @@ -57,14 +57,14 @@ PPC_FUNC(sub_82195500) if (auto pCameraMode = pCameraman->m_spCameraModeManager->m_spCameraMode.get()) { - guest_stack_var msgCameramanChangeMode; - msgCameramanChangeMode->PadID = pInputManager->m_PadID; - msgCameramanChangeMode->TargetActorID = pPlayer->m_ActorID; - msgCameramanChangeMode->IsDemoCamera = pCameraMode->m_pVftable.ptr != 0x82002004; + guest_stack_var msgChangeMode; + msgChangeMode->PadID = pInputManager->m_PadID; + msgChangeMode->TargetActorID = pPlayer->m_ActorID; + msgChangeMode->IsDemoCamera = pCameraMode->m_pVftable.ptr != 0x82002004; - LOGFN("Demo Camera: {}", msgCameramanChangeMode->IsDemoCamera ? "Enabled" : "Disabled"); + LOGFN("Demo Camera: {}", msgChangeMode->IsDemoCamera ? "Enabled" : "Disabled"); - pCameraman->ProcessMessage(msgCameramanChangeMode.get()); + pCameraman->ProcessMessage(msgChangeMode.get()); } } } @@ -253,12 +253,12 @@ void RestoreChainJumpFlips(PPCRegister& r31, PPCRegister& r30, PPCRegister& r11, { auto pFixture = GuestToHostFunction(sub_821609D0, pActorManager, &pMessage->ActorID); - auto msgObjJump123GetNextPoint = guest_stack_var(); - msgObjJump123GetNextPoint->Rotation = { 0, 0, 0, 1 }; - msgObjJump123GetNextPoint->Position = { 0, 0, 0, 1 }; + auto msgGetNextPoint = guest_stack_var(); + msgGetNextPoint->Rotation = { 0, 0, 0, 1 }; + msgGetNextPoint->Position = { 0, 0, 0, 1 }; - if (pFixture->ProcessMessage(msgObjJump123GetNextPoint.get())) - target = msgObjJump123GetNextPoint->Position; + if (pFixture->ProcessMessage(msgGetNextPoint.get())) + target = msgGetNextPoint->Position; } auto magnitudeHorz = f1.f64; diff --git a/MarathonRecomp/patches/text_patches.cpp b/MarathonRecomp/patches/text_patches.cpp index 036ac4f4..5a6475cd 100644 --- a/MarathonRecomp/patches/text_patches.cpp +++ b/MarathonRecomp/patches/text_patches.cpp @@ -1,4 +1,5 @@ #include "text_patches.h" +#include #include #include #include @@ -26,6 +27,25 @@ PPC_FUNC(sub_825ECB48) __imp__sub_825ECB48(ctx, base); + auto pTextCard = (boost::shared_ptr*)(base + ctx.r3.u32); + + for (auto& replacement : TextPatches::s_replacedMessages) + { + if (HashStr(pMessage) != replacement.first) + continue; + + auto& message = Localise(replacement.second); + auto wideMessage = std::wstring(message.begin(), message.end()); + auto wideMessageLen = (wideMessage.length() * 2) + 2; + + auto pReplacedMessage = (uint16_t*)g_userHeap.Alloc(wideMessageLen); + + for (size_t i = 0; i < wideMessageLen; i++) + pReplacedMessage[i] = ByteSwap(wideMessage.c_str()[i]); + + pTextCard->get()->m_pText = (const uint16_t*)pReplacedMessage; + } + if (!pNewMessage) return; diff --git a/MarathonRecomp/patches/text_patches.h b/MarathonRecomp/patches/text_patches.h index c53ad69b..ce706425 100644 --- a/MarathonRecomp/patches/text_patches.h +++ b/MarathonRecomp/patches/text_patches.h @@ -7,10 +7,16 @@ class TextPatches public: static inline xxHashMap s_redirectedMessages { - { HashStr("msg_deviceselect"), "msg_retry" }, // Replace "Select storage device." text with "Retry" for alert windows. + { HashStr("msg_deviceselect"), "msg_retry" }, // Replace "Select storage device." text with "Retry" for alert windows. { HashStr("msg_gamequitconfirm4"), "msg_backtotitle1" } // Replace "Exit the game." text with "Go back to the Title Screen." }; + static inline xxHashMap s_replacedMessages + { + { HashStr("msg_goldmedalresults"), "MainMenu_GoldMedalResults_Name" }, + { HashStr("msg_goldmedalresults_c"), "MainMenu_GoldMedalResults_Description" } + }; + static inline std::vector s_hintPatterns = { "hint_all01_a0*", diff --git a/MarathonRecomp/ui/achievement_menu.cpp b/MarathonRecomp/ui/achievement_menu.cpp index 9e74cacf..3d3dd8d3 100644 --- a/MarathonRecomp/ui/achievement_menu.cpp +++ b/MarathonRecomp/ui/achievement_menu.cpp @@ -1,241 +1,240 @@ #include "achievement_menu.h" -#include #include #include #include #include #include #include +#include #include #include #include #include #include #include -#include -#include +static constexpr int MAX_VISIBLE_ROWS = 4; -constexpr double HEADER_CONTAINER_INTRO_MOTION_START = 0; -constexpr double HEADER_CONTAINER_INTRO_MOTION_END = 15; -constexpr double HEADER_CONTAINER_OUTRO_MOTION_START = 0; -constexpr double HEADER_CONTAINER_OUTRO_MOTION_END = 40; -constexpr double HEADER_CONTAINER_INTRO_FADE_START = 5; -constexpr double HEADER_CONTAINER_INTRO_FADE_END = 14; -constexpr double HEADER_CONTAINER_OUTRO_FADE_START = 0; -constexpr double HEADER_CONTAINER_OUTRO_FADE_END = 7; +static double g_time{}; +static double g_rowSelectionTime{}; +static double g_scrollArrowsTime{}; +static double g_lastIncrementTime{}; +static double g_lastTappedTime{}; -constexpr double CONTENT_CONTAINER_COMMON_MOTION_START = 11; -constexpr double CONTENT_CONTAINER_COMMON_MOTION_END = 12; +static bool g_up{}; +static bool g_upWasHeld{}; +static bool g_down{}; +static bool g_downWasHeld{}; -constexpr double COUNTER_INTRO_FADE_START = 15; -constexpr double COUNTER_INTRO_FADE_END = 16; +static int g_rowCount{}; +static int g_selectedIndex{}; -constexpr double SELECTION_CONTAINER_BREATHE = 30; +static std::vector> g_achievements{}; -static bool g_isClosing = false; +static void MoveCursor(int& cursorIndex, int min = 0, int max = INT_MAX) +{ + auto time = ImGui::GetTime(); -static double g_appearTime; + auto scrollUp = g_up; + auto scrollDown = g_down; -static std::vector> g_achievements; + if (scrollUp || scrollDown) + g_lastTappedTime = time; -static ImFont* g_rodinFont; -static ImFont* g_newRodinFont; + static constexpr auto FAST_SCROLL_THRESHOLD = 0.6; + static constexpr auto FAST_SCROLL_SPEED = 1.0 / 6.5; -static std::unique_ptr g_upTrophyIcon; + auto fastScroll = (time - g_lastTappedTime) > FAST_SCROLL_THRESHOLD; -static int g_firstVisibleRowIndex; -static int g_selectedRowIndex; -static double g_rowSelectionTime; -static double g_lastTappedTime; -static double g_lastIncrementTime; + if (fastScroll) + { + if ((time - g_lastIncrementTime) < FAST_SCROLL_SPEED) + { + fastScroll = false; + } + else + { + g_lastIncrementTime = time; -static bool g_upWasHeld; -static bool g_downWasHeld; + scrollUp = g_upWasHeld; + scrollDown = g_downWasHeld; + } + } -static void ResetSelection() -{ - g_firstVisibleRowIndex = 0; - g_selectedRowIndex = 0; - g_rowSelectionTime = ImGui::GetTime(); - g_upWasHeld = false; - g_downWasHeld = false; -} + if (scrollUp) + { + --cursorIndex; -static void DrawContainer(ImVec2 min, ImVec2 max, ImU32 gradientTop, ImU32 gradientBottom, float alpha = 1, float cornerRadius = 25) -{ - auto drawList = ImGui::GetBackgroundDrawList(); + if (cursorIndex < min) + cursorIndex = max - 1; + } + else if (scrollDown) + { + ++cursorIndex; + + if (cursorIndex >= max) + cursorIndex = min; + } - // DrawPauseContainer(min, max, alpha); + if (scrollUp || scrollDown) + { + Game_PlaySound("move"); - drawList->PushClipRect({ min.x, min.y + Scale(20) }, { max.x, max.y - Scale(5) }); + g_rowSelectionTime = time; + g_scrollArrowsTime = g_rowSelectionTime; + } } -static void DrawHeaderContainer(const char* text) +static bool DrawContainer(ImVec2 min, ImVec2 max) { auto drawList = ImGui::GetBackgroundDrawList(); - auto fontSize = Scale(24); - auto minTextSize = Scale(294.575989); - auto textSize = g_newRodinFont->CalcTextSizeA(fontSize, FLT_MAX, 0, text); - auto cornerRadius = 23; - auto textMarginX = Scale(16) + (Scale(cornerRadius) / 2); - - auto containerMotion = g_isClosing - ? ComputeMotion(g_appearTime, HEADER_CONTAINER_OUTRO_MOTION_START, HEADER_CONTAINER_OUTRO_MOTION_END) - : ComputeMotion(g_appearTime, HEADER_CONTAINER_INTRO_MOTION_START, HEADER_CONTAINER_INTRO_MOTION_END); - - auto colourMotion = g_isClosing - ? ComputeMotion(g_appearTime, HEADER_CONTAINER_OUTRO_FADE_START, HEADER_CONTAINER_OUTRO_FADE_END) - : ComputeMotion(g_appearTime, HEADER_CONTAINER_INTRO_FADE_START, HEADER_CONTAINER_INTRO_FADE_END); - - // Slide animation. - auto containerMarginX = g_isClosing - ? Hermite(251, 151, containerMotion) - : Hermite(151, 251, containerMotion); - - // Transparency fade animation. - auto alpha = g_isClosing - ? Lerp(1, 0, colourMotion) - : Lerp(0, 1, colourMotion); - - ImVec2 min = { g_aspectRatioOffsetX + Scale(containerMarginX), g_aspectRatioOffsetY + Scale(136) }; - ImVec2 max = { std::max(min.x + minTextSize, min.x + textMarginX * 2 + textSize.x + Scale(5)), g_aspectRatioOffsetY + Scale(196) }; - - // DrawPauseHeaderContainer(min, max, alpha); - - SetTextSkew((min.y + max.y) / 2.0f, Scale(3.0f)); - - DrawTextWithOutline - ( - g_newRodinFont, - fontSize, - { /* X */ min.x + textMarginX, /* Y */ CENTRE_TEXT_VERT(min, max, textSize) - Scale(5) }, - IM_COL32(255, 255, 255, 255 * alpha), - text, - 4, - IM_COL32(0, 0, 0, 255 * alpha) - ); - - ResetTextSkew(); + + auto containerTopCornerUVs = PIXELS_TO_UV_COORDS(1024, 1024, 1, 400, 50, 50); + auto containerTopCentreUVs = PIXELS_TO_UV_COORDS(1024, 1024, 50, 400, 50, 50); + auto containerSideUVs = PIXELS_TO_UV_COORDS(1024, 1024, 1, 450, 50, 50); + auto containerCentreUVs = PIXELS_TO_UV_COORDS(1024, 1024, 50, 450, 50, 50); + auto containerColourMotion = AchievementMenu::IsClosing() ? 0 : ComputeLinearMotion(g_time, 0, 10); + auto containerColour = IM_COL32(255, 255, 255, 63 * containerColourMotion); + auto containerEdgeSize = Scale(50, true); + + ImVec2 containerTopLeftCornerMin = min; + ImVec2 containerTopLeftCornerMax = { containerTopLeftCornerMin.x + containerEdgeSize, containerTopLeftCornerMin.y + containerEdgeSize }; + ImVec2 containerTopCentreCornerMin = { containerTopLeftCornerMax.x, containerTopLeftCornerMin.y }; + ImVec2 containerTopCentreCornerMax = { max.x - containerEdgeSize, containerTopCentreCornerMin.y + containerEdgeSize }; + ImVec2 containerTopRightCornerMin = { containerTopCentreCornerMax.x, containerTopCentreCornerMin.y }; + ImVec2 containerTopRightCornerMax = { containerTopRightCornerMin.x + containerEdgeSize, containerTopRightCornerMin.y + containerEdgeSize }; + ImVec2 containerLeftMin = { containerTopLeftCornerMin.x, containerTopLeftCornerMax.y }; + ImVec2 containerLeftMax = { containerTopLeftCornerMax.x, max.y }; + ImVec2 containerRightMin = { containerTopRightCornerMin.x, containerTopRightCornerMax.y }; + ImVec2 containerRightMax = { containerTopRightCornerMax.x, max.y }; + ImVec2 containerCentreMin = containerTopLeftCornerMax; + ImVec2 containerCentreMax = { containerRightMin.x, containerRightMax.y }; + + auto containerBottomFadeStart = Scale(40, true); + auto containerBottomFadeEnd = Scale(10, true); + ImVec2 containerBottomFadeMin = { containerLeftMin.x, containerLeftMax.y - containerBottomFadeStart }; + ImVec2 containerBottomFadeMax = { containerRightMax.x, containerRightMax.y + containerBottomFadeEnd }; + + SetVerticalGradient(containerBottomFadeMin, containerBottomFadeMax, IM_COL32_WHITE, IM_COL32_WHITE_TRANS); + drawList->AddImage(g_upTexMainMenu1.get(), containerTopLeftCornerMin, containerTopLeftCornerMax, GET_UV_COORDS(containerTopCornerUVs), containerColour); + drawList->AddImage(g_upTexMainMenu1.get(), containerTopCentreCornerMin, containerTopCentreCornerMax, GET_UV_COORDS(containerTopCentreUVs), containerColour); + AddImageFlipped(g_upTexMainMenu1.get(), containerTopRightCornerMin, containerTopRightCornerMax, GET_UV_COORDS(containerTopCornerUVs), containerColour, true); + drawList->AddImage(g_upTexMainMenu1.get(), containerLeftMin, containerLeftMax, GET_UV_COORDS(containerSideUVs), containerColour); + AddImageFlipped(g_upTexMainMenu1.get(), containerRightMin, containerRightMax, GET_UV_COORDS(containerSideUVs), containerColour, true); + drawList->AddImage(g_upTexMainMenu1.get(), containerCentreMin, containerCentreMax, GET_UV_COORDS(containerCentreUVs), containerColour); + ResetGradient(); + + drawList->PushClipRect(containerTopLeftCornerMin, containerRightMax); + + return containerColourMotion >= 1.0; } -static void DrawAchievement(int rowIndex, float yOffset, Achievement& achievement, bool isUnlocked) +static void DrawAchievement(int rowIndex, Achievement& achievement, bool isUnlocked) { auto drawList = ImGui::GetBackgroundDrawList(); - auto clipRectMin = drawList->GetClipRectMin(); auto clipRectMax = drawList->GetClipRectMax(); - auto itemWidth = Scale(700); - auto itemHeight = Scale(94); - auto itemMarginX = Scale(18); - auto imageMarginX = Scale(25); - auto imageMarginY = Scale(18); - auto imageSize = Scale(60); + auto itemHeight = Scale(94, true); + auto itemMarginX = Scale(18, true); + auto imageMarginX = Scale(34, true); + auto imageMarginY = Scale(20, true); + auto imageSize = Scale(64, true); - ImVec2 min = { itemMarginX + clipRectMin.x, clipRectMin.y + itemHeight * rowIndex + yOffset }; - ImVec2 max = { itemMarginX + min.x + itemWidth, min.y + itemHeight }; + auto offsetScroll = 0.0f; + auto startIndex = 0; - auto icon = g_xdbfTextureCache[achievement.ID]; - auto isSelected = rowIndex == g_selectedRowIndex; + // Only scroll if page has more than four items. + if (g_rowCount > MAX_VISIBLE_ROWS) + { + if (g_selectedIndex >= g_rowCount - (MAX_VISIBLE_ROWS - 1)) + { + // Stop scrolling near bottom to use cursor instead. + startIndex = g_rowCount - MAX_VISIBLE_ROWS; + offsetScroll = -startIndex * itemHeight; + } + else if (g_selectedIndex >= 1) + { + // Start scrolling from the middle item. + startIndex = g_selectedIndex - 1; + offsetScroll = -startIndex * itemHeight; + } + } + + auto offsetY = itemHeight * rowIndex + offsetScroll; + auto isCurrent = g_selectedIndex == rowIndex; + auto isVisible = ((rowIndex - startIndex + g_rowCount) % g_rowCount) < MAX_VISIBLE_ROWS; + + if (!isVisible) + return; + + ImVec2 min = { clipRectMin.x + itemMarginX, clipRectMin.y + offsetY }; + ImVec2 max = { clipRectMax.x + itemMarginX, min.y + itemHeight }; + + auto rowUVs = PIXELS_TO_UV_COORDS(1024, 1024, 605, 450, 10, 30); + auto rowMarginX = Scale(2, true); + auto rowMarginY = Scale(24, true); + auto rowColourMotion = BREATHE_MOTION(1.0f, 0.0f, g_rowSelectionTime, 0.9f); + auto rowColour = IM_COL32(255, 255, 255, isCurrent ? Lerp(165, 94, rowColourMotion) : 94); + ImVec2 rowMin = { clipRectMin.x + rowMarginX, min.y + itemHeight - rowMarginY }; + ImVec2 rowMax = { clipRectMax.x - rowMarginX, rowMin.y + Scale(30, true) }; - // if (isSelected) - // DrawSelectionContainer(min, max); + SetAdditive(true); + SetVerticalGradient(rowMin, rowMax, IM_COL32_WHITE_TRANS, rowColour); + drawList->AddImage(g_upTexMainMenu1.get(), rowMin, rowMax, GET_UV_COORDS(rowUVs), rowColour); + ResetGradient(); + ResetAdditive(); - auto desc = isUnlocked ? achievement.UnlockedDesc.c_str() : achievement.LockedDesc.c_str(); - auto fontSize = Scale(24); - auto textSize = g_rodinFont->CalcTextSizeA(fontSize, FLT_MAX, 0, desc); - auto textX = min.x + imageMarginX + imageSize + itemMarginX * 2; - auto textMarqueeX = min.x + imageMarginX + imageSize; - auto titleTextY = Scale(20); - auto descTextY = Scale(52); + auto image = g_xdbfTextureCache[achievement.ID]; + ImVec2 imageMin = { min.x + imageMarginX, min.y + imageMarginY }; + ImVec2 imageMax = { min.x + imageMarginX + imageSize, min.y + imageMarginY + imageSize }; if (!isUnlocked) SetShaderModifier(IMGUI_SHADER_MODIFIER_GRAYSCALE); - // Draw achievement icon. - drawList->AddImage - ( - icon, - { /* X */ min.x + imageMarginX, /* Y */ min.y + imageMarginY }, - { /* X */ min.x + imageMarginX + imageSize, /* Y */ min.y + imageMarginY + imageSize }, - { /* U */ 0, /* V */ 0 }, - { /* U */ 1, /* V */ 1 }, - IM_COL32(255, 255, 255, 255 * (isUnlocked ? 1 : 0.5f)) - ); + // Draw achievement image. + drawList->AddImage(image, imageMin, imageMax, { 0, 0 }, { 1, 1 }, IM_COL32_WHITE); if (!isUnlocked) SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); - drawList->PushClipRect(min, max, true); + auto fontSize = Scale(Config::Language == ELanguage::Japanese ? 28 : 27, true); - auto colLockedText = IM_COL32(80, 80, 80, 127); + auto textX = imageMax.x + itemMarginX * 2; + auto textColour = isUnlocked ? IM_COL32_WHITE : IM_COL32(137, 137, 137, 255); - auto colTextShadow = isUnlocked - ? IM_COL32(0, 0, 0, 255) - : IM_COL32(20, 20, 20, 127); + auto descText = isUnlocked ? achievement.UnlockedDesc.c_str() : achievement.LockedDesc.c_str(); + auto descTextOffsetY = Scale(32, true); + auto descSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, descText); - auto shadowOffset = isUnlocked ? 2 : 1; - auto shadowRadius = isUnlocked ? 1 : 0.5f; + + ImVec2 namePos = { textX, imageMin.y + Scale(3, true) }; + ImVec2 descPos = { textX, namePos.y + descTextOffsetY }; // Draw achievement name. - DrawTextWithShadow - ( - g_rodinFont, - fontSize, - { textX, min.y + titleTextY }, - isUnlocked ? IM_COL32(252, 243, 5, 255) : colLockedText, - achievement.Name.c_str(), - shadowOffset, - shadowRadius, - colTextShadow - ); - - ImVec2 marqueeMin = { textMarqueeX, min.y }; - ImVec2 marqueeMax = { max.x - Scale(10) /* timestamp margin X */, max.y }; - - SetHorizontalMarqueeFade(marqueeMin, marqueeMax, Scale(32)); - - if (isSelected && textX + textSize.x >= max.x - Scale(10)) + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); + drawList->AddText(g_pFntRodin, fontSize, namePos, textColour, achievement.Name.c_str()); + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); + + auto marqueeFadeScale = Scale(18, true); + ImVec2 marqueeMin = { imageMax.x, min.y }; + ImVec2 marqueeMax = { max.x - marqueeFadeScale, max.y }; + + if (isCurrent && textX + descSize.x >= max.x - marqueeFadeScale) { // Draw achievement description with marquee. - DrawTextWithMarqueeShadow - ( - g_rodinFont, - fontSize, - { textX, min.y + descTextY }, - marqueeMin, - marqueeMax, - isUnlocked ? IM_COL32_WHITE : colLockedText, - desc, - g_rowSelectionTime, - 0.9, - Scale(250), - shadowOffset, - shadowRadius, - colTextShadow - ); + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); + DrawTextWithMarquee(g_pFntRodin, fontSize, descPos, marqueeMin, marqueeMax, textColour, descText, g_rowSelectionTime, 0.9, Scale(200, true)); + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); } else { // Draw achievement description. - DrawTextWithShadow - ( - g_rodinFont, - fontSize, - { textX, min.y + descTextY }, - isUnlocked ? IM_COL32_WHITE : colLockedText, - desc, - shadowOffset, - shadowRadius, - colTextShadow - ); + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); + drawList->AddText(g_pFntRodin, fontSize, descPos, textColour, descText); + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); } - ResetMarqueeFade(); - - drawList->PopClipRect(); - if (!isUnlocked) return; @@ -245,6 +244,7 @@ static void DrawAchievement(int rowIndex, float yOffset, Achievement& achievemen return; char buffer[32]; + #ifdef _WIN32 tm timeStruct; tm *timePtr = &timeStruct; @@ -252,506 +252,189 @@ static void DrawAchievement(int rowIndex, float yOffset, Achievement& achievemen #else tm *timePtr = localtime(×tamp); #endif - strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M", timePtr); - fontSize = Scale(12); - textSize = g_newRodinFont->CalcTextSizeA(fontSize, FLT_MAX, 0, buffer); - - auto containerMarginX = Scale(10); - auto textMarginX = Scale(8); - - ImVec2 timestampMin = { max.x - containerMarginX - textSize.x - (textMarginX * 2), min.y + titleTextY }; - ImVec2 timestampMax = { max.x - containerMarginX, min.y + Scale(46) }; - - drawList->PushClipRect(min, max, true); - - auto bevelOffset = Scale(6); - - // Left - drawList->AddRectFilledMultiColor - ( - timestampMin, - { timestampMin.x + bevelOffset, timestampMax.y }, - IM_COL32(255, 255, 255, 255), - IM_COL32(149, 149, 149, 40), - IM_COL32(149, 149, 149, 40), - IM_COL32(255, 255, 255, 255) - ); - - // Right - drawList->AddRectFilledMultiColor - ( - { timestampMax.x - bevelOffset, timestampMin.y }, - { timestampMax.x, timestampMax.y }, - IM_COL32(149, 149, 149, 40), - IM_COL32(255, 255, 255, 255), - IM_COL32(255, 255, 255, 255), - IM_COL32(149, 149, 149, 40) - ); - - // Centre - drawList->AddRectFilled - ( - { timestampMin.x, timestampMin.y + bevelOffset }, - { timestampMax.x, timestampMax.y - bevelOffset }, - IM_COL32(38, 38, 38, 172) - ); - - // Top - drawList->AddRectFilledMultiColor - ( - timestampMin, - { timestampMax.x, timestampMin.y + bevelOffset }, - IM_COL32(16, 16, 16, 192), - IM_COL32(16, 16, 16, 192), - IM_COL32(38, 38, 38, 172), - IM_COL32(38, 38, 38, 172) - ); - - // Bottom - drawList->AddRectFilledMultiColor - ( - { timestampMin.x, timestampMax.y - bevelOffset }, - { timestampMax.x, timestampMax.y }, - IM_COL32(38, 40, 38, 169), - IM_COL32(38, 40, 38, 169), - IM_COL32(16, 16, 16, 192), - IM_COL32(16, 16, 16, 192) - ); + strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M", timePtr); + + auto timestampOffsetX = Scale(60, true); + auto timestampSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, buffer); // Draw timestamp text. - DrawTextWithOutline - ( - g_newRodinFont, - fontSize, - { /* X */ CENTRE_TEXT_HORZ(timestampMin, timestampMax, textSize), /* Y */ CENTRE_TEXT_VERT(timestampMin, timestampMax, textSize) }, - IM_COL32(255, 255, 255, 255), - buffer, - 4, - IM_COL32(8, 8, 8, 255) - ); - - drawList->PopClipRect(); + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); + drawList->AddText(g_pFntRodin, fontSize, { max.x - timestampSize.x - timestampOffsetX, namePos.y }, IM_COL32_WHITE, buffer); + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); } -static void DrawTrophySparkles(ImVec2 min, ImVec2 max, int recordCount, int trophyFrameIndex) +void AchievementMenu::Draw() { - auto drawList = ImGui::GetBackgroundDrawList(); - - constexpr auto recordsHalfTotal = ACH_RECORDS / 2; - - // Don't sparkle the bronze trophy. - if (recordCount < recordsHalfTotal) + if (AchievementMenu::IsClosing() && s_commonMenu.Close() >= 1.0) + { + s_isVisible = false; return; + } - static int trophyAnimCycles = 0; - static bool isIncrementedCycles = false; + if (!s_isVisible) + return; - bool isGoldTrophy = recordCount >= ACH_RECORDS; + auto* drawList = ImGui::GetBackgroundDrawList(); + auto& res = ImGui::GetIO().DisplaySize; - if (!trophyFrameIndex && !isIncrementedCycles) + if (s_commonMenu.ShowDescription) { - trophyAnimCycles++; - trophyAnimCycles %= isGoldTrophy ? 4 : 3; - isIncrementedCycles = true; - } + auto horzMargin = Scale(128, true); + auto gradientTop = IM_COL32(0, 103, 255, 255); + auto gradientBottom = IM_COL32(0, 41, 100, 255); - if (trophyFrameIndex >= 1) - isIncrementedCycles = false; + ImVec2 footerClipMin = { g_horzCentre + horzMargin, res.y - g_vertCentre - Scale(152, true) }; + ImVec2 footerClipMax = { res.x - g_horzCentre - horzMargin, res.y - g_vertCentre - Scale(107, true) }; - if (trophyAnimCycles >= 2) - { - auto marginX = Scale(9); - auto uv = PIXELS_TO_UV_COORDS(2048, 1024, 1984, 960, 64, 64); - auto& uv0 = std::get<0>(uv); - auto& uv1 = std::get<1>(uv); - auto colour = IM_COL32(240, 240, 200, 200); + drawList->PushClipRect(footerClipMin, footerClipMax); + drawList->AddRectFilledMultiColor({ 0.0f, g_vertCentre }, { res.x, res.y - g_vertCentre }, gradientTop, gradientTop, gradientBottom, gradientBottom); + drawList->PopClipRect(); + } - static auto scaleStart = ImGui::GetTime(); - auto scale = Scale(18) * Hermite(1.0f, 0.0f, (sin((ImGui::GetTime() - scaleStart) * (2.0f * M_PI / (15.0f / 60.0f))) + 1.0f) / 2.0f); + s_commonMenu.Draw(); - // Don't do extra sparkles for the silver trophy. - if (isGoldTrophy) + if (s_pMainMenuTask->m_State == Sonicteam::MainMenuTask::MainMenuState_GoldMedalResults) + { + if (auto& spInputManager = App::s_pApp->m_pDoc->m_vspInputManager[0]) { - if (trophyFrameIndex >= 0 && trophyFrameIndex <= 5) + auto& rPadState = spInputManager->m_PadState; + + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_B)) { - auto marginXAdd = Scale(1); - - // Centre Left - drawList->AddImage - ( - g_upTrophyIcon.get(), - { min.x - scale / 2 + marginX + marginXAdd, max.y - ((max.y - min.y) / 2) - scale / 2 }, - { min.x + scale / 2 + marginX + marginXAdd, max.y - ((max.y - min.y) / 2) + scale / 2 }, - uv0, uv1, - colour - ); + AchievementMenu::Close(); + ButtonWindow::Close(); } - if (trophyFrameIndex >= 16 && trophyFrameIndex <= 21) + switch (AchievementMenu::s_state) { - auto marginXAdd = Scale(4); - auto marginY = Scale(11); - - // Bottom Right - drawList->AddImage - ( - g_upTrophyIcon.get(), - { max.x - scale / 2 - (marginX + marginXAdd), max.y - scale / 2 - marginY }, - { max.x + scale / 2 - (marginX + marginXAdd), max.y + scale / 2 - marginY }, - uv0, uv1, - colour - ); + case AchievementMenuState::GoldMedals: + { + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_Y)) + { + AchievementMenu::SetState(AchievementMenuState::Achievements); + ButtonWindow::Open("Button_GoldMedalsBack"); + SetGoldMedalResultsVisible(s_pMainMenuTask, false); + Game_PlaySound("window_open"); + } + + break; + } + + case AchievementMenuState::Achievements: + { + // Discard all buttons besides B. + if ((s_pMainMenuTask->m_PressedButtons.get() & 0x20) == 0) + s_pMainMenuTask->m_PressedButtons = 0; + + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_Y)) + { + AchievementMenu::SetState(AchievementMenuState::GoldMedals); + ButtonWindow::Open("Button_AchievementsBack"); + SetGoldMedalResultsVisible(s_pMainMenuTask, true); + Game_PlaySound("window_close"); + } + + break; + } } } - - if (trophyFrameIndex >= 24 && trophyFrameIndex <= 29) - { - auto marginY = Scale(1); - - // Top Right - drawList->AddImage - ( - g_upTrophyIcon.get(), - { max.x - scale / 2 - marginX, min.y - scale / 2 + marginY }, - { max.x + scale / 2 - marginX, min.y + scale / 2 + marginY }, - uv0, uv1, - colour - ); - } } -} -static void DrawAchievementTotal(ImVec2 min, ImVec2 max) -{ - auto drawList = ImGui::GetBackgroundDrawList(); + switch (s_state) + { + case AchievementMenuState::GoldMedals: + s_commonMenu.SetTitle(Localise("Achievements_GoldMedals_Uppercase")); + s_commonMenu.ShowDescription = false; + break; - // Transparency fade animation. - auto alpha = Cubic(0, 1, ComputeMotion(g_appearTime, COUNTER_INTRO_FADE_START, COUNTER_INTRO_FADE_END)); + case AchievementMenuState::Achievements: + { + s_commonMenu.SetTitle(Localise("Achievements_Title_Uppercase")); + s_commonMenu.ShowDescription = true; - auto imageMarginX = Scale(5); - auto imageMarginY = Scale(5); - auto imageSize = Scale(45); + auto containerWidth = Scale(1158, true); + auto containerTop = Scale(148, true); + auto containerHeight = Scale(405, true); - ImVec2 imageMin = { max.x - imageSize - imageMarginX, min.y - imageSize - imageMarginY }; - ImVec2 imageMax = { imageMin.x + imageSize, imageMin.y + imageSize }; + ImVec2 min = { (res.x / 2) - containerWidth / 2, g_vertCentre + containerTop }; + ImVec2 max = { min.x + containerWidth, min.y + containerHeight }; - constexpr auto columns = 8; - constexpr auto rows = 4; - constexpr auto spriteSize = 256.0f; - constexpr auto textureWidth = 2048.0f; - constexpr auto textureHeight = 1024.0f; - auto frameIndex = int32_t(floor(ImGui::GetTime() * 30.0f)) % 30; - auto columnIndex = frameIndex % columns; - auto rowIndex = frameIndex / columns; - auto uv0 = ImVec2(columnIndex * spriteSize / textureWidth, rowIndex * spriteSize / textureHeight); - auto uv1 = ImVec2((columnIndex + 1) * spriteSize / textureWidth, (rowIndex + 1) * spriteSize / textureHeight); + if (DrawContainer(min, max)) + { + auto rowIndex = 0; - constexpr auto recordsHalfTotal = ACH_RECORDS / 2; - auto records = AchievementManager::GetTotalRecords(); + for (auto& tpl : g_achievements) + { + auto& achievement = std::get<0>(tpl); - ImVec4 colBronze = ImGui::ColorConvertU32ToFloat4(IM_COL32(198, 105, 15, 255 * alpha)); - ImVec4 colSilver = ImGui::ColorConvertU32ToFloat4(IM_COL32(220, 220, 220, 255 * alpha)); - ImVec4 colGold = ImGui::ColorConvertU32ToFloat4(IM_COL32(255, 195, 56, 255 * alpha)); - ImVec4 colResult; + if (AchievementManager::IsUnlocked(achievement.ID)) + DrawAchievement(rowIndex++, achievement, true); + } - if (records <= 25) - { - float t = (float)records / 25.0f; + for (auto& tpl : g_achievements) + { + auto& achievement = std::get<0>(tpl); - // Fade from bronze to silver. - colResult.x = colBronze.x + t * (colSilver.x - colBronze.x); - colResult.y = colBronze.y + t * (colSilver.y - colBronze.y); - colResult.z = colBronze.z + t * (colSilver.z - colBronze.z); - colResult.w = colBronze.w + t * (colSilver.w - colBronze.w); - } - else if (records <= 50) - { - float t = ((float)records - 25.0f) / 25.0f; + if (!AchievementManager::IsUnlocked(achievement.ID)) + DrawAchievement(rowIndex++, achievement, false); + } - // Fade from silver to gold. - colResult.x = colSilver.x + t * (colGold.x - colSilver.x); - colResult.y = colSilver.y + t * (colGold.y - colSilver.y); - colResult.z = colSilver.z + t * (colGold.z - colSilver.z); - colResult.w = colSilver.w + t * (colGold.w - colSilver.w); - } - else - { - colResult = colGold; - } + drawList->PopClipRect(); - drawList->AddImage(g_upTrophyIcon.get(), imageMin, imageMax, uv0, uv1, ImGui::ColorConvertFloat4ToU32(colResult)); - - // Add extra luminance to the trophy for bronze and gold. - if (records < recordsHalfTotal || records >= ACH_RECORDS) - drawList->AddImage(g_upTrophyIcon.get(), imageMin, imageMax, uv0, uv1, IM_COL32(255, 255, 255, 12)); - - // Draw sparkles on the trophy for silver and gold. - if (records >= recordsHalfTotal || records >= ACH_RECORDS) - DrawTrophySparkles(imageMin, imageMax, records, frameIndex); - - auto str = fmt::format("{} / {}", records, ACH_RECORDS); - auto fontSize = Scale(20); - auto textSize = g_newRodinFont->CalcTextSizeA(fontSize, FLT_MAX, 0, str.c_str()); - - DrawTextWithOutline - ( - g_newRodinFont, - fontSize, - { /* X */ imageMin.x - textSize.x - Scale(6), /* Y */ CENTRE_TEXT_VERT(imageMin, imageMax, textSize) }, - IM_COL32(255, 255, 255, 255 * alpha), - str.c_str(), - 4, - IM_COL32(0, 0, 0, 255 * alpha) - ); -} + auto scrollArrowsOffsetX = Scale(17, true); + auto scrollArrowsOffsetTop = Scale(40, true); + auto scrollArrowsOffsetBottom = Scale(60, true); + ImVec2 scrollArrowsMin = { max.x + scrollArrowsOffsetX, min.y + scrollArrowsOffsetTop }; + ImVec2 scrollArrowsMax = { scrollArrowsMin.x + scrollArrowsOffsetX, max.y - scrollArrowsOffsetBottom }; -//static void DrawContentContainer() -//{ -// auto drawList = ImGui::GetBackgroundDrawList(); -// -// // Expand/retract animation. -// auto motion = g_isClosing -// ? ComputeMotion(g_appearTime, 0, CONTENT_CONTAINER_COMMON_MOTION_START) -// : ComputeMotion(g_appearTime, CONTENT_CONTAINER_COMMON_MOTION_START, CONTENT_CONTAINER_COMMON_MOTION_END); -// -// auto minX = g_isClosing -// ? Hermite(251, 301, motion) -// : Hermite(301, 251, motion); -// -// auto minY = g_isClosing -// ? Hermite(189, 206, motion) -// : Hermite(206, 189, motion); -// -// auto maxX = g_isClosing -// ? Hermite(1031, 978, motion) -// : Hermite(978, 1031, motion); -// -// auto maxY = g_isClosing -// ? Hermite(604, 573, motion) -// : Hermite(573, 604, motion); -// -// ImVec2 min = { g_aspectRatioOffsetX + Scale(minX), g_aspectRatioOffsetY + Scale(minY) }; -// ImVec2 max = { g_aspectRatioOffsetX + Scale(maxX), g_aspectRatioOffsetY + Scale(maxY) }; -// -// // Transparency fade animation. -// auto alpha = g_isClosing -// ? Hermite(1, 0, motion) -// : Hermite(0, 1, motion); -// -// DrawContainer(min, max, IM_COL32(197, 194, 197, 200), IM_COL32(115, 113, 115, 236), alpha); -// -// if (motion < 1.0f) -// { -// drawList->PopClipRect(); -// return; -// } -// else if (g_isClosing) -// { -// AchievementMenu::s_isVisible = false; -// drawList->PopClipRect(); -// return; -// } -// -// auto clipRectMin = drawList->GetClipRectMin(); -// auto clipRectMax = drawList->GetClipRectMax(); -// -// auto itemHeight = Scale(94); -// auto yOffset = -g_firstVisibleRowIndex * itemHeight + Scale(2); -// auto rowCount = 0; -// -// // Draw separators. -// for (int i = 1; i <= 3; i++) -// { -// ImVec2 lineMin = { clipRectMin.x + Scale(35), clipRectMin.y + itemHeight * i + Scale(2) }; -// ImVec2 lineMax = { clipRectMax.x - Scale(55), lineMin.y + Scale(1.3f) }; -// -// SetAdditive(true); -// drawList->AddRectFilled(lineMin, lineMax, IM_COL32(160, 160, 160, 60)); -// SetAdditive(false); -// } -// -// for (auto& tpl : g_achievements) -// { -// auto achievement = std::get<0>(tpl); -// -// if (AchievementManager::IsUnlocked(achievement.ID)) -// DrawAchievement(rowCount++, yOffset, achievement, true); -// } -// -// for (auto& tpl : g_achievements) -// { -// auto achievement = std::get<0>(tpl); -// -// if (!AchievementManager::IsUnlocked(achievement.ID)) -// DrawAchievement(rowCount++, yOffset, achievement, false); -// } -// -// auto inputState = SWA::CInputState::GetInstance(); -// -// bool upIsHeld = inputState->GetPadState().IsDown(SWA::eKeyState_DpadUp) || -// inputState->GetPadState().LeftStickVertical > 0.5f; -// -// bool downIsHeld = inputState->GetPadState().IsDown(SWA::eKeyState_DpadDown) || -// inputState->GetPadState().LeftStickVertical < -0.5f; -// -// bool isReachedTop = g_selectedRowIndex == 0; -// bool isReachedBottom = g_selectedRowIndex == rowCount - 1; -// -// bool scrollUp = !g_upWasHeld && upIsHeld; -// bool scrollDown = !g_downWasHeld && downIsHeld; -// -// auto time = ImGui::GetTime(); -// auto fastScroll = (time - g_lastTappedTime) > 0.6; -// auto fastScrollSpeed = 1.0 / 3.5; -// static auto fastScrollSpeedUp = false; -// -// if (scrollUp || scrollDown) -// g_lastTappedTime = time; -// -// if (!upIsHeld && !downIsHeld) -// fastScrollSpeedUp = false; -// -// if (fastScrollSpeedUp) -// fastScrollSpeed /= 2; -// -// if (fastScroll) -// { -// if ((time - g_lastIncrementTime) < fastScrollSpeed) -// { -// fastScroll = false; -// } -// else -// { -// g_lastIncrementTime = time; -// -// scrollUp = upIsHeld; -// scrollDown = downIsHeld; -// fastScrollSpeedUp = true; -// } -// } -// -// if (scrollUp) -// { -// --g_selectedRowIndex; -// if (g_selectedRowIndex < 0) -// g_selectedRowIndex = rowCount - 1; -// } -// else if (scrollDown) -// { -// ++g_selectedRowIndex; -// if (g_selectedRowIndex >= rowCount) -// g_selectedRowIndex = 0; -// } -// -// if (scrollUp || scrollDown) -// { -// g_rowSelectionTime = time; -// Game_PlaySound("sys_actstg_pausecursor"); -// } -// -// g_upWasHeld = upIsHeld; -// g_downWasHeld = downIsHeld; -// -// int visibleRowCount = int(floor((clipRectMax.y - clipRectMin.y) / itemHeight)); -// -// if (g_firstVisibleRowIndex > g_selectedRowIndex) -// g_firstVisibleRowIndex = g_selectedRowIndex; -// -// if (g_firstVisibleRowIndex + visibleRowCount - 1 < g_selectedRowIndex) -// g_firstVisibleRowIndex = std::max(0, g_selectedRowIndex - visibleRowCount + 1); -// -// // Pop clip rect from DrawContentContainer -// drawList->PopClipRect(); -// -// DrawAchievementTotal(min, max); -// -// // Draw scroll bar -// if (rowCount > visibleRowCount) -// { -// float cornerRadius = Scale(25); -// float totalHeight = (clipRectMax.y - clipRectMin.y - cornerRadius) - Scale(5); -// float heightRatio = float(visibleRowCount) / float(rowCount); -// float offsetRatio = float(g_firstVisibleRowIndex) / float(rowCount); -// float offsetX = clipRectMax.x - Scale(39); -// float offsetY = offsetRatio * totalHeight + clipRectMin.y + Scale(4); -// float maxY = max.y - cornerRadius - Scale(3); -// float lineThickness = Scale(1); -// float innerMarginX = Scale(2); -// float outerMarginX = Scale(24); -// -// // Outline -// drawList->AddRect -// ( -// { /* X */ offsetX - lineThickness, /* Y */ clipRectMin.y - lineThickness }, -// { /* X */ clipRectMax.x - outerMarginX + lineThickness, /* Y */ maxY + lineThickness }, -// IM_COL32(255, 255, 255, 155), -// Scale(1) -// ); -// -// // Background -// drawList->AddRectFilledMultiColor -// ( -// { /* X */ offsetX, /* Y */ clipRectMin.y }, -// { /* X */ clipRectMax.x - outerMarginX, /* Y */ maxY }, -// IM_COL32(123, 125, 123, 255), -// IM_COL32(123, 125, 123, 255), -// IM_COL32(97, 99, 97, 255), -// IM_COL32(97, 99, 97, 255) -// ); -// -// // Scroll Bar Outline -// drawList->AddRectFilledMultiColor -// ( -// { /* X */ offsetX + innerMarginX, /* Y */ offsetY - lineThickness }, -// { /* X */ clipRectMax.x - outerMarginX - innerMarginX, /* Y */ offsetY + lineThickness + totalHeight * heightRatio }, -// IM_COL32(185, 185, 185, 255), -// IM_COL32(185, 185, 185, 255), -// IM_COL32(172, 172, 172, 255), -// IM_COL32(172, 172, 172, 255) -// ); -// -// // Scroll Bar -// drawList->AddRectFilled -// ( -// { /* X */ offsetX + innerMarginX + lineThickness, /* Y */ offsetY }, -// { /* X */ clipRectMax.x - outerMarginX - innerMarginX - lineThickness, /* Y */ offsetY + totalHeight * heightRatio }, -// IM_COL32(255, 255, 255, 255) -// ); -// } -//} - -void AchievementMenu::Init() -{ - auto& io = ImGui::GetIO(); + DrawScrollArrows(scrollArrowsMin, scrollArrowsMax, Scale(25, true), g_scrollArrowsTime, g_selectedIndex > MAX_VISIBLE_ROWS / 2, g_selectedIndex < g_rowCount - MAX_VISIBLE_ROWS / 2); - g_rodinFont = ImFontAtlasSnapshot::GetFont("FOT-RodinPro-DB.otf"); - g_newRodinFont = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-UB.otf"); + auto upIsHeld = false; + auto downIsHeld = false; - g_upTrophyIcon = LOAD_ZSTD_TEXTURE(g_trophy); -} + for (auto& spInputManager : App::s_pApp->m_pDoc->m_vspInputManager) + { + auto& rPadState = spInputManager->m_PadState; -void AchievementMenu::Draw() -{ - if (!s_isVisible) - return; + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DPadUp) || -rPadState.LeftStickVertical > 0.5f) + upIsHeld = true; + + if (!g_upWasHeld && upIsHeld) + g_up = true; + + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DPadDown) || -rPadState.LeftStickVertical < -0.5f) + downIsHeld = true; + + if (!g_downWasHeld && downIsHeld) + g_down = true; + } + + MoveCursor(g_selectedIndex, 0, g_rowCount); - DrawHeaderContainer(Localise("Achievements_Name_Uppercase").c_str()); -// DrawContentContainer(); + g_up = false; + g_upWasHeld = upIsHeld; + g_down = false; + g_downWasHeld = downIsHeld; + } + + drawList->PopClipRect(); + + break; + } + } } void AchievementMenu::Open() { - s_isVisible = true; - g_isClosing = false; - g_appearTime = ImGui::GetTime(); + if (s_isVisible) + return; g_achievements.clear(); for (auto& achievement : g_xdbfWrapper.GetAchievements((EXDBFLanguage)Config::Language.Value)) { - if (Config::Language == ELanguage::English) - achievement.Name = xdbf::FixInvalidSequences(achievement.Name); + achievement.LockedDesc = xdbf::FixInvalidSequences(achievement.LockedDesc); g_achievements.push_back(std::make_tuple(achievement, AchievementManager::GetTimestamp(achievement.ID))); } @@ -761,26 +444,57 @@ void AchievementMenu::Open() return std::get<1>(a) > std::get<1>(b); }); - ButtonWindow::Open("Button_Back"); + g_rowCount = g_achievements.size(); + + // Format achievement progress. + char descriptionText[128]; + snprintf(descriptionText, sizeof(descriptionText), Localise("Achievements_Progress").c_str(), + std::min(100, int(AchievementManager::GetTotalRecords()) / g_rowCount) * 100); + + s_commonMenu = CommonMenu(Localise("Achievements_GoldMedals_Uppercase"), descriptionText, false); + s_commonMenu.ShowDescription = false; + s_commonMenu.ShowVersionString = false; + s_commonMenu.ReduceDraw = true; + s_commonMenu.Open(); - ResetSelection(); - Game_PlaySound("sys_actstg_pausewinopen"); + s_isVisible = true; + s_state = AchievementMenuState::GoldMedals; + g_time = ImGui::GetTime(); + g_selectedIndex = 0; - hid::SetProhibitedInputs(XAMINPUT_GAMEPAD_START); + ButtonWindow::Open("Button_AchievementsBack"); + MainMenuTaskPatches::HideButtonWindow = true; } void AchievementMenu::Close() { - if (!g_isClosing) + if (AchievementMenu::IsClosing()) + return; + + switch (s_state) { - g_appearTime = ImGui::GetTime(); - g_isClosing = true; + case AchievementMenuState::GoldMedals: + s_state = AchievementMenuState::ClosingGoldMedals; + break; - hid::SetProhibitedInputs(); + case AchievementMenuState::Achievements: + s_state = AchievementMenuState::ClosingAchievements; + break; } + g_time = ImGui::GetTime(); + ButtonWindow::Close(); + MainMenuTaskPatches::HideButtonWindow = false; +} + +void AchievementMenu::SetState(AchievementMenuState state) +{ + s_state = state; + g_time = ImGui::GetTime(); +} - Game_PlaySound("sys_actstg_pausewinclose"); - Game_PlaySound("sys_actstg_pausecansel"); +bool AchievementMenu::IsClosing() +{ + return s_state == AchievementMenuState::ClosingGoldMedals || s_state == AchievementMenuState::ClosingAchievements; } diff --git a/MarathonRecomp/ui/achievement_menu.h b/MarathonRecomp/ui/achievement_menu.h index db8d6d4b..6b6b902d 100644 --- a/MarathonRecomp/ui/achievement_menu.h +++ b/MarathonRecomp/ui/achievement_menu.h @@ -1,12 +1,49 @@ #pragma once +#include +#include + +#define MARATHON_RECOMP_ACHIEVEMENT_MENU + +enum class AchievementMenuState +{ + GoldMedals, + Achievements, + ClosingGoldMedals, + ClosingAchievements +}; + class AchievementMenu { public: - inline static bool s_isVisible = false; + static inline CommonMenu s_commonMenu{}; + static inline AchievementMenuState s_state{}; + static inline Sonicteam::MainMenuTask* s_pMainMenuTask{}; + static inline bool s_isVisible = false; - static void Init(); static void Draw(); static void Open(); static void Close(); + static void SetState(AchievementMenuState state); + static bool IsClosing(); + + static void SetGoldMedalResultsVisible(Sonicteam::MainMenuTask* pMainMenuTask, bool isVisible) + { + std::array states{}; + + if (isVisible) + { + states = { 0, 3, 5, 11 }; + } + else + { + states = { 1, 4, 6, 12 }; + } + + for (auto& state : states) + { + guest_stack_var msgChangeState(state, pMainMenuTask->m_GoldMedalEpisodeIndex); + pMainMenuTask->m_pHUDGoldMedal->ProcessMessage(msgChangeState.get()); + } + } }; diff --git a/MarathonRecomp/ui/common_menu.cpp b/MarathonRecomp/ui/common_menu.cpp index 51258f11..7301b176 100644 --- a/MarathonRecomp/ui/common_menu.cpp +++ b/MarathonRecomp/ui/common_menu.cpp @@ -110,7 +110,7 @@ void CommonMenu::Draw() auto titleFontSize = Scale(33, true); auto titleSize = g_pFntNewRodin->CalcTextSizeA(titleFontSize, FLT_MAX, 0.0f, titleText); auto titleOffsetX = redStripCornerMax.x - Scale(105, true); - auto titleOffsetY = redStripMotion + Scale(3.25, true); + auto titleOffsetY = redStripMotion + Scale(3.5, true); auto titleMotionTime = ComputeLinearMotion(m_titleTime, PlayTransitions && !m_isClosing ? 10 : 0, 10, m_isClosing); auto titleOffsetXMotion = Lerp(max.x + titleSize.x, titleOffsetX, titleMotionTime); @@ -154,143 +154,146 @@ void CommonMenu::Draw() } } - auto textCoverCornerUVs = PIXELS_TO_UV_COORDS(1024, 1024, 801, 400, 150, 150); - auto textCoverCornerExtendUVs = PIXELS_TO_UV_COORDS(1024, 1024, 801, 400, 125, 150); - auto textCoverCentreUVs = PIXELS_TO_UV_COORDS(1024, 1024, 950, 400, 50, 150); - auto textCoverCornerUVCompensation = Scale(2, true); - auto textCoverOffsetY = Scale(16.4, true); - auto textCoverWidth = Scale(149.5, true); - auto textCoverHeight = Scale(150, true); - auto textCoverMotion = Lerp(max.y + textCoverHeight, max.y - textCoverOffsetY - textCoverHeight, borderMotionTime); - auto textCoverColour = IM_COL32(0, 23, 57, 255); - - ImVec2 textCoverCornerLeftMin = { min.x, textCoverMotion }; - ImVec2 textCoverCornerLeftMax = { textCoverCornerLeftMin.x + textCoverWidth, textCoverCornerLeftMin.y + textCoverHeight }; - ImVec2 textCoverCentreMin = { textCoverCornerLeftMax.x, textCoverCornerLeftMin.y }; - ImVec2 textCoverCentreMax = { max.x - textCoverWidth, textCoverCornerLeftMax.y }; - ImVec2 textCoverCornerRightMin = { max.x - textCoverWidth, textCoverCornerLeftMin.y }; - ImVec2 textCoverCornerRightMax = { max.x, textCoverCornerLeftMax.y }; - - if (ReduceDraw) + if (ShowDescription) { - auto horzMargin = Scale(128, true); - - ImVec2 textCoverClipMin = { textCoverCornerLeftMin.x + horzMargin, textCoverCornerLeftMin.y + Scale(14, true) }; - ImVec2 textCoverClipMax = { textCoverCornerRightMax.x - horzMargin, textCoverCornerRightMax.y - Scale(90, true) }; - - drawList->PushClipRect(textCoverClipMin, textCoverClipMax); - } + auto textCoverCornerUVs = PIXELS_TO_UV_COORDS(1024, 1024, 801, 400, 150, 150); + auto textCoverCornerExtendUVs = PIXELS_TO_UV_COORDS(1024, 1024, 801, 400, 125, 150); + auto textCoverCentreUVs = PIXELS_TO_UV_COORDS(1024, 1024, 950, 400, 50, 150); + auto textCoverCornerUVCompensation = Scale(2, true); + auto textCoverOffsetY = Scale(16.4, true); + auto textCoverWidth = Scale(149.5, true); + auto textCoverHeight = Scale(150, true); + auto textCoverMotion = Lerp(max.y + textCoverHeight, max.y - textCoverOffsetY - textCoverHeight, borderMotionTime); + auto textCoverColour = IM_COL32(0, 23, 57, 255); + + ImVec2 textCoverCornerLeftMin = { min.x, textCoverMotion }; + ImVec2 textCoverCornerLeftMax = { textCoverCornerLeftMin.x + textCoverWidth, textCoverCornerLeftMin.y + textCoverHeight }; + ImVec2 textCoverCentreMin = { textCoverCornerLeftMax.x, textCoverCornerLeftMin.y }; + ImVec2 textCoverCentreMax = { max.x - textCoverWidth, textCoverCornerLeftMax.y }; + ImVec2 textCoverCornerRightMin = { max.x - textCoverWidth, textCoverCornerLeftMin.y }; + ImVec2 textCoverCornerRightMax = { max.x, textCoverCornerLeftMax.y }; + + if (ReduceDraw) + { + auto horzMargin = Scale(128, true); - if (!Description.empty()) - { - auto descFadeScale = Scale(20, true); - auto descFontSize = Scale(Config::Language == ELanguage::Japanese ? 28 : 27, true); - auto descSize = g_pFntRodin->CalcTextSizeA(descFontSize, FLT_MAX, 0.0f, Description.data()); + ImVec2 textCoverClipMin = { textCoverCornerLeftMin.x + horzMargin, textCoverCornerLeftMin.y + Scale(14, true) }; + ImVec2 textCoverClipMax = { textCoverCornerRightMax.x - horzMargin, textCoverCornerRightMax.y - Scale(90, true) }; - ImVec2 descBoundsMin = { textCoverCentreMin.x - Scale(18, true), textCoverCentreMin.y + Scale(20, true) }; - ImVec2 descBoundsMax = { textCoverCentreMax.x + Scale(18, true), textCoverCentreMax.y - Scale(90, true) }; - auto descBoundsWidth = descBoundsMax.x - descBoundsMin.x; + drawList->PushClipRect(textCoverClipMin, textCoverClipMax); + } - m_descPos = + if (!Description.empty()) { - descBoundsMin.x + ((descBoundsMax.x - descBoundsMin.x) / 2) - (descSize.x / 2), - descBoundsMin.y + ((descBoundsMax.y - descBoundsMin.y) / 2) - (descSize.y / 2) - }; + auto descFadeScale = Scale(20, true); + auto descFontSize = Scale(Config::Language == ELanguage::Japanese ? 28 : 27, true); + auto descSize = g_pFntRodin->CalcTextSizeA(descFontSize, FLT_MAX, 0.0f, Description.data()); - if (descSize.x > descBoundsWidth) - { - auto descScrollMax = descSize.x - (descBoundsWidth - descFadeScale * 2); - auto descScrollSpeed = Scale(150, true); - auto descScrollDelay = 1.2f; + ImVec2 descBoundsMin = { textCoverCentreMin.x - Scale(18, true), textCoverCentreMin.y + Scale(20, true) }; + ImVec2 descBoundsMax = { textCoverCentreMax.x + Scale(18, true), textCoverCentreMax.y - Scale(90, true) }; + auto descBoundsWidth = descBoundsMax.x - descBoundsMin.x; - if (descScrollMax > 0.0f) + m_descPos = { - auto horz = -m_inputListener.RightStickX; + descBoundsMin.x + ((descBoundsMax.x - descBoundsMin.x) / 2) - (descSize.x / 2), + descBoundsMin.y + ((descBoundsMax.y - descBoundsMin.y) / 2) - (descSize.y / 2) + }; - if (fabs(horz) > 0.25f) - { - m_isDescManualScrolling = true; - m_descScrollOffset += horz * descScrollSpeed * App::s_deltaTime; - } - else if (m_isDescManualScrolling && fabs(horz) <= 0.25f) - { - m_isDescScrolling = false; - m_isDescManualScrolling = false; - m_descScrollTimer = 0.0f; - m_descScrollDirection = horz > 0.0f ? 1.0f : -1.0f; - } + if (descSize.x > descBoundsWidth) + { + auto descScrollMax = descSize.x - (descBoundsWidth - descFadeScale * 2); + auto descScrollSpeed = Scale(150, true); + auto descScrollDelay = 1.2f; - if (!m_isDescManualScrolling) + if (descScrollMax > 0.0f) { - if (!m_isDescScrolling) - { - m_descScrollTimer += App::s_deltaTime; + auto horz = -m_inputListener.RightStickX; - if (m_descScrollTimer >= descScrollDelay) - m_isDescScrolling = true; + if (fabs(horz) > 0.25f) + { + m_isDescManualScrolling = true; + m_descScrollOffset += horz * descScrollSpeed * App::s_deltaTime; } - - if (m_isDescScrolling) + else if (m_isDescManualScrolling && fabs(horz) <= 0.25f) { - m_descScrollOffset += descScrollSpeed * m_descScrollDirection * App::s_deltaTime; + m_isDescScrolling = false; + m_isDescManualScrolling = false; + m_descScrollTimer = 0.0f; + m_descScrollDirection = horz > 0.0f ? 1.0f : -1.0f; + } - if (m_descScrollOffset >= descScrollMax) + if (!m_isDescManualScrolling) + { + if (!m_isDescScrolling) { - m_isDescScrolling = false; - m_descScrollOffset = descScrollMax; - m_descScrollTimer = 0.0f; - m_descScrollDirection = -1.0f; + m_descScrollTimer += App::s_deltaTime; + + if (m_descScrollTimer >= descScrollDelay) + m_isDescScrolling = true; } - else if (m_descScrollOffset <= 0.0f) + + if (m_isDescScrolling) { - m_isDescScrolling = false; - m_descScrollOffset = 0; - m_descScrollTimer = 0.0f; - m_descScrollDirection = 1.0f; + m_descScrollOffset += descScrollSpeed * m_descScrollDirection * App::s_deltaTime; + + if (m_descScrollOffset >= descScrollMax) + { + m_isDescScrolling = false; + m_descScrollOffset = descScrollMax; + m_descScrollTimer = 0.0f; + m_descScrollDirection = -1.0f; + } + else if (m_descScrollOffset <= 0.0f) + { + m_isDescScrolling = false; + m_descScrollOffset = 0; + m_descScrollTimer = 0.0f; + m_descScrollDirection = 1.0f; + } } } + + m_descScrollOffset = std::clamp(m_descScrollOffset, 0.0f, descScrollMax); + } + else + { + m_isDescScrolling = false; + m_descScrollOffset = 0.0f; + m_descScrollTimer = 0.0f; + m_descScrollDirection = 1.0f; } - m_descScrollOffset = std::clamp(m_descScrollOffset, 0.0f, descScrollMax); + m_descPos.x = (descBoundsMin.x + descFadeScale) - m_descScrollOffset; } - else - { - m_isDescScrolling = false; - m_descScrollOffset = 0.0f; - m_descScrollTimer = 0.0f; - m_descScrollDirection = 1.0f; - } - - m_descPos.x = (descBoundsMin.x + descFadeScale) - m_descScrollOffset; - } - auto descAlphaMotionTime = ComputeLinearMotion(m_descTime, PlayTransitions ? 10 : 0, 15, m_isClosing); + auto descAlphaMotionTime = ComputeLinearMotion(m_descTime, PlayTransitions ? 10 : 0, 15, m_isClosing); - // Draw text cover backdrop. - drawList->AddRectFilled({ 0.0f, textCoverMotion }, { res.x, textCoverMotion + textCoverHeight }, IM_COL32(0, 0, 0, 65)); + // Draw text cover backdrop. + drawList->AddRectFilled({ 0.0f, textCoverMotion }, { res.x, textCoverMotion + textCoverHeight }, IM_COL32(0, 0, 0, 65)); - // Draw previous description fading out. - if (!m_previousDesc.empty()) - drawList->AddText(g_pFntRodin, descFontSize, m_previousDescPos, IM_COL32(255, 255, 255, Lerp(255, 0, descAlphaMotionTime)), m_previousDesc.data()); + // Draw previous description fading out. + if (!m_isClosing && !m_previousDesc.empty()) + drawList->AddText(g_pFntRodin, descFontSize, m_previousDescPos, IM_COL32(255, 255, 255, Lerp(255, 0, descAlphaMotionTime)), m_previousDesc.data()); - // Draw description. - drawList->AddText(g_pFntRodin, descFontSize, m_descPos, IM_COL32(255, 255, 255, Lerp(0, 255, descAlphaMotionTime)), Description.data()); + // Draw description. + drawList->AddText(g_pFntRodin, descFontSize, m_descPos, IM_COL32(255, 255, 255, Lerp(0, 255, descAlphaMotionTime)), Description.data()); - // Draw left text cover. - drawList->AddImage(g_upTexMainMenu1.get(), { 0.0f, textCoverCornerLeftMin.y }, { textCoverCornerLeftMin.x + textCoverCornerUVCompensation, textCoverCornerLeftMax.y }, GET_UV_COORDS(textCoverCornerExtendUVs), textCoverColour); - drawList->AddImage(g_upTexMainMenu1.get(), textCoverCornerLeftMin, textCoverCornerLeftMax, GET_UV_COORDS(textCoverCornerUVs), textCoverColour); + // Draw left text cover. + drawList->AddImage(g_upTexMainMenu1.get(), { 0.0f, textCoverCornerLeftMin.y }, { textCoverCornerLeftMin.x + textCoverCornerUVCompensation, textCoverCornerLeftMax.y }, GET_UV_COORDS(textCoverCornerExtendUVs), textCoverColour); + drawList->AddImage(g_upTexMainMenu1.get(), textCoverCornerLeftMin, textCoverCornerLeftMax, GET_UV_COORDS(textCoverCornerUVs), textCoverColour); - // Draw centre text cover. - drawList->AddImage(g_upTexMainMenu1.get(), textCoverCentreMin, textCoverCentreMax, GET_UV_COORDS(textCoverCentreUVs), textCoverColour); + // Draw centre text cover. + drawList->AddImage(g_upTexMainMenu1.get(), textCoverCentreMin, textCoverCentreMax, GET_UV_COORDS(textCoverCentreUVs), textCoverColour); - // Draw right text cover. - AddImageFlipped(g_upTexMainMenu1.get(), { textCoverCornerRightMax.x - textCoverCornerUVCompensation, textCoverCornerRightMin.y }, { res.x, textCoverCornerRightMax.y }, GET_UV_COORDS(textCoverCornerExtendUVs), textCoverColour); - AddImageFlipped(g_upTexMainMenu1.get(), textCoverCornerRightMin, textCoverCornerRightMax, GET_UV_COORDS(textCoverCornerUVs), textCoverColour, true); - } - else - { - // Draw blank text cover. - drawList->AddRectFilled({ 0.0f, textCoverCornerLeftMin.y }, { res.x, textCoverCornerLeftMax.y }, textCoverColour); + // Draw right text cover. + AddImageFlipped(g_upTexMainMenu1.get(), { textCoverCornerRightMax.x - textCoverCornerUVCompensation, textCoverCornerRightMin.y }, { res.x, textCoverCornerRightMax.y }, GET_UV_COORDS(textCoverCornerExtendUVs), textCoverColour); + AddImageFlipped(g_upTexMainMenu1.get(), textCoverCornerRightMin, textCoverCornerRightMax, GET_UV_COORDS(textCoverCornerUVs), textCoverColour, true); + } + else + { + // Draw blank text cover. + drawList->AddRectFilled({ 0.0f, textCoverCornerLeftMin.y }, { res.x, textCoverCornerLeftMax.y }, textCoverColour); + } } if (ReduceDraw) diff --git a/MarathonRecomp/ui/common_menu.h b/MarathonRecomp/ui/common_menu.h index dd968653..4663d484 100644 --- a/MarathonRecomp/ui/common_menu.h +++ b/MarathonRecomp/ui/common_menu.h @@ -48,6 +48,7 @@ class CommonMenu std::string Title{}; std::string Description{}; bool PlayTransitions{}; + bool ShowDescription{ true }; bool ShowVersionString{ true }; bool ReduceDraw{}; diff --git a/MarathonRecomp/ui/imgui_utils.cpp b/MarathonRecomp/ui/imgui_utils.cpp index d617d17c..9d32e968 100644 --- a/MarathonRecomp/ui/imgui_utils.cpp +++ b/MarathonRecomp/ui/imgui_utils.cpp @@ -852,6 +852,47 @@ double DrawWindow(const ImVec2 min, const ImVec2 max, bool isAnimated, double ti return motionTime; } +void DrawScrollArrows(ImVec2 min, ImVec2 max, float scale, double& time, bool top, bool bottom) +{ + auto drawList = ImGui::GetBackgroundDrawList(); + + auto scrollArrowUVs = PIXELS_TO_UV_COORDS(1024, 1024, 500, 450, 50, 50); + auto scrollArrowOffsetX = Scale(64, true); + auto scrollArrowAlphaMotionInTime = ComputeLinearMotion(time, 0, 3); + auto scrollArrowAlphaMotionPauseTime = ComputeLinearMotion(time, 3, 11); + auto scrollArrowAlphaMotionOutTime = ComputeLinearMotion(time, 11, 4, true); + auto scrollArrowAlphaMotionLoopTime = ComputeLinearMotion(time, 15, 50); + auto scrollArrowAlphaMotion = 255; + + if (scrollArrowAlphaMotionPauseTime >= 1.0) + { + // Fade out arrows. + scrollArrowAlphaMotion = 255 * scrollArrowAlphaMotionOutTime; + + // Reset loop. + if (scrollArrowAlphaMotionLoopTime >= 1.0) + time = ImGui::GetTime(); + } + else + { + // Fade in arrows. + scrollArrowAlphaMotion = 255 * scrollArrowAlphaMotionInTime; + } + + auto scrollArrowColourMotion = IM_COL32(255, 255, 255, scrollArrowAlphaMotion); + + ImVec2 scrollArrowTopMin = min; + ImVec2 scrollArrowTopMax = { scrollArrowTopMin.x + scale, scrollArrowTopMin.y + scale }; + ImVec2 scrollArrowBottomMin = { scrollArrowTopMin.x, max.y - scale }; + ImVec2 scrollArrowBottomMax = { scrollArrowTopMax.x, scrollArrowBottomMin.y + scale }; + + if (top) + AddImageFlipped(g_upTexMainMenu1.get(), scrollArrowTopMin, scrollArrowTopMax, GET_UV_COORDS(scrollArrowUVs), scrollArrowColourMotion, false, true); + + if (bottom) + drawList->AddImage(g_upTexMainMenu1.get(), scrollArrowBottomMin, scrollArrowBottomMax, GET_UV_COORDS(scrollArrowUVs), scrollArrowColourMotion); +} + // Taken from ImGui because we need to modify to break for '\u200B\ too // Simple word-wrapping for English, not full-featured. Please submit failing cases! // This will return the next location to wrap from. If no wrapping if necessary, this will fast-forward to e.g. text_end. diff --git a/MarathonRecomp/ui/imgui_utils.h b/MarathonRecomp/ui/imgui_utils.h index 795fd729..2fffa213 100644 --- a/MarathonRecomp/ui/imgui_utils.h +++ b/MarathonRecomp/ui/imgui_utils.h @@ -103,6 +103,7 @@ ImVec2 Lerp(const ImVec2& a, const ImVec2& b, float t); ImU32 ColourLerp(ImU32 c0, ImU32 c1, float t); void DrawVersionString(const ImU32 colour = IM_COL32(255, 255, 255, 70)); double DrawWindow(const ImVec2 min, const ImVec2 max, bool isAnimated = false, double time = 0.0, bool isClosing = false); +void DrawScrollArrows(ImVec2 min, ImVec2 max, float scale, double& time, bool top = true, bool bottom = true); const char* CalcWordWrapPositionA(const ImFont* font, float scale, const char* text, const char* text_end, float wrap_width); ImVec2 MeasureInterpolatedText(const ImFont* pFont, float fontSize, const char* pText, ImGuiTextInterpData* pInterpData = nullptr); void DrawInterpolatedText(const ImFont* pFont, float fontSize, const ImVec2& pos, ImU32 colour, const char* pText, ImGuiTextInterpData* pInterpData = nullptr); diff --git a/MarathonRecomp/ui/message_window.cpp b/MarathonRecomp/ui/message_window.cpp index 2b7c93f2..796fabbd 100644 --- a/MarathonRecomp/ui/message_window.cpp +++ b/MarathonRecomp/ui/message_window.cpp @@ -221,10 +221,10 @@ void MessageWindow::Draw() g_joypadAxis.y = -rPadState.LeftStickVertical; - if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DpadUp)) + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DPadUp)) g_joypadAxis.y = 1.0f; - if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DpadDown)) + if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DPadDown)) g_joypadAxis.y = -1.0f; g_isAccepted = rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_A); diff --git a/MarathonRecomp/ui/options_menu.cpp b/MarathonRecomp/ui/options_menu.cpp index d478490f..7d92e4be 100644 --- a/MarathonRecomp/ui/options_menu.cpp +++ b/MarathonRecomp/ui/options_menu.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -898,42 +899,10 @@ static void DrawOptions(ImVec2 min, ImVec2 max) if (g_optionCount > 3 && OptionsMenu::s_flowState == OptionsMenuFlowState::OptionCursor) { - auto scrollArrowUVs = PIXELS_TO_UV_COORDS(1024, 1024, 500, 450, 50, 50); - auto scrollArrowOffsetX = Scale(64, true); - auto scrollArrowScale = Scale(20, true); - auto scrollArrowAlphaMotionInTime = ComputeLinearMotion(g_scrollArrowsTime, 0, 3); - auto scrollArrowAlphaMotionPauseTime = ComputeLinearMotion(g_scrollArrowsTime, 3, 11); - auto scrollArrowAlphaMotionOutTime = ComputeLinearMotion(g_scrollArrowsTime, 11, 4, true); - auto scrollArrowAlphaMotionLoopTime = ComputeLinearMotion(g_scrollArrowsTime, 15, 50); - auto scrollArrowAlphaMotion = 255; - - if (scrollArrowAlphaMotionPauseTime >= 1.0) - { - // Fade out arrows. - scrollArrowAlphaMotion = 255 * scrollArrowAlphaMotionOutTime; - - // Reset loop. - if (scrollArrowAlphaMotionLoopTime >= 1.0) - g_scrollArrowsTime = ImGui::GetTime(); - } - else - { - // Fade in arrows. - scrollArrowAlphaMotion = 255 * scrollArrowAlphaMotionInTime; - } - - auto scrollArrowColourMotion = IM_COL32(255, 255, 255, scrollArrowAlphaMotion); - - ImVec2 scrollArrowTopMin = { max.x - scrollArrowScale - scrollArrowOffsetX - BlackBar::s_pillarboxWidth, min.y + Scale(12, true) }; - ImVec2 scrollArrowTopMax = { scrollArrowTopMin.x + scrollArrowScale, scrollArrowTopMin.y + scrollArrowScale }; - ImVec2 scrollArrowBottomMin = { scrollArrowTopMin.x, max.y - scrollArrowScale - Scale(16, true) }; - ImVec2 scrollArrowBottomMax = { scrollArrowTopMax.x, scrollArrowBottomMin.y + scrollArrowScale }; + ImVec2 scrollArrowsMin = { max.x - BlackBar::s_pillarboxWidth - Scale(84, true), min.y + Scale(12, true) }; + ImVec2 scrollArrowsMax = { max.x, max.y - Scale(16, true) }; - // Draw top arrow. - AddImageFlipped(g_upTexMainMenu1.get(), scrollArrowTopMin, scrollArrowTopMax, GET_UV_COORDS(scrollArrowUVs), scrollArrowColourMotion, false, true); - - // Draw bottom arrow. - drawList->AddImage(g_upTexMainMenu1.get(), scrollArrowBottomMin, scrollArrowBottomMax, GET_UV_COORDS(scrollArrowUVs), scrollArrowColourMotion); + DrawScrollArrows(scrollArrowsMin, scrollArrowsMax, Scale(20, true), g_scrollArrowsTime, g_optionIndex > 1, g_optionIndex < rowCount - 2); } } @@ -1038,25 +1007,25 @@ void OptionsMenu::Draw() { auto& rPadState = spInputManager->m_PadState; - if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadUp) || -rPadState.LeftStickVertical > 0.5f) + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DPadUp) || -rPadState.LeftStickVertical > 0.5f) upIsHeld = true; if (!g_upWasHeld && upIsHeld) g_up = true; - if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadDown) || -rPadState.LeftStickVertical < -0.5f) + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DPadDown) || -rPadState.LeftStickVertical < -0.5f) downIsHeld = true; if (!g_downWasHeld && downIsHeld) g_down = true; - if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadLeft) || -rPadState.LeftStickHorizontal > 0.5f) + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DPadLeft) || -rPadState.LeftStickHorizontal > 0.5f) leftIsHeld = true; if (!g_leftWasHeld && leftIsHeld) g_left = true; - if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DpadRight) || -rPadState.LeftStickHorizontal < -0.5f) + if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DPadRight) || -rPadState.LeftStickHorizontal < -0.5f) rightIsHeld = true; if (!g_rightWasHeld && rightIsHeld) @@ -1095,6 +1064,7 @@ void OptionsMenu::Draw() { if (closingTime >= 1.0) { + MainMenuTaskPatches::HideButtonWindow = false; g_isClosingButtonWindow = true; s_isProcessedMessages = false; s_pMainMenuTask = nullptr; @@ -1104,22 +1074,22 @@ void OptionsMenu::Draw() if (!s_isProcessedMessages && s_pMainMenuTask) { - guest_stack_var msgHUDMainMenuSetCursor + guest_stack_var msgSetCursor ( Sonicteam::HUDMainMenu::HUDMainMenuState_MainCursorIntro, s_pMainMenuTask->m_MainMenuSelectedIndex ); // Play cursor intro animation. - s_pMainMenuTask->m_pHUDMainMenu->ProcessMessage(msgHUDMainMenuSetCursor.get()); + s_pMainMenuTask->m_pHUDMainMenu->ProcessMessage(msgSetCursor.get()); - guest_stack_var msgHUDMainMenuTransition + guest_stack_var msgTransition ( Sonicteam::HUDMainMenu::HUDMainMenuState_OptionsIntro, 3 ); // Play options -> main menu transition. - s_pMainMenuTask->m_pHUDMainMenu->ProcessMessage(msgHUDMainMenuTransition.get()); + s_pMainMenuTask->m_pHUDMainMenu->ProcessMessage(msgTransition.get()); s_isProcessedMessages = true; } @@ -1226,6 +1196,7 @@ void OptionsMenu::Draw() MoveCursor(g_optionIndex, g_flowStateTime, 0, g_optionCount, []() { g_cursorArrowsTime = ImGui::GetTime(); + g_scrollArrowsTime = g_cursorArrowsTime; }); if (CheckAndDiscard(g_isDeclined)) @@ -1290,6 +1261,7 @@ void OptionsMenu::Open(bool isPause) ResetSelection(); ButtonWindow::Open("Button_SelectBack", s_isPause); + MainMenuTaskPatches::HideButtonWindow = true; } void OptionsMenu::Close() diff --git a/MarathonRecompLib/config/Marathon.toml b/MarathonRecompLib/config/Marathon.toml index 93197774..8d2128ac 100644 --- a/MarathonRecompLib/config/Marathon.toml +++ b/MarathonRecompLib/config/Marathon.toml @@ -736,3 +736,8 @@ jump_address_on_true = 0x821857B4 name = "UnlockAchievement" address = 0x825B0960 registers = ["r29"] + +[[midasm_hook]] +name = "MainMenuTask_GoldMedalResults_SkipOutro" +address = 0x82502E14 +jump_address_on_true = 0x82502E90 diff --git a/MarathonRecompResources b/MarathonRecompResources index 85158ca4..8e90f7bf 160000 --- a/MarathonRecompResources +++ b/MarathonRecompResources @@ -1 +1 @@ -Subproject commit 85158ca4b9bff7b05f8942032d649323fc340f19 +Subproject commit 8e90f7bff32f9afa2d50e09b4a880a860d5dae73 From df465afad139f2af5a1531ce8d8f020cea3e98d3 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Sat, 8 Nov 2025 14:31:57 +0000 Subject: [PATCH 03/29] Update resources submodule --- MarathonRecompResources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MarathonRecompResources b/MarathonRecompResources index 8e90f7bf..d7922605 160000 --- a/MarathonRecompResources +++ b/MarathonRecompResources @@ -1 +1 @@ -Subproject commit 8e90f7bff32f9afa2d50e09b4a880a860d5dae73 +Subproject commit d79226053bef9e28068edd30c8db24c5497793c0 From df98c2fb7776fb5c7c72040d5910dd597d16b202 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Sat, 8 Nov 2025 14:43:24 +0000 Subject: [PATCH 04/29] Added Achievement Notifications option --- MarathonRecomp/ui/options_menu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MarathonRecomp/ui/options_menu.cpp b/MarathonRecomp/ui/options_menu.cpp index 7d92e4be..6af0a892 100644 --- a/MarathonRecomp/ui/options_menu.cpp +++ b/MarathonRecomp/ui/options_menu.cpp @@ -820,7 +820,7 @@ static void DrawOptions(ImVec2 min, ImVec2 max) DrawOption(rowCount++, &Config::Hints, true); DrawOption(rowCount++, &Config::ControlTutorial, true); DrawOption(rowCount++, &Config::Autosave, true); - DrawOption(rowCount++, &Config::AchievementNotifications, false, devReason); // TODO: implement achievements. DrawOption(rowCount++, &Config::AchievementNotifications, true); + DrawOption(rowCount++, &Config::AchievementNotifications, true); break; case OptionsMenuCategory::Input: From d2570d66658859d84a144e513dbea0e3a703c013 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Sat, 8 Nov 2025 14:51:40 +0000 Subject: [PATCH 05/29] Use total instead of percentage for achievement progress --- MarathonRecomp/locale/locale.cpp | 12 ++++++------ MarathonRecomp/ui/achievement_menu.cpp | 3 +-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/MarathonRecomp/locale/locale.cpp b/MarathonRecomp/locale/locale.cpp index 66bee63a..5791e0e6 100644 --- a/MarathonRecomp/locale/locale.cpp +++ b/MarathonRecomp/locale/locale.cpp @@ -373,12 +373,12 @@ std::unordered_map> { "Achievements_Progress", { - { ELanguage::English, "PROGRESS %d%%" }, - { ELanguage::Japanese, "PROGRESS %d%%" }, - { ELanguage::German, "FORTSCHRITT %d%%" }, - { ELanguage::French, "PROGRESSION %d%%" }, - { ELanguage::Spanish, "PROGRESO %d%%" }, - { ELanguage::Italian, "PROGRESSI %d%%" } + { ELanguage::English, "PROGRESS %d/%d" }, + { ELanguage::Japanese, "PROGRESS %d/%d" }, + { ELanguage::German, "FORTSCHRITT %d/%d" }, + { ELanguage::French, "PROGRESSION %d/%d" }, + { ELanguage::Spanish, "PROGRESO %d/%d" }, + { ELanguage::Italian, "PROGRESSI %d/%d" } } }, { diff --git a/MarathonRecomp/ui/achievement_menu.cpp b/MarathonRecomp/ui/achievement_menu.cpp index 3d3dd8d3..08d995d1 100644 --- a/MarathonRecomp/ui/achievement_menu.cpp +++ b/MarathonRecomp/ui/achievement_menu.cpp @@ -448,8 +448,7 @@ void AchievementMenu::Open() // Format achievement progress. char descriptionText[128]; - snprintf(descriptionText, sizeof(descriptionText), Localise("Achievements_Progress").c_str(), - std::min(100, int(AchievementManager::GetTotalRecords()) / g_rowCount) * 100); + snprintf(descriptionText, sizeof(descriptionText), Localise("Achievements_Progress").c_str(), AchievementManager::GetTotalRecords(), g_rowCount); s_commonMenu = CommonMenu(Localise("Achievements_GoldMedals_Uppercase"), descriptionText, false); s_commonMenu.ShowDescription = false; From 03ebc6f06f8323ff0fda4c66b04196176a6a4f89 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Sat, 8 Nov 2025 15:04:45 +0000 Subject: [PATCH 06/29] Use original game's time format --- MarathonRecomp/ui/achievement_menu.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/MarathonRecomp/ui/achievement_menu.cpp b/MarathonRecomp/ui/achievement_menu.cpp index 08d995d1..dee32e6f 100644 --- a/MarathonRecomp/ui/achievement_menu.cpp +++ b/MarathonRecomp/ui/achievement_menu.cpp @@ -246,14 +246,15 @@ static void DrawAchievement(int rowIndex, Achievement& achievement, bool isUnloc char buffer[32]; #ifdef _WIN32 - tm timeStruct; - tm *timePtr = &timeStruct; - localtime_s(timePtr, ×tamp); + tm time{}; + tm* pTime = &time; + localtime_s(pTime, ×tamp); #else - tm *timePtr = localtime(×tamp); + tm* pTime = localtime(×tamp); #endif - strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M", timePtr); + snprintf(buffer, sizeof(buffer), "%d/%d/%d %02d:%02d", + pTime->tm_year + 1900, pTime->tm_mon + 1, pTime->tm_mday, pTime->tm_hour, pTime->tm_min); auto timestampOffsetX = Scale(60, true); auto timestampSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, buffer); From fcf549e22737a3e6334d8f34acda836de3fae4ec Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Tue, 11 Nov 2025 11:51:31 +0000 Subject: [PATCH 07/29] Fix gold medals not reappearing when switching between menus --- MarathonRecomp/api/Sonicteam/HUDGoldMedal.h | 27 ++++++++++++ .../api/Sonicteam/MainMenuExpositionTask.h | 15 +++++++ MarathonRecomp/api/Sonicteam/MainMenuTask.h | 16 +++++-- .../patches/MainMenuTask_patches.cpp | 11 +++-- MarathonRecomp/ui/achievement_menu.cpp | 44 +++++++++++++++++-- MarathonRecomp/ui/achievement_menu.h | 23 +--------- MarathonRecompLib/config/Marathon.toml | 5 +++ 7 files changed, 108 insertions(+), 33 deletions(-) create mode 100644 MarathonRecomp/api/Sonicteam/MainMenuExpositionTask.h diff --git a/MarathonRecomp/api/Sonicteam/HUDGoldMedal.h b/MarathonRecomp/api/Sonicteam/HUDGoldMedal.h index 340ce873..ea2f09ec 100644 --- a/MarathonRecomp/api/Sonicteam/HUDGoldMedal.h +++ b/MarathonRecomp/api/Sonicteam/HUDGoldMedal.h @@ -1,6 +1,14 @@ #pragma once #include +#include +#include +#include +#include +#include +#include +#include +#include namespace Sonicteam { @@ -8,5 +16,24 @@ namespace Sonicteam { public: xpointer m_pCsdObject; + xpointer m_pMedalCountText; + xpointer m_pDoc; + MARATHON_INSERT_PADDING(0x3C); + be m_EpisodeIndex; + be m_ScrollIndex; + be m_MedalCount; + MARATHON_INSERT_PADDING(0x18); + boost::shared_ptr m_aspTextCards[5]; + boost::shared_ptr m_aspTextEntities[5]; }; + + MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_pCsdObject, 0x54); + MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_pMedalCountText, 0x58); + MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_pDoc, 0x5C); + MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_EpisodeIndex, 0x9C); + MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_ScrollIndex, 0xA0); + MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_MedalCount, 0xA4); + MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_aspTextCards, 0xC0); + MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_aspTextEntities, 0xE8); + MARATHON_ASSERT_SIZEOF(HUDGoldMedal, 0x110); } diff --git a/MarathonRecomp/api/Sonicteam/MainMenuExpositionTask.h b/MarathonRecomp/api/Sonicteam/MainMenuExpositionTask.h new file mode 100644 index 00000000..42b24982 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/MainMenuExpositionTask.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace Sonicteam +{ + class MainMenuExpositionTask : public SoX::RefCountObject, public SoX::Engine::Task + { + public: + be m_TextMotionState; + }; + + MARATHON_ASSERT_OFFSETOF(MainMenuExpositionTask, m_TextMotionState, 0x54); +} diff --git a/MarathonRecomp/api/Sonicteam/MainMenuTask.h b/MarathonRecomp/api/Sonicteam/MainMenuTask.h index cd69ba86..a0e3aabf 100644 --- a/MarathonRecomp/api/Sonicteam/MainMenuTask.h +++ b/MarathonRecomp/api/Sonicteam/MainMenuTask.h @@ -2,8 +2,10 @@ #include #include +#include #include #include +#include namespace Sonicteam { @@ -42,12 +44,16 @@ namespace Sonicteam xpointer m_pHUDGoldMedal; MARATHON_INSERT_PADDING(0x14); xpointer m_pButtonWindowTask; - xpointer m_pMainMenuExpositionTask; + SoX::RefSharedPointer m_spMainMenuExpositionTask; be m_MainMenuSelectedIndex; be m_SinglePlayerSelectedIndex; - MARATHON_INSERT_PADDING(0x78); + MARATHON_INSERT_PADDING(0x14); + be m_FieldBC; + MARATHON_INSERT_PADDING(0x60); be m_GoldMedalEpisodeIndex; - MARATHON_INSERT_PADDING(0x14C); + MARATHON_INSERT_PADDING(4); + be m_GoldMedalScrollIndex; + MARATHON_INSERT_PADDING(0x144); be m_IsChangingState; MARATHON_INSERT_PADDING(8); be m_PressedButtons; @@ -61,10 +67,12 @@ namespace Sonicteam MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_pHUDMainMenu, 0x74); MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_pHUDGoldMedal, 0x80); MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_pButtonWindowTask, 0x98); - MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_pMainMenuExpositionTask, 0x9C); + MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_spMainMenuExpositionTask, 0x9C); MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_MainMenuSelectedIndex, 0xA0); MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_SinglePlayerSelectedIndex, 0xA4); + MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_FieldBC, 0xBC); MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_GoldMedalEpisodeIndex, 0x120); + MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_GoldMedalScrollIndex, 0x128); MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_IsChangingState, 0x270); MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_PressedButtons, 0x27C); MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_Field298, 0x298); diff --git a/MarathonRecomp/patches/MainMenuTask_patches.cpp b/MarathonRecomp/patches/MainMenuTask_patches.cpp index 09b2495f..393ab317 100644 --- a/MarathonRecomp/patches/MainMenuTask_patches.cpp +++ b/MarathonRecomp/patches/MainMenuTask_patches.cpp @@ -83,10 +83,7 @@ PPC_FUNC(sub_824FFCF8) } if (pMainMenuTask->m_State == Sonicteam::MainMenuTask::MainMenuState_GoldMedalResultsOpen) - { - AchievementMenu::s_pMainMenuTask = pMainMenuTask; - AchievementMenu::Open(); - } + AchievementMenu::Open(pMainMenuTask); #endif static float s_buttonWindowTextOffsetY{}; @@ -117,6 +114,12 @@ PPC_FUNC(sub_824FFCF8) __imp__sub_824FFCF8(ctx, base); } +bool HUDGoldMedal_ShouldDestroyTable() +{ + return AchievementMenu::s_state == AchievementMenuState::ClosingGoldMedals || + AchievementMenu::s_state == AchievementMenuState::ClosingAchievements; +} + bool MainMenuTask_GoldMedalResults_SkipOutro() { return AchievementMenu::s_state == AchievementMenuState::ClosingAchievements; diff --git a/MarathonRecomp/ui/achievement_menu.cpp b/MarathonRecomp/ui/achievement_menu.cpp index dee32e6f..57e57e6c 100644 --- a/MarathonRecomp/ui/achievement_menu.cpp +++ b/MarathonRecomp/ui/achievement_menu.cpp @@ -295,7 +295,7 @@ void AchievementMenu::Draw() s_commonMenu.Draw(); - if (s_pMainMenuTask->m_State == Sonicteam::MainMenuTask::MainMenuState_GoldMedalResults) + if (s_pMainMenuTask && s_pMainMenuTask->m_State == Sonicteam::MainMenuTask::MainMenuState_GoldMedalResults) { if (auto& spInputManager = App::s_pApp->m_pDoc->m_vspInputManager[0]) { @@ -315,7 +315,7 @@ void AchievementMenu::Draw() { AchievementMenu::SetState(AchievementMenuState::Achievements); ButtonWindow::Open("Button_GoldMedalsBack"); - SetGoldMedalResultsVisible(s_pMainMenuTask, false); + SetGoldMedalResultsVisible(false); Game_PlaySound("window_open"); } @@ -332,7 +332,7 @@ void AchievementMenu::Draw() { AchievementMenu::SetState(AchievementMenuState::GoldMedals); ButtonWindow::Open("Button_AchievementsBack"); - SetGoldMedalResultsVisible(s_pMainMenuTask, true); + SetGoldMedalResultsVisible(true); Game_PlaySound("window_close"); } @@ -426,8 +426,10 @@ void AchievementMenu::Draw() } } -void AchievementMenu::Open() +void AchievementMenu::Open(Sonicteam::MainMenuTask* pMainMenuTask) { + s_pMainMenuTask = pMainMenuTask; + if (s_isVisible) return; @@ -498,3 +500,37 @@ bool AchievementMenu::IsClosing() { return s_state == AchievementMenuState::ClosingGoldMedals || s_state == AchievementMenuState::ClosingAchievements; } + +void AchievementMenu::SetGoldMedalResultsVisible(bool isVisible) +{ + if (!s_pMainMenuTask) + return; + + std::vector states; + + if (isVisible) + { + states = { 0, 3, 5, 11 }; + } + else + { + states = { 1, 4, 6, 12 }; + } + + for (auto& state : states) + { + guest_stack_var msgChangeState(state, s_pMainMenuTask->m_GoldMedalEpisodeIndex); + s_pMainMenuTask->m_pHUDGoldMedal->ProcessMessage(msgChangeState.get()); + } + + for (int i = 0; i < 5; i++) + { + auto& spTextEntity = s_pMainMenuTask->m_pHUDGoldMedal->m_aspTextEntities[i]; + + if (auto pTextEntity = spTextEntity.get()) + { + for (size_t i = 0; i < pTextEntity->m_CharacterVertexCount; i++) + pTextEntity->m_pCharacterVertices[i].Colour = isVisible ? 0xFFFFFFFF : 0x00FFFFFF; + } + } +} diff --git a/MarathonRecomp/ui/achievement_menu.h b/MarathonRecomp/ui/achievement_menu.h index 6b6b902d..159f0a08 100644 --- a/MarathonRecomp/ui/achievement_menu.h +++ b/MarathonRecomp/ui/achievement_menu.h @@ -22,28 +22,9 @@ class AchievementMenu static inline bool s_isVisible = false; static void Draw(); - static void Open(); + static void Open(Sonicteam::MainMenuTask* pMainMenuTask); static void Close(); static void SetState(AchievementMenuState state); static bool IsClosing(); - - static void SetGoldMedalResultsVisible(Sonicteam::MainMenuTask* pMainMenuTask, bool isVisible) - { - std::array states{}; - - if (isVisible) - { - states = { 0, 3, 5, 11 }; - } - else - { - states = { 1, 4, 6, 12 }; - } - - for (auto& state : states) - { - guest_stack_var msgChangeState(state, pMainMenuTask->m_GoldMedalEpisodeIndex); - pMainMenuTask->m_pHUDGoldMedal->ProcessMessage(msgChangeState.get()); - } - } + static void SetGoldMedalResultsVisible(bool isVisible); }; diff --git a/MarathonRecompLib/config/Marathon.toml b/MarathonRecompLib/config/Marathon.toml index 8d2128ac..5bb714be 100644 --- a/MarathonRecompLib/config/Marathon.toml +++ b/MarathonRecompLib/config/Marathon.toml @@ -737,6 +737,11 @@ name = "UnlockAchievement" address = 0x825B0960 registers = ["r29"] +[[midasm_hook]] +name = "HUDGoldMedal_ShouldDestroyTable" +address = 0x824D6720 +jump_address_on_false = 0x824D6724 + [[midasm_hook]] name = "MainMenuTask_GoldMedalResults_SkipOutro" address = 0x82502E14 From 476d5ebbb105878fa9098477437b46a0892f3096 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Tue, 11 Nov 2025 14:09:05 +0000 Subject: [PATCH 08/29] English localisation --- MarathonRecomp/CMakeLists.txt | 1 + MarathonRecomp/api/Marathon.h | 1 + MarathonRecomp/api/Sonicteam/MainMenuTask.h | 6 +- MarathonRecomp/kernel/xdbf.h | 12 +- MarathonRecomp/locale/achievement_locale.cpp | 1288 +++++++++++++++++ MarathonRecomp/locale/achievement_locale.h | 21 + MarathonRecomp/locale/locale.cpp | 4 +- .../patches/MainMenuTask_patches.cpp | 6 +- MarathonRecomp/patches/MainMenuTask_patches.h | 8 +- .../patches/aspect_ratio_patches.cpp | 12 +- MarathonRecomp/patches/camera_patches.cpp | 2 +- MarathonRecomp/patches/text_patches.cpp | 4 +- MarathonRecomp/ui/achievement_menu.cpp | 29 +- MarathonRecomp/ui/achievement_overlay.cpp | 16 +- MarathonRecomp/ui/options_menu.cpp | 4 +- MarathonRecomp/user/config_def.h | 1 + README.md | 4 +- 17 files changed, 1377 insertions(+), 42 deletions(-) create mode 100644 MarathonRecomp/locale/achievement_locale.cpp create mode 100644 MarathonRecomp/locale/achievement_locale.h diff --git a/MarathonRecomp/CMakeLists.txt b/MarathonRecomp/CMakeLists.txt index c02239e1..9216b899 100644 --- a/MarathonRecomp/CMakeLists.txt +++ b/MarathonRecomp/CMakeLists.txt @@ -81,6 +81,7 @@ set(MARATHON_RECOMP_KERNEL_CXX_SOURCES ) set(MARATHON_RECOMP_LOCALE_CXX_SOURCES + "locale/achievement_locale.cpp" "locale/config_locale.cpp" "locale/locale.cpp" ) diff --git a/MarathonRecomp/api/Marathon.h b/MarathonRecomp/api/Marathon.h index ff87211d..09c3d0aa 100644 --- a/MarathonRecomp/api/Marathon.h +++ b/MarathonRecomp/api/Marathon.h @@ -57,6 +57,7 @@ #include "Sonicteam/HudTextParts.h" #include "Sonicteam/ImageFilter.h" #include "Sonicteam/MainDisplayTask.h" +#include "Sonicteam/MainMenuExpositionTask.h" #include "Sonicteam/MainMenuTask.h" #include "Sonicteam/MainMode.h" #include "Sonicteam/Message/Camera/Cameraman/MsgChangeMode.h" diff --git a/MarathonRecomp/api/Sonicteam/MainMenuTask.h b/MarathonRecomp/api/Sonicteam/MainMenuTask.h index a0e3aabf..525e7149 100644 --- a/MarathonRecomp/api/Sonicteam/MainMenuTask.h +++ b/MarathonRecomp/api/Sonicteam/MainMenuTask.h @@ -16,7 +16,7 @@ namespace Sonicteam { MainMenuState_MainMenuBack = 1, MainMenuState_MainMenu = 2, - MainMenuState_MainMenuExitPrompt = 4, + MainMenuState_ExitPrompt = 4, MainMenuState_SinglePlayer = 6, MainMenuState_EpisodeSelect = 9, MainMenuState_TrialSelect = 0x0D, @@ -33,8 +33,8 @@ namespace Sonicteam MainMenuState_AudioRoom = 0x2F, MainMenuState_TheaterRoom = 0x31, MainMenuState_Options = 0x33, - MainMenuState_MainMenuExitToStage = 0x3B, - MainMenuState_MainMenuExitToTitle = 0x3C + MainMenuState_ExitToStage = 0x3B, + MainMenuState_ExitToTitle = 0x3C }; be m_State; diff --git a/MarathonRecomp/kernel/xdbf.h b/MarathonRecomp/kernel/xdbf.h index 7a32631e..d326a4ed 100644 --- a/MarathonRecomp/kernel/xdbf.h +++ b/MarathonRecomp/kernel/xdbf.h @@ -10,24 +10,24 @@ namespace xdbf { inline std::string& FixInvalidSequences(std::string& str) { - static std::array invalidSequences = + static std::array s_invalidSequences = { "\n" }; - static std::array replaceSequences = + static std::array s_replaceSequences = { " " }; - for (int i = 0; i < invalidSequences.size(); i++) + for (int i = 0; i < s_invalidSequences.size(); i++) { size_t pos = 0; - auto& invalidSeq = invalidSequences[i]; - auto& replaceSeq = replaceSequences[i]; + auto& invalidSeq = s_invalidSequences[i]; + auto& replaceSeq = s_replaceSequences[i]; - while ((pos = str.find(invalidSequences[i], pos)) != std::string::npos) + while ((pos = str.find(s_invalidSequences[i], pos)) != std::string::npos) { str = str.replace(pos, invalidSeq.length(), replaceSeq); diff --git a/MarathonRecomp/locale/achievement_locale.cpp b/MarathonRecomp/locale/achievement_locale.cpp new file mode 100644 index 00000000..bab75300 --- /dev/null +++ b/MarathonRecomp/locale/achievement_locale.cpp @@ -0,0 +1,1288 @@ +#include +#include + +std::unordered_map> g_achLocale = +{ + // Sonic Episode: Cleared + { + 1, + { + { + ELanguage::English, + { + "Sonic's Episode: Cleared", + "Clear Sonic's episode!", + "You have cleared Sonic's episode." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Shadow Episode: Cleared + { + 2, + { + { + ELanguage::English, + { + "Shadow's Episode: Cleared", + "Clear Shadow's episode!", + "You have cleared Shadow's episode." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Silver Episode: Cleared + { + 3, + { + { + ELanguage::English, + { + "Silver's Episode: Cleared", + "Clear Silver's episode!", + "You have cleared Silver's episode." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // One to reach the end + { + 4, + { + { + ELanguage::English, + { + "One To Reach The End", + "Clear the last hidden episode!", + "You have cleared the last episode." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Sonic Episode: Completed + { + 5, + { + { + ELanguage::English, + { + "Sonic's Episode: Completed", + "Clear all of Sonic's hard ACT missions.", + "You have cleared all of Sonic's ACT missions." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Shadow Episode: Completed + { + 6, + { + { + ELanguage::English, + { + "Shadow's Episode: Completed", + "Clear all of Shadow's hard ACT missions.", + "You have cleared all of Shadow's ACT missions." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Silver Episode: Completed + { + 7, + { + { + ELanguage::English, + { + "Silver's Episode: Completed", + "Clear all of Silver's hard ACT missions.", + "You have cleared all of Silver's ACT missions." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Shadow Episode: Mastered + { + 8, + { + { + ELanguage::English, + { + "Shadow's Episode: Mastered", + "Clear all of Shadow's ACT missions with Rank S.", + "You have achieved Rank S on all of Shadow's ACT missions." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Sonic Episode: Mastered + { + 9, + { + { + ELanguage::English, + { + "Sonic's Episode: Mastered", + "Clear all of Sonic's ACT missions with Rank S.", + "You have achieved Rank S on all of Sonic's ACT missions." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Silver Episode: Mastered + { + 10, + { + { + ELanguage::English, + { + "Silver's Episode: Mastered", + "Clear all of Silver's ACT missions with Rank S.", + "You have achieved Rank S on all of Silver's ACT missions." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Nights of Kronos + { + 11, + { + { + ELanguage::English, + { + "Knights of Kronos", + "Clear the last hidden episode with Rank S.", + "You have achieved Rank S on all of the last episode." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Legend of Soleanna + { + 12, + { + { + ELanguage::English, + { + "Legend of Soleanna", + "Clear all ACT missions with Rank S.", + "You have achieved Rank S on all ACT missions." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Silver Medalist + { + 13, + { + { + ELanguage::English, + { + "Silver Medalist", + "Collect all of the Silver Medals scattered around Soleanna.", + "You have collected all of the Silver Medals in Soleanna." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Gold Medalist + { + 14, + { + { + ELanguage::English, + { + "Gold Medalist", + "Collect all of the legendary Gold Medals of Soleanna.", + "You have collected all of the Gold Medals of Soleanna." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Blue Phantom + { + 15, + { + { + ELanguage::English, + { + "Blue Phantom", + "Unlock all of Sonic's abilities.", + "You have learned all of Sonic's abilities." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Ultimate Life Form + { + 16, + { + { + ELanguage::English, + { + "Ultimate Life Form", + "Unlock all of Shadow's abilities.", + "You have learned all of Shadow's abilities." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Psychic Soldier + { + 17, + { + { + ELanguage::English, + { + "Psychic Soldier", + "Unlock all of Silver's abilities.", + "You have learned all of Silver's abilities." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Soleanna's Hero + { + 18, + { + { + ELanguage::English, + { + "Soleanna's Hero", + "Clear all of Sonic's TOWN missions.", + "You have cleared all of Sonic's TOWN missions." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Elite Agent + { + 19, + { + { + ELanguage::English, + { + "Elite Agent", + "Clear all of Shadow's TOWN missions.", + "You have cleared all of Shadow's TOWN missions." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Silver The Liberator + { + 20, + { + { + ELanguage::English, + { + "Silver the Liberator", + "Clear all of Silver's TOWN missions.", + "You have cleared all of Silver's TOWN missions." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Soleanna's blue wind + { + 21, + { + { + ELanguage::English, + { + "Soleanna's Blue Wind", + "Clear all of Sonic's TOWN missions with Rank S.", + "You have achieved Rank S on all of Sonic's TOWN missions." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Dark Hero + { + 22, + { + { + ELanguage::English, + { + "Dark Hero", + "Clear all of Shadow's TOWN missions with Rank S.", + "You have achieved Rank S on all of Shadow's TOWN missions." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + }, + + // Silver The Savior. + { + 23, + { + { + ELanguage::English, + { + "Silver the Savior", + "Clear all of Silver's TOWN missions with Rank S.", + "You have achieved Rank S on all of Silver's TOWN missions." + } + }, + { + ELanguage::Japanese, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::German, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::French, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Spanish, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + { + ELanguage::Italian, + { + "DUMMY", + "DUMMY", + "DUMMY" + } + }, + } + } +}; + +AchievementLocale& GetAchievementLocale(const int key) +{ + auto localeFindResult = g_achLocale.find(key); + + if (localeFindResult != g_achLocale.end()) + { + auto languageFindResult = localeFindResult->second.find(Config::Language); + + if (languageFindResult == localeFindResult->second.end()) + languageFindResult = localeFindResult->second.find(ELanguage::English); + + if (languageFindResult != localeFindResult->second.end()) + return languageFindResult->second; + } + + return g_achLocaleMissing; +} diff --git a/MarathonRecomp/locale/achievement_locale.h b/MarathonRecomp/locale/achievement_locale.h new file mode 100644 index 00000000..1d163fb9 --- /dev/null +++ b/MarathonRecomp/locale/achievement_locale.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +struct AchievementLocale +{ + std::string Name{}; + std::string LockedDesc{}; + std::string UnlockedDesc{}; +}; + +inline AchievementLocale g_achLocaleMissing = +{ + g_localeMissing, + g_localeMissing, + g_localeMissing +}; + +extern std::unordered_map> g_achLocale; + +AchievementLocale& GetAchievementLocale(const int key); diff --git a/MarathonRecomp/locale/locale.cpp b/MarathonRecomp/locale/locale.cpp index 5791e0e6..3c5de929 100644 --- a/MarathonRecomp/locale/locale.cpp +++ b/MarathonRecomp/locale/locale.cpp @@ -296,7 +296,7 @@ std::unordered_map> { "MainMenu_GoldMedalResults_Name", { - { ELanguage::English, "RESULTS" }, + { ELanguage::English, "STATISTICS" }, { ELanguage::Japanese, "DUMMY" }, { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, @@ -307,7 +307,7 @@ std::unordered_map> { "MainMenu_GoldMedalResults_Description", { - { ELanguage::English, "Results: Displays lists of Gold Medals and Achievements" }, + { ELanguage::English, "Statistics: Displays lists of Gold Medals and Achievements" }, { ELanguage::Japanese, "DUMMY" }, { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, diff --git a/MarathonRecomp/patches/MainMenuTask_patches.cpp b/MarathonRecomp/patches/MainMenuTask_patches.cpp index 393ab317..cd4d2f11 100644 --- a/MarathonRecomp/patches/MainMenuTask_patches.cpp +++ b/MarathonRecomp/patches/MainMenuTask_patches.cpp @@ -89,7 +89,7 @@ PPC_FUNC(sub_824FFCF8) static float s_buttonWindowTextOffsetY{}; auto& rButtonWindowTextOffsetY = pMainMenuTask->m_pButtonWindowTask->m_pHUDButtonWindow->m_pHudTextParts->m_OffsetY; - if (MainMenuTaskPatches::HideButtonWindow) + if (MainMenuTaskPatches::s_hideButtonWindow) { static constexpr double HIDE_TEXT_OFFSET = -100000.0f; @@ -106,9 +106,9 @@ PPC_FUNC(sub_824FFCF8) rButtonWindowTextOffsetY = s_buttonWindowTextOffsetY; } - MainMenuTaskPatches::State = (Sonicteam::MainMenuTask::MainMenuState)pMainMenuTask->m_State.get(); + MainMenuTaskPatches::s_state = (Sonicteam::MainMenuTask::MainMenuState)pMainMenuTask->m_State.get(); - for (auto& event : MainMenuTaskPatches::Events) + for (auto& event : MainMenuTaskPatches::s_events) event->Update(pMainMenuTask, ctx.f1.f64); __imp__sub_824FFCF8(ctx, base); diff --git a/MarathonRecomp/patches/MainMenuTask_patches.h b/MarathonRecomp/patches/MainMenuTask_patches.h index a1764427..1071676b 100644 --- a/MarathonRecomp/patches/MainMenuTask_patches.h +++ b/MarathonRecomp/patches/MainMenuTask_patches.h @@ -1,12 +1,12 @@ #pragma once -#include "hook_event.h" #include +#include class MainMenuTaskPatches { public: - static inline bool HideButtonWindow{}; - static inline Sonicteam::MainMenuTask::MainMenuState State{}; - static inline std::vector*> Events{}; + static inline bool s_hideButtonWindow{}; + static inline Sonicteam::MainMenuTask::MainMenuState s_state{}; + static inline std::vector*> s_events{}; }; diff --git a/MarathonRecomp/patches/aspect_ratio_patches.cpp b/MarathonRecomp/patches/aspect_ratio_patches.cpp index 3b088494..c45da256 100644 --- a/MarathonRecomp/patches/aspect_ratio_patches.cpp +++ b/MarathonRecomp/patches/aspect_ratio_patches.cpp @@ -141,7 +141,7 @@ float ComputeScale(float aspectRatio) void AspectRatioPatches::Init() { LoadingPatches::Events.push_back(&g_loadingPillarboxEvent); - MainMenuTaskPatches::Events.push_back(&g_chevronAnimResetEvent); + MainMenuTaskPatches::s_events.push_back(&g_chevronAnimResetEvent); } void AspectRatioPatches::ComputeOffsets() @@ -411,7 +411,7 @@ void Draw(PPCContext& ctx, uint8_t* base, PPCFunc* original, uint32_t stride) } // Hide original button window whilst the options menu is visible. - if ((modifier.Flags & CSD_BUTTON_WINDOW) != 0 && MainMenuTaskPatches::HideButtonWindow) + if ((modifier.Flags & CSD_BUTTON_WINDOW) != 0 && MainMenuTaskPatches::s_hideButtonWindow) return; // Remove all flags if the aspect ratio is above 16:9. @@ -1650,11 +1650,11 @@ PPC_FUNC(sub_824E11D0) static constexpr double HIDE_TEXT_OFFSET = -100000.0f; - auto isTrialSelect = MainMenuTaskPatches::State >= 12 && MainMenuTaskPatches::State <= 15; + auto isTrialSelect = MainMenuTaskPatches::s_state >= 12 && MainMenuTaskPatches::s_state <= 15; - auto isTag = MainMenuTaskPatches::State == Sonicteam::MainMenuTask::MainMenuState_Tag || - MainMenuTaskPatches::State == Sonicteam::MainMenuTask::MainMenuState_Tag1PSelect || - MainMenuTaskPatches::State == Sonicteam::MainMenuTask::MainMenuState_MainMenuExitToStage; + auto isTag = MainMenuTaskPatches::s_state == Sonicteam::MainMenuTask::MainMenuState_Tag || + MainMenuTaskPatches::s_state == Sonicteam::MainMenuTask::MainMenuState_Tag1PSelect || + MainMenuTaskPatches::s_state == Sonicteam::MainMenuTask::MainMenuState_ExitToStage; while (pHudTextRoot) { diff --git a/MarathonRecomp/patches/camera_patches.cpp b/MarathonRecomp/patches/camera_patches.cpp index 3fbd05e6..67d9d26d 100644 --- a/MarathonRecomp/patches/camera_patches.cpp +++ b/MarathonRecomp/patches/camera_patches.cpp @@ -21,7 +21,7 @@ g_mainMenuCameraUpdateEvent{}; void CameraPatches::Init() { - MainMenuTaskPatches::Events.push_back(&g_mainMenuCameraUpdateEvent); + MainMenuTaskPatches::s_events.push_back(&g_mainMenuCameraUpdateEvent); } void CameraImp_SetFOV(PPCRegister& f1) diff --git a/MarathonRecomp/patches/text_patches.cpp b/MarathonRecomp/patches/text_patches.cpp index 5a6475cd..1907cc2e 100644 --- a/MarathonRecomp/patches/text_patches.cpp +++ b/MarathonRecomp/patches/text_patches.cpp @@ -27,7 +27,7 @@ PPC_FUNC(sub_825ECB48) __imp__sub_825ECB48(ctx, base); - auto pTextCard = (boost::shared_ptr*)(base + ctx.r3.u32); + auto pspTextCard = (boost::shared_ptr*)(base + ctx.r3.u32); for (auto& replacement : TextPatches::s_replacedMessages) { @@ -43,7 +43,7 @@ PPC_FUNC(sub_825ECB48) for (size_t i = 0; i < wideMessageLen; i++) pReplacedMessage[i] = ByteSwap(wideMessage.c_str()[i]); - pTextCard->get()->m_pText = (const uint16_t*)pReplacedMessage; + pspTextCard->get()->m_pText = (const uint16_t*)pReplacedMessage; } if (!pNewMessage) diff --git a/MarathonRecomp/ui/achievement_menu.cpp b/MarathonRecomp/ui/achievement_menu.cpp index 57e57e6c..a6eefdb4 100644 --- a/MarathonRecomp/ui/achievement_menu.cpp +++ b/MarathonRecomp/ui/achievement_menu.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -203,17 +204,35 @@ static void DrawAchievement(int rowIndex, Achievement& achievement, bool isUnloc auto textX = imageMax.x + itemMarginX * 2; auto textColour = isUnlocked ? IM_COL32_WHITE : IM_COL32(137, 137, 137, 255); - auto descText = isUnlocked ? achievement.UnlockedDesc.c_str() : achievement.LockedDesc.c_str(); + std::string name; + std::string lockedDesc; + std::string unlockedDesc; + + if (Config::UseOfficialAchievementText) + { + name = achievement.Name; + lockedDesc = achievement.LockedDesc; + unlockedDesc = achievement.UnlockedDesc; + } + else + { + auto& newLocale = GetAchievementLocale(achievement.ID); + + name = newLocale.Name; + lockedDesc = newLocale.LockedDesc; + unlockedDesc = newLocale.UnlockedDesc; + } + + auto descText = isUnlocked ? unlockedDesc.c_str() : lockedDesc.c_str(); auto descTextOffsetY = Scale(32, true); auto descSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, descText); - ImVec2 namePos = { textX, imageMin.y + Scale(3, true) }; ImVec2 descPos = { textX, namePos.y + descTextOffsetY }; // Draw achievement name. SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); - drawList->AddText(g_pFntRodin, fontSize, namePos, textColour, achievement.Name.c_str()); + drawList->AddText(g_pFntRodin, fontSize, namePos, textColour, name.c_str()); SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); auto marqueeFadeScale = Scale(18, true); @@ -465,7 +484,7 @@ void AchievementMenu::Open(Sonicteam::MainMenuTask* pMainMenuTask) g_selectedIndex = 0; ButtonWindow::Open("Button_AchievementsBack"); - MainMenuTaskPatches::HideButtonWindow = true; + MainMenuTaskPatches::s_hideButtonWindow = true; } void AchievementMenu::Close() @@ -487,7 +506,7 @@ void AchievementMenu::Close() g_time = ImGui::GetTime(); ButtonWindow::Close(); - MainMenuTaskPatches::HideButtonWindow = false; + MainMenuTaskPatches::s_hideButtonWindow = false; } void AchievementMenu::SetState(AchievementMenuState state) diff --git a/MarathonRecomp/ui/achievement_overlay.cpp b/MarathonRecomp/ui/achievement_overlay.cpp index 8939997b..d84c277e 100644 --- a/MarathonRecomp/ui/achievement_overlay.cpp +++ b/MarathonRecomp/ui/achievement_overlay.cpp @@ -1,6 +1,7 @@ #include "achievement_overlay.h" #include #include +#include #include #include #include @@ -45,13 +46,16 @@ void AchievementOverlay::Draw() auto drawList = ImGui::GetBackgroundDrawList(); auto& res = ImGui::GetIO().DisplaySize; - auto strAchievementUnlocked = Localise("Achievements_Unlock").c_str(); - auto strAchievementName = g_achievement.Name.c_str(); + auto& strAchievementUnlocked = Localise("Achievements_Unlock"); + auto& strAchievementName = g_achievement.Name; + + if (!Config::UseOfficialAchievementText) + strAchievementName = GetAchievementLocale(g_achievement.ID).Name; // Calculate text sizes. auto fontSize = Scale(Config::Language == ELanguage::Japanese ? 28 : 27, true); - auto headerSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementUnlocked); - auto nameSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementName); + auto headerSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementUnlocked.c_str()); + auto nameSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementName.c_str()); auto maxSize = std::max(headerSize.x, nameSize.x) + Scale(5); // Calculate image margins. @@ -95,7 +99,7 @@ void AchievementOverlay::Draw() fontSize, { /* X */ min.x + textMarginX + (maxSize - headerSize.x) / 2, /* Y */ min.y + textMarginY }, headerColour, - strAchievementUnlocked + strAchievementUnlocked.c_str() ); // Draw achievement name. @@ -105,7 +109,7 @@ void AchievementOverlay::Draw() fontSize, { /* X */ min.x + textMarginX + (maxSize - nameSize.x) / 2, /* Y */ min.y + textMarginY + nameSize.y + Scale(6) }, IM_COL32(255, 255, 255, 255), - strAchievementName + strAchievementName.c_str() ); SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); diff --git a/MarathonRecomp/ui/options_menu.cpp b/MarathonRecomp/ui/options_menu.cpp index 6af0a892..7542636e 100644 --- a/MarathonRecomp/ui/options_menu.cpp +++ b/MarathonRecomp/ui/options_menu.cpp @@ -1064,7 +1064,7 @@ void OptionsMenu::Draw() { if (closingTime >= 1.0) { - MainMenuTaskPatches::HideButtonWindow = false; + MainMenuTaskPatches::s_hideButtonWindow = false; g_isClosingButtonWindow = true; s_isProcessedMessages = false; s_pMainMenuTask = nullptr; @@ -1261,7 +1261,7 @@ void OptionsMenu::Open(bool isPause) ResetSelection(); ButtonWindow::Open("Button_SelectBack", s_isPause); - MainMenuTaskPatches::HideButtonWindow = true; + MainMenuTaskPatches::s_hideButtonWindow = true; } void OptionsMenu::Close() diff --git a/MarathonRecomp/user/config_def.h b/MarathonRecomp/user/config_def.h index 2b2412c0..8db35258 100644 --- a/MarathonRecomp/user/config_def.h +++ b/MarathonRecomp/user/config_def.h @@ -99,6 +99,7 @@ CONFIG_DEFINE_HIDDEN("Codes", bool, RestoreSonicActionGauge, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, SkipIntroLogos, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, TailsGauge, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, UnlimitedAntigravity, false, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, UseOfficialAchievementText, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, UseOfficialTitleOnTitleBar, false, true); CONFIG_DEFINE("Update", time_t, LastChecked, 0, false); diff --git a/README.md b/README.md index 8479d65a..471047b8 100644 --- a/README.md +++ b/README.md @@ -177,8 +177,6 @@ This project does not plan to support any more platforms other than Windows, Lin - [NextinHKRY](https://github.com/NextinMono): Provided Italian localization for the custom menus. -- [Syko](https://x.com/UltraSyko): Aided in identifying fonts used by the original SonicNext logo. - - [Hotline Sehwani](https://www.youtube.com/channel/UC9NBX5UPq4fYvbr7bzvRvOg) & SilverIceSound: Produced the [installer music](https://www.youtube.com/watch?v=8mfOSTcTQNs) ([original prod.](https://www.youtube.com/watch?v=k_mGNwrxR5M) by [Tomoya Ohtani](https://www.youtube.com/@TomoyaOhtaniChannel)). ### Special Thanks @@ -188,6 +186,8 @@ This project does not plan to support any more platforms other than Windows, Lin - [Darío](https://github.com/DarioSamo): Creator of the graphics hardware abstraction layer [plume](https://github.com/renderbag/plume), used by the project's graphics backend. +- [Syko](https://x.com/UltraSyko): Aided in identifying fonts used by the original SonicNext logo. + - Ray Vassos: Aided with German localization for the custom menus. - [ocornut](https://github.com/ocornut): Creator of [Dear ImGui](https://github.com/ocornut/imgui), which is used as the backbone of the custom menus. From f956e32ada84f45b7c13d0cb7bf586fda9c8d62a Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Tue, 11 Nov 2025 15:00:23 +0000 Subject: [PATCH 09/29] Update locale --- MarathonRecomp/kernel/xdbf.h | 39 +++++++++-------- MarathonRecomp/locale/achievement_locale.cpp | 46 ++++++++++---------- MarathonRecomp/locale/locale.cpp | 28 ++++++------ 3 files changed, 58 insertions(+), 55 deletions(-) diff --git a/MarathonRecomp/kernel/xdbf.h b/MarathonRecomp/kernel/xdbf.h index d326a4ed..d641e409 100644 --- a/MarathonRecomp/kernel/xdbf.h +++ b/MarathonRecomp/kernel/xdbf.h @@ -8,33 +8,36 @@ extern std::unordered_map g_xdbfTextureCache; namespace xdbf { - inline std::string& FixInvalidSequences(std::string& str) + inline std::string FixInvalidSequences(std::string& str) { - static std::array s_invalidSequences = - { - "\n" - }; + std::string result; - static std::array s_replaceSequences = - { - " " - }; + result.reserve(str.size()); - for (int i = 0; i < s_invalidSequences.size(); i++) + for (size_t i = 0; i < str.size(); ++i) { - size_t pos = 0; - - auto& invalidSeq = s_invalidSequences[i]; - auto& replaceSeq = s_replaceSequences[i]; + auto& c = str[i]; - while ((pos = str.find(s_invalidSequences[i], pos)) != std::string::npos) + if (c == '\r' || c == '\n') { - str = str.replace(pos, invalidSeq.length(), replaceSeq); + // Remove spaces before line breaks. + while (!result.empty() && result.back() == ' ') + result.pop_back(); - pos += replaceSeq.length(); + // Skip duplicate line breaks. + while (i + 1 < str.size() && (str[i + 1] == '\r' || str[i + 1] == '\n')) + ++i; + + // Add a space if the next char isn't one. + if (i + 1 < str.size() && str[i + 1] != ' ') + result += ' '; + } + else + { + result += c; } } - return str; + return result; } } diff --git a/MarathonRecomp/locale/achievement_locale.cpp b/MarathonRecomp/locale/achievement_locale.cpp index bab75300..47ff0f86 100644 --- a/MarathonRecomp/locale/achievement_locale.cpp +++ b/MarathonRecomp/locale/achievement_locale.cpp @@ -12,7 +12,7 @@ std::unordered_map> g_achL { "Sonic's Episode: Cleared", "Clear Sonic's episode!", - "You have cleared Sonic's episode." + "Cleared Sonic's episode." } }, { @@ -67,7 +67,7 @@ std::unordered_map> g_achL { "Shadow's Episode: Cleared", "Clear Shadow's episode!", - "You have cleared Shadow's episode." + "Cleared Shadow's episode." } }, { @@ -122,7 +122,7 @@ std::unordered_map> g_achL { "Silver's Episode: Cleared", "Clear Silver's episode!", - "You have cleared Silver's episode." + "Cleared Silver's episode." } }, { @@ -177,7 +177,7 @@ std::unordered_map> g_achL { "One To Reach The End", "Clear the last hidden episode!", - "You have cleared the last episode." + "Cleared the last episode." } }, { @@ -232,7 +232,7 @@ std::unordered_map> g_achL { "Sonic's Episode: Completed", "Clear all of Sonic's hard ACT missions.", - "You have cleared all of Sonic's ACT missions." + "Cleared all of Sonic's ACT missions." } }, { @@ -287,7 +287,7 @@ std::unordered_map> g_achL { "Shadow's Episode: Completed", "Clear all of Shadow's hard ACT missions.", - "You have cleared all of Shadow's ACT missions." + "Cleared all of Shadow's ACT missions." } }, { @@ -342,7 +342,7 @@ std::unordered_map> g_achL { "Silver's Episode: Completed", "Clear all of Silver's hard ACT missions.", - "You have cleared all of Silver's ACT missions." + "Cleared all of Silver's ACT missions." } }, { @@ -397,7 +397,7 @@ std::unordered_map> g_achL { "Shadow's Episode: Mastered", "Clear all of Shadow's ACT missions with Rank S.", - "You have achieved Rank S on all of Shadow's ACT missions." + "Achieved Rank S on all of Shadow's ACT missions." } }, { @@ -452,7 +452,7 @@ std::unordered_map> g_achL { "Sonic's Episode: Mastered", "Clear all of Sonic's ACT missions with Rank S.", - "You have achieved Rank S on all of Sonic's ACT missions." + "Achieved Rank S on all of Sonic's ACT missions." } }, { @@ -507,7 +507,7 @@ std::unordered_map> g_achL { "Silver's Episode: Mastered", "Clear all of Silver's ACT missions with Rank S.", - "You have achieved Rank S on all of Silver's ACT missions." + "Achieved Rank S on all of Silver's ACT missions." } }, { @@ -562,7 +562,7 @@ std::unordered_map> g_achL { "Knights of Kronos", "Clear the last hidden episode with Rank S.", - "You have achieved Rank S on all of the last episode." + "Achieved Rank S on all of the last episode." } }, { @@ -617,7 +617,7 @@ std::unordered_map> g_achL { "Legend of Soleanna", "Clear all ACT missions with Rank S.", - "You have achieved Rank S on all ACT missions." + "Achieved Rank S on all ACT missions." } }, { @@ -672,7 +672,7 @@ std::unordered_map> g_achL { "Silver Medalist", "Collect all of the Silver Medals scattered around Soleanna.", - "You have collected all of the Silver Medals in Soleanna." + "Collected all of the Silver Medals in Soleanna." } }, { @@ -727,7 +727,7 @@ std::unordered_map> g_achL { "Gold Medalist", "Collect all of the legendary Gold Medals of Soleanna.", - "You have collected all of the Gold Medals of Soleanna." + "Collected all of the Gold Medals of Soleanna." } }, { @@ -782,7 +782,7 @@ std::unordered_map> g_achL { "Blue Phantom", "Unlock all of Sonic's abilities.", - "You have learned all of Sonic's abilities." + "Learned all of Sonic's abilities." } }, { @@ -837,7 +837,7 @@ std::unordered_map> g_achL { "Ultimate Life Form", "Unlock all of Shadow's abilities.", - "You have learned all of Shadow's abilities." + "Learned all of Shadow's abilities." } }, { @@ -892,7 +892,7 @@ std::unordered_map> g_achL { "Psychic Soldier", "Unlock all of Silver's abilities.", - "You have learned all of Silver's abilities." + "Learned all of Silver's abilities." } }, { @@ -947,7 +947,7 @@ std::unordered_map> g_achL { "Soleanna's Hero", "Clear all of Sonic's TOWN missions.", - "You have cleared all of Sonic's TOWN missions." + "Cleared all of Sonic's TOWN missions." } }, { @@ -1002,7 +1002,7 @@ std::unordered_map> g_achL { "Elite Agent", "Clear all of Shadow's TOWN missions.", - "You have cleared all of Shadow's TOWN missions." + "Cleared all of Shadow's TOWN missions." } }, { @@ -1057,7 +1057,7 @@ std::unordered_map> g_achL { "Silver the Liberator", "Clear all of Silver's TOWN missions.", - "You have cleared all of Silver's TOWN missions." + "Cleared all of Silver's TOWN missions." } }, { @@ -1112,7 +1112,7 @@ std::unordered_map> g_achL { "Soleanna's Blue Wind", "Clear all of Sonic's TOWN missions with Rank S.", - "You have achieved Rank S on all of Sonic's TOWN missions." + "Achieved Rank S on all of Sonic's TOWN missions." } }, { @@ -1167,7 +1167,7 @@ std::unordered_map> g_achL { "Dark Hero", "Clear all of Shadow's TOWN missions with Rank S.", - "You have achieved Rank S on all of Shadow's TOWN missions." + "Achieved Rank S on all of Shadow's TOWN missions." } }, { @@ -1222,7 +1222,7 @@ std::unordered_map> g_achL { "Silver the Savior", "Clear all of Silver's TOWN missions with Rank S.", - "You have achieved Rank S on all of Silver's TOWN missions." + "Achieved Rank S on all of Silver's TOWN missions." } }, { diff --git a/MarathonRecomp/locale/locale.cpp b/MarathonRecomp/locale/locale.cpp index 3c5de929..2a395ad0 100644 --- a/MarathonRecomp/locale/locale.cpp +++ b/MarathonRecomp/locale/locale.cpp @@ -775,8 +775,8 @@ std::unordered_map> { { ELanguage::English, "Installation check has finished.\n\nAll files seem to be correct.\n\nThe game will now close. Remove the launch argument to play the game." }, { ELanguage::Japanese, "インストールチェックが終了しました\n\nすべてのファイルは正しいようです\n\nゲームは終了します、ゲームをプレイするには起動引数を削除してください" }, - { ELanguage::German, "Die Installation wurde überprüft.\n\nAlle Dateien scheinen korrekt zu sein.\n\nDas Spiel wird nun geschlossen. Entferne die Startoption, um das Spiel zu spielen." }, - { ELanguage::French, "La vérification de l'installation est terminée.\n\nTous les fichiers semblent corrects.\n\nL'application va maintenant se fermer. Retirez l'argument de lancement pour pouvoir lancer le jeu." }, + { ELanguage::German, "Die Installation wurde überprüft.\n\nAlle Dateien scheinen korrekt zu sein.\n\nDas Spiel wird nun geschlossen. Entferne die Startoption, um das Spiel zu spielen." }, + { ELanguage::French, "La vérification de l'installation est terminée.\n\nTous les fichiers semblent corrects.\n\nL'application va maintenant se fermer. Retirez l'argument de lancement pour pouvoir lancer le jeu." }, { ELanguage::Spanish, "La verificación de la instalación ha terminado.\n\nTodos los archivos parecen correctos.\n\nEl juego se cerrará ahora. Elimina el argumento de lanzamiento para jugar al juego." }, { ELanguage::Italian, "La verifica dei file d'installazione è terminata.\n\nTutti i file sembrano corretti.\n\nIl gioco si chiuderà. Rimuovi l'argomento di avvio per poter giocare." } } @@ -786,8 +786,8 @@ std::unordered_map> { { ELanguage::English, "Installation check has failed.\n\nError: %s\n\nThe game will now close. Try reinstalling the game by using the --install launch argument." }, { ELanguage::Japanese, "インストールチェックに失敗しました\n\nエラー:%s\n\nゲームは終了します、--install 起動引数を使用してゲームを再インストールしてください" }, - { ELanguage::German, "Die Installationsprüfung ist fehlgeschlagen.\n\nFehler: %s\n\nDas Spiel wird nun geschlossen. Versuche das Spiel durch Verwendung der Startoption --install neu zu installieren." }, - { ELanguage::French, "La vérification de l'installation a échoué.\n\nErreur : %s\n\nL'application va maintenant se fermer. Essayez de réinstaller le jeu en utilisant l'argument de lancement --install." }, + { ELanguage::German, "Die Installationsprüfung ist fehlgeschlagen.\n\nFehler: %s\n\nDas Spiel wird nun geschlossen. Versuche das Spiel durch Verwendung der Startoption --install neu zu installieren." }, + { ELanguage::French, "La vérification de l'installation a échoué.\n\nErreur : %s\n\nL'application va maintenant se fermer. Essayez de réinstaller le jeu en utilisant l'argument de lancement --install." }, { ELanguage::Spanish, "La verificación de la instalación ha fallado.\n\nError: %s\n\nEl juego se cerrará ahora. Intenta reinstalar el juego utilizando el argumento de lanzamiento --install." }, { ELanguage::Italian, "La verifica dei file d'installazione non è andata a buon fine.\n\nErrore: %s\n\nIl gioco si chiuderà. Prova a reinstallare il gioco utilizzando l'argomento di avvio --install." } } @@ -928,22 +928,22 @@ std::unordered_map> "Common_Retry", { { ELanguage::English, "Retry" }, - { ELanguage::Japanese, "DUMMY" }, - { ELanguage::German, "DUMMY" }, - { ELanguage::French, "DUMMY" }, - { ELanguage::Spanish, "DUMMY" }, - { ELanguage::Italian, "DUMMY" } + { ELanguage::Japanese, "リトライ" }, + { ELanguage::German, "Wiederholen" }, + { ELanguage::French, "Réessayer" }, + { ELanguage::Spanish, "Reintentar" }, + { ELanguage::Italian, "Riprova" } } }, { "Common_ContinueWithoutSaving", { { ELanguage::English, "Continue without saving." }, - { ELanguage::Japanese, "DUMMY" }, - { ELanguage::German, "DUMMY" }, - { ELanguage::French, "DUMMY" }, - { ELanguage::Spanish, "DUMMY" }, - { ELanguage::Italian, "DUMMY" } + { ELanguage::Japanese, "セーブせずにゲームを続けます" }, + { ELanguage::German, "Fortsetzen ohne zu speichern." }, + { ELanguage::French, "Continuer sans sauvegarder." }, + { ELanguage::Spanish, "Continuar sin guardar." }, + { ELanguage::Italian, "Continua senza salvare." } } }, { From 293d21be957dd1175fb792e9e8f621538a44b7b2 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Tue, 11 Nov 2025 17:22:31 +0000 Subject: [PATCH 10/29] Added sliding transition to achievements menu --- MarathonRecomp/ui/achievement_menu.cpp | 47 +++++++++++++++++++------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/MarathonRecomp/ui/achievement_menu.cpp b/MarathonRecomp/ui/achievement_menu.cpp index a6eefdb4..9faa72b4 100644 --- a/MarathonRecomp/ui/achievement_menu.cpp +++ b/MarathonRecomp/ui/achievement_menu.cpp @@ -14,6 +14,13 @@ #include #include +static constexpr double CONTAINER_MOVE_OFFSET = 0; +static constexpr double CONTAINER_MOVE_DURATION = 5; +static constexpr double CONTAINER_FADE_OFFSET = CONTAINER_MOVE_OFFSET; +static constexpr double CONTAINER_FADE_DURATION = 10; +static constexpr double ROW_FADE_OFFSET = CONTAINER_FADE_DURATION; +static constexpr double ROW_FADE_TIME = 5; + static constexpr int MAX_VISIBLE_ROWS = 4; static double g_time{}; @@ -26,6 +33,7 @@ static bool g_up{}; static bool g_upWasHeld{}; static bool g_down{}; static bool g_downWasHeld{}; +static bool g_hasSwitched{}; static int g_rowCount{}; static int g_selectedIndex{}; @@ -94,8 +102,8 @@ static bool DrawContainer(ImVec2 min, ImVec2 max) auto containerTopCentreUVs = PIXELS_TO_UV_COORDS(1024, 1024, 50, 400, 50, 50); auto containerSideUVs = PIXELS_TO_UV_COORDS(1024, 1024, 1, 450, 50, 50); auto containerCentreUVs = PIXELS_TO_UV_COORDS(1024, 1024, 50, 450, 50, 50); - auto containerColourMotion = AchievementMenu::IsClosing() ? 0 : ComputeLinearMotion(g_time, 0, 10); - auto containerColour = IM_COL32(255, 255, 255, 63 * containerColourMotion); + auto containerAlphaMotionTime = AchievementMenu::IsClosing() ? 0 : ComputeLinearMotion(g_time, CONTAINER_FADE_OFFSET, CONTAINER_FADE_DURATION); + auto containerColour = IM_COL32(255, 255, 255, AchievementMenu::s_state == AchievementMenuState::GoldMedals ? Lerp(63, 0, containerAlphaMotionTime) : 63); auto containerEdgeSize = Scale(50, true); ImVec2 containerTopLeftCornerMin = min; @@ -127,7 +135,7 @@ static bool DrawContainer(ImVec2 min, ImVec2 max) drawList->PushClipRect(containerTopLeftCornerMin, containerRightMax); - return containerColourMotion >= 1.0; + return containerAlphaMotionTime >= 1.0; } static void DrawAchievement(int rowIndex, Achievement& achievement, bool isUnlocked) @@ -175,8 +183,10 @@ static void DrawAchievement(int rowIndex, Achievement& achievement, bool isUnloc auto rowUVs = PIXELS_TO_UV_COORDS(1024, 1024, 605, 450, 10, 30); auto rowMarginX = Scale(2, true); auto rowMarginY = Scale(24, true); - auto rowColourMotion = BREATHE_MOTION(1.0f, 0.0f, g_rowSelectionTime, 0.9f); - auto rowColour = IM_COL32(255, 255, 255, isCurrent ? Lerp(165, 94, rowColourMotion) : 94); + auto rowColourBreatheMotionTime = BREATHE_MOTION(1.0f, 0.0f, g_rowSelectionTime, 0.9f); + auto rowColourTransitionMotionTime = ComputeLinearMotion(g_time, ROW_FADE_OFFSET, ROW_FADE_TIME); + auto rowColourAlpha = isCurrent ? Lerp(165, 94, rowColourBreatheMotionTime) * rowColourTransitionMotionTime : 94 * rowColourTransitionMotionTime; + auto rowColour = IM_COL32(255, 255, 255, rowColourAlpha); ImVec2 rowMin = { clipRectMin.x + rowMarginX, min.y + itemHeight - rowMarginY }; ImVec2 rowMax = { clipRectMax.x - rowMarginX, rowMin.y + Scale(30, true) }; @@ -336,6 +346,7 @@ void AchievementMenu::Draw() ButtonWindow::Open("Button_GoldMedalsBack"); SetGoldMedalResultsVisible(false); Game_PlaySound("window_open"); + g_hasSwitched = true; } break; @@ -361,25 +372,36 @@ void AchievementMenu::Draw() } } + auto containerMotionTime = ComputeLinearMotion(g_time, CONTAINER_MOVE_OFFSET, CONTAINER_MOVE_DURATION, s_state == AchievementMenuState::GoldMedals); + auto containerTop = Scale(Lerp(217, 148, containerMotionTime), true); + auto containerBottom = Scale(554, true); + auto containerWidth = Scale(1158, true); + + ImVec2 min = { (res.x / 2) - containerWidth / 2, g_vertCentre + containerTop }; + ImVec2 max = { min.x + containerWidth, g_vertCentre + containerBottom }; + switch (s_state) { case AchievementMenuState::GoldMedals: + { s_commonMenu.SetTitle(Localise("Achievements_GoldMedals_Uppercase")); s_commonMenu.ShowDescription = false; + + if (g_hasSwitched) + { + // For outro animation. + DrawContainer(min, max); + drawList->PopClipRect(); + } + break; + } case AchievementMenuState::Achievements: { s_commonMenu.SetTitle(Localise("Achievements_Title_Uppercase")); s_commonMenu.ShowDescription = true; - auto containerWidth = Scale(1158, true); - auto containerTop = Scale(148, true); - auto containerHeight = Scale(405, true); - - ImVec2 min = { (res.x / 2) - containerWidth / 2, g_vertCentre + containerTop }; - ImVec2 max = { min.x + containerWidth, min.y + containerHeight }; - if (DrawContainer(min, max)) { auto rowIndex = 0; @@ -481,6 +503,7 @@ void AchievementMenu::Open(Sonicteam::MainMenuTask* pMainMenuTask) s_isVisible = true; s_state = AchievementMenuState::GoldMedals; g_time = ImGui::GetTime(); + g_hasSwitched = false; g_selectedIndex = 0; ButtonWindow::Open("Button_AchievementsBack"); From eec51a8c48200df191a437490f45318a37895da5 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Thu, 13 Nov 2025 01:06:17 +0000 Subject: [PATCH 11/29] Update locale --- MarathonRecomp/locale/achievement_locale.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MarathonRecomp/locale/achievement_locale.cpp b/MarathonRecomp/locale/achievement_locale.cpp index 47ff0f86..13fa561f 100644 --- a/MarathonRecomp/locale/achievement_locale.cpp +++ b/MarathonRecomp/locale/achievement_locale.cpp @@ -561,7 +561,7 @@ std::unordered_map> g_achL ELanguage::English, { "Knights of Kronos", - "Clear the last hidden episode with Rank S.", + "Clear all of the last hidden episode with Rank S.", "Achieved Rank S on all of the last episode." } }, From 5028082d0a3fe4b9048d3da4347f89d613ab1663 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Thu, 13 Nov 2025 01:06:39 +0000 Subject: [PATCH 12/29] Italian localisation Co-authored-by: NextinHKRY <38560522+NextinMono@users.noreply.github.com> --- MarathonRecomp/locale/achievement_locale.cpp | 138 +++++++++---------- MarathonRecomp/locale/locale.cpp | 14 +- MarathonRecomp/patches/text_patches.cpp | 37 ++--- 3 files changed, 95 insertions(+), 94 deletions(-) diff --git a/MarathonRecomp/locale/achievement_locale.cpp b/MarathonRecomp/locale/achievement_locale.cpp index 13fa561f..cd5cba18 100644 --- a/MarathonRecomp/locale/achievement_locale.cpp +++ b/MarathonRecomp/locale/achievement_locale.cpp @@ -50,9 +50,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio di Sonic: completato", + "Completa l'episodio di Sonic!", + "Hai completato l'episodio di Sonic." } }, } @@ -105,9 +105,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio di Shadow: completato", + "Completa l'episodio di Shadow!", + "Hai completato l'episodio di Shadow." } }, } @@ -160,9 +160,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio di Silver: completato", + "Completa l'episodio di Silver!", + "Hai completato l'episodio di Silver." } }, } @@ -215,9 +215,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Colui che giunse alla fine", + "Concludi l'ultimo episodio nascosto!", + "Hai completato l'ultimo episodio." } }, } @@ -270,9 +270,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio di Sonic: superato", + "Completa tutte le missioni ATTO difficili di Sonic.", + "Hai completato tutte le missioni ATTO difficili di Sonic." } }, } @@ -325,9 +325,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio di Shadow: superato", + "Completa tutte le missioni ATTO difficili di Shadow.", + "Hai completato tutte le missioni ATTO difficili di Shadow." } }, } @@ -380,9 +380,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio di Silver: superato", + "Completa tutte le missioni ATTO difficili di Silver.", + "Hai completato tutte le missioni ATTO difficili di Silver." } }, } @@ -435,9 +435,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio di Shadow: stravinto", + "Completa tutte le missioni ATTO di Shadow in posizione S.", + "Hai ottenuto la posizione S in tutte le missioni ATTO di Shadow." } }, } @@ -490,9 +490,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio di Sonic: stravinto", + "Completa tutte le missioni ATTO di Sonic in posizione S.", + "Hai ottenuto la posizione S in tutte le missioni ATTO di Sonic." } }, } @@ -545,9 +545,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio di Silver: stravinto", + "Completa tutte le missioni ATTO di Silver in posizione S.", + "Hai ottenuto la posizione S in tutte le missioni ATTO di Silver." } }, } @@ -600,9 +600,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Cavalieri di Kronos", + "Completa tutto l'ultimo episodio nascosto in posizione S.", + "Hai ottenuto la posizione S in tutto l'ultimo episodio." } }, } @@ -655,9 +655,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Leggenda di Soleanna", + "Completa tutte le missioni ATTO in posizione S.", + "Hai ottenuto la posizione S in tutte le missioni ATTO." } }, } @@ -710,9 +710,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Medaglie d'argento", + "Raccogli tutte le medaglie d'argento disseminate in giro per Soleanna.", + "Hai raccolto tutte le medaglie d'argento di Soleanna." } }, } @@ -765,9 +765,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Medaglie d'oro", + "Raccogli tutte le medaglie d'oro di Soleanna.", + "Hai raccolto tutte le medaglie d'oro di Soleanna." } }, } @@ -820,9 +820,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Fantasma Blu", + "Sblocca tutte le abilità di Sonic.", + "Hai imparato tutte le abilità di Sonic." } }, } @@ -875,9 +875,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Essere Supremo", + "Sblocca tutte le abilità di Shadow.", + "Hai imparato tutte le abilità di Shadow." } }, } @@ -930,9 +930,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Soldato Psichico", + "Sblocca tutte le abilità di Silver.", + "Hai imparato tutte le abilità di Silver." } }, } @@ -985,9 +985,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Eroe di Soleanna", + "Completa tutte le missioni CITTÀ di Sonic.", + "Hai completato tutte le missioni CITTÀ di Sonic." } }, } @@ -1040,9 +1040,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Agente Scelto", + "Completa tutte le missioni CITTÀ di Shadow.", + "Hai completato tutte le missioni CITTÀ di Shadow." } }, } @@ -1095,9 +1095,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Silver il Liberatore", + "Completa tutte le missioni CITTÀ di Silver.", + "Hai completato tutte le missioni CITTÀ di Silver." } }, } @@ -1150,9 +1150,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Vento Blu di Soleanna", + "Completa tutte le missioni CITTÀ di Sonic in posizione S.", + "Hai ottenuto la posizione S in tutte le missioni CITTÀ di Sonic." } }, } @@ -1205,9 +1205,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Eroe Oscuro", + "Completa tutte le missioni CITTÀ di Shadow in posizione S.", + "Hai ottenuto la posizione S in tutte le missioni CITTÀ di Shadow." } }, } @@ -1260,9 +1260,9 @@ std::unordered_map> g_achL { ELanguage::Italian, { - "DUMMY", - "DUMMY", - "DUMMY" + "Silver il Salvatore", + "Completa tutte le missioni CITTÀ di Silver in posizione S.", + "Hai ottenuto la posizione S in tutte le missioni CITTÀ di Silver." } }, } diff --git a/MarathonRecomp/locale/locale.cpp b/MarathonRecomp/locale/locale.cpp index 2a395ad0..3193340e 100644 --- a/MarathonRecomp/locale/locale.cpp +++ b/MarathonRecomp/locale/locale.cpp @@ -301,7 +301,7 @@ std::unordered_map> { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, { ELanguage::Spanish, "DUMMY" }, - { ELanguage::Italian, "DUMMY" } + { ELanguage::Italian, "STATISTICHE" } } }, { @@ -312,7 +312,7 @@ std::unordered_map> { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, { ELanguage::Spanish, "DUMMY" }, - { ELanguage::Italian, "DUMMY" } + { ELanguage::Italian, "Statistiche: mostra gli elenchi delle medaglie d'oro e gli obiettivi" } } }, { @@ -345,7 +345,7 @@ std::unordered_map> { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, { ELanguage::Spanish, "DUMMY" }, - { ELanguage::Italian, "DUMMY" } + { ELanguage::Italian, "Medaglie d'oro" } } }, { @@ -356,7 +356,7 @@ std::unordered_map> { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, { ELanguage::Spanish, "DUMMY" }, - { ELanguage::Italian, "DUMMY" } + { ELanguage::Italian, "MEDAGLIE D'ORO" } } }, { @@ -697,7 +697,7 @@ std::unordered_map> { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, { ELanguage::Spanish, "DUMMY" }, - { ELanguage::Italian, "DUMMY" } + { ELanguage::Italian, "Caricamento fallito. I dati degli obiettivi\nsono danneggiati. Se continui perderai tutti\ni tuoi obiettivi." } } }, { @@ -710,7 +710,7 @@ std::unordered_map> { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, { ELanguage::Spanish, "DUMMY" }, - { ELanguage::Italian, "DUMMY" } + { ELanguage::Italian, "Caricamento fallito. Impossibile caricare i dati\ndegli obiettivi. Se continui non potrai salvare\ni tuoi obiettivi." } } }, { @@ -723,7 +723,7 @@ std::unordered_map> { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, { ELanguage::Spanish, "DUMMY" }, - { ELanguage::Italian, "DUMMY" } + { ELanguage::Italian, "Salvataggio fallito. Impossibile salvare i dati\ndegli obiettivi. Se continui non potrai salvare\ni tuoi obiettivi." } } }, { diff --git a/MarathonRecomp/patches/text_patches.cpp b/MarathonRecomp/patches/text_patches.cpp index 1907cc2e..02be7b37 100644 --- a/MarathonRecomp/patches/text_patches.cpp +++ b/MarathonRecomp/patches/text_patches.cpp @@ -27,24 +27,25 @@ PPC_FUNC(sub_825ECB48) __imp__sub_825ECB48(ctx, base); - auto pspTextCard = (boost::shared_ptr*)(base + ctx.r3.u32); - - for (auto& replacement : TextPatches::s_replacedMessages) - { - if (HashStr(pMessage) != replacement.first) - continue; - - auto& message = Localise(replacement.second); - auto wideMessage = std::wstring(message.begin(), message.end()); - auto wideMessageLen = (wideMessage.length() * 2) + 2; - - auto pReplacedMessage = (uint16_t*)g_userHeap.Alloc(wideMessageLen); - - for (size_t i = 0; i < wideMessageLen; i++) - pReplacedMessage[i] = ByteSwap(wideMessage.c_str()[i]); - - pspTextCard->get()->m_pText = (const uint16_t*)pReplacedMessage; - } + // FIXME + // auto pspTextCard = (boost::shared_ptr*)(base + ctx.r3.u32); + // + // for (auto& replacement : TextPatches::s_replacedMessages) + // { + // if (HashStr(pMessage) != replacement.first) + // continue; + // + // auto& message = Localise(replacement.second); + // auto wideMessage = std::wstring(message.begin(), message.end()); + // auto wideMessageLen = (wideMessage.length() * 2) + 2; + // + // auto pReplacedMessage = (uint16_t*)g_userHeap.Alloc(wideMessageLen); + // + // for (size_t i = 0; i < wideMessageLen; i++) + // pReplacedMessage[i] = ByteSwap(wideMessage.c_str()[i]); + // + // pspTextCard->get()->m_pText = (const uint16_t*)pReplacedMessage; + // } if (!pNewMessage) return; From 1744c8f5a8d299f248cbcb745a68080403d4c1d9 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Thu, 13 Nov 2025 01:25:56 +0000 Subject: [PATCH 13/29] Spanish localisation by DaGuAr Co-authored-by: DaGuAr --- MarathonRecomp/locale/achievement_locale.cpp | 138 +++++++++---------- MarathonRecomp/locale/locale.cpp | 14 +- 2 files changed, 76 insertions(+), 76 deletions(-) diff --git a/MarathonRecomp/locale/achievement_locale.cpp b/MarathonRecomp/locale/achievement_locale.cpp index cd5cba18..d16bae5d 100644 --- a/MarathonRecomp/locale/achievement_locale.cpp +++ b/MarathonRecomp/locale/achievement_locale.cpp @@ -42,9 +42,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio de Sonic: Superado", + "¡Supera el episodio de Sonic!", + "Has superado el episodio de Sonic." } }, { @@ -97,9 +97,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio de Shadow: Superado", + "¡Supera el episodio de Shadow!", + "Has superado el episodio de Shadow." } }, { @@ -152,9 +152,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio de Silver: Superado", + "¡Supera el episodio de Silver!", + "Has superado el episodio de Silver." } }, { @@ -207,9 +207,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Llegar hasta el final", + "¡Supera el último episodio secreto!", + "Has superado el último episodio." } }, { @@ -262,9 +262,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio de Sonic: Completado", + "Supera todas las misiones ACTO de Sonic en modo difícil.", + "Has superado todas las misiones ACTO de Sonic." } }, { @@ -317,9 +317,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio de Shadow: Completado", + "Supera todas las misiones ACTO de Shadow en modo difícil.", + "Has superado todas las misiones ACTO de Shadow." } }, { @@ -372,9 +372,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio de Silver: Completado", + "Supera todas las misiones ACTO de Silver en modo difícil.", + "Has superado todas las misiones ACTO de Silver." } }, { @@ -427,9 +427,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio de Shadow: Dominado", + "Supera todas las misiones ACTO de Shadow con rango S.", + "Has conseguido el rango S en todas las misiones ACTO de Shadow." } }, { @@ -482,9 +482,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio de Sonic: Dominado", + "Supera todas las misiones ACTO de Sonic con rango S.", + "Has conseguido el rango S en todas las misiones ACTO de Sonic." } }, { @@ -537,9 +537,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episodio de Silver: Dominado", + "Supera todas las misiones ACTO de Silver con rango S.", + "Has conseguido el rango S en todas las misiones ACTO de Silver." } }, { @@ -592,9 +592,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Caballeros de Kronos", + "Supera el último episodio secreto con rango S.", + "Has conseguido el rango S en todo el último episodio." } }, { @@ -647,9 +647,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Leyenda de Soleanna", + "Supera todas las misiones ACTO con rango S.", + "Has conseguido el rango S en todas las misiones ACTO." } }, { @@ -702,9 +702,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Medallista de plata", + "Reúne todas las medallas de plata repartidas por Soleanna.", + "Has conseguido todas las medallas de plata de Soleanna." } }, { @@ -757,9 +757,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Medallista de oro", + "Reúne todas las legendarias medallas de oro de Soleanna.", + "Has conseguido todas las medallas de oro de Soleanna." } }, { @@ -812,9 +812,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Fantasma azul", + "Desbloquea todas las habilidades de Sonic.", + "Has aprendido todas las habilidades de Sonic." } }, { @@ -867,9 +867,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Forma de vida definitiva", + "Desbloquea todas las habilidades de Shadow.", + "Has aprendido todas las habilidades de Shadow." } }, { @@ -922,9 +922,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Soldado psíquico", + "Desbloquea todas las habilidades de Silver.", + "Has aprendido todas las habilidades de Silver." } }, { @@ -977,9 +977,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Héroe de Soleanna", + "Supera todas las misiones CIUDAD de Sonic.", + "Has superado todas las misiones CIUDAD de Sonic." } }, { @@ -1032,9 +1032,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Agente de élite", + "Supera todas las misiones CIUDAD de Shadow.", + "Has superado todas las misiones CIUDAD de Shadow." } }, { @@ -1087,9 +1087,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Silver el Liberador", + "Supera todas las misiones CIUDAD de Silver.", + "Has superado todas las misiones CIUDAD de Silver." } }, { @@ -1142,9 +1142,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "El viento azul de Soleanna", + "Supera todas las misiones CIUDAD de Sonic con rango S.", + "Has conseguido rango S en todas las misiones CIUDAD de Sonic." } }, { @@ -1197,9 +1197,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Héroe oscuro", + "Supera todas las misiones CIUDAD de Shadow con rango S.", + "Has conseguido rango S en todas las misiones CIUDAD de Shadow." } }, { @@ -1252,9 +1252,9 @@ std::unordered_map> g_achL { ELanguage::Spanish, { - "DUMMY", - "DUMMY", - "DUMMY" + "Silver el Salvador", + "Supera todas las misiones CIUDAD de Silver con rango S.", + "Has conseguido rango S en todas las misiones CIUDAD de Silver." } }, { diff --git a/MarathonRecomp/locale/locale.cpp b/MarathonRecomp/locale/locale.cpp index 3193340e..00fbbdd9 100644 --- a/MarathonRecomp/locale/locale.cpp +++ b/MarathonRecomp/locale/locale.cpp @@ -300,7 +300,7 @@ std::unordered_map> { ELanguage::Japanese, "DUMMY" }, { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, - { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Spanish, "ESTADÍSTICAS" }, { ELanguage::Italian, "STATISTICHE" } } }, @@ -311,7 +311,7 @@ std::unordered_map> { ELanguage::Japanese, "DUMMY" }, { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, - { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Spanish, "Estadísticas: Muestra los listados de las medallas de oro y los logros" }, { ELanguage::Italian, "Statistiche: mostra gli elenchi delle medaglie d'oro e gli obiettivi" } } }, @@ -344,7 +344,7 @@ std::unordered_map> { ELanguage::Japanese, "DUMMY" }, { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, - { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Spanish, "Medallas de oro" }, { ELanguage::Italian, "Medaglie d'oro" } } }, @@ -355,7 +355,7 @@ std::unordered_map> { ELanguage::Japanese, "DUMMY" }, { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, - { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Spanish, "MEDALLAS DE ORO" }, { ELanguage::Italian, "MEDAGLIE D'ORO" } } }, @@ -696,7 +696,7 @@ std::unordered_map> { ELanguage::Japanese, "DUMMY" }, { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, - { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Spanish, "Error al cargar. Los datos de los logros están\ndañados. Si continúas, se perderá el progreso\nde tus logros." }, { ELanguage::Italian, "Caricamento fallito. I dati degli obiettivi\nsono danneggiati. Se continui perderai tutti\ni tuoi obiettivi." } } }, @@ -709,7 +709,7 @@ std::unordered_map> { ELanguage::Japanese, "DUMMY" }, { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, - { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Spanish, "Error al cargar. No se pueden cargar los datos\nde los logros. Si continúas, no podrás guardar\nel progreso de tus logros." }, { ELanguage::Italian, "Caricamento fallito. Impossibile caricare i dati\ndegli obiettivi. Se continui non potrai salvare\ni tuoi obiettivi." } } }, @@ -722,7 +722,7 @@ std::unordered_map> { ELanguage::Japanese, "DUMMY" }, { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, - { ELanguage::Spanish, "DUMMY" }, + { ELanguage::Spanish, "Error al guardar. No se pueden guardar los datos\nde los logros. Si continúas, no podrás guardar\ntu progreso en los logros." }, { ELanguage::Italian, "Salvataggio fallito. Impossibile salvare i dati\ndegli obiettivi. Se continui non potrai salvare\ni tuoi obiettivi." } } }, From bdf30a3f373398b94f5ed6f853269097ee6718e0 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Thu, 13 Nov 2025 01:44:48 +0000 Subject: [PATCH 14/29] Adjusted achievement text marquee start/end position --- MarathonRecomp/ui/achievement_menu.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/MarathonRecomp/ui/achievement_menu.cpp b/MarathonRecomp/ui/achievement_menu.cpp index 9faa72b4..fd575591 100644 --- a/MarathonRecomp/ui/achievement_menu.cpp +++ b/MarathonRecomp/ui/achievement_menu.cpp @@ -245,11 +245,14 @@ static void DrawAchievement(int rowIndex, Achievement& achievement, bool isUnloc drawList->AddText(g_pFntRodin, fontSize, namePos, textColour, name.c_str()); SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); - auto marqueeFadeScale = Scale(18, true); - ImVec2 marqueeMin = { imageMax.x, min.y }; - ImVec2 marqueeMax = { max.x - marqueeFadeScale, max.y }; + auto timestampOffsetX = Scale(60, true); + + ImVec2 marqueeMin = { textX + Scale(2, true), min.y}; + ImVec2 marqueeMax = { max.x - timestampOffsetX - Scale(1, true), max.y }; + + drawList->PushClipRect(marqueeMin, marqueeMax); - if (isCurrent && textX + descSize.x >= max.x - marqueeFadeScale) + if (isCurrent && marqueeMin.x + descSize.x >= marqueeMax.x) { // Draw achievement description with marquee. SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); @@ -264,6 +267,8 @@ static void DrawAchievement(int rowIndex, Achievement& achievement, bool isUnloc SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); } + drawList->PopClipRect(); + if (!isUnlocked) return; @@ -285,7 +290,6 @@ static void DrawAchievement(int rowIndex, Achievement& achievement, bool isUnloc snprintf(buffer, sizeof(buffer), "%d/%d/%d %02d:%02d", pTime->tm_year + 1900, pTime->tm_mon + 1, pTime->tm_mday, pTime->tm_hour, pTime->tm_min); - auto timestampOffsetX = Scale(60, true); auto timestampSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, buffer); // Draw timestamp text. From bab6a9cdd54ca99c1b65265089b8456f637d5e26 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:53:17 +0000 Subject: [PATCH 15/29] Fix text card string replacement causing crashes on main menu exit --- MarathonRecomp/api/Sonicteam/HUDGoldMedal.h | 4 +- .../api/boost/smart_ptr/make_shared_object.h | 16 ++++- .../api/boost/smart_ptr/shared_ptr.h | 26 +++++-- MarathonRecomp/locale/locale.h | 3 +- MarathonRecomp/patches/text_patches.cpp | 71 +++++++++++++------ MarathonRecomp/patches/text_patches.h | 12 +++- 6 files changed, 96 insertions(+), 36 deletions(-) diff --git a/MarathonRecomp/api/Sonicteam/HUDGoldMedal.h b/MarathonRecomp/api/Sonicteam/HUDGoldMedal.h index ea2f09ec..12b40596 100644 --- a/MarathonRecomp/api/Sonicteam/HUDGoldMedal.h +++ b/MarathonRecomp/api/Sonicteam/HUDGoldMedal.h @@ -22,7 +22,8 @@ namespace Sonicteam be m_EpisodeIndex; be m_ScrollIndex; be m_MedalCount; - MARATHON_INSERT_PADDING(0x18); + boost::shared_ptr m_spMedalCountTextCard; + MARATHON_INSERT_PADDING(0x10); boost::shared_ptr m_aspTextCards[5]; boost::shared_ptr m_aspTextEntities[5]; }; @@ -33,6 +34,7 @@ namespace Sonicteam MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_EpisodeIndex, 0x9C); MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_ScrollIndex, 0xA0); MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_MedalCount, 0xA4); + MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_spMedalCountTextCard, 0xA8); MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_aspTextCards, 0xC0); MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_aspTextEntities, 0xE8); MARATHON_ASSERT_SIZEOF(HUDGoldMedal, 0x110); diff --git a/MarathonRecomp/api/boost/smart_ptr/make_shared_object.h b/MarathonRecomp/api/boost/smart_ptr/make_shared_object.h index 9bba6b4b..1b344ee4 100644 --- a/MarathonRecomp/api/boost/smart_ptr/make_shared_object.h +++ b/MarathonRecomp/api/boost/smart_ptr/make_shared_object.h @@ -4,9 +4,19 @@ namespace boost { - template - shared_ptr make_shared(TArgs&&... args) + template + shared_ptr make_shared(T* p, uint32_t vftable) { - return shared_ptr(new T(std::forward(args)...)); + shared_ptr sp; + + auto* counted = (detail::sp_counted_impl_p*)g_userHeap.Alloc(sizeof(detail::sp_counted_impl_p)); + + new (counted) detail::sp_counted_impl_p(p); + + sp.px = p; + sp.pn = (detail::sp_counted_base*)counted; + sp.pn->vftable_.ptr = vftable; + + return sp; } } diff --git a/MarathonRecomp/api/boost/smart_ptr/shared_ptr.h b/MarathonRecomp/api/boost/smart_ptr/shared_ptr.h index 2b63eaab..e0e6e8e5 100644 --- a/MarathonRecomp/api/boost/smart_ptr/shared_ptr.h +++ b/MarathonRecomp/api/boost/smart_ptr/shared_ptr.h @@ -18,14 +18,15 @@ namespace boost be destroy; be get_deleter; }; - + public: xpointer vftable_; + protected: be use_count_; be weak_count_; public: // TODO - sp_counted_base() = delete; + sp_counted_base() {} void add_ref() { @@ -76,7 +77,7 @@ namespace boost uint32_t use_count() const { - return ByteSwap(static_cast(use_count_.value)); + return ByteSwap(static_cast(use_count_.value)); } bool unique() const @@ -85,6 +86,22 @@ namespace boost } }; + template + class sp_counted_impl_p : public sp_counted_base + { + public: + sp_counted_impl_p(T* p) : ptr_(p) + { + use_count_ = 1; + weak_count_ = 1; + } + + ~sp_counted_impl_p() {} + + private: + xpointer ptr_; + }; + template struct sp_dereference { typedef T& type; @@ -99,7 +116,7 @@ namespace boost template class shared_ptr { - private: + public: xpointer px; xpointer pn; @@ -115,7 +132,6 @@ namespace boost pn->release(); } - public: shared_ptr() : px(), pn() {} // TODO diff --git a/MarathonRecomp/locale/locale.h b/MarathonRecomp/locale/locale.h index e0e1d472..3757a9b3 100644 --- a/MarathonRecomp/locale/locale.h +++ b/MarathonRecomp/locale/locale.h @@ -2,7 +2,8 @@ enum class ELanguage : uint32_t { - English = 1, + Unknown, + English, Japanese, German, French, diff --git a/MarathonRecomp/patches/text_patches.cpp b/MarathonRecomp/patches/text_patches.cpp index 02be7b37..43acd2c6 100644 --- a/MarathonRecomp/patches/text_patches.cpp +++ b/MarathonRecomp/patches/text_patches.cpp @@ -27,30 +27,55 @@ PPC_FUNC(sub_825ECB48) __imp__sub_825ECB48(ctx, base); - // FIXME - // auto pspTextCard = (boost::shared_ptr*)(base + ctx.r3.u32); - // - // for (auto& replacement : TextPatches::s_replacedMessages) - // { - // if (HashStr(pMessage) != replacement.first) - // continue; - // - // auto& message = Localise(replacement.second); - // auto wideMessage = std::wstring(message.begin(), message.end()); - // auto wideMessageLen = (wideMessage.length() * 2) + 2; - // - // auto pReplacedMessage = (uint16_t*)g_userHeap.Alloc(wideMessageLen); - // - // for (size_t i = 0; i < wideMessageLen; i++) - // pReplacedMessage[i] = ByteSwap(wideMessage.c_str()[i]); - // - // pspTextCard->get()->m_pText = (const uint16_t*)pReplacedMessage; - // } - - if (!pNewMessage) - return; + if (pNewMessage) + g_userHeap.Free(pNewMessage); + + static uint16_t* s_replacementStringPool{}; + static ELanguage s_replacementStringPoolLanguage{}; + + if (s_replacementStringPoolLanguage != Config::Language) + { + auto length = 0; + + // Compute string pool length. + for (auto& replacement : TextPatches::s_replacedMessages) + length += (Localise(replacement.second.pKey).length() * 2) + 2; + + if (s_replacementStringPool) + g_userHeap.Free(s_replacementStringPool); + + s_replacementStringPool = (uint16_t*)g_userHeap.Alloc(length); + s_replacementStringPoolLanguage = Config::Language; + + auto stringPoolPos = s_replacementStringPool; + + for (auto& replacement : TextPatches::s_replacedMessages) + { + auto& message = Localise(replacement.second.pKey); + auto wideMessage = std::wstring(message.begin(), message.end()); + auto wideMessageLen = wideMessage.length() + 1; - g_userHeap.Free(pNewMessage); + replacement.second.pGuestText = stringPoolPos; + + for (size_t i = 0; i < wideMessageLen; i++) + { + *stringPoolPos = ByteSwap(wideMessage.c_str()[i]); + stringPoolPos++; + } + } + } + + auto pspTextCard = (boost::shared_ptr*)(base + ctx.r3.u32); + + for (auto& replacement : TextPatches::s_replacedMessages) + { + if (HashStr(pMessage) != replacement.first) + continue; + + pspTextCard->get()->m_spResource = boost::make_shared(0, 0x820334C4); + pspTextCard->get()->m_pText = replacement.second.pGuestText; + pspTextCard->get()->m_pVariables = (const char*)0x8200139C; + } } // Load text book. diff --git a/MarathonRecomp/patches/text_patches.h b/MarathonRecomp/patches/text_patches.h index ce706425..ec7ceb03 100644 --- a/MarathonRecomp/patches/text_patches.h +++ b/MarathonRecomp/patches/text_patches.h @@ -2,6 +2,12 @@ #include +struct ReplacementMessage +{ + const char* pKey{}; + xpointer pGuestText{}; +}; + class TextPatches { public: @@ -11,10 +17,10 @@ class TextPatches { HashStr("msg_gamequitconfirm4"), "msg_backtotitle1" } // Replace "Exit the game." text with "Go back to the Title Screen." }; - static inline xxHashMap s_replacedMessages + static inline xxHashMap s_replacedMessages { - { HashStr("msg_goldmedalresults"), "MainMenu_GoldMedalResults_Name" }, - { HashStr("msg_goldmedalresults_c"), "MainMenu_GoldMedalResults_Description" } + { HashStr("msg_goldmedalresults"), { "MainMenu_GoldMedalResults_Name" } }, + { HashStr("msg_goldmedalresults_c"), { "MainMenu_GoldMedalResults_Description" } } }; static inline std::vector s_hintPatterns = From 550de85270f1b0d27344416daf75bce12f1b2f09 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:24:52 +0000 Subject: [PATCH 16/29] Fixed UTF-8 std::string -> std::wstring conversion --- MarathonRecomp/framework.h | 8 ++++++++ MarathonRecomp/patches/text_patches.cpp | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/MarathonRecomp/framework.h b/MarathonRecomp/framework.h index 5730c284..d13bab1d 100644 --- a/MarathonRecomp/framework.h +++ b/MarathonRecomp/framework.h @@ -1,5 +1,7 @@ #pragma once +#include + #define PROC_ADDRESS(libraryName, procName) \ GetProcAddress(LoadLibrary(TEXT(libraryName)), procName) @@ -107,3 +109,9 @@ inline bool strcmpWildcard(const char* str, const char* pattern) return false; } + +inline std::wstring TransformUTF8ToWString(const std::string& str) +{ + std::wstring_convert> converter; + return converter.from_bytes(str); +} diff --git a/MarathonRecomp/patches/text_patches.cpp b/MarathonRecomp/patches/text_patches.cpp index 43acd2c6..04158290 100644 --- a/MarathonRecomp/patches/text_patches.cpp +++ b/MarathonRecomp/patches/text_patches.cpp @@ -52,7 +52,7 @@ PPC_FUNC(sub_825ECB48) for (auto& replacement : TextPatches::s_replacedMessages) { auto& message = Localise(replacement.second.pKey); - auto wideMessage = std::wstring(message.begin(), message.end()); + auto wideMessage = TransformUTF8ToWString(message); auto wideMessageLen = wideMessage.length() + 1; replacement.second.pGuestText = stringPoolPos; From a98013b6aefe27b693db18049ea8b09e367a528f Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:25:08 +0000 Subject: [PATCH 17/29] Update Spanish localisation --- MarathonRecomp/locale/locale.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MarathonRecomp/locale/locale.cpp b/MarathonRecomp/locale/locale.cpp index 00fbbdd9..91146586 100644 --- a/MarathonRecomp/locale/locale.cpp +++ b/MarathonRecomp/locale/locale.cpp @@ -311,7 +311,7 @@ std::unordered_map> { ELanguage::Japanese, "DUMMY" }, { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, - { ELanguage::Spanish, "Estadísticas: Muestra los listados de las medallas de oro y los logros" }, + { ELanguage::Spanish, "Estadísticas: Muestra los listados de las medallas de oro y logros" }, { ELanguage::Italian, "Statistiche: mostra gli elenchi delle medaglie d'oro e gli obiettivi" } } }, From e31b7df0bccc0d43aa22f36afb139c4af47417ac Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:31:38 +0000 Subject: [PATCH 18/29] Improve transitions and visual feedback --- MarathonRecomp/ui/achievement_menu.cpp | 60 +++++++++++++++----------- MarathonRecomp/ui/options_menu.cpp | 2 +- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/MarathonRecomp/ui/achievement_menu.cpp b/MarathonRecomp/ui/achievement_menu.cpp index fd575591..1beb14be 100644 --- a/MarathonRecomp/ui/achievement_menu.cpp +++ b/MarathonRecomp/ui/achievement_menu.cpp @@ -18,8 +18,8 @@ static constexpr double CONTAINER_MOVE_OFFSET = 0; static constexpr double CONTAINER_MOVE_DURATION = 5; static constexpr double CONTAINER_FADE_OFFSET = CONTAINER_MOVE_OFFSET; static constexpr double CONTAINER_FADE_DURATION = 10; -static constexpr double ROW_FADE_OFFSET = CONTAINER_FADE_DURATION; -static constexpr double ROW_FADE_TIME = 5; +static constexpr double ROW_FADE_OFFSET = CONTAINER_MOVE_DURATION; +static constexpr double ROW_FADE_TIME = CONTAINER_MOVE_DURATION; static constexpr int MAX_VISIBLE_ROWS = 4; @@ -50,7 +50,7 @@ static void MoveCursor(int& cursorIndex, int min = 0, int max = INT_MAX) if (scrollUp || scrollDown) g_lastTappedTime = time; - static constexpr auto FAST_SCROLL_THRESHOLD = 0.6; + static constexpr auto FAST_SCROLL_THRESHOLD = 0.3; static constexpr auto FAST_SCROLL_SPEED = 1.0 / 6.5; auto fastScroll = (time - g_lastTappedTime) > FAST_SCROLL_THRESHOLD; @@ -94,7 +94,7 @@ static void MoveCursor(int& cursorIndex, int min = 0, int max = INT_MAX) } } -static bool DrawContainer(ImVec2 min, ImVec2 max) +static void DrawContainer(ImVec2 min, ImVec2 max) { auto drawList = ImGui::GetBackgroundDrawList(); @@ -134,8 +134,6 @@ static bool DrawContainer(ImVec2 min, ImVec2 max) ResetGradient(); drawList->PushClipRect(containerTopLeftCornerMin, containerRightMax); - - return containerAlphaMotionTime >= 1.0; } static void DrawAchievement(int rowIndex, Achievement& achievement, bool isUnlocked) @@ -180,13 +178,22 @@ static void DrawAchievement(int rowIndex, Achievement& achievement, bool isUnloc ImVec2 min = { clipRectMin.x + itemMarginX, clipRectMin.y + offsetY }; ImVec2 max = { clipRectMax.x + itemMarginX, min.y + itemHeight }; - auto rowUVs = PIXELS_TO_UV_COORDS(1024, 1024, 605, 450, 10, 30); + auto rowUVs = PIXELS_TO_UV_COORDS(1024, 1024, 605, 449, 10, 30); auto rowMarginX = Scale(2, true); auto rowMarginY = Scale(24, true); + auto rowColourBreatheMotionTime = BREATHE_MOTION(1.0f, 0.0f, g_rowSelectionTime, 0.9f); auto rowColourTransitionMotionTime = ComputeLinearMotion(g_time, ROW_FADE_OFFSET, ROW_FADE_TIME); - auto rowColourAlpha = isCurrent ? Lerp(165, 94, rowColourBreatheMotionTime) * rowColourTransitionMotionTime : 94 * rowColourTransitionMotionTime; + + constexpr auto rowColourAlphaMax = 165; + constexpr auto rowColourAlphaMin = 115; + + auto rowColourAlpha = isCurrent + ? Lerp(rowColourAlphaMax, rowColourAlphaMin, rowColourBreatheMotionTime) * rowColourTransitionMotionTime + : rowColourAlphaMin * rowColourTransitionMotionTime; + auto rowColour = IM_COL32(255, 255, 255, rowColourAlpha); + ImVec2 rowMin = { clipRectMin.x + rowMarginX, min.y + itemHeight - rowMarginY }; ImVec2 rowMax = { clipRectMax.x - rowMarginX, rowMin.y + Scale(30, true) }; @@ -200,6 +207,9 @@ static void DrawAchievement(int rowIndex, Achievement& achievement, bool isUnloc ImVec2 imageMin = { min.x + imageMarginX, min.y + imageMarginY }; ImVec2 imageMax = { min.x + imageMarginX + imageSize, min.y + imageMarginY + imageSize }; + if (isCurrent) + DrawArrowCursor({ rowMin.x + Scale(2, true), imageMin.y + ((imageMax.y - imageMin.y) / 3)}, g_time, false); + if (!isUnlocked) SetShaderModifier(IMGUI_SHADER_MODIFIER_GRAYSCALE); @@ -247,27 +257,22 @@ static void DrawAchievement(int rowIndex, Achievement& achievement, bool isUnloc auto timestampOffsetX = Scale(60, true); - ImVec2 marqueeMin = { textX + Scale(2, true), min.y}; - ImVec2 marqueeMax = { max.x - timestampOffsetX - Scale(1, true), max.y }; - - drawList->PushClipRect(marqueeMin, marqueeMax); + ImVec2 marqueeMin = { textX, min.y }; + ImVec2 marqueeMax = { max.x - timestampOffsetX, max.y }; + constexpr auto marqueeDelay = 0.9; + auto shouldMarquee = isCurrent && marqueeMin.x + descSize.x >= marqueeMax.x; - if (isCurrent && marqueeMin.x + descSize.x >= marqueeMax.x) + // Reduce clip rect size when marquee starts to make the + // scrolling text align more with text above or below it. + if (shouldMarquee && ImGui::GetTime() - (g_rowSelectionTime + marqueeDelay) > 0.0) { - // Draw achievement description with marquee. - SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); - DrawTextWithMarquee(g_pFntRodin, fontSize, descPos, marqueeMin, marqueeMax, textColour, descText, g_rowSelectionTime, 0.9, Scale(200, true)); - SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); - } - else - { - // Draw achievement description. - SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); - drawList->AddText(g_pFntRodin, fontSize, descPos, textColour, descText); - SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); + marqueeMin.x += Scale(2, true); + marqueeMax.x -= Scale(1, true); } - drawList->PopClipRect(); + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); + DrawTextWithMarquee(g_pFntRodin, fontSize, descPos, marqueeMin, marqueeMax, textColour, descText, g_rowSelectionTime, marqueeDelay, shouldMarquee ? Scale(200, true) : 0); + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); if (!isUnlocked) return; @@ -406,7 +411,9 @@ void AchievementMenu::Draw() s_commonMenu.SetTitle(Localise("Achievements_Title_Uppercase")); s_commonMenu.ShowDescription = true; - if (DrawContainer(min, max)) + DrawContainer(min, max); + + if (containerMotionTime >= 1.0) { auto rowIndex = 0; @@ -540,6 +547,7 @@ void AchievementMenu::SetState(AchievementMenuState state) { s_state = state; g_time = ImGui::GetTime(); + g_rowSelectionTime = g_time; } bool AchievementMenu::IsClosing() diff --git a/MarathonRecomp/ui/options_menu.cpp b/MarathonRecomp/ui/options_menu.cpp index 7542636e..ce2bf958 100644 --- a/MarathonRecomp/ui/options_menu.cpp +++ b/MarathonRecomp/ui/options_menu.cpp @@ -125,7 +125,7 @@ static void MoveCursor(int& cursorIndex, double& cursorTime, int min = 0, int ma if (scrollUp || scrollDown) g_lastTappedTime = time; - static constexpr auto FAST_SCROLL_THRESHOLD = 0.6; + static constexpr auto FAST_SCROLL_THRESHOLD = 0.3; static constexpr auto FAST_SCROLL_SPEED = 1.0 / 6.5; auto fastScroll = (time - g_lastTappedTime) > FAST_SCROLL_THRESHOLD; From 4e8ae7263fc6cc4e8017ffc80154c930e66ecec3 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:33:42 +0000 Subject: [PATCH 19/29] Update Spanish localisation Co-authored-by: DaGuAr --- MarathonRecomp/locale/achievement_locale.cpp | 2 +- MarathonRecomp/locale/locale.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MarathonRecomp/locale/achievement_locale.cpp b/MarathonRecomp/locale/achievement_locale.cpp index d16bae5d..ddf73981 100644 --- a/MarathonRecomp/locale/achievement_locale.cpp +++ b/MarathonRecomp/locale/achievement_locale.cpp @@ -593,7 +593,7 @@ std::unordered_map> g_achL ELanguage::Spanish, { "Caballeros de Kronos", - "Supera el último episodio secreto con rango S.", + "Supera todo el último episodio secreto con rango S.", "Has conseguido el rango S en todo el último episodio." } }, diff --git a/MarathonRecomp/locale/locale.cpp b/MarathonRecomp/locale/locale.cpp index 91146586..030a2dc6 100644 --- a/MarathonRecomp/locale/locale.cpp +++ b/MarathonRecomp/locale/locale.cpp @@ -311,7 +311,7 @@ std::unordered_map> { ELanguage::Japanese, "DUMMY" }, { ELanguage::German, "DUMMY" }, { ELanguage::French, "DUMMY" }, - { ELanguage::Spanish, "Estadísticas: Muestra los listados de las medallas de oro y logros" }, + { ELanguage::Spanish, "Estadísticas: Muestra los listados de las medallas y los logros" }, { ELanguage::Italian, "Statistiche: mostra gli elenchi delle medaglie d'oro e gli obiettivi" } } }, From 56da57e59337c1e473ccfdefe259f09c1ac672a0 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Fri, 14 Nov 2025 16:10:19 +0000 Subject: [PATCH 20/29] options_menu: disable description fade animation if current option is Language --- MarathonRecomp/ui/options_menu.cpp | 5 +++-- MarathonRecomp/user/config.cpp | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MarathonRecomp/ui/options_menu.cpp b/MarathonRecomp/ui/options_menu.cpp index ce2bf958..82807573 100644 --- a/MarathonRecomp/ui/options_menu.cpp +++ b/MarathonRecomp/ui/options_menu.cpp @@ -481,14 +481,15 @@ static void DrawOption auto setValueDescription = [=]() { auto valueDescription = config->GetValueDescription(Config::Language); + auto isLanguageOption = (ConfigDef*)config == &Config::Language; if (valueDescription.empty()) { - OptionsMenu::s_commonMenu.SetDescription(config->GetDescription(Config::Language)); + OptionsMenu::s_commonMenu.SetDescription(config->GetDescription(Config::Language), !isLanguageOption); } else { - OptionsMenu::s_commonMenu.SetDescription(valueDescription); + OptionsMenu::s_commonMenu.SetDescription(valueDescription, !isLanguageOption); } }; diff --git a/MarathonRecomp/user/config.cpp b/MarathonRecomp/user/config.cpp index 1c198053..0070de61 100644 --- a/MarathonRecomp/user/config.cpp +++ b/MarathonRecomp/user/config.cpp @@ -782,7 +782,6 @@ void Config::CreateCallbacks() return; OptionsMenu::s_commonMenu.SetTitle(Localise("Options_Header_Name"), false); - OptionsMenu::s_commonMenu.SetDescription(def->GetDescription(def->Value)); }; Config::WindowSize.LockCallback = [](ConfigDef* def) From 0171b3c320f51648bbacdb3a39281ebce71c9459 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Sat, 15 Nov 2025 19:35:44 +0000 Subject: [PATCH 21/29] German localisation by Ray Vassos --- MarathonRecomp/locale/achievement_locale.cpp | 138 +++++++++---------- MarathonRecomp/locale/locale.cpp | 18 +-- MarathonRecomp/ui/achievement_menu.cpp | 3 +- README.md | 6 +- 4 files changed, 83 insertions(+), 82 deletions(-) diff --git a/MarathonRecomp/locale/achievement_locale.cpp b/MarathonRecomp/locale/achievement_locale.cpp index ddf73981..3828abdd 100644 --- a/MarathonRecomp/locale/achievement_locale.cpp +++ b/MarathonRecomp/locale/achievement_locale.cpp @@ -26,9 +26,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Sonic's Episode: Abgeschlossen", + "Schließe Sonic's Episode ab!", + "Sonic's Episode abgeschlossen." } }, { @@ -81,9 +81,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Shadow's Episode: Abgeschlossen", + "Schließe Shadow's Episode ab!", + "Shadow's Episode abgeschlossen." } }, { @@ -136,9 +136,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Silver's Episode: Abgeschlossen", + "Schließe Silver's Episode ab!", + "Silver's Episode abgeschlossen." } }, { @@ -191,9 +191,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Das Ende erreicht", + "Schließe die letzte versteckte Episode ab!", + "Die letzte Episode abgeschlossen." } }, { @@ -246,9 +246,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Sonic's Episode: absolviert", + "Absolviere alle von Sonic's AKT-Missionen auf der schwierigsten Stufe.", + "Alle von Sonic's AKT-Missionen absolviert." } }, { @@ -301,9 +301,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Shadow's Episode: absolviert", + "Absolviere alle von Shadow's AKT-Missionen auf der schwierigsten Stufe.", + "Alle von Shadow's AKT-Missionen absolviert." } }, { @@ -356,9 +356,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Silver's Episode: absolviert", + "Absolviere alle von Silver's AKT-Missionen auf der schwierigsten Stufe.", + "Alle von Silver's AKT-Missionen absolviert." } }, { @@ -411,9 +411,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Shadow's Episode: gemeistert", + "Schließe alle von Shadow's AKT-Missionen mit einen S-Rang ab.", + "Einen S-Rang bei allen AKT-Missionen von Shadow erreicht." } }, { @@ -466,9 +466,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Sonic's Episode: gemeistert", + "Schließe alle von Sonic's AKT-Missionen mit einen S-Rang ab.", + "Einen S-Rang bei allen AKT-Missionen von Sonic erreicht." } }, { @@ -521,9 +521,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Silver's Episode: gemeistert", + "Schließe alle von Silver's AKT-Missionen mit einen S-Rang ab.", + "Einen S-Rang bei allen AKT-Missionen von Silver erreicht." } }, { @@ -576,9 +576,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Ritter von Kronos", + "Schließe alles von der letzten versteckten Episode mit einem S-Rang ab.", + "Einen S-Rang in der letzten Episode erreicht." } }, { @@ -631,9 +631,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Legende von Soleanna", + "Schließe alle AKT-Missionen mit einem S-Rang ab.", + "Einen S-Rang bei allen AKT-Missionen erreicht." } }, { @@ -686,9 +686,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Silbermedaillengewinner", + "Sammel alle Silbermedaillen die um Soleanna verstreut sind.", + "Alle Silbermedaillen in Soleanna gesammelt." } }, { @@ -741,9 +741,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Goldmedaillengewinner", + "Sammel alle legendäre Goldmedaillen in Soleanna.", + "Alle Goldmedaillen in Soleanna gesammelt." } }, { @@ -796,9 +796,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Blaues Phantom", + "Schalte alle von Sonic's Fähigkeiten frei.", + "Alle von Sonic's Fähigkeiten gelernt." } }, { @@ -851,9 +851,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Ultimative Lebensform", + "Schalte alle von Shadow's Fähigkeiten frei.", + "Alle von Shadow's Fähigkeiten gelernt." } }, { @@ -906,9 +906,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Übernatürlicher Soldat", + "Schalte alle von Silver's Fähigkeiten frei.", + "Alle von Silver's Fähigkeiten gelernt." } }, { @@ -961,9 +961,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Soleanna's Held", + "Schließe alle von Sonic's STADT-Missionen ab.", + "Alle von Sonic's STADT-Missionen abgeschlossen." } }, { @@ -1016,9 +1016,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Elite-Agent", + "Schließe alle von Shadow's STADT-Missionen ab.", + "Alle von Shadow's STADT-Missionen abgeschlossen." } }, { @@ -1071,9 +1071,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Silver der Befreier", + "Schließe alle von Silver's STADT-Missionen ab.", + "Alle von Silver's STADT-Missionen abgeschlossen." } }, { @@ -1126,9 +1126,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Soleanna's Blauer Wind", + "Schließe alle von Sonic's STADT-Missionen mit einem S-Rang ab.", + "Einen S-Rang in allen STADT-Missionen mit Sonic erreicht." } }, { @@ -1181,9 +1181,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Dunkler Held", + "Schließe alle von Shadow's STADT-Missionen mit einem S-Rang ab.", + "Einen S-Rang in allen STADT-Missionen mit Shadow erreicht." } }, { @@ -1236,9 +1236,9 @@ std::unordered_map> g_achL { ELanguage::German, { - "DUMMY", - "DUMMY", - "DUMMY" + "Silver der Retter", + "Schließe alle von Silver's STADT-Missionen mit einem S-Rang ab.", + "Einen S-Rang in allen STADT-Missionen mit Silver erreicht." } }, { diff --git a/MarathonRecomp/locale/locale.cpp b/MarathonRecomp/locale/locale.cpp index 030a2dc6..355433a3 100644 --- a/MarathonRecomp/locale/locale.cpp +++ b/MarathonRecomp/locale/locale.cpp @@ -298,7 +298,7 @@ std::unordered_map> { { ELanguage::English, "STATISTICS" }, { ELanguage::Japanese, "DUMMY" }, - { ELanguage::German, "DUMMY" }, + { ELanguage::German, "STATISTIKEN" }, { ELanguage::French, "DUMMY" }, { ELanguage::Spanish, "ESTADÍSTICAS" }, { ELanguage::Italian, "STATISTICHE" } @@ -309,7 +309,7 @@ std::unordered_map> { { ELanguage::English, "Statistics: Displays lists of Gold Medals and Achievements" }, { ELanguage::Japanese, "DUMMY" }, - { ELanguage::German, "DUMMY" }, + { ELanguage::German, "Statistiken: Zeigt eine Liste von Gold Medaillen und Erfolgen an" }, { ELanguage::French, "DUMMY" }, { ELanguage::Spanish, "Estadísticas: Muestra los listados de las medallas y los logros" }, { ELanguage::Italian, "Statistiche: mostra gli elenchi delle medaglie d'oro e gli obiettivi" } @@ -342,7 +342,7 @@ std::unordered_map> { { ELanguage::English, "Gold Medals" }, { ELanguage::Japanese, "DUMMY" }, - { ELanguage::German, "DUMMY" }, + { ELanguage::German, "Gold Medaillen" }, { ELanguage::French, "DUMMY" }, { ELanguage::Spanish, "Medallas de oro" }, { ELanguage::Italian, "Medaglie d'oro" } @@ -353,7 +353,7 @@ std::unordered_map> { { ELanguage::English, "GOLD MEDALS" }, { ELanguage::Japanese, "DUMMY" }, - { ELanguage::German, "DUMMY" }, + { ELanguage::German, "GOLD MEDAILLEN" }, { ELanguage::French, "DUMMY" }, { ELanguage::Spanish, "MEDALLAS DE ORO" }, { ELanguage::Italian, "MEDAGLIE D'ORO" } @@ -694,7 +694,7 @@ std::unordered_map> { { ELanguage::English, "Load failed. Achievement data is corrupted.\nIf you continue your achievement progress will\nbe lost." }, { ELanguage::Japanese, "DUMMY" }, - { ELanguage::German, "DUMMY" }, + { ELanguage::German, "Laden fehlgeschlagen. Erfolgs dateien sind\nkorrupt. Wenn du fortfährst wird dein Erfolgs\nfortschritt gelöscht." }, { ELanguage::French, "DUMMY" }, { ELanguage::Spanish, "Error al cargar. Los datos de los logros están\ndañados. Si continúas, se perderá el progreso\nde tus logros." }, { ELanguage::Italian, "Caricamento fallito. I dati degli obiettivi\nsono danneggiati. Se continui perderai tutti\ni tuoi obiettivi." } @@ -707,7 +707,7 @@ std::unordered_map> { { ELanguage::English, "Load failed. Achievement data cannot be loaded.\nIf you continue you will not be able to save\nyour achievement progress." }, { ELanguage::Japanese, "DUMMY" }, - { ELanguage::German, "DUMMY" }, + { ELanguage::German, "Laden fehlgeschlagen. Erfolgs dateien können nicht\ngeladen werden. Wenn du fortfährst wirst du deinen\nErfolgs fortschritt nicht speichern können." }, { ELanguage::French, "DUMMY" }, { ELanguage::Spanish, "Error al cargar. No se pueden cargar los datos\nde los logros. Si continúas, no podrás guardar\nel progreso de tus logros." }, { ELanguage::Italian, "Caricamento fallito. Impossibile caricare i dati\ndegli obiettivi. Se continui non potrai salvare\ni tuoi obiettivi." } @@ -720,7 +720,7 @@ std::unordered_map> { { ELanguage::English, "Save failed. Achievement data cannot be saved.\nIf you continue you will not be able to save\nyour achievement progress." }, { ELanguage::Japanese, "DUMMY" }, - { ELanguage::German, "DUMMY" }, + { ELanguage::German, "Speichern fehlgeschlagen. Erfolgs dateien können\nnicht gespeichert werden. Wenn du fortfährst wirst\ndu deinen Erfolgs fortschritt nicht speichern können." }, { ELanguage::French, "DUMMY" }, { ELanguage::Spanish, "Error al guardar. No se pueden guardar los datos\nde los logros. Si continúas, no podrás guardar\ntu progreso en los logros." }, { ELanguage::Italian, "Salvataggio fallito. Impossibile salvare i dati\ndegli obiettivi. Se continui non potrai salvare\ni tuoi obiettivi." } @@ -995,7 +995,7 @@ std::unordered_map> "Button_GoldMedalsBack", { { ELanguage::English, "${picture(button_y)}${locale(Achievements_GoldMedals)} ${picture(button_b)}${locale(Common_Back)}" }, - { ELanguage::German, "${picture(button_y)} ${locale(Achievements_GoldMedals)}  ${picture(button_b)} ${locale(Common_Back)}" }, + { ELanguage::German, "${picture(button_y)}${locale(Achievements_GoldMedals)}  ${picture(button_b)}${locale(Common_Back)}" }, { ELanguage::Spanish, "${picture(button_y)} ${locale(Achievements_GoldMedals)} ${picture(button_b)} ${locale(Common_Back)}" } } }, @@ -1003,7 +1003,7 @@ std::unordered_map> "Button_AchievementsBack", { { ELanguage::English, "${picture(button_y)}${locale(Achievements_Title)} ${picture(button_b)}${locale(Common_Back)}" }, - { ELanguage::German, "${picture(button_y)} ${locale(Achievements_Title)}  ${picture(button_b)} ${locale(Common_Back)}" }, + { ELanguage::German, "${picture(button_y)}${locale(Achievements_Title)}  ${picture(button_b)}${locale(Common_Back)}" }, { ELanguage::Spanish, "${picture(button_y)} ${locale(Achievements_Title)} ${picture(button_b)} ${locale(Common_Back)}" } } } diff --git a/MarathonRecomp/ui/achievement_menu.cpp b/MarathonRecomp/ui/achievement_menu.cpp index 1beb14be..8a9e82e0 100644 --- a/MarathonRecomp/ui/achievement_menu.cpp +++ b/MarathonRecomp/ui/achievement_menu.cpp @@ -103,7 +103,8 @@ static void DrawContainer(ImVec2 min, ImVec2 max) auto containerSideUVs = PIXELS_TO_UV_COORDS(1024, 1024, 1, 450, 50, 50); auto containerCentreUVs = PIXELS_TO_UV_COORDS(1024, 1024, 50, 450, 50, 50); auto containerAlphaMotionTime = AchievementMenu::IsClosing() ? 0 : ComputeLinearMotion(g_time, CONTAINER_FADE_OFFSET, CONTAINER_FADE_DURATION); - auto containerColour = IM_COL32(255, 255, 255, AchievementMenu::s_state == AchievementMenuState::GoldMedals ? Lerp(63, 0, containerAlphaMotionTime) : 63); + auto containerAlphaMotion = AchievementMenu::s_state == AchievementMenuState::GoldMedals ? Lerp(63, 0, containerAlphaMotionTime) : 63; + auto containerColour = IM_COL32(255, 255, 255, containerAlphaMotion); auto containerEdgeSize = Scale(50, true); ImVec2 containerTopLeftCornerMin = min; diff --git a/README.md b/README.md index 471047b8..05e65b62 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,9 @@ This project does not plan to support any more platforms other than Windows, Lin - [brianuuuSonic](https://github.com/brianuuu): Provided Japanese localization for the custom menus. -- [Kitzuku](https://github.com/Kitzuku): Provided German localization for the custom menus. +- [Kitzuku](https://github.com/Kitzuku): Provided German localization for the options menu. + +- Ray Vassos: Provided German localization for the achievements menu. - [DaGuAr](https://x.com/TheDaguar): Provided Spanish localization for the custom menus. @@ -188,8 +190,6 @@ This project does not plan to support any more platforms other than Windows, Lin - [Syko](https://x.com/UltraSyko): Aided in identifying fonts used by the original SonicNext logo. -- Ray Vassos: Aided with German localization for the custom menus. - - [ocornut](https://github.com/ocornut): Creator of [Dear ImGui](https://github.com/ocornut/imgui), which is used as the backbone of the custom menus. - Raymond Chen: Useful resources on Windows application development with his blog ["The Old New Thing"](https://devblogs.microsoft.com/oldnewthing/). From 78c63978c94268542a22676d4464be77760117bf Mon Sep 17 00:00:00 2001 From: Isaac Marovitz Date: Fri, 26 Dec 2025 22:16:13 +0000 Subject: [PATCH 22/29] French & Japanese Locale Signed-off-by: Isaac Marovitz --- MarathonRecomp/locale/achievement_locale.cpp | 276 +++++++++---------- 1 file changed, 138 insertions(+), 138 deletions(-) diff --git a/MarathonRecomp/locale/achievement_locale.cpp b/MarathonRecomp/locale/achievement_locale.cpp index 3828abdd..cdd5d3d9 100644 --- a/MarathonRecomp/locale/achievement_locale.cpp +++ b/MarathonRecomp/locale/achievement_locale.cpp @@ -18,9 +18,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "ソニックエピソードクリア", + "ソニックのエピソードをクリアして!", + "ソニックのエピソードをクリアした。" } }, { @@ -34,9 +34,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episode de Sonic : réussi", + "Réussir l'épisode de Sonic!", + "Réussissez l'épisode de Sonic." } }, { @@ -73,9 +73,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "シャドウエピソードクリア", + "シャドウのエピソードをクリアして!", + "シャドウのエピソードをクリアした。" } }, { @@ -89,9 +89,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episode de Shadow : réussi", + "Réussir l'épisode de Shadow!", + "Réussissez l'épisode de Shadow." } }, { @@ -128,9 +128,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "シルバーエピソードクリア", + "シルバーのエピソードをクリアして!", + "シルバーのエピソードをクリアした。" } }, { @@ -144,9 +144,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episode de Silver : réussi", + "Réussir l'épisode de Silver!", + "Réussissez l'épisode de Silver." } }, { @@ -183,9 +183,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "終わりを刻む者", + "ラスト隠れたエピソードをクリアして!。", + "ラストエピソードをクリアした。" } }, { @@ -199,9 +199,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "A atteint la fin", + "Réussir dans l'épisode secret final!", + "Réussissez dans l'épisode final." } }, { @@ -238,9 +238,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "ソニックエピソードコンプリート", + "ソニックのACTミッションを全てクリアして。", + "ソニックのACTミッションを全てクリアした。" } }, { @@ -254,9 +254,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episode de Sonic : terminé", + "Réussir toutes les missions ACTE difficiles de Sonic.", + "Réussissez toutes les missions ACTE de Sonic." } }, { @@ -293,9 +293,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "シャドウエピソードコンプリート", + "シャドウのACTミッションを全てクリアして。", + "シャドウのACTミッションを全てクリアした。" } }, { @@ -309,9 +309,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episode de Shadow : terminé", + "Réussir toutes les missions ACTE difficiles de Shadow.", + "Réussissez toutes les missions ACTE difficiles de Shadow." } }, { @@ -348,9 +348,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "シルバーエピソードコンプリート", + "シルバーのACTミッションを全てクリアして。", + "シルバーのACTミッションを全てクリアした。" } }, { @@ -364,9 +364,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episode de Silver : terminé", + "Réussir toutes les missions ACTE difficiles de Silver.", + "Réussissez toutes les missions ACTE difficiles de Silver." } }, { @@ -403,9 +403,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "シャドウエピソードマスター", + "シャドウのACTミッションを全てランクSでクリアして。", + "シャドウのACTミッションを全てランクSでクリアした。" } }, { @@ -419,9 +419,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episode de Shadow : dominé", + "Terminez toutes les missions ACTE de Shadow avec un rang S.", + "Obtenu un rang S dans toutes les missions ACTE de Shadow." } }, { @@ -458,9 +458,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "ソニックエピソードマスター", + "ソニックのACTミッションを全てランクSでクリアして。", + "ソニックのACTミッションを全てランクSでクリアした。" } }, { @@ -474,9 +474,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episode de Sonic : dominé", + "Terminez toutes les missions ACTE de Sonic avec un rang S.", + "Obtenu un rang S dans toutes les missions ACTE de Sonic." } }, { @@ -513,9 +513,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "シルバーエピソードマスター", + "シルバーのACTミッションを全てランクSでクリアして。", + "シルバーのACTミッションを全てランクSでクリアした。" } }, { @@ -529,9 +529,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Episode de Silver : dominé", + "Terminez toutes les missions ACTE de Silver avec un rang S.", + "Obtenu un rang S dans toutes les missions ACTE de Silver." } }, { @@ -568,9 +568,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "ナイツオブクロノス", + "ラスト隠れたエピソードを全てランクSでクリアして。", + "ラストエピソードを全てランクSでクリアした。" } }, { @@ -584,9 +584,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Chevaliers de Kronos", + "Terminez toutes l'épisode secret final avec un rang S.", + "Obtenu un rang S dans toutes l'épisode secret final." } }, { @@ -623,9 +623,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "ソレアナの伝説", + "全てのACTミッションをランクSでクリアして。", + "全てのACTミッションをランクSでクリアした。" } }, { @@ -639,9 +639,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Légende de Soleanna", + "Terminez toutes les missions ACTE avec un rang S.", + "Obtenu un rang S dans toutes les missions ACTE." } }, { @@ -678,9 +678,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "シルバーメダリスト", + "ソレアナのシルバーメダルを全て取得して。", + "ソレアナのシルバーメダルを全て取得した。" } }, { @@ -694,9 +694,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Médaillé d'argent", + "Collectionnez toutes les médailles d'argent dispersées autour de Soleanna.", + "Collectionné toutes les médailles d'argent à Soleanna." } }, { @@ -733,9 +733,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "ゴールドメダリスト", + "ソレアナのゴールドメダルを全て取得して。", + "ソレアナのゴールドメダルを全て取得した。" } }, { @@ -749,9 +749,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Médaillé d'or", + "Collectionnez toutes les médailles d'or légendaires de Soleanna.", + "Collectionné toutes les médailles d'or légendaires de Soleanna." } }, { @@ -788,9 +788,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "蒼き幻影", + "ソニックのアクションを全て習得して。", + "ソニックのアクションを全て習得した。" } }, { @@ -804,9 +804,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Fantôme bleu", + "Débloquez toutes les capacités de Sonic.", + "Appris toutes les capacités de Sonic." } }, { @@ -843,9 +843,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "究極の生命体", + "シャドウのアクションを全て習得して。", + "シャドウのアクションを全て習得した。" } }, { @@ -859,9 +859,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Forme de vie ultime", + "Débloquez toutes les capacités de Shadow.", + "Appris toutes les capacités de Shadow." } }, { @@ -898,9 +898,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "サイキックソルジャー", + "シルバーのアクションを全て習得して。", + "シルバーのアクションを全て習得した。" } }, { @@ -914,9 +914,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Soldat psychique", + "Débloquez toutes les capacités de Silver.", + "Appris toutes les capacités de Silver." } }, { @@ -953,9 +953,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "ソレアナの英雄", + "ソニックのタウンミッションを全てクリアして。", + "ソニックのタウンミッションを全てクリアした。" } }, { @@ -969,9 +969,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Héros de Soleanna", + "Terminez toutes les missions VILLE de Sonic.", + "Terminé toutes les missions VILLE de Sonic." } }, { @@ -1008,9 +1008,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "エリートエージェント", + "シャドウのタウンミッションを全てクリアして。", + "シャドウのタウンミッションを全てクリアした。" } }, { @@ -1024,9 +1024,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Agent d'élite", + "Terminez toutes les missions VILLE de Shadow.", + "Terminé toutes les missions VILLE de Shadow." } }, { @@ -1063,9 +1063,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "シルバーの解放者", + "シルバーのタウンミッションを全てクリアして。", + "シルバーのタウンミッションを全てクリアした。" } }, { @@ -1079,9 +1079,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Silver le libérateur", + "Terminez toutes les missions VILLE de Silver.", + "Terminé toutes les missions VILLE de Silver." } }, { @@ -1118,9 +1118,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "ソレアナの蒼き風", + "ソニックのタウンミッションを全てランクSでクリアして。", + "ソニックのタウンミッションを全てランクSでクリアした。" } }, { @@ -1134,9 +1134,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Vent bleu de Soleanna", + "Terminez toutes les missions VILLE de Sonic avec un rang S.", + "Obtenu un rang S dans toutes les missions VILLE de Sonic." } }, { @@ -1173,9 +1173,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "ダーク英雄", + "シャドウのタウンミッションを全てランクSでクリアして。", + "シャドウのタウンミッションを全てランクSでクリアした。" } }, { @@ -1189,9 +1189,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Héros des ténèbres", + "Terminez toutes les missions VILLE de Shadow avec un rang S.", + "Obtenu un rang S dans toutes les missions VILLE de Shadow." } }, { @@ -1228,9 +1228,9 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "DUMMY", - "DUMMY", - "DUMMY" + "シルバーの救世主", + "シルバーのタウンミッションを全てランクSでクリアして。", + "シルバーのタウンミッションを全てランクSでクリアした。" } }, { @@ -1244,9 +1244,9 @@ std::unordered_map> g_achL { ELanguage::French, { - "DUMMY", - "DUMMY", - "DUMMY" + "Silver le sauveur", + "Terminez toutes les missions VILLE de Silver avec un rang S.", + "Obtenu un rang S dans toutes les missions VILLE de Silver." } }, { From db1e2b3af5dd93bea1ec27313d996ac9b9248c4f Mon Sep 17 00:00:00 2001 From: Isaac Marovitz Date: Fri, 26 Dec 2025 22:22:59 +0000 Subject: [PATCH 23/29] Fix typo Signed-off-by: Isaac Marovitz --- MarathonRecomp/locale/achievement_locale.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MarathonRecomp/locale/achievement_locale.cpp b/MarathonRecomp/locale/achievement_locale.cpp index cdd5d3d9..6bdc9660 100644 --- a/MarathonRecomp/locale/achievement_locale.cpp +++ b/MarathonRecomp/locale/achievement_locale.cpp @@ -184,7 +184,7 @@ std::unordered_map> g_achL ELanguage::Japanese, { "終わりを刻む者", - "ラスト隠れたエピソードをクリアして!。", + "ラスト隠れたエピソードをクリアして!", "ラストエピソードをクリアした。" } }, From 7ecb3e93d0c3f107306e1953ad4c666155a74ffe Mon Sep 17 00:00:00 2001 From: Isaac Marovitz Date: Sun, 28 Dec 2025 11:50:04 +0000 Subject: [PATCH 24/29] Finish French Signed-off-by: Isaac Marovitz --- MarathonRecomp/locale/locale.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/MarathonRecomp/locale/locale.cpp b/MarathonRecomp/locale/locale.cpp index 355433a3..00a871dc 100644 --- a/MarathonRecomp/locale/locale.cpp +++ b/MarathonRecomp/locale/locale.cpp @@ -297,9 +297,9 @@ std::unordered_map> "MainMenu_GoldMedalResults_Name", { { ELanguage::English, "STATISTICS" }, - { ELanguage::Japanese, "DUMMY" }, + { ELanguage::Japanese, "スタティスティックス" }, { ELanguage::German, "STATISTIKEN" }, - { ELanguage::French, "DUMMY" }, + { ELanguage::French, "STATISTIQUES" }, { ELanguage::Spanish, "ESTADÍSTICAS" }, { ELanguage::Italian, "STATISTICHE" } } @@ -310,7 +310,7 @@ std::unordered_map> { ELanguage::English, "Statistics: Displays lists of Gold Medals and Achievements" }, { ELanguage::Japanese, "DUMMY" }, { ELanguage::German, "Statistiken: Zeigt eine Liste von Gold Medaillen und Erfolgen an" }, - { ELanguage::French, "DUMMY" }, + { ELanguage::French, "Statistiques : Affiche une liste des Médailles d'Or et des Accomplissements" }, { ELanguage::Spanish, "Estadísticas: Muestra los listados de las medallas y los logros" }, { ELanguage::Italian, "Statistiche: mostra gli elenchi delle medaglie d'oro e gli obiettivi" } } @@ -321,7 +321,7 @@ std::unordered_map> { ELanguage::English, "Achievements" }, { ELanguage::Japanese, "実績" }, { ELanguage::German, "Erfolge" }, - { ELanguage::French, "Succès" }, + { ELanguage::French, "Accomplissements" }, { ELanguage::Spanish, "Logros" }, { ELanguage::Italian, "Obiettivi" } } @@ -332,7 +332,7 @@ std::unordered_map> { ELanguage::English, "ACHIEVEMENTS" }, { ELanguage::Japanese, "実績" }, { ELanguage::German, "ERFOLGE" }, - { ELanguage::French, "SUCCÈS" }, + { ELanguage::French, "ACCOMPLISSEMENTS" }, { ELanguage::Spanish, "LOGROS" }, { ELanguage::Italian, "OBIETTIVI" } } @@ -343,7 +343,7 @@ std::unordered_map> { ELanguage::English, "Gold Medals" }, { ELanguage::Japanese, "DUMMY" }, { ELanguage::German, "Gold Medaillen" }, - { ELanguage::French, "DUMMY" }, + { ELanguage::French, "Médailles d'or" }, { ELanguage::Spanish, "Medallas de oro" }, { ELanguage::Italian, "Medaglie d'oro" } } @@ -354,7 +354,7 @@ std::unordered_map> { ELanguage::English, "GOLD MEDALS" }, { ELanguage::Japanese, "DUMMY" }, { ELanguage::German, "GOLD MEDAILLEN" }, - { ELanguage::French, "DUMMY" }, + { ELanguage::French, "MÉDAILLES D'OR" }, { ELanguage::Spanish, "MEDALLAS DE ORO" }, { ELanguage::Italian, "MEDAGLIE D'ORO" } } @@ -695,7 +695,7 @@ std::unordered_map> { ELanguage::English, "Load failed. Achievement data is corrupted.\nIf you continue your achievement progress will\nbe lost." }, { ELanguage::Japanese, "DUMMY" }, { ELanguage::German, "Laden fehlgeschlagen. Erfolgs dateien sind\nkorrupt. Wenn du fortfährst wird dein Erfolgs\nfortschritt gelöscht." }, - { ELanguage::French, "DUMMY" }, + { ELanguage::French, "Chargement échoué. Les données d'Accomplissements\nsont corrompus. Si vous continuez, vos\naccomplissements seront perdus." }, { ELanguage::Spanish, "Error al cargar. Los datos de los logros están\ndañados. Si continúas, se perderá el progreso\nde tus logros." }, { ELanguage::Italian, "Caricamento fallito. I dati degli obiettivi\nsono danneggiati. Se continui perderai tutti\ni tuoi obiettivi." } } @@ -708,7 +708,7 @@ std::unordered_map> { ELanguage::English, "Load failed. Achievement data cannot be loaded.\nIf you continue you will not be able to save\nyour achievement progress." }, { ELanguage::Japanese, "DUMMY" }, { ELanguage::German, "Laden fehlgeschlagen. Erfolgs dateien können nicht\ngeladen werden. Wenn du fortfährst wirst du deinen\nErfolgs fortschritt nicht speichern können." }, - { ELanguage::French, "DUMMY" }, + { ELanguage::French, "Chargement échoué. Les données d'Accomplissements\nn'ont pas pu être chargé. Si vous continuez, vos\naccomplissements seront perdus." }, { ELanguage::Spanish, "Error al cargar. No se pueden cargar los datos\nde los logros. Si continúas, no podrás guardar\nel progreso de tus logros." }, { ELanguage::Italian, "Caricamento fallito. Impossibile caricare i dati\ndegli obiettivi. Se continui non potrai salvare\ni tuoi obiettivi." } } @@ -721,7 +721,7 @@ std::unordered_map> { ELanguage::English, "Save failed. Achievement data cannot be saved.\nIf you continue you will not be able to save\nyour achievement progress." }, { ELanguage::Japanese, "DUMMY" }, { ELanguage::German, "Speichern fehlgeschlagen. Erfolgs dateien können\nnicht gespeichert werden. Wenn du fortfährst wirst\ndu deinen Erfolgs fortschritt nicht speichern können." }, - { ELanguage::French, "DUMMY" }, + { ELanguage::French, "Sauvegarde échoué. Les données d'Accomplissements\nn'ont pas pu être sauvegardé. Si vous continuez, vos\naccomplissements seront perdus." }, { ELanguage::Spanish, "Error al guardar. No se pueden guardar los datos\nde los logros. Si continúas, no podrás guardar\ntu progreso en los logros." }, { ELanguage::Italian, "Salvataggio fallito. Impossibile salvare i dati\ndegli obiettivi. Se continui non potrai salvare\ni tuoi obiettivi." } } From 5f979d86cca7afe5b6916d2cac979fb80a6bfcce Mon Sep 17 00:00:00 2001 From: Isaac Marovitz Date: Sun, 28 Dec 2025 11:52:44 +0000 Subject: [PATCH 25/29] Remove JP full stops Signed-off-by: Isaac Marovitz --- MarathonRecomp/locale/achievement_locale.cpp | 84 ++++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/MarathonRecomp/locale/achievement_locale.cpp b/MarathonRecomp/locale/achievement_locale.cpp index 6bdc9660..bc63bcf7 100644 --- a/MarathonRecomp/locale/achievement_locale.cpp +++ b/MarathonRecomp/locale/achievement_locale.cpp @@ -20,7 +20,7 @@ std::unordered_map> g_achL { "ソニックエピソードクリア", "ソニックのエピソードをクリアして!", - "ソニックのエピソードをクリアした。" + "ソニックのエピソードをクリアした" } }, { @@ -75,7 +75,7 @@ std::unordered_map> g_achL { "シャドウエピソードクリア", "シャドウのエピソードをクリアして!", - "シャドウのエピソードをクリアした。" + "シャドウのエピソードをクリアした" } }, { @@ -130,7 +130,7 @@ std::unordered_map> g_achL { "シルバーエピソードクリア", "シルバーのエピソードをクリアして!", - "シルバーのエピソードをクリアした。" + "シルバーのエピソードをクリアした" } }, { @@ -185,7 +185,7 @@ std::unordered_map> g_achL { "終わりを刻む者", "ラスト隠れたエピソードをクリアして!", - "ラストエピソードをクリアした。" + "ラストエピソードをクリアした" } }, { @@ -239,8 +239,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "ソニックエピソードコンプリート", - "ソニックのACTミッションを全てクリアして。", - "ソニックのACTミッションを全てクリアした。" + "ソニックのACTミッションを全てクリアして", + "ソニックのACTミッションを全てクリアした" } }, { @@ -294,8 +294,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "シャドウエピソードコンプリート", - "シャドウのACTミッションを全てクリアして。", - "シャドウのACTミッションを全てクリアした。" + "シャドウのACTミッションを全てクリアして", + "シャドウのACTミッションを全てクリアした" } }, { @@ -349,8 +349,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "シルバーエピソードコンプリート", - "シルバーのACTミッションを全てクリアして。", - "シルバーのACTミッションを全てクリアした。" + "シルバーのACTミッションを全てクリアして", + "シルバーのACTミッションを全てクリアした" } }, { @@ -404,8 +404,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "シャドウエピソードマスター", - "シャドウのACTミッションを全てランクSでクリアして。", - "シャドウのACTミッションを全てランクSでクリアした。" + "シャドウのACTミッションを全てランクSでクリアして", + "シャドウのACTミッションを全てランクSでクリアした" } }, { @@ -459,8 +459,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "ソニックエピソードマスター", - "ソニックのACTミッションを全てランクSでクリアして。", - "ソニックのACTミッションを全てランクSでクリアした。" + "ソニックのACTミッションを全てランクSでクリアして", + "ソニックのACTミッションを全てランクSでクリアした" } }, { @@ -514,8 +514,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "シルバーエピソードマスター", - "シルバーのACTミッションを全てランクSでクリアして。", - "シルバーのACTミッションを全てランクSでクリアした。" + "シルバーのACTミッションを全てランクSでクリアして", + "シルバーのACTミッションを全てランクSでクリアした" } }, { @@ -569,8 +569,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "ナイツオブクロノス", - "ラスト隠れたエピソードを全てランクSでクリアして。", - "ラストエピソードを全てランクSでクリアした。" + "ラスト隠れたエピソードを全てランクSでクリアして", + "ラストエピソードを全てランクSでクリアした" } }, { @@ -624,8 +624,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "ソレアナの伝説", - "全てのACTミッションをランクSでクリアして。", - "全てのACTミッションをランクSでクリアした。" + "全てのACTミッションをランクSでクリアして", + "全てのACTミッションをランクSでクリアした" } }, { @@ -679,8 +679,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "シルバーメダリスト", - "ソレアナのシルバーメダルを全て取得して。", - "ソレアナのシルバーメダルを全て取得した。" + "ソレアナのシルバーメダルを全て取得して", + "ソレアナのシルバーメダルを全て取得した" } }, { @@ -734,8 +734,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "ゴールドメダリスト", - "ソレアナのゴールドメダルを全て取得して。", - "ソレアナのゴールドメダルを全て取得した。" + "ソレアナのゴールドメダルを全て取得して", + "ソレアナのゴールドメダルを全て取得した" } }, { @@ -789,8 +789,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "蒼き幻影", - "ソニックのアクションを全て習得して。", - "ソニックのアクションを全て習得した。" + "ソニックのアクションを全て習得して", + "ソニックのアクションを全て習得した" } }, { @@ -844,8 +844,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "究極の生命体", - "シャドウのアクションを全て習得して。", - "シャドウのアクションを全て習得した。" + "シャドウのアクションを全て習得して", + "シャドウのアクションを全て習得した" } }, { @@ -899,8 +899,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "サイキックソルジャー", - "シルバーのアクションを全て習得して。", - "シルバーのアクションを全て習得した。" + "シルバーのアクションを全て習得して", + "シルバーのアクションを全て習得した" } }, { @@ -954,8 +954,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "ソレアナの英雄", - "ソニックのタウンミッションを全てクリアして。", - "ソニックのタウンミッションを全てクリアした。" + "ソニックのタウンミッションを全てクリアして", + "ソニックのタウンミッションを全てクリアした" } }, { @@ -1009,8 +1009,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "エリートエージェント", - "シャドウのタウンミッションを全てクリアして。", - "シャドウのタウンミッションを全てクリアした。" + "シャドウのタウンミッションを全てクリアして", + "シャドウのタウンミッションを全てクリアした" } }, { @@ -1064,8 +1064,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "シルバーの解放者", - "シルバーのタウンミッションを全てクリアして。", - "シルバーのタウンミッションを全てクリアした。" + "シルバーのタウンミッションを全てクリアして", + "シルバーのタウンミッションを全てクリアした" } }, { @@ -1119,8 +1119,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "ソレアナの蒼き風", - "ソニックのタウンミッションを全てランクSでクリアして。", - "ソニックのタウンミッションを全てランクSでクリアした。" + "ソニックのタウンミッションを全てランクSでクリアして", + "ソニックのタウンミッションを全てランクSでクリアした" } }, { @@ -1174,8 +1174,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "ダーク英雄", - "シャドウのタウンミッションを全てランクSでクリアして。", - "シャドウのタウンミッションを全てランクSでクリアした。" + "シャドウのタウンミッションを全てランクSでクリアして", + "シャドウのタウンミッションを全てランクSでクリアした" } }, { @@ -1229,8 +1229,8 @@ std::unordered_map> g_achL ELanguage::Japanese, { "シルバーの救世主", - "シルバーのタウンミッションを全てランクSでクリアして。", - "シルバーのタウンミッションを全てランクSでクリアした。" + "シルバーのタウンミッションを全てランクSでクリアして", + "シルバーのタウンミッションを全てランクSでクリアした" } }, { From f2898aaef0bd4090aa9ced89ebeabf61b42d1357 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz Date: Sun, 28 Dec 2025 12:47:24 +0000 Subject: [PATCH 26/29] Japanese Signed-off-by: Isaac Marovitz --- MarathonRecomp/locale/locale.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/MarathonRecomp/locale/locale.cpp b/MarathonRecomp/locale/locale.cpp index 00a871dc..a5f71082 100644 --- a/MarathonRecomp/locale/locale.cpp +++ b/MarathonRecomp/locale/locale.cpp @@ -308,7 +308,7 @@ std::unordered_map> "MainMenu_GoldMedalResults_Description", { { ELanguage::English, "Statistics: Displays lists of Gold Medals and Achievements" }, - { ELanguage::Japanese, "DUMMY" }, + { ELanguage::Japanese, "スタティスティックス: ゴールドメダルと実績のリストを表示する" }, { ELanguage::German, "Statistiken: Zeigt eine Liste von Gold Medaillen und Erfolgen an" }, { ELanguage::French, "Statistiques : Affiche une liste des Médailles d'Or et des Accomplissements" }, { ELanguage::Spanish, "Estadísticas: Muestra los listados de las medallas y los logros" }, @@ -341,7 +341,7 @@ std::unordered_map> "Achievements_GoldMedals", { { ELanguage::English, "Gold Medals" }, - { ELanguage::Japanese, "DUMMY" }, + { ELanguage::Japanese, "ゴールドメダル" }, { ELanguage::German, "Gold Medaillen" }, { ELanguage::French, "Médailles d'or" }, { ELanguage::Spanish, "Medallas de oro" }, @@ -352,7 +352,7 @@ std::unordered_map> "Achievements_GoldMedals_Uppercase", { { ELanguage::English, "GOLD MEDALS" }, - { ELanguage::Japanese, "DUMMY" }, + { ELanguage::Japanese, "ゴールドメダル" }, { ELanguage::German, "GOLD MEDAILLEN" }, { ELanguage::French, "MÉDAILLES D'OR" }, { ELanguage::Spanish, "MEDALLAS DE ORO" }, @@ -693,7 +693,7 @@ std::unordered_map> "Title_Message_LoadAchievementDataCorrupt", { { ELanguage::English, "Load failed. Achievement data is corrupted.\nIf you continue your achievement progress will\nbe lost." }, - { ELanguage::Japanese, "DUMMY" }, + { ELanguage::Japanese, "ロード失敗。業績のデータが破損している。\nセーブしていない進行状況は失われます" }, { ELanguage::German, "Laden fehlgeschlagen. Erfolgs dateien sind\nkorrupt. Wenn du fortfährst wird dein Erfolgs\nfortschritt gelöscht." }, { ELanguage::French, "Chargement échoué. Les données d'Accomplissements\nsont corrompus. Si vous continuez, vos\naccomplissements seront perdus." }, { ELanguage::Spanish, "Error al cargar. Los datos de los logros están\ndañados. Si continúas, se perderá el progreso\nde tus logros." }, @@ -706,9 +706,9 @@ std::unordered_map> "Title_Message_LoadAchievementDataIOError", { { ELanguage::English, "Load failed. Achievement data cannot be loaded.\nIf you continue you will not be able to save\nyour achievement progress." }, - { ELanguage::Japanese, "DUMMY" }, + { ELanguage::Japanese, "ロード失敗。業績のデータがロードできません。\n進行状況は失われますセーブしません" }, { ELanguage::German, "Laden fehlgeschlagen. Erfolgs dateien können nicht\ngeladen werden. Wenn du fortfährst wirst du deinen\nErfolgs fortschritt nicht speichern können." }, - { ELanguage::French, "Chargement échoué. Les données d'Accomplissements\nn'ont pas pu être chargé. Si vous continuez, vos\naccomplissements seront perdus." }, + { ELanguage::French, "Chargement échoué. Les données d'Accomplissements\nn'ont pas pu être chargé. Si vous continuez, vous ne\npourrez pas sauvegarder vos accomplissements." }, { ELanguage::Spanish, "Error al cargar. No se pueden cargar los datos\nde los logros. Si continúas, no podrás guardar\nel progreso de tus logros." }, { ELanguage::Italian, "Caricamento fallito. Impossibile caricare i dati\ndegli obiettivi. Se continui non potrai salvare\ni tuoi obiettivi." } } @@ -719,9 +719,9 @@ std::unordered_map> "Title_Message_SaveAchievementDataIOError", { { ELanguage::English, "Save failed. Achievement data cannot be saved.\nIf you continue you will not be able to save\nyour achievement progress." }, - { ELanguage::Japanese, "DUMMY" }, + { ELanguage::Japanese, "セーブ失敗。業績のデータがセーブできません。\n進行状況は失われますセーブしません" }, { ELanguage::German, "Speichern fehlgeschlagen. Erfolgs dateien können\nnicht gespeichert werden. Wenn du fortfährst wirst\ndu deinen Erfolgs fortschritt nicht speichern können." }, - { ELanguage::French, "Sauvegarde échoué. Les données d'Accomplissements\nn'ont pas pu être sauvegardé. Si vous continuez, vos\naccomplissements seront perdus." }, + { ELanguage::French, "Sauvegarde échoué. Les données d'Accomplissements\nn'ont pas pu être sauvegardé. Si vous continuez, vous ne\npourrez pas sauvegarder vos accomplissements." }, { ELanguage::Spanish, "Error al guardar. No se pueden guardar los datos\nde los logros. Si continúas, no podrás guardar\ntu progreso en los logros." }, { ELanguage::Italian, "Salvataggio fallito. Impossibile salvare i dati\ndegli obiettivi. Se continui non potrai salvare\ni tuoi obiettivi." } } From 1f26bd28b65c7ac38d9437568963ee9ed43b93cb Mon Sep 17 00:00:00 2001 From: Isaac Marovitz Date: Sun, 28 Dec 2025 12:48:25 +0000 Subject: [PATCH 27/29] Font atlas Signed-off-by: Isaac Marovitz --- MarathonRecompResources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MarathonRecompResources b/MarathonRecompResources index d7922605..cd6bbf1e 160000 --- a/MarathonRecompResources +++ b/MarathonRecompResources @@ -1 +1 @@ -Subproject commit d79226053bef9e28068edd30c8db24c5497793c0 +Subproject commit cd6bbf1ec8e4c054f469c320a5551b3bb9e12152 From ce56d3a881d1048ace27fb0b866ac9312387d5f2 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz Date: Sun, 28 Dec 2025 13:00:30 +0000 Subject: [PATCH 28/29] Minor adjustments Signed-off-by: Isaac Marovitz --- MarathonRecomp/locale/achievement_locale.cpp | 4 ++-- MarathonRecomp/locale/locale.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MarathonRecomp/locale/achievement_locale.cpp b/MarathonRecomp/locale/achievement_locale.cpp index bc63bcf7..203c3bfb 100644 --- a/MarathonRecomp/locale/achievement_locale.cpp +++ b/MarathonRecomp/locale/achievement_locale.cpp @@ -1063,7 +1063,7 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "シルバーの解放者", + "解放者シルバー", "シルバーのタウンミッションを全てクリアして", "シルバーのタウンミッションを全てクリアした" } @@ -1228,7 +1228,7 @@ std::unordered_map> g_achL { ELanguage::Japanese, { - "シルバーの救世主", + "救世主シルバー", "シルバーのタウンミッションを全てランクSでクリアして", "シルバーのタウンミッションを全てランクSでクリアした" } diff --git a/MarathonRecomp/locale/locale.cpp b/MarathonRecomp/locale/locale.cpp index a5f71082..5b7dbb8c 100644 --- a/MarathonRecomp/locale/locale.cpp +++ b/MarathonRecomp/locale/locale.cpp @@ -69,7 +69,7 @@ std::unordered_map> "Options_Desc_Category_System", { { ELanguage::English, "Adjust system settings." }, - { ELanguage::Japanese, "システムの設定変更します" }, + { ELanguage::Japanese, "システムの設定を変更します" }, { ELanguage::German, "Verändere System-Einstellungen." }, { ELanguage::French, "Modifier les paramètres système." }, { ELanguage::Spanish, "Ajustar la configuración del sistema." }, @@ -91,7 +91,7 @@ std::unordered_map> "Options_Desc_Category_Input", { { ELanguage::English, "Adjust input settings." }, - { ELanguage::Japanese, "入力の設定変更します" }, + { ELanguage::Japanese, "入力の設定を変更します" }, { ELanguage::German, "Verändere Eingabe-Einstellungen." }, { ELanguage::French, "Modifier les paramètres d'entrée." }, { ELanguage::Spanish, "Ajustar la configuración de entrada." }, From 5d42ac0a0d769a5d72fab6b208683e151b24da97 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz Date: Sun, 28 Dec 2025 13:08:19 +0000 Subject: [PATCH 29/29] =?UTF-8?q?Fix=20missing=20=E2=80=9Clegendary?= =?UTF-8?q?=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Isaac Marovitz --- MarathonRecomp/locale/achievement_locale.cpp | 2 +- MarathonRecompResources | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MarathonRecomp/locale/achievement_locale.cpp b/MarathonRecomp/locale/achievement_locale.cpp index 203c3bfb..e388f6a9 100644 --- a/MarathonRecomp/locale/achievement_locale.cpp +++ b/MarathonRecomp/locale/achievement_locale.cpp @@ -734,7 +734,7 @@ std::unordered_map> g_achL ELanguage::Japanese, { "ゴールドメダリスト", - "ソレアナのゴールドメダルを全て取得して", + "ソレアナの伝説的なゴールドメダルを全て取得して", "ソレアナのゴールドメダルを全て取得した" } }, diff --git a/MarathonRecompResources b/MarathonRecompResources index cd6bbf1e..ba675633 160000 --- a/MarathonRecompResources +++ b/MarathonRecompResources @@ -1 +1 @@ -Subproject commit cd6bbf1ec8e4c054f469c320a5551b3bb9e12152 +Subproject commit ba675633caaa69bebf402ff83b108f887b0139a3