diff --git a/UnleashedRecomp/framework.h b/UnleashedRecomp/framework.h index e870fec3..fb8a53ff 100644 --- a/UnleashedRecomp/framework.h +++ b/UnleashedRecomp/framework.h @@ -74,3 +74,23 @@ constexpr size_t FirstBitLow(TValue value) return 0; } + +static std::unique_ptr ReadAllBytes(const char* filePath, size_t& fileSize) +{ + FILE* file = fopen(filePath, "rb"); + + if (!file) + return std::make_unique(0); + + fseek(file, 0, SEEK_END); + + fileSize = ftell(file); + fseek(file, 0, SEEK_SET); + + auto data = std::make_unique(fileSize); + fread(data.get(), 1, fileSize, file); + + fclose(file); + + return data; +} diff --git a/UnleashedRecomp/ui/achievement_menu.cpp b/UnleashedRecomp/ui/achievement_menu.cpp index 1e10a1ae..23325211 100644 --- a/UnleashedRecomp/ui/achievement_menu.cpp +++ b/UnleashedRecomp/ui/achievement_menu.cpp @@ -21,6 +21,11 @@ constexpr double HEADER_CONTAINER_OUTRO_FADE_END = 7; constexpr double CONTENT_CONTAINER_COMMON_MOTION_START = 11; constexpr double CONTENT_CONTAINER_COMMON_MOTION_END = 12; +constexpr double COUNTER_INTRO_FADE_START = 15; +constexpr double COUNTER_INTRO_FADE_END = 16; +constexpr double COUNTER_SPRITE_FRAME_START = 0; +constexpr double COUNTER_SPRITE_FRAME_END = 30; + constexpr double SELECTION_CONTAINER_BREATHE = 30; static bool g_isClosing = false; @@ -33,6 +38,8 @@ static ImFont* g_fntSeurat; static ImFont* g_fntNewRodinDB; static ImFont* g_fntNewRodinUB; +static std::unique_ptr g_upTrophyIcon; + static int g_firstVisibleRowIndex; static int g_selectedRowIndex; static double g_rowSelectionTime; @@ -350,6 +357,43 @@ static void DrawAchievement(int rowIndex, float yOffset, Achievement& achievemen drawList->PopClipRect(); } +static void DrawAchievementTotal(ImVec2 min, ImVec2 max) +{ + auto drawList = ImGui::GetForegroundDrawList(); + + // Transparency fade animation. + auto alpha = Cubic(0, 1, ComputeMotion(g_appearTime, COUNTER_INTRO_FADE_START, COUNTER_INTRO_FADE_END)); + + auto imageMarginX = Scale(5); + auto imageMarginY = Scale(5); + auto imageSize = Scale(45); + + ImVec2 imageMin = { max.x - imageSize - imageMarginX, min.y - imageSize - imageMarginY }; + ImVec2 imageMax = { imageMin.x + imageSize, imageMin.y + imageSize }; + + auto frm = int32_t(floor(ImGui::GetTime() * 30.0f)) % 30; + auto w = 256.0f / 7680.0f; + auto uv0 = ImVec2(frm * w, 0); + auto uv1 = ImVec2((frm + 1) * w, 1); + + drawList->AddImage(g_upTrophyIcon.get(), imageMin, imageMax, uv0, uv1, IM_COL32(255, 255, 255, 255 * alpha)); + + auto str = std::format("{} / {}", AchievementData::GetTotalRecords(), ACH_RECORDS); + auto fontSize = Scale(20); + auto textSize = g_fntNewRodinDB->CalcTextSizeA(fontSize, FLT_MAX, 0, str.c_str()); + + DrawTextWithOutline + ( + g_fntNewRodinDB, + fontSize, + { /* X */ imageMin.x - textSize.x - Scale(6), /* Y */ imageMin.y + ((imageMax.y - imageMin.y) - textSize.y) / 2 }, + IM_COL32(255, 255, 255, 255 * alpha), + str.c_str(), + 2, + IM_COL32(0, 0, 0, 255 * alpha) + ); +} + static void DrawContentContainer() { auto drawList = ImGui::GetForegroundDrawList(); @@ -519,6 +563,8 @@ static void DrawContentContainer() // Pop clip rect from DrawContentContainer drawList->PopClipRect(); + DrawAchievementTotal(min, max); + // Draw scroll bar if (rowCount > visibleRowCount) { @@ -582,6 +628,14 @@ void AchievementMenu::Init() g_fntSeurat = io.Fonts->AddFontFromFileTTF("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE); 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); + + size_t bufferSize = 0; + auto buffer = ReadAllBytes("achievements_trophy.png", bufferSize); + + if (!bufferSize) + return; + + g_upTrophyIcon = LoadTexture(buffer.get(), bufferSize); } void AchievementMenu::Draw() diff --git a/UnleashedRecomp/user/achievement_data.cpp b/UnleashedRecomp/user/achievement_data.cpp index f08d5164..be0dba44 100644 --- a/UnleashedRecomp/user/achievement_data.cpp +++ b/UnleashedRecomp/user/achievement_data.cpp @@ -1,5 +1,4 @@ #include "achievement_data.h" -#include #include #include @@ -19,6 +18,21 @@ time_t AchievementData::GetTimestamp(uint16_t id) return 0; } +int AchievementData::GetTotalRecords() +{ + auto result = 0; + + for (int i = 0; i < NUM_RECORDS; i++) + { + if (!Data.Records[i].ID) + break; + + result++; + } + + return result; +} + bool AchievementData::IsUnlocked(uint16_t id) { for (int i = 0; i < NUM_RECORDS; i++) diff --git a/UnleashedRecomp/user/achievement_data.h b/UnleashedRecomp/user/achievement_data.h index 942c9841..6c6edb36 100644 --- a/UnleashedRecomp/user/achievement_data.h +++ b/UnleashedRecomp/user/achievement_data.h @@ -4,6 +4,7 @@ #define ACH_SIGNATURE { 'A', 'C', 'H', ' ' } #define ACH_VERSION { 1, 0, 0 } +#define ACH_RECORDS 50 class AchievementData { @@ -39,7 +40,7 @@ public: Version Version{}; uint32_t Checksum; uint32_t Reserved; - Record Records[50]; + Record Records[ACH_RECORDS]; }; inline static Data Data{ ACH_SIGNATURE, ACH_VERSION }; @@ -50,6 +51,7 @@ public: } static time_t GetTimestamp(uint16_t id); + static int GetTotalRecords(); static bool IsUnlocked(uint16_t id); static void Unlock(uint16_t id); static uint32_t CalculateChecksum();