mirror of
https://github.com/hedge-dev/UnleashedRecomp.git
synced 2026-01-01 09:12:37 -06:00
244 lines
9.2 KiB
C++
244 lines
9.2 KiB
C++
#include "achievement_overlay.h"
|
|
#include <gpu/imgui/imgui_snapshot.h>
|
|
#include <gpu/video.h>
|
|
#include <kernel/memory.h>
|
|
#include <kernel/xdbf.h>
|
|
#include <locale/locale.h>
|
|
#include <ui/imgui_utils.h>
|
|
#include <user/achievement_data.h>
|
|
#include <user/config.h>
|
|
#include <app.h>
|
|
#include <exports.h>
|
|
#include <decompressor.h>
|
|
#include <patches/inspire_patches.h>
|
|
|
|
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 = 3;
|
|
|
|
static bool g_isClosing = false;
|
|
|
|
static double g_appearTime = 0;
|
|
|
|
static Achievement g_achievement;
|
|
|
|
static ImFont* g_fntSeurat;
|
|
|
|
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_fntSeurat = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.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);
|
|
}
|
|
|
|
// 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()
|
|
{
|
|
if (g_soundAdministratorUpdated && std::this_thread::get_id() == g_mainThreadId && !AchievementOverlay::s_queue.empty())
|
|
{
|
|
// Check if we can actually play any audio right now. If not, we'll wait until we can.
|
|
uint32_t audioCenter = *reinterpret_cast<be<uint32_t>*>(g_memory.Translate(0x83362FFC));
|
|
if (audioCenter != NULL)
|
|
{
|
|
uint32_t member = *reinterpret_cast<be<uint32_t>*>(g_memory.Translate(audioCenter + 0x4));
|
|
uint32_t category = !InspirePatches::s_sceneName.empty() ? 10 : 7; // EVENT category is used during Inspire cutscenes.
|
|
|
|
// Check if the volume is non zero.
|
|
return *reinterpret_cast<uint32_t*>(g_memory.Translate(member + 0x7C + category * 0x10 + 0x8)) != 0;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void AchievementOverlay::Draw()
|
|
{
|
|
if (!AchievementOverlay::s_isVisible && CanDequeueAchievement())
|
|
{
|
|
s_isVisible = true;
|
|
g_isClosing = false;
|
|
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");
|
|
}
|
|
|
|
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;
|
|
|
|
auto strAchievementUnlocked = Localise("Achievements_Unlock").c_str();
|
|
auto strAchievementName = g_achievement.Name.c_str();
|
|
|
|
// Calculate text sizes.
|
|
auto fontSize = Scale(24);
|
|
auto headerSize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementUnlocked);
|
|
auto bodySize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementName);
|
|
auto maxSize = std::max(headerSize.x, bodySize.x) + Scale(5);
|
|
|
|
// Calculate image margins.
|
|
auto imageMarginX = Scale(25);
|
|
auto imageMarginY = Scale(22.5f);
|
|
auto imageSize = Scale(60);
|
|
|
|
// Calculate text margins.
|
|
auto textMarginX = imageMarginX * 2 + imageSize - Scale(5);
|
|
auto textMarginY = imageMarginY + Scale(2);
|
|
|
|
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))
|
|
{
|
|
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_fntSeurat, // 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_fntSeurat, // 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();
|
|
}
|
|
}
|
|
|
|
void AchievementOverlay::Open(int id)
|
|
{
|
|
s_queue.push(id);
|
|
}
|
|
|
|
void AchievementOverlay::Close()
|
|
{
|
|
if (!g_isClosing)
|
|
{
|
|
g_appearTime = ImGui::GetTime();
|
|
g_isClosing = true;
|
|
}
|
|
|
|
if (CanDequeueAchievement())
|
|
s_isVisible = false;
|
|
}
|