From 6151be142e7de7b182bc0041529e5685704a4a67 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Sun, 1 Dec 2024 01:31:24 +0000 Subject: [PATCH] achievements_menu: added timestamps --- UnleashedRecomp/ui/achievement_menu.cpp | 125 ++++++++++++++++++++-- UnleashedRecomp/user/achievement_data.cpp | 12 +++ UnleashedRecomp/user/achievement_data.h | 4 +- 3 files changed, 130 insertions(+), 11 deletions(-) diff --git a/UnleashedRecomp/ui/achievement_menu.cpp b/UnleashedRecomp/ui/achievement_menu.cpp index b54ecc13..c9c601c2 100644 --- a/UnleashedRecomp/ui/achievement_menu.cpp +++ b/UnleashedRecomp/ui/achievement_menu.cpp @@ -27,10 +27,11 @@ static bool g_isClosing = false; static double g_appearTime = 0; -static std::vector g_achievements; +static std::vector> g_achievements; static ImFont* g_fntSeurat; -static ImFont* g_fntNewRodin; +static ImFont* g_fntNewRodinDB; +static ImFont* g_fntNewRodinUB; static int g_firstVisibleRowIndex; static int g_selectedRowIndex; @@ -112,7 +113,7 @@ static void DrawHeaderContainer(const char* text) { auto drawList = ImGui::GetForegroundDrawList(); auto fontSize = Scale(26); - auto textSize = g_fntNewRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, text); + auto textSize = g_fntNewRodinUB->CalcTextSizeA(fontSize, FLT_MAX, 0, text); auto cornerRadius = 23; auto textMarginX = Scale(16) + (Scale(cornerRadius) / 2); @@ -143,9 +144,9 @@ static void DrawHeaderContainer(const char* text) // TODO: skew this text and apply bevel. DrawTextWithOutline ( - g_fntNewRodin, + g_fntNewRodinUB, fontSize, - { /* X */ min.x + textMarginX, /* Y */ min.y + textSize.y / Scale(2) + Scale(2.5f) /* 2.5 = container outline thickness */ }, + { /* X */ min.x + textMarginX, /* Y */ min.y + ((max.y - min.y) - textSize.y) / 2 }, IM_COL32(255, 255, 255, 255 * alpha), text, 3, @@ -256,6 +257,97 @@ static void DrawAchievement(int rowIndex, float yOffset, Achievement& achievemen } drawList->PopClipRect(); + + if (!isUnlocked) + return; + + auto timestamp = AchievementData::GetTimestamp(achievement.ID); + + if (!timestamp) + return; + + char buffer[32]; + struct tm time; + localtime_s(&time, ×tamp); + strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M", &time); + + fontSize = Scale(14); + textSize = g_fntNewRodinDB->CalcTextSizeA(fontSize, FLT_MAX, 0, buffer); + + auto containerMarginX = Scale(10); + auto textMarginX = Scale(3); + + 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) + ); + + DrawTextWithOutline + ( + g_fntNewRodinDB, + fontSize, + { /* X */ timestampMin.x + textMarginX, /* Y */ timestampMin.y + ((timestampMax.y - timestampMin.y) - textSize.y) / 2 }, + IM_COL32(255, 255, 255, 255), + buffer, + 2, + IM_COL32(8, 8, 8, 255) + ); + + drawList->PopClipRect(); } static void DrawContentContainer() @@ -322,14 +414,18 @@ static void DrawContentContainer() drawList->AddLine({ lineMin.x, lineMin.y + Scale(1) }, { lineMax.x, lineMax.y + Scale(1) }, IM_COL32(143, 148, 143, 255)); } - for (auto achievement : g_achievements) + for (auto& tpl : g_achievements) { + auto achievement = std::get<0>(tpl); + if (AchievementData::IsUnlocked(achievement.ID)) DrawAchievement(rowCount++, yOffset, achievement, true); } - for (auto achievement : g_achievements) + for (auto& tpl : g_achievements) { + auto achievement = std::get<0>(tpl); + if (!AchievementData::IsUnlocked(achievement.ID)) DrawAchievement(rowCount++, yOffset, achievement, false); } @@ -482,9 +578,8 @@ void AchievementMenu::Init() constexpr float FONT_SCALE = 2.0f; g_fntSeurat = io.Fonts->AddFontFromFileTTF("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE); - g_fntNewRodin = io.Fonts->AddFontFromFileTTF("FOT-NewRodinPro-UB.otf", 20.0f * FONT_SCALE); - - g_achievements = g_xdbfWrapper.GetAchievements((EXDBFLanguage)Config::Language.Value); + g_fntNewRodinDB = io.Fonts->AddFontFromFileTTF("FOT-NewRodinPro-DB.otf", 20.0f * FONT_SCALE); + g_fntNewRodinUB = io.Fonts->AddFontFromFileTTF("FOT-NewRodinPro-UB.otf", 20.0f * FONT_SCALE); } void AchievementMenu::Draw() @@ -502,6 +597,16 @@ void AchievementMenu::Open() g_isClosing = false; g_appearTime = ImGui::GetTime(); + g_achievements.clear(); + + for (auto& achievement : g_xdbfWrapper.GetAchievements((EXDBFLanguage)Config::Language.Value)) + g_achievements.push_back(std::make_tuple(achievement, AchievementData::GetTimestamp(achievement.ID))); + + std::sort(g_achievements.begin(), g_achievements.end(), [](const auto& a, const auto& b) + { + return std::get<1>(a) > std::get<1>(b); + }); + ResetSelection(); Game_PlaySound("sys_actstg_pausewinopen"); } diff --git a/UnleashedRecomp/user/achievement_data.cpp b/UnleashedRecomp/user/achievement_data.cpp index 640dcade..0325ab27 100644 --- a/UnleashedRecomp/user/achievement_data.cpp +++ b/UnleashedRecomp/user/achievement_data.cpp @@ -24,6 +24,7 @@ void AchievementData::Unlock(uint16_t id) if (Data.Records[i].ID == 0) { Data.Records[i].ID = id; + Data.Records[i].Timestamp = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); break; } } @@ -32,6 +33,17 @@ void AchievementData::Unlock(uint16_t id) AchievementOverlay::Open(id); } +time_t AchievementData::GetTimestamp(uint16_t id) +{ + for (int i = 0; i < sizeof(Data.Records) / sizeof(Record); i++) + { + if (Data.Records[i].ID == id) + return Data.Records[i].Timestamp; + } + + return 0; +} + void AchievementData::Load() { auto dataPath = GetDataPath(); diff --git a/UnleashedRecomp/user/achievement_data.h b/UnleashedRecomp/user/achievement_data.h index cfb8e0d1..df06e7bd 100644 --- a/UnleashedRecomp/user/achievement_data.h +++ b/UnleashedRecomp/user/achievement_data.h @@ -13,7 +13,8 @@ public: struct Record { uint16_t ID; - uint16_t Reserved[7]; + time_t Timestamp; + uint16_t Reserved[3]; }; #pragma pack(pop) @@ -35,6 +36,7 @@ public: static bool IsUnlocked(uint16_t id); static void Unlock(uint16_t id); + static time_t GetTimestamp(uint16_t id); static void Load(); static void Save(); };