Implement RetroAchivements
This commit is contained in:
@ -2,6 +2,7 @@
|
||||
|
||||
#include "fullscreen_ui.h"
|
||||
#include "IconsFontAwesome5.h"
|
||||
#include "cheevos.h"
|
||||
#include "common/byte_stream.h"
|
||||
#include "common/file_system.h"
|
||||
#include "common/log.h"
|
||||
@ -81,6 +82,7 @@ static void ClearImGuiFocus();
|
||||
static void ReturnToMainWindow();
|
||||
static void DrawLandingWindow();
|
||||
static void DrawQuickMenu(MainWindowType type);
|
||||
static void DrawAchievementWindow();
|
||||
static void DrawDebugMenu();
|
||||
static void DrawStatsOverlay();
|
||||
static void DrawOSDMessages();
|
||||
@ -304,9 +306,11 @@ void Render()
|
||||
DrawSettingsWindow();
|
||||
break;
|
||||
case MainWindowType::QuickMenu:
|
||||
case MainWindowType::MoreQuickMenu:
|
||||
DrawQuickMenu(s_current_main_window);
|
||||
break;
|
||||
case MainWindowType::Achievements:
|
||||
DrawAchievementWindow();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -1073,7 +1077,7 @@ void DrawSettingsWindow()
|
||||
ICON_FA_GAMEPAD " Controller Settings", ICON_FA_KEYBOARD " Hotkey Settings",
|
||||
ICON_FA_SD_CARD " Memory Card Settings", ICON_FA_TV " Display Settings",
|
||||
ICON_FA_MAGIC " Enhancement Settings", ICON_FA_HEADPHONES " Audio Settings",
|
||||
ICON_FA_EXCLAMATION_TRIANGLE " Advanced Settings"}};
|
||||
ICON_FA_TROPHY " Achievements Settings", ICON_FA_EXCLAMATION_TRIANGLE " Advanced Settings"}};
|
||||
|
||||
BeginMenuButtons();
|
||||
for (u32 i = 0; i < static_cast<u32>(titles.size()); i++)
|
||||
@ -1932,6 +1936,108 @@ void DrawSettingsWindow()
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingsPage::AchievementsSetings:
|
||||
{
|
||||
#ifdef WITH_CHEEVOS
|
||||
BeginMenuButtons();
|
||||
|
||||
MenuHeading("Settings");
|
||||
settings_changed |= ToggleButtonForNonSetting(
|
||||
"Enable RetroAchievements", "When enabled and logged in, DuckStation will scan for achievements on startup.",
|
||||
"Cheevos", "Enabled", false);
|
||||
settings_changed |= ToggleButtonForNonSetting(
|
||||
"Rich Presence",
|
||||
"When enabled, rich presence information will be collected and sent to the server where supported.",
|
||||
"Cheevos", "RichPresence", true);
|
||||
settings_changed |=
|
||||
ToggleButtonForNonSetting("Test Mode",
|
||||
"When enabled, DuckStation will assume all achievements are locked and not "
|
||||
"send any unlock notifications to the server.",
|
||||
"Cheevos", "TestMode", false);
|
||||
settings_changed |= ToggleButtonForNonSetting("Use First Disc From Playlist",
|
||||
"When enabled, the first disc in a playlist will be used for "
|
||||
"achievements, regardless of which disc is active.",
|
||||
"Cheevos", "UseFirstDiscFromPlaylist", true);
|
||||
|
||||
MenuHeading("Account");
|
||||
if (Cheevos::IsLoggedIn())
|
||||
{
|
||||
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImGui::GetStyle().Colors[ImGuiCol_Text]);
|
||||
ActiveButton(SmallString::FromFormat(ICON_FA_USER " Username: %s", Cheevos::GetUsername().c_str()), false,
|
||||
false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
|
||||
Timestamp ts;
|
||||
TinyString ts_string;
|
||||
ts.SetUnixTimestamp(StringUtil::FromChars<u64>(s_host_interface->GetSettingsInterface()->GetStringValue(
|
||||
"Cheevos", "LoginTimestamp", "0"))
|
||||
.value_or(0));
|
||||
ts.ToString(ts_string, "%Y-%m-%d %H:%M:%S");
|
||||
ActiveButton(SmallString::FromFormat(ICON_FA_CLOCK " Login token generated on %s", ts_string.GetCharArray()),
|
||||
false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
if (MenuButton(ICON_FA_KEY " Logout", "Logs out of RetroAchievements."))
|
||||
Cheevos::Logout();
|
||||
}
|
||||
else
|
||||
{
|
||||
ActiveButton(SmallString::FromFormat(ICON_FA_USER " Not Logged In", Cheevos::GetUsername().c_str()), false,
|
||||
false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
|
||||
if (MenuButton(ICON_FA_KEY " Login", "Logs in to RetroAchievements."))
|
||||
Cheevos::LoginAsync("", "");
|
||||
}
|
||||
|
||||
MenuHeading("Current Game");
|
||||
if (Cheevos::HasActiveGame())
|
||||
{
|
||||
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImGui::GetStyle().Colors[ImGuiCol_Text]);
|
||||
ActiveButton(TinyString::FromFormat(ICON_FA_BOOKMARK " Game ID: %u", Cheevos::GetGameID()), false, false,
|
||||
ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
ActiveButton(TinyString::FromFormat(ICON_FA_BOOK " Game Title: %s", Cheevos::GetGameTitle().c_str()), false,
|
||||
false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
ActiveButton(
|
||||
TinyString::FromFormat(ICON_FA_DESKTOP " Game Developer: %s", Cheevos::GetGameDeveloper().c_str()), false,
|
||||
false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
ActiveButton(
|
||||
TinyString::FromFormat(ICON_FA_DESKTOP " Game Publisher: %s", Cheevos::GetGamePublisher().c_str()), false,
|
||||
false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
ActiveButton(TinyString::FromFormat(ICON_FA_TROPHY " Achievements: %u (%u points)",
|
||||
Cheevos::GetAchievementCount(), Cheevos::GetMaximumPointsForGame()),
|
||||
false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
|
||||
const std::string& rich_presence_string = Cheevos::GetRichPresenceString();
|
||||
if (!rich_presence_string.empty())
|
||||
{
|
||||
ActiveButton(SmallString::FromFormat(ICON_FA_MAP " %s", rich_presence_string.c_str()), false, false,
|
||||
ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
}
|
||||
else
|
||||
{
|
||||
ActiveButton(ICON_FA_MAP " Rich presence inactive or unsupported.", false, false,
|
||||
ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
else
|
||||
{
|
||||
ActiveButton(ICON_FA_BAN " Game not loaded or no RetroAchievements available.", false, false,
|
||||
ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
}
|
||||
|
||||
EndMenuButtons();
|
||||
#else
|
||||
BeginMenuButtons();
|
||||
ActiveButton(ICON_FA_BAN " This build was not compiled with RetroAchivements support.", false, false,
|
||||
ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
EndMenuButtons();
|
||||
#endif
|
||||
// ImGuiFullscreen::moda
|
||||
// if (ImGui::BeginPopup("))
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingsPage::AdvancedSettings:
|
||||
{
|
||||
BeginMenuButtons();
|
||||
@ -2073,7 +2179,7 @@ void DrawQuickMenu(MainWindowType type)
|
||||
if (BeginFullscreenWindow(window_pos, window_size, "pause_menu", ImVec4(0.0f, 0.0f, 0.0f, 0.0f), 0.0f, 10.0f,
|
||||
ImGuiWindowFlags_NoBackground))
|
||||
{
|
||||
BeginMenuButtons(11, 1.0f, ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING,
|
||||
BeginMenuButtons(12, 1.0f, ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING,
|
||||
ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING,
|
||||
ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
|
||||
@ -2087,6 +2193,17 @@ void DrawQuickMenu(MainWindowType type)
|
||||
CloseQuickMenu();
|
||||
}
|
||||
|
||||
#ifdef WITH_CHEEVOS
|
||||
const bool achievements_enabled = Cheevos::HasActiveGame() && (Cheevos::GetAchievementCount() > 0);
|
||||
if (ActiveButton(ICON_FA_TROPHY " Achievements", false, achievements_enabled))
|
||||
{
|
||||
CloseQuickMenu();
|
||||
s_current_main_window = MainWindowType::Achievements;
|
||||
}
|
||||
#else
|
||||
ActiveButton(ICON_FA_TROPHY " Achievements", false, false);
|
||||
#endif
|
||||
|
||||
if (ActiveButton(ICON_FA_CAMERA " Save Screenshot", false))
|
||||
{
|
||||
CloseQuickMenu();
|
||||
@ -3578,6 +3695,242 @@ void DrawDebugDebugMenu()
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_CHEEVOS
|
||||
|
||||
static void DrawAchievement(const Cheevos::Achievement& cheevo)
|
||||
{
|
||||
static constexpr float alpha = 0.8f;
|
||||
|
||||
TinyString id_str;
|
||||
id_str.Format("%u", cheevo.id);
|
||||
|
||||
ImRect bb;
|
||||
bool visible, hovered;
|
||||
bool pressed =
|
||||
MenuButtonFrame(id_str, true, LAYOUT_MENU_BUTTON_HEIGHT, &visible, &hovered, &bb.Min, &bb.Max, 0, alpha);
|
||||
if (!visible)
|
||||
return;
|
||||
|
||||
const ImVec2 image_size(LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT, LAYOUT_MENU_BUTTON_HEIGHT));
|
||||
const std::string& badge_path = cheevo.locked ? cheevo.locked_badge_path : cheevo.unlocked_badge_path;
|
||||
if (!badge_path.empty())
|
||||
{
|
||||
HostDisplayTexture* badge = GetCachedTexture(badge_path);
|
||||
if (badge)
|
||||
{
|
||||
ImGui::GetWindowDrawList()->AddImage(badge->GetHandle(), bb.Min, bb.Min + image_size, ImVec2(0.0f, 0.0f),
|
||||
ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255));
|
||||
}
|
||||
}
|
||||
|
||||
const float midpoint = bb.Min.y + g_large_font->FontSize + LayoutScale(4.0f);
|
||||
const float text_start_x = bb.Min.x + image_size.x + LayoutScale(15.0f);
|
||||
const ImRect title_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
|
||||
const ImRect summary_bb(ImVec2(text_start_x, midpoint), bb.Max);
|
||||
SmallString text;
|
||||
|
||||
ImGui::PushFont(g_large_font);
|
||||
ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, cheevo.title.c_str(), cheevo.title.c_str() + cheevo.title.size(),
|
||||
nullptr, ImVec2(0.0f, 0.0f), &title_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
if (!cheevo.description.empty())
|
||||
{
|
||||
ImGui::PushFont(g_medium_font);
|
||||
ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, cheevo.description.c_str(),
|
||||
cheevo.description.c_str() + cheevo.description.size(), nullptr, ImVec2(0.0f, 0.0f),
|
||||
&summary_bb);
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
#if 0
|
||||
// The API doesn't seem to send us this :(
|
||||
if (!cheevo.locked)
|
||||
{
|
||||
ImGui::PushFont(g_medium_font);
|
||||
|
||||
const ImRect time_bb(ImVec2(text_start_x, bb.Min.y),
|
||||
ImVec2(bb.Max.x, bb.Min.y + g_medium_font->FontSize + LayoutScale(4.0f)));
|
||||
text.Format("Unlocked 21 Feb, 2019 @ 3:14am");
|
||||
ImGui::RenderTextClipped(time_bb.Min, time_bb.Max, text.GetCharArray(), text.GetCharArray() + text.GetLength(),
|
||||
nullptr, ImVec2(1.0f, 0.0f), &time_bb);
|
||||
ImGui::PopFont();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (pressed)
|
||||
{
|
||||
// TODO: What should we do here?
|
||||
// Display information or something..
|
||||
}
|
||||
}
|
||||
|
||||
void DrawAchievementWindow()
|
||||
{
|
||||
static constexpr float alpha = 0.8f;
|
||||
static constexpr float heading_height_unscaled = 110.0f;
|
||||
|
||||
ImGui::SetNextWindowBgAlpha(alpha);
|
||||
|
||||
const ImVec4 background(0.13f, 0.13f, 0.13f, alpha);
|
||||
const ImVec2 display_size(ImGui::GetIO().DisplaySize);
|
||||
const float window_width = LayoutScale(LAYOUT_SCREEN_WIDTH);
|
||||
const float heading_height = LayoutScale(heading_height_unscaled);
|
||||
|
||||
if (BeginFullscreenWindow(
|
||||
ImVec2(0.0f, 0.0f), ImVec2(display_size.x, heading_height), "achievements_heading", background, 0.0f, 0.0f,
|
||||
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoScrollWithMouse))
|
||||
{
|
||||
ImRect bb;
|
||||
bool visible, hovered;
|
||||
bool pressed = MenuButtonFrame("achievements_heading", false, heading_height_unscaled, &visible, &hovered, &bb.Min,
|
||||
&bb.Max, 0, alpha);
|
||||
if (visible)
|
||||
{
|
||||
const float padding = LayoutScale(10.0f);
|
||||
const float spacing = LayoutScale(10.0f);
|
||||
const float image_height = LayoutScale(85.0f);
|
||||
|
||||
const ImVec2 icon_min(bb.Min + ImVec2(padding, padding));
|
||||
const ImVec2 icon_max(icon_min + ImVec2(image_height, image_height));
|
||||
|
||||
const std::string& icon_path = Cheevos::GetGameIcon();
|
||||
if (!icon_path.empty())
|
||||
{
|
||||
HostDisplayTexture* badge = GetCachedTexture(icon_path);
|
||||
if (badge)
|
||||
{
|
||||
ImGui::GetWindowDrawList()->AddImage(badge->GetHandle(), icon_min, icon_max, ImVec2(0.0f, 0.0f),
|
||||
ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255));
|
||||
}
|
||||
}
|
||||
|
||||
float left = bb.Min.x + padding + image_height + spacing;
|
||||
float right = bb.Max.x - padding;
|
||||
float top = bb.Min.y + padding;
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
SmallString text;
|
||||
ImVec2 text_size;
|
||||
|
||||
const u32 unlocked_count = Cheevos::GetUnlockedAchiementCount();
|
||||
const u32 achievement_count = Cheevos::GetAchievementCount();
|
||||
const u32 current_points = Cheevos::GetCurrentPointsForGame();
|
||||
const u32 total_points = Cheevos::GetMaximumPointsForGame();
|
||||
|
||||
text.Format(ICON_FA_TIMES);
|
||||
text_size = g_large_font->CalcTextSizeA(g_large_font->FontSize, right, -1.0f, text.GetCharArray(),
|
||||
text.GetCharArray() + text.GetLength());
|
||||
const ImRect close_button_bb(ImVec2(right - padding - text_size.x, top), ImVec2(right, top + text_size.y));
|
||||
|
||||
bool close_held, close_hovered;
|
||||
bool close_clicked = ImGui::ButtonBehavior(close_button_bb, ImGui::GetCurrentWindow()->GetID("close_button"),
|
||||
&close_hovered, &close_held);
|
||||
if (close_clicked)
|
||||
{
|
||||
ReturnToMainWindow();
|
||||
}
|
||||
else if (close_hovered || close_held)
|
||||
{
|
||||
const ImU32 col = ImGui::GetColorU32(close_held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered, alpha);
|
||||
ImGui::RenderFrame(close_button_bb.Min, close_button_bb.Max, col, true, 0.0f);
|
||||
}
|
||||
|
||||
ImGui::PushFont(g_large_font);
|
||||
ImGui::RenderTextClipped(close_button_bb.Min, close_button_bb.Max, text.GetCharArray(),
|
||||
text.GetCharArray() + text.GetLength(), nullptr, ImVec2(0.0f, 0.0f), &close_button_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
const ImRect title_bb(ImVec2(left, top), ImVec2(right, top + g_large_font->FontSize));
|
||||
text.Assign(Cheevos::GetGameTitle());
|
||||
|
||||
const std::string& developer = Cheevos::GetGameDeveloper();
|
||||
if (!developer.empty())
|
||||
text.AppendFormattedString(" (%s)", developer.c_str());
|
||||
|
||||
top += g_large_font->FontSize + spacing;
|
||||
|
||||
ImGui::PushFont(g_large_font);
|
||||
ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, text.GetCharArray(), text.GetCharArray() + text.GetLength(),
|
||||
nullptr, ImVec2(0.0f, 0.0f), &title_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
const ImRect summary_bb(ImVec2(left, top), ImVec2(right, top + g_medium_font->FontSize));
|
||||
if (unlocked_count == achievement_count)
|
||||
{
|
||||
text.Format("You have unlocked all achievements and earned %u points!", total_points);
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Format("You have unlocked %u of %u achievements, earning %u of %u possible points.", unlocked_count,
|
||||
achievement_count, current_points, total_points);
|
||||
}
|
||||
|
||||
top += g_medium_font->FontSize + spacing;
|
||||
|
||||
ImGui::PushFont(g_medium_font);
|
||||
ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, text.GetCharArray(),
|
||||
text.GetCharArray() + text.GetLength(), nullptr, ImVec2(0.0f, 0.0f), &summary_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
const float progress_height = LayoutScale(20.0f);
|
||||
const ImRect progress_bb(ImVec2(left, top), ImVec2(right, top + progress_height));
|
||||
const float fraction = static_cast<float>(unlocked_count) / static_cast<float>(achievement_count);
|
||||
dl->AddRectFilled(progress_bb.Min, progress_bb.Max, ImGui::GetColorU32(ImGuiFullscreen::UIPrimaryDarkColor()));
|
||||
dl->AddRectFilled(progress_bb.Min,
|
||||
ImVec2(progress_bb.Min.x + fraction * progress_bb.GetWidth(), progress_bb.Max.y),
|
||||
ImGui::GetColorU32(ImGuiFullscreen::UISecondaryColor()));
|
||||
|
||||
text.Format("%d%%", static_cast<int>(std::round(fraction * 100.0f)));
|
||||
text_size = ImGui::CalcTextSize(text);
|
||||
const ImVec2 text_pos(progress_bb.Min.x + ((progress_bb.Max.x - progress_bb.Min.x) / 2.0f) - (text_size.x / 2.0f),
|
||||
progress_bb.Min.y + ((progress_bb.Max.y - progress_bb.Min.y) / 2.0f) -
|
||||
(text_size.y / 2.0f));
|
||||
dl->AddText(g_medium_font, g_medium_font->FontSize, text_pos,
|
||||
ImGui::GetColorU32(ImGuiFullscreen::UIPrimaryTextColor()), text.GetCharArray(),
|
||||
text.GetCharArray() + text.GetLength());
|
||||
top += progress_height + spacing;
|
||||
}
|
||||
}
|
||||
EndFullscreenWindow();
|
||||
|
||||
ImGui::SetNextWindowBgAlpha(alpha);
|
||||
|
||||
if (BeginFullscreenWindow(ImVec2(0.0f, heading_height), ImVec2(display_size.x, display_size.x - heading_height),
|
||||
"achievements", background, 0.0f, 0.0f, 0))
|
||||
{
|
||||
|
||||
BeginMenuButtons();
|
||||
|
||||
MenuHeading("Unlocked Achievements");
|
||||
Cheevos::EnumerateAchievements([](const Cheevos::Achievement& cheevo) -> bool {
|
||||
if (!cheevo.locked)
|
||||
DrawAchievement(cheevo);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (Cheevos::GetUnlockedAchiementCount() != Cheevos::GetAchievementCount())
|
||||
{
|
||||
MenuHeading("Locked Achievements");
|
||||
Cheevos::EnumerateAchievements([](const Cheevos::Achievement& cheevo) -> bool {
|
||||
if (cheevo.locked)
|
||||
DrawAchievement(cheevo);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
EndMenuButtons();
|
||||
}
|
||||
EndFullscreenWindow();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void DrawAchievementWindow() {}
|
||||
|
||||
#endif
|
||||
|
||||
bool SetControllerNavInput(FrontendCommon::ControllerNavigationButton button, bool value)
|
||||
{
|
||||
s_nav_input_values[static_cast<u32>(button)] = value;
|
||||
|
||||
Reference in New Issue
Block a user