Implement RetroAchivements

This commit is contained in:
Connor McLaughlin
2021-02-27 00:44:53 +10:00
parent ef524d7dea
commit 0ec2c87a0e
34 changed files with 2419 additions and 65 deletions

View File

@ -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;