Compare commits

...
Sign in to create a new pull request.

19 commits

Author SHA1 Message Date
Henrik Rydgård
21b474ec30 Fix achievement icon cache key issue. Bump rcheevos 2023-07-04 23:52:33 +02:00
Henrik Rydgård
8966998868 Simple display of measured progress on achievements. 2023-07-04 23:32:40 +02:00
Henrik Rydgård
8b8f8c7a3c More rendering style changes 2023-07-04 23:32:40 +02:00
Henrik Rydgård
24844a6fb6 Bump rcheevos submodule 2023-07-04 23:32:39 +02:00
Henrik Rydgård
40d0448ef1 Minor design tweak 2023-07-04 23:32:39 +02:00
Henrik Rydgård
f947e64f26 Show unlocked achievement icons in progress/challenge indicators, bump rcheevos to fix bugs 2023-07-04 23:32:39 +02:00
Henrik Rydgård
a81a6d4b21 Implement RetroAchievements challenge mode restrictions, and fix related bugs 2023-07-04 23:32:39 +02:00
Henrik Rydgård
a902189c56 Minor cleanup 2023-07-04 23:32:39 +02:00
Henrik Rydgård
1abe1f703e Implement more OSD types (challenge, progress indicators). Not really tested. 2023-07-04 23:32:39 +02:00
Henrik Rydgård
041d0ee717 Add setting to enable unofficial achievements 2023-07-04 23:32:39 +02:00
Henrik Rydgård
4d11665646 rc_client: Hook up UMD change (untested) 2023-07-04 23:32:39 +02:00
Henrik Rydgård
f7faa08705 Safety: Make sure we're not executing game code while waiting for RetroAchievements identification. 2023-07-04 23:32:39 +02:00
Henrik Rydgård
afdaf5a12e Remove the HTTP wrapper (that was previously needed for the DuckStation-derived code) 2023-07-04 23:32:39 +02:00
Henrik Rydgård
abbd24581e Implement Encore Mode. Clean some stuff up. 2023-07-04 23:32:39 +02:00
Henrik Rydgård
31a4ffff0e Add game achievement summary view directly on pause screen, code cleanup 2023-07-04 23:32:39 +02:00
Henrik Rydgård
290631a28e Proper rendering including icons of leaderboard view 2023-07-04 23:32:39 +02:00
Henrik Rydgård
4b4cddd769 Achievement savestate fixes 2023-07-04 23:32:39 +02:00
Henrik Rydgård
634286945b Switch over to rc-client 2023-07-04 23:32:39 +02:00
Henrik Rydgård
70ac433c23 UIContext: Add an easy way to draw drop shadows under rectangular things 2023-07-04 23:32:39 +02:00
40 changed files with 1457 additions and 2286 deletions

2
.gitmodules vendored
View file

@ -46,4 +46,4 @@
url = https://github.com/google/cpu_features.git
[submodule "ext/rcheevos"]
path = ext/rcheevos
url = https://github.com/RetroAchievements/rcheevos.git
url = https://github.com/Jamiras/rcheevos.git

View file

@ -5,6 +5,7 @@
#include <functional>
#include <vector>
#include "Common/Data/Collections/FastVec.h"
#include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/GPU/GPUBackendCommon.h"
@ -205,4 +206,3 @@ private:
uint32_t usage_ = 0;
bool grow_;
};

View file

@ -69,6 +69,9 @@ struct Bounds {
Bounds Expand(float xAmount, float yAmount) const {
return Bounds(x - xAmount, y - yAmount, w + xAmount * 2, h + yAmount * 2);
}
Bounds Expand(float left, float top, float right, float bottom) const {
return Bounds(x - left, y - top, w + left + right, h + top + bottom);
}
Bounds Offset(float xAmount, float yAmount) const {
return Bounds(x + xAmount, y + yAmount, w, h);
}

View file

@ -147,6 +147,11 @@ public:
void DoMarker(const char *prevName, u32 arbitraryNumber = 0x42);
void SkipBytes(size_t bytes) {
// Should work in all modes.
*ptr += bytes;
}
size_t Offset() const { return *ptr - ptrStart_; }
private:

View file

@ -2,6 +2,7 @@
#include "Common/System/OSD.h"
#include "Common/TimeUtil.h"
#include "Common/Log.h"
OnScreenDisplay g_OSD;
@ -17,6 +18,14 @@ void OnScreenDisplay::Update() {
}
}
for (auto iter = sideEntries_.begin(); iter != sideEntries_.end(); ) {
if (now >= iter->endTime) {
iter = sideEntries_.erase(iter);
} else {
iter++;
}
}
for (auto iter = bars_.begin(); iter != bars_.end(); ) {
if (now >= iter->endTime) {
iter = bars_.erase(iter);
@ -31,6 +40,11 @@ std::vector<OnScreenDisplay::Entry> OnScreenDisplay::Entries() {
return entries_; // makes a copy.
}
std::vector<OnScreenDisplay::Entry> OnScreenDisplay::SideEntries() {
std::lock_guard<std::mutex> guard(mutex_);
return sideEntries_; // makes a copy.
}
std::vector<OnScreenDisplay::ProgressBar> OnScreenDisplay::ProgressBars() {
std::lock_guard<std::mutex> guard(mutex_);
return bars_; // makes a copy.
@ -99,6 +113,88 @@ void OnScreenDisplay::ShowAchievementUnlocked(int achievementID) {
entries_.insert(entries_.begin(), msg);
}
void OnScreenDisplay::ShowAchievementProgress(int achievementID, float duration_s) {
double now = time_now_d();
for (auto &entry : sideEntries_) {
if (entry.numericID == achievementID && entry.type == OSDType::ACHIEVEMENT_PROGRESS) {
// Duplicate, let's just bump the timer.
entry.startTime = now;
entry.endTime = now + (double)duration_s;
// We're done.
return;
}
}
// OK, let's make a new side-entry.
Entry entry;
entry.numericID = achievementID;
entry.type = OSDType::ACHIEVEMENT_PROGRESS;
entry.startTime = now;
entry.endTime = now + (double)duration_s;
sideEntries_.insert(sideEntries_.begin(), entry);
}
void OnScreenDisplay::ShowChallengeIndicator(int achievementID, bool show) {
double now = time_now_d();
for (auto &entry : sideEntries_) {
if (entry.numericID == achievementID && entry.type == OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR && !show) {
// Hide and eventually delete it.
entry.endTime = now + (double)FadeoutTime();
// Found it, we're done.
return;
}
}
if (!show) {
// Sanity check
return;
}
// OK, let's make a new side-entry.
Entry entry;
entry.numericID = achievementID;
entry.type = OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR;
entry.startTime = now;
entry.endTime = now + 10000000.0; // Don't auto-fadeout.
sideEntries_.insert(sideEntries_.begin(), entry);
}
void OnScreenDisplay::ShowLeaderboardTracker(int leaderboardTrackerID, const char *trackerText, bool show) { // show=true is used both for create and update.
double now = time_now_d();
for (auto &entry : sideEntries_) {
if (entry.numericID == leaderboardTrackerID && entry.type == OSDType::LEADERBOARD_TRACKER) {
if (show) {
// Just an update.
entry.text = trackerText;
} else {
// Keep the current text, hide and eventually delete it.
entry.endTime = now + (double)FadeoutTime();
}
// Found it, we're done.
return;
}
}
if (!show) {
// Sanity check
return;
}
// OK, let's make a new side-entry.
Entry entry;
entry.numericID = leaderboardTrackerID;
entry.type = OSDType::LEADERBOARD_TRACKER;
entry.startTime = now;
entry.endTime = now + 10000000.0; // Don't auto-fadeout
if (trackerText) {
entry.text = trackerText;
}
sideEntries_.insert(sideEntries_.begin(), entry);
}
void OnScreenDisplay::ShowOnOff(const std::string &message, bool on, float duration_s) {
// TODO: translate "on" and "off"? Or just get rid of this whole thing?
Show(OSDType::MESSAGE_INFO, message + ": " + (on ? "on" : "off"), duration_s);
@ -129,12 +225,12 @@ void OnScreenDisplay::SetProgressBar(std::string id, std::string &&message, int
bars_.push_back(bar);
}
void OnScreenDisplay::RemoveProgressBar(std::string id, float fadeout_s) {
void OnScreenDisplay::RemoveProgressBar(std::string id) {
std::lock_guard<std::mutex> guard(mutex_);
for (auto iter = bars_.begin(); iter != bars_.end(); iter++) {
if (iter->id == id) {
iter->progress = iter->maxValue;
iter->endTime = time_now_d() + (double)fadeout_s;
iter->endTime = time_now_d() + FadeoutTime();
break;
}
}

View file

@ -16,8 +16,10 @@ enum class OSDType {
ACHIEVEMENT_UNLOCKED,
// PROGRESS_BAR,
// PROGRESS_INDETERMINATE,
// Side entries
ACHIEVEMENT_PROGRESS, // Achievement icon + "measured_progress" text, auto-hide after 2s
ACHIEVEMENT_CHALLENGE_INDICATOR, // Achievement icon ONLY, no auto-hide
LEADERBOARD_TRACKER,
};
// Data holder for on-screen messages.
@ -31,7 +33,6 @@ public:
Show(type, text, text2, "", duration_s, id);
}
void Show(OSDType type, const std::string &text, const std::string &text2, const std::string &icon, float duration_s = 0.0f, const char *id = nullptr);
void ShowAchievementUnlocked(int achievementID);
void ShowOnOff(const std::string &message, bool on, float duration_s = 0.0f);
@ -40,10 +41,17 @@ public:
// Call this every frame, cleans up old entries.
void Update();
// Specialized achievement-related types. These go to the side notifications, not the top-middle.
void ShowAchievementUnlocked(int achievementID);
void ShowAchievementProgress(int achievementID, float duration_s);
void ShowChallengeIndicator(int achievementID, bool show); // call with show=false to hide.
void ShowLeaderboardTracker(int leaderboardTrackerID, const char *trackerText, bool show); // show=true is used both for create and update.
// Progress bar controls
// Set is both create and update.
// Set is both create and update. If you set maxValue <= minValue, you'll create an "indeterminate" progress
// bar that doesn't show a specific amount of progress.
void SetProgressBar(std::string id, std::string &&message, int minValue, int maxValue, int progress);
void RemoveProgressBar(std::string id, float fadeout_s);
void RemoveProgressBar(std::string id);
struct Entry {
OSDType type;
@ -66,10 +74,14 @@ public:
};
std::vector<Entry> Entries();
std::vector<Entry> SideEntries();
std::vector<ProgressBar> ProgressBars();
static float FadeoutTime() { return 0.25f; }
private:
std::vector<Entry> entries_;
std::vector<Entry> sideEntries_;
std::vector<ProgressBar> bars_;
std::mutex mutex_;
};

View file

@ -313,6 +313,17 @@ void UIContext::FillRect(const UI::Drawable &drawable, const Bounds &bounds) {
}
}
void UIContext::DrawRectDropShadow(const Bounds &bounds, float radius, float alpha, uint32_t color) {
if (alpha <= 0.0f)
return;
color = colorAlpha(color, alpha);
// Bias the shadow downwards a bit.
Bounds shadowBounds = bounds.Expand(radius, 0.5 * radius, radius, 1.5 * radius);
Draw()->DrawImage4Grid(theme->dropShadow4Grid, shadowBounds.x, shadowBounds.y, shadowBounds.x2(), shadowBounds.y2(), color, radius * (1.0f / 24.0f) * 2.0f);
}
void UIContext::DrawImageVGradient(ImageID image, uint32_t color1, uint32_t color2, const Bounds &bounds) {
uidrawbuffer_->DrawImageStretchVGradient(image, bounds.x, bounds.y, bounds.x2(), bounds.y2(), color1, color2);
}

View file

@ -77,6 +77,8 @@ public:
void SetTintSaturation(float tint, float sat);
// High level drawing functions. They generally assume the default texture to be bounds.
void SetFontStyle(const UI::FontStyle &style);
const UI::FontStyle &GetFontStyle() { return *fontStyle_; }
void SetFontScale(float scaleX, float scaleY);
@ -88,6 +90,7 @@ public:
void DrawTextRect(const char *str, const Bounds &bounds, uint32_t color, int align = 0);
void DrawTextShadowRect(const char *str, const Bounds &bounds, uint32_t color, int align = 0);
void FillRect(const UI::Drawable &drawable, const Bounds &bounds);
void DrawRectDropShadow(const Bounds &bounds, float radius, float alpha, uint32_t color = 0);
void DrawImageVGradient(ImageID image, uint32_t color1, uint32_t color2, const Bounds &bounds);
// in dps, like dp_xres and dp_yres

View file

@ -266,20 +266,17 @@ static bool DefaultSasThread() {
static const ConfigSetting achievementSettings[] = {
ConfigSetting("AchievementsEnable", &g_Config.bAchievementsEnable, true, CfgFlag::DEFAULT),
ConfigSetting("AchievementsLeaderboards", &g_Config.bAchievementsLeaderboards, false, CfgFlag::DEFAULT),
ConfigSetting("AchievementsTestMode", &g_Config.bAchievementsTestMode, false, CfgFlag::DEFAULT),
ConfigSetting("AchievementsUnofficialTestMode", &g_Config.bAchievementsUnofficialTestMode, false, CfgFlag::DEFAULT),
ConfigSetting("AchievementsRichPresence", &g_Config.bAchievementsRichPresence, true, CfgFlag::DEFAULT),
ConfigSetting("AchievementsChallengeMode", &g_Config.bAchievementsChallengeMode, false, CfgFlag::DEFAULT),
ConfigSetting("AchievementsEncoreMode", &g_Config.bAchievementsEncoreMode, false, CfgFlag::DEFAULT),
ConfigSetting("AchievementsUnofficial", &g_Config.bAchievementsUnofficial, false, CfgFlag::DEFAULT),
ConfigSetting("AchievementsSoundEffects", &g_Config.bAchievementsSoundEffects, true, CfgFlag::DEFAULT),
ConfigSetting("AchievementsNotifications", &g_Config.bAchievementsNotifications, true, CfgFlag::DEFAULT),
ConfigSetting("AchievementsLogBadMemReads", &g_Config.bAchievementsLogBadMemReads, false, CfgFlag::DEFAULT),
// Achievements login info. Note that password is NOT stored, only a login token.
// And that login token is stored separately from the ini, see NativeSaveSecret.
ConfigSetting("AchievementsUserName", &g_Config.sAchievementsUserName, "", CfgFlag::DEFAULT),
// And that login token is stored separately from the ini, see NativeSaveSecret, but it can also be loaded
// from the ini if manually entered (useful when testing various builds on Android).
ConfigSetting("AchievementsToken", &g_Config.sAchievementsToken, "", CfgFlag::DONT_SAVE),
ConfigSetting("AchievementsLoginTimestamp", &g_Config.sAchievementsLoginTimestamp, "", CfgFlag::DEFAULT),
ConfigSetting("AchievementsUserName", &g_Config.sAchievementsUserName, "", CfgFlag::DEFAULT),
};
static const ConfigSetting cpuSettings[] = {

View file

@ -485,20 +485,16 @@ public:
// Retro Achievement settings
// Copied from Duckstation, we might want to remove some.
bool bAchievementsEnable;
bool bAchievementsLeaderboards;
bool bAchievementsTestMode;
bool bAchievementsUnofficialTestMode;
bool bAchievementsRichPresence;
bool bAchievementsChallengeMode;
bool bAchievementsEncoreMode;
bool bAchievementsUnofficial;
bool bAchievementsSoundEffects;
bool bAchievementsNotifications;
bool bAchievementsLogBadMemReads;
// Achivements login info. Note that password is NOT stored, only a login token.
// Still, we may wanna store it more securely than in PPSSPP.ini, especially on Android.
std::string sAchievementsUserName;
std::string sAchievementsToken; // Not saved, to be used if you want to manually make your RA login persistent. See Native_SaveSecret for the normal case.
std::string sAchievementsLoginTimestamp;
// Various directories. Autoconfigured, not read from ini.
Path currentDirectory; // The directory selected in the game browsing window.

View file

@ -49,6 +49,7 @@
#include "Core/HLE/sceKernelInterrupt.h"
#include "Core/HW/Display.h"
#include "Core/Util/PPGeDraw.h"
#include "Core/RetroAchievements.h"
#include "GPU/GPU.h"
#include "GPU/GPUState.h"
@ -348,12 +349,15 @@ void __DisplaySetWasPaused() {
}
static int FrameTimingLimit() {
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1)
return g_Config.iFpsLimit1;
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2)
return g_Config.iFpsLimit2;
if (PSP_CoreParameter().fpsLimit == FPSLimit::ANALOG)
return PSP_CoreParameter().analogFpsLimit;
if (!Achievements::ChallengeModeActive()) {
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1)
return g_Config.iFpsLimit1;
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2)
return g_Config.iFpsLimit2;
if (PSP_CoreParameter().fpsLimit == FPSLimit::ANALOG)
return PSP_CoreParameter().analogFpsLimit;
}
// Note: Fast-forward is OK in challenge mode.
if (PSP_CoreParameter().fastForward)
return 0;
return framerate;

View file

@ -85,8 +85,7 @@
#include "sceDmac.h"
#include "sceMp4.h"
#include "sceOpenPSID.h"
#include "../Util/PPGeDraw.h"
#include "Core/Util/PPGeDraw.h"
/*
17: [MIPS32 R4K 00000000 ]: Loader: Type: 1 Vaddr: 00000000 Filesz: 2856816 Memsz: 2856816
@ -286,8 +285,6 @@ void __KernelDoState(PointerWrap &p)
__AACDoState(p);
__UsbGpsDoState(p);
__UsbMicDoState(p);
// IMPORTANT! Add new sections last!
}
{

View file

@ -34,6 +34,7 @@
#include "Core/HLE/sceKernelInterrupt.h"
#include "Core/HLE/sceKernelMemory.h"
#include "Core/HLE/KernelWaitHelpers.h"
#include "Core/RetroAchievements.h"
#include "Core/FileSystems/BlockDevices.h"
#include "Core/FileSystems/MetaFileSystem.h"
@ -483,13 +484,15 @@ static u32 sceUmdGetErrorStat()
return umdErrorStat;
}
void __UmdReplace(Path filepath) {
void __UmdReplace(const Path &filepath) {
std::string error = "";
if (!UmdReplace(filepath, error)) {
ERROR_LOG(SCEIO, "UMD Replace failed: %s", error.c_str());
return;
}
Achievements::ChangeUMD(filepath);
UMDInserted = false;
// Wake any threads waiting for the disc to be removed.
UmdWakeThreads();

View file

@ -43,7 +43,7 @@ enum pspUmdType {
void __UmdInit();
void __UmdDoState(PointerWrap &p);
void __UmdReplace(Path filepath);
void __UmdReplace(const Path &filepath);
bool getUMDReplacePermit();
void Register_sceUmdUser();

File diff suppressed because it is too large Load diff

View file

@ -9,9 +9,6 @@
#pragma once
#include "Common/StringUtils.h"
#include "Common/CommonTypes.h"
#include <functional>
#include <optional>
#include <string>
@ -19,54 +16,17 @@
#include <vector>
#include <mutex>
#include "Common/StringUtils.h"
#include "Common/CommonTypes.h"
#include "ext/rcheevos/include/rc_client.h"
class Path;
class PointerWrap;
namespace Achievements {
enum class AchievementCategory : u8
{
Local = 0,
Core = 3,
Unofficial = 5
};
struct Achievement
{
u32 id;
std::string title;
std::string description;
std::string memaddr;
std::string badge_name;
u32 points;
AchievementCategory category;
bool locked;
bool active;
bool primed;
bool disabled; // due to bad memory access, presumably
};
struct Leaderboard
{
u32 id;
std::string title;
std::string description;
int format;
bool hidden;
};
struct LeaderboardEntry
{
std::string user;
std::string formatted_score;
time_t submitted;
u32 rank;
bool is_self;
};
struct Statistics
{
struct Statistics {
// Debug stats
int badMemoryAccessCount;
};
@ -85,101 +45,64 @@ static inline bool IsUsingRAIntegration()
#endif
bool IsActive();
// Returns true if the user is logged in properly, and everything is set up for playing games with achievements.
bool IsLoggedIn();
// Returns true if in a game, and achievements are active in the current game.
bool IsActive();
// Returns true if unofficial achievements are enabled.
bool UnofficialEnabled();
// Returns true if the emulator should hold off on executing game code, such as during game identification.
bool IsBlockingExecution();
// Returns true if features such as save states should be disabled.
// This should only be used for controlling functionality of the following things, which are banned in Challenge/Hardcore mode:
//
// * Savestates
// * Slowdown time (though hard to fully prevent, could use crazy post shaders or software rendering...)
bool ChallengeModeActive();
bool LeaderboardsActive();
bool IsTestModeActive();
bool IsUnofficialTestModeActive();
bool IsRichPresenceEnabled();
bool HasActiveGame();
u32 GetGameID();
// Same as ChallengeModeActive but comes with a convenient user message. Don't use for every-frame checks or UI enablement,
// only for shortcut keys and commands. You should look up the message in I18NCat::ACHIEVEMENTS.
// If no message is specified, a standard "This feature is not available in Challenge Mode" message will be shown.
// Also returns true if challenge mode is active.
bool WarnUserIfChallengeModeActive(const char *message = nullptr);
/// Acquires the achievements lock. Must be held when accessing any achievement state from another thread.
std::unique_lock<std::recursive_mutex> GetLock();
// The new API is so much nicer that we can use it directly instead of wrapping it. So let's expose the client.
// Will of course return nullptr if not active.
rc_client_t *GetClient();
void Initialize();
void UpdateSettings();
/// Called when the system is being reset. If it returns false, the reset should be aborted.
bool ConfirmSystemReset();
/// Called when the system is being shut down. If Shutdown() returns false, the shutdown should be aborted if possible.
bool Shutdown();
/// Called once a frame at vsync time on the CPU thread.
void DownloadImageIfMissing(const std::string &cache_key, std::string &&url);
/// Called once a frame at vsync time on the CPU thread, during gameplay.
void FrameUpdate();
/// Called when the system is paused, because FrameUpdate() won't be getting called.
void ProcessPendingHTTPRequests();
/// Called every frame to let background processing happen.
void Idle();
/// Saves/loads state.
bool DoState(PointerWrap &sw);
void DoState(PointerWrap &p);
/// Returns true if the current game has any achievements or leaderboards.
/// Does not need to have the lock held.
bool SafeHasAchievementsOrLeaderboards();
const std::string &GetUsername();
const std::string &GetRichPresenceString();
bool HasAchievementsOrLeaderboards();
bool LoginAsync(const char *username, const char *password);
void Logout();
void GameChanged(const Path &path);
void LeftGame();
/// Re-enables hardcode mode if it is enabled in the settings.
bool ResetChallengeMode();
/// Forces hardcore mode off until next reset.
void DisableChallengeMode();
/// Prompts the user to disable hardcore mode, if they agree, returns true.
bool ConfirmChallengeModeDisable(const char *trigger);
/// Returns true if features such as save states should be disabled.
bool ChallengeModeActive();
const std::string &GetGameTitle();
const std::string &GetGameIcon();
bool EnumerateAchievements(std::function<bool(const Achievement &)> callback);
// TODO: Make these support multiple games, not just the current games, with cached info.
u32 GetUnlockedAchiementCount();
u32 GetAchievementCount();
u32 GetMaximumPointsForGame();
u32 GetCurrentPointsForGame();
void SetGame(const Path &path);
void ChangeUMD(const Path &path); // for in-game UMD change
void UnloadGame(); // Call when leaving a game.
Statistics GetStatistics();
bool EnumerateLeaderboards(std::function<bool(const Leaderboard &)> callback);
// Unlike most other functions here, this you're supposed to poll until you get a valid std::optional.
std::optional<bool> TryEnumerateLeaderboardEntries(u32 id, std::function<bool(const LeaderboardEntry &)> callback);
const Leaderboard *GetLeaderboardByID(u32 id);
u32 GetLeaderboardCount();
bool IsLeaderboardTimeType(const Leaderboard &leaderboard);
u32 GetPrimedAchievementCount();
const Achievement *GetAchievementByID(u32 id);
std::pair<u32, u32> GetAchievementProgress(const Achievement &achievement);
std::string GetGameAchievementSummary();
std::string GetAchievementProgressText(const Achievement &achievement);
std::string GetAchievementBadgePath(const Achievement &achievement, bool download_if_missing = true,
bool force_unlocked_icon = false);
std::string GetAchievementBadgeURL(const Achievement &achievement);
#ifdef WITH_RAINTEGRATION
void SwitchToRAIntegration();
namespace RAIntegration {
void MainWindowChanged(void *new_handle);
void GameChanged();
std::vector<std::tuple<int, std::string, bool>> GetMenuItems();
void ActivateMenuItem(int item);
} // namespace RAIntegration
#endif
} // namespace Achievements

View file

@ -47,6 +47,7 @@
#include "Core/MemMap.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/JitCommon/JitBlockCache.h"
#include "Core/RetroAchievements.h"
#include "HW/MemoryStick.h"
#include "GPU/GPUState.h"
@ -391,12 +392,18 @@ namespace SaveState
currentMIPS->DoState(p);
HLEDoState(p);
__KernelDoState(p);
Achievements::DoState(p);
// Kernel object destructors might close open files, so do the filesystem last.
pspFileSystem.DoState(p);
}
void Enqueue(SaveState::Operation op)
{
if (Achievements::ChallengeModeActive()) {
// No savestate operations are permitted, let's just ignore it.
return;
}
std::lock_guard<std::mutex> guard(mutex);
pending.push_back(op);

View file

@ -830,10 +830,22 @@ void SystemInfoScreen::CreateTabs() {
return UI::EVENT_DONE;
});
internals->Add(new Choice(si->T("Clear")))->OnClick.Add([&](UI::EventParams &) {
g_OSD.RemoveProgressBar("testprogress", 0.25f);
g_OSD.RemoveProgressBar("testprogress");
return UI::EVENT_DONE;
});
internals->Add(new ItemHeader(si->T("Achievement tests")));
internals->Add(new Choice(si->T("Leaderboard tracker: Show")))->OnClick.Add([=](UI::EventParams &) {
g_OSD.ShowLeaderboardTracker(1, "My leaderboard tracker", true);
return UI::EVENT_DONE;
});
internals->Add(new Choice(si->T("Leaderboard tracker: Update")))->OnClick.Add([=](UI::EventParams &) {
g_OSD.ShowLeaderboardTracker(1, "Updated tracker", true);
return UI::EVENT_DONE;
});
internals->Add(new Choice(si->T("Leaderboard tracker: Hide")))->OnClick.Add([=](UI::EventParams &) {
g_OSD.ShowLeaderboardTracker(1, nullptr, false);
return UI::EVENT_DONE;
});
}
void AddressPromptScreen::CreatePopupContents(UI::ViewGroup *parent) {

View file

@ -348,7 +348,7 @@ void EmuScreen::bootGame(const Path &filename) {
g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("DefaultCPUClockRequired", "Warning: This game requires the CPU clock to be set to default."), 10.0f);
}
Achievements::GameChanged(filename);
Achievements::SetGame(filename);
loadingViewColor_->Divert(0xFFFFFFFF, 0.75f);
loadingViewVisible_->Divert(UI::V_VISIBLE, 0.75f);
@ -405,6 +405,7 @@ void EmuScreen::bootComplete() {
EmuScreen::~EmuScreen() {
if (!invalid_ || bootPending_) {
// If we were invalid, it would already be shutdown.
Achievements::UnloadGame();
PSP_Shutdown();
}
@ -581,7 +582,7 @@ void EmuScreen::onVKey(int virtualKeyCode, bool down) {
break;
case VIRTKEY_SPEED_TOGGLE:
if (down) {
if (down && !Achievements::WarnUserIfChallengeModeActive()) {
// Cycle through enabled speeds.
if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL && g_Config.iFpsLimit1 >= 0) {
PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM1;
@ -597,28 +598,32 @@ void EmuScreen::onVKey(int virtualKeyCode, bool down) {
break;
case VIRTKEY_SPEED_CUSTOM1:
if (down) {
if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL) {
PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM1;
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("fixed", "Speed: alternate"), 1.0);
}
} else {
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1) {
PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL;
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("standard", "Speed: standard"), 1.0);
if (!Achievements::WarnUserIfChallengeModeActive()) {
if (down) {
if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL) {
PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM1;
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("fixed", "Speed: alternate"), 1.0);
}
} else {
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1) {
PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL;
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("standard", "Speed: standard"), 1.0);
}
}
}
break;
case VIRTKEY_SPEED_CUSTOM2:
if (down) {
if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL) {
PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM2;
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("SpeedCustom2", "Speed: alternate 2"), 1.0);
}
} else {
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2) {
PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL;
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("standard", "Speed: standard"), 1.0);
if (!Achievements::WarnUserIfChallengeModeActive()) {
if (down) {
if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL) {
PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM2;
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("SpeedCustom2", "Speed: alternate 2"), 1.0);
}
} else {
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2) {
PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL;
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("standard", "Speed: standard"), 1.0);
}
}
}
break;
@ -634,13 +639,15 @@ void EmuScreen::onVKey(int virtualKeyCode, bool down) {
break;
case VIRTKEY_FRAME_ADVANCE:
if (down) {
// If game is running, pause emulation immediately. Otherwise, advance a single frame.
if (Core_IsStepping()) {
frameStep_ = true;
Core_EnableStepping(false);
} else if (!frameStep_) {
Core_EnableStepping(true, "ui.frameAdvance", 0);
if (!Achievements::WarnUserIfChallengeModeActive()) {
if (down) {
// If game is running, pause emulation immediately. Otherwise, advance a single frame.
if (Core_IsStepping()) {
frameStep_ = true;
Core_EnableStepping(false);
} else if (!frameStep_) {
Core_EnableStepping(true, "ui.frameAdvance", 0);
}
}
}
break;
@ -690,7 +697,7 @@ void EmuScreen::onVKey(int virtualKeyCode, bool down) {
#endif
case VIRTKEY_REWIND:
if (down) {
if (down && !Achievements::WarnUserIfChallengeModeActive()) {
if (SaveState::CanRewind()) {
SaveState::Rewind(&AfterSaveStateAction);
} else {
@ -699,21 +706,21 @@ void EmuScreen::onVKey(int virtualKeyCode, bool down) {
}
break;
case VIRTKEY_SAVE_STATE:
if (down)
if (down && !Achievements::WarnUserIfChallengeModeActive())
SaveState::SaveSlot(gamePath_, g_Config.iCurrentStateSlot, &AfterSaveStateAction);
break;
case VIRTKEY_LOAD_STATE:
if (down)
if (down && !Achievements::WarnUserIfChallengeModeActive())
SaveState::LoadSlot(gamePath_, g_Config.iCurrentStateSlot, &AfterSaveStateAction);
break;
case VIRTKEY_PREVIOUS_SLOT:
if (down) {
if (down && !Achievements::WarnUserIfChallengeModeActive()) {
SaveState::PrevSlot();
NativeMessageReceived("savestate_displayslot", "");
}
break;
case VIRTKEY_NEXT_SLOT:
if (down) {
if (down && !Achievements::WarnUserIfChallengeModeActive()) {
SaveState::NextSlot();
NativeMessageReceived("savestate_displayslot", "");
}
@ -1494,50 +1501,52 @@ void EmuScreen::render() {
Core_UpdateDebugStats(g_Config.bShowDebugStats || g_Config.bLogFrameDrops);
PSP_BeginHostFrame();
bool blockedExecution = Achievements::IsBlockingExecution();
if (!blockedExecution) {
PSP_BeginHostFrame();
PSP_RunLoopWhileState();
PSP_RunLoopWhileState();
// Hopefully coreState is now CORE_NEXTFRAME
switch (coreState) {
case CORE_NEXTFRAME:
// Reached the end of the frame, all good. Set back to running for the next frame
coreState = CORE_RUNNING;
break;
case CORE_STEPPING:
case CORE_RUNTIME_ERROR:
{
// If there's an exception, display information.
const MIPSExceptionInfo &info = Core_GetExceptionInfo();
if (info.type != MIPSExceptionType::NONE) {
// Clear to blue background screen
bool dangerousSettings = !Reporting::IsSupported();
uint32_t color = dangerousSettings ? 0xFF900050 : 0xFF900000;
thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::DONT_CARE, RPAction::DONT_CARE, color }, "EmuScreen_RuntimeError");
// The info is drawn later in renderUI
} else {
// If we're stepping, it's convenient not to clear the screen entirely, so we copy display to output.
// This won't work in non-buffered, but that's fine.
thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::DONT_CARE, RPAction::DONT_CARE }, "EmuScreen_Stepping");
// Just to make sure.
if (PSP_IsInited()) {
gpu->CopyDisplayToOutput(true);
// Hopefully coreState is now CORE_NEXTFRAME
switch (coreState) {
case CORE_NEXTFRAME:
// Reached the end of the frame, all good. Set back to running for the next frame
coreState = CORE_RUNNING;
break;
case CORE_STEPPING:
case CORE_RUNTIME_ERROR:
{
// If there's an exception, display information.
const MIPSExceptionInfo &info = Core_GetExceptionInfo();
if (info.type != MIPSExceptionType::NONE) {
// Clear to blue background screen
bool dangerousSettings = !Reporting::IsSupported();
uint32_t color = dangerousSettings ? 0xFF900050 : 0xFF900000;
thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::DONT_CARE, RPAction::DONT_CARE, color }, "EmuScreen_RuntimeError");
// The info is drawn later in renderUI
} else {
// If we're stepping, it's convenient not to clear the screen entirely, so we copy display to output.
// This won't work in non-buffered, but that's fine.
thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::DONT_CARE, RPAction::DONT_CARE }, "EmuScreen_Stepping");
// Just to make sure.
if (PSP_IsInited()) {
gpu->CopyDisplayToOutput(true);
}
}
break;
}
default:
// Didn't actually reach the end of the frame, ran out of the blockTicks cycles.
// In this case we need to bind and wipe the backbuffer, at least.
// It's possible we never ended up outputted anything - make sure we have the backbuffer cleared
thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR }, "EmuScreen_NoFrame");
break;
}
break;
}
default:
// Didn't actually reach the end of the frame, ran out of the blockTicks cycles.
// In this case we need to bind and wipe the backbuffer, at least.
// It's possible we never ended up outputted anything - make sure we have the backbuffer cleared
thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR }, "EmuScreen_NoFrame");
break;
}
PSP_EndHostFrame();
PSP_EndHostFrame();
// This must happen after PSP_EndHostFrame so that things like push buffers are end-frame'd before we start destroying stuff.
checkPowerDown();
// This must happen after PSP_EndHostFrame so that things like push buffers are end-frame'd before we start destroying stuff.
checkPowerDown();
}
if (invalid_)
return;

View file

@ -1214,7 +1214,8 @@ void NativeUpdate() {
g_requestManager.ProcessRequests();
Achievements::ProcessPendingHTTPRequests();
// it's ok to call this redundantly with DoFrame from EmuScreen
Achievements::Idle();
g_DownloadManager.Update();
g_screenManager->update();

View file

@ -46,14 +46,6 @@ static const float extraTextScale = 0.7f;
// Align only matters here for the ASCII-only flag.
static void MeasureOSDEntry(UIContext &dc, const OnScreenDisplay::Entry &entry, int align, float *width, float *height, float *height1) {
if (entry.type == OSDType::ACHIEVEMENT_UNLOCKED) {
const Achievements::Achievement *achievement = Achievements::GetAchievementByID(entry.numericID);
MeasureAchievement(dc, *achievement, width, height);
*width = 550.0f;
*height1 = *height;
return;
}
dc.SetFontStyle(dc.theme->uiFont);
dc.MeasureText(dc.theme->uiFont, 1.0f, 1.0f, entry.text.c_str(), width, height, align);
*height1 = *height;
@ -85,8 +77,10 @@ static void MeasureOSDEntry(UIContext &dc, const OnScreenDisplay::Entry &entry,
static void RenderOSDEntry(UIContext &dc, const OnScreenDisplay::Entry &entry, Bounds bounds, float height1, int align, float alpha) {
if (entry.type == OSDType::ACHIEVEMENT_UNLOCKED) {
const Achievements::Achievement *achievement = Achievements::GetAchievementByID(entry.numericID);
RenderAchievement(dc, *achievement, AchievementRenderStyle::UNLOCKED, bounds, alpha, entry.startTime, time_now_d());
const rc_client_achievement_t *achievement = rc_client_get_achievement_info(Achievements::GetClient(), entry.numericID);
if (achievement) {
RenderAchievement(dc, achievement, AchievementRenderStyle::UNLOCKED, bounds, alpha, entry.startTime, time_now_d());
}
return;
}
@ -94,10 +88,7 @@ static void RenderOSDEntry(UIContext &dc, const OnScreenDisplay::Entry &entry, B
uint32_t foreGround = whiteAlpha(alpha);
Bounds shadowBounds = bounds.Expand(10.0f);
dc.Draw()->DrawImage4Grid(dc.theme->dropShadow4Grid, shadowBounds.x, shadowBounds.y + 4.0f, shadowBounds.x2(), shadowBounds.y2(), alphaMul(0xFF000000, 0.9f * alpha), 1.0f);
dc.DrawRectDropShadow(bounds, 12.0f, 0.7f * alpha);
dc.FillRect(background, bounds);
ImageID iconID = GetOSDIcon(entry.type);
@ -144,9 +135,7 @@ static void MeasureOSDProgressBar(UIContext &dc, const OnScreenDisplay::Progress
static void RenderOSDProgressBar(UIContext &dc, const OnScreenDisplay::ProgressBar &entry, Bounds bounds, int align, float alpha) {
uint32_t foreGround = whiteAlpha(alpha);
Bounds shadowBounds = bounds.Expand(10.0f);
dc.Draw()->DrawImage4Grid(dc.theme->dropShadow4Grid, shadowBounds.x, shadowBounds.y + 4.0f, shadowBounds.x2(), shadowBounds.y2(), alphaMul(0xFF000000, 0.9f * alpha), 1.0f);
dc.DrawRectDropShadow(bounds, 12.0f, 0.7f * alpha);
uint32_t backgroundColor = colorAlpha(0x806050, alpha);
uint32_t progressBackgroundColor = colorAlpha(0xa08070, alpha);
@ -182,6 +171,23 @@ static void RenderOSDProgressBar(UIContext &dc, const OnScreenDisplay::ProgressB
dc.DrawTextShadowRect(entry.message.c_str(), bounds, colorAlpha(0xFFFFFFFF, alpha), (align & FLAG_DYNAMIC_ASCII) | ALIGN_CENTER);
}
static void MeasureLeaderboardTracker(UIContext &dc, const std::string &text, float *width, float *height) {
dc.MeasureText(dc.GetFontStyle(), 1.0f, 1.0f, text.c_str(), width, height);
*width += 10.0f;
*height += 10.0f;
}
static void RenderLeaderboardTracker(UIContext &dc, const Bounds &bounds, const std::string &text, float alpha) {
// TODO: Awful color.
uint32_t backgroundColor = colorAlpha(0x806050, alpha);
UI::Drawable background = UI::Drawable(backgroundColor);
dc.DrawRectDropShadow(bounds, 12.0f, 0.7f * alpha);
dc.FillRect(background, bounds);
dc.SetFontStyle(dc.theme->uiFont);
dc.SetFontScale(1.0f, 1.0f);
dc.DrawTextShadowRect(text.c_str(), bounds.Inset(5.0f, 5.0f), colorAlpha(0xFFFFFFFF, alpha), ALIGN_VCENTER | ALIGN_LEFT);
}
void OnScreenMessagesView::Draw(UIContext &dc) {
if (!g_Config.bShowOnScreenMessages) {
return;
@ -191,11 +197,73 @@ void OnScreenMessagesView::Draw(UIContext &dc) {
double now = time_now_d();
float y = 10.0f;
const float fadeoutCoef = 1.0f / OnScreenDisplay::FadeoutTime();
// Draw side entries. Top entries should apply on top of them if there's a collision, so drawing
// these first makes sense.
const std::vector<OnScreenDisplay::Entry> sideEntries = g_OSD.SideEntries();
for (auto &entry : sideEntries) {
float tw, th;
const rc_client_achievement_t *achievement = nullptr;
AchievementRenderStyle style;
switch (entry.type) {
case OSDType::ACHIEVEMENT_PROGRESS:
{
achievement = rc_client_get_achievement_info(Achievements::GetClient(), entry.numericID);
if (!achievement)
continue;
style = AchievementRenderStyle::PROGRESS_INDICATOR;
MeasureAchievement(dc, achievement, style, &tw, &th);
break;
}
case OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR:
{
achievement = rc_client_get_achievement_info(Achievements::GetClient(), entry.numericID);
if (!achievement)
continue;
style = AchievementRenderStyle::CHALLENGE_INDICATOR;
MeasureAchievement(dc, achievement, style, &tw, &th);
break;
}
case OSDType::LEADERBOARD_TRACKER:
{
MeasureLeaderboardTracker(dc, entry.text, &tw, &th);
break;
}
default:
continue;
}
Bounds b(10.0f, y, tw, th);
float alpha = Clamp((float)(entry.endTime - now) * fadeoutCoef, 0.0f, 1.0f);
// OK, render the thing.
switch (entry.type) {
case OSDType::ACHIEVEMENT_PROGRESS:
case OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR:
{
RenderAchievement(dc, achievement, style, b, alpha, entry.startTime, now);
break;
}
case OSDType::LEADERBOARD_TRACKER:
RenderLeaderboardTracker(dc, b, entry.text, alpha);
break;
default:
continue;
}
y += (b.h + 4.0f) * alpha; // including alpha here gets us smooth animations.
}
// Get height
float w, h;
dc.MeasureText(dc.theme->uiFont, 1.0f, 1.0f, "Wg", &w, &h);
float y = 10.0f;
y = 10.0f;
// Then draw them all.
const std::vector<OnScreenDisplay::ProgressBar> bars = g_OSD.ProgressBars();
for (auto &bar : bars) {
@ -210,19 +278,34 @@ void OnScreenMessagesView::Draw(UIContext &dc) {
}
const std::vector<OnScreenDisplay::Entry> entries = g_OSD.Entries();
for (auto iter = entries.begin(); iter != entries.end(); ++iter) {
for (const auto &entry : entries) {
dc.SetFontScale(1.0f, 1.0f);
// Messages that are wider than the screen are left-aligned instead of centered.
int align = 0;
// If we have newlines, we may be looking at ASCII debug output. But let's verify.
if (iter->text.find('\n') != 0) {
if (!UTF8StringHasNonASCII(iter->text.c_str()))
if (entry.text.find('\n') != 0) {
if (!UTF8StringHasNonASCII(entry.text.c_str()))
align |= FLAG_DYNAMIC_ASCII;
}
float tw, th, h1;
MeasureOSDEntry(dc, *iter, align, &tw, &th, &h1);
float tw, th = 0.0f, h1 = 0.0f;
switch (entry.type) {
case OSDType::ACHIEVEMENT_UNLOCKED:
{
const rc_client_achievement_t *achievement = rc_client_get_achievement_info(Achievements::GetClient(), entry.numericID);
if (achievement) {
MeasureAchievement(dc, achievement, AchievementRenderStyle::UNLOCKED, &tw, &th);
h1 = th;
}
tw = 550.0f;
break;
}
default:
MeasureOSDEntry(dc, entry, align, &tw, &th, &h1);
break;
}
Bounds b(0.0f, y, tw, th);
@ -244,8 +327,8 @@ void OnScreenMessagesView::Draw(UIContext &dc) {
b.h *= scale;
}
float alpha = Clamp((float)(iter->endTime - now) * 4.0f, 0.0f, 1.0f);
RenderOSDEntry(dc, *iter, b, h1, align, alpha);
float alpha = Clamp((float)(entry.endTime - now) * 4.0f, 0.0f, 1.0f);
RenderOSDEntry(dc, entry, b, h1, align, alpha);
y += (b.h * scale + 4.0f) * alpha; // including alpha here gets us smooth animations.
}

View file

@ -265,27 +265,13 @@ GamePauseScreen::~GamePauseScreen() {
__DisplaySetWasPaused();
}
void GamePauseScreen::CreateViews() {
void GamePauseScreen::CreateSavestateControls(UI::LinearLayout *leftColumnItems, bool vertical) {
auto pa = GetI18NCategory(I18NCat::PAUSE);
static const int NUM_SAVESLOTS = 5;
using namespace UI;
bool vertical = UseVerticalLayout();
Margins scrollMargins(0, 20, 0, 0);
Margins actionMenuMargins(0, 20, 15, 0);
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
auto pa = GetI18NCategory(I18NCat::PAUSE);
root_ = new LinearLayout(ORIENT_HORIZONTAL);
ViewGroup *leftColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0, scrollMargins));
root_->Add(leftColumn);
LinearLayout *leftColumnItems = new LinearLayoutList(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, WRAP_CONTENT));
leftColumn->Add(leftColumnItems);
leftColumnItems->Add(new Spacer(0.0));
leftColumnItems->SetSpacing(10.0);
for (int i = 0; i < NUM_SAVESLOTS; i++) {
SaveSlotView *slot = leftColumnItems->Add(new SaveSlotView(gamePath_, i, vertical, new LayoutParams(FILL_PARENT, WRAP_CONTENT)));
@ -311,6 +297,36 @@ void GamePauseScreen::CreateViews() {
rewindButton->SetEnabled(SaveState::CanRewind());
rewindButton->OnClick.Handle(this, &GamePauseScreen::OnRewind);
}
}
void GamePauseScreen::CreateViews() {
using namespace UI;
bool vertical = UseVerticalLayout();
Margins scrollMargins(0, 10, 0, 0);
Margins actionMenuMargins(0, 10, 15, 0);
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
auto pa = GetI18NCategory(I18NCat::PAUSE);
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
root_ = new LinearLayout(ORIENT_HORIZONTAL);
ViewGroup *leftColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0, scrollMargins));
root_->Add(leftColumn);
LinearLayout *leftColumnItems = new LinearLayoutList(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, WRAP_CONTENT));
leftColumn->Add(leftColumnItems);
leftColumnItems->Add(new Spacer(0.0));
if (Achievements::IsActive()) {
leftColumnItems->Add(new GameAchievementSummaryView());
leftColumnItems->Add(new Spacer(5.0));
}
if (!Achievements::ChallengeModeActive()) {
CreateSavestateControls(leftColumnItems, vertical);
}
ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(vertical ? 200 : 300, FILL_PARENT, actionMenuMargins));
root_->Add(rightColumn);
@ -347,7 +363,7 @@ void GamePauseScreen::CreateViews() {
return UI::EVENT_DONE;
});
}
if (g_Config.bAchievementsEnable && Achievements::SafeHasAchievementsOrLeaderboards()) {
if (g_Config.bAchievementsEnable && Achievements::HasAchievementsOrLeaderboards()) {
rightColumnItems->Add(new Choice(pa->T("Achievements")))->OnClick.Add([&](UI::EventParams &e) {
screenManager()->push(new RetroAchievementsListScreen(gamePath_));
return UI::EVENT_DONE;

View file

@ -45,6 +45,8 @@ protected:
void CallbackDeleteConfig(bool yes);
private:
void CreateSavestateControls(UI::LinearLayout *viewGroup, bool vertical);
UI::EventReturn OnGameSettings(UI::EventParams &e);
UI::EventReturn OnExitToMenu(UI::EventParams &e);
UI::EventReturn OnReportFeedback(UI::EventParams &e);

View file

@ -17,11 +17,9 @@ void RetroAchievementsListScreen::CreateTabs() {
achievements->SetSpacing(5.0f);
CreateAchievementsTab(achievements);
if (Achievements::GetLeaderboardCount() > 0) {
UI::LinearLayout *leaderboards = AddTab("Leaderboards", ac->T("Leaderboards"));
leaderboards->SetSpacing(5.0f);
CreateLeaderboardsTab(leaderboards);
}
UI::LinearLayout *leaderboards = AddTab("Leaderboards", ac->T("Leaderboards"));
leaderboards->SetSpacing(5.0f);
CreateLeaderboardsTab(leaderboards);
#ifdef _DEBUG
CreateStatisticsTab(AddTab("AchievementsStatistics", ac->T("Statistics")));
@ -34,29 +32,50 @@ void RetroAchievementsListScreen::CreateAchievementsTab(UI::ViewGroup *achieveme
using namespace UI;
std::vector<Achievements::Achievement> unlockedAchievements;
std::vector<Achievements::Achievement> lockedAchievements;
int filter = RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE;
if (Achievements::UnofficialEnabled()) {
filter = RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL;
}
rc_client_achievement_list_t *list = rc_client_create_achievement_list(Achievements::GetClient(),
filter, RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE);
std::vector<const rc_client_achievement_t *> unlockedAchievements;
std::vector<const rc_client_achievement_t *> lockedAchievements;
std::vector<const rc_client_achievement_t *> otherAchievements;
for (uint32_t i = 0; i < list->num_buckets; i++) {
const rc_client_achievement_bucket_t &bucket = list->buckets[i];
for (uint32_t j = 0; j < bucket.num_achievements; j++) {
switch (bucket.bucket_type) {
case RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED:
lockedAchievements.push_back(bucket.achievements[j]);
break;
case RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED:
unlockedAchievements.push_back(bucket.achievements[j]);
break;
default:
otherAchievements.push_back(bucket.achievements[j]);
break;
}
}
}
achievements->Add(new ItemHeader(ac->T("Achievements")));
achievements->Add(new GameAchievementSummaryView(Achievements::GetGameID()));
Achievements::EnumerateAchievements([&](const Achievements::Achievement &achievement) {
if (achievement.locked) {
lockedAchievements.push_back(achievement);
} else {
unlockedAchievements.push_back(achievement);
}
return true;
});
achievements->Add(new GameAchievementSummaryView());
achievements->Add(new ItemHeader(ac->T("Unlocked achievements")));
for (auto &achievement : unlockedAchievements) {
achievements->Add(new AchievementView(std::move(achievement)));
achievements->Add(new AchievementView(achievement));
}
achievements->Add(new ItemHeader(ac->T("Locked achievements")));
for (auto &achievement : lockedAchievements) {
achievements->Add(new AchievementView(std::move(achievement)));
achievements->Add(new AchievementView(achievement));
}
achievements->Add(new ItemHeader(ac->T("Other achievements")));
for (auto &achievement : otherAchievements) {
achievements->Add(new AchievementView(achievement));
}
}
@ -66,25 +85,25 @@ void RetroAchievementsListScreen::CreateLeaderboardsTab(UI::ViewGroup *viewGroup
using namespace UI;
viewGroup->Add(new GameAchievementSummaryView(Achievements::GetGameID()));
viewGroup->Add(new GameAchievementSummaryView());
viewGroup->Add(new ItemHeader(ac->T("Leaderboards")));
std::vector<Achievements::Leaderboard> leaderboards;
Achievements::EnumerateLeaderboards([&](const Achievements::Leaderboard &leaderboard) {
leaderboards.push_back(leaderboard);
return true;
});
std::vector<rc_client_leaderboard_t *> leaderboards;
rc_client_leaderboard_list_t *list = rc_client_create_leaderboard_list(Achievements::GetClient(), RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE);
for (uint32_t i = 0; i < list->num_buckets; i++) {
const rc_client_leaderboard_bucket_t &bucket = list->buckets[i];
for (uint32_t j = 0; j < bucket.num_leaderboards; j++) {
leaderboards.push_back(bucket.leaderboards[j]);
}
}
for (auto &leaderboard : leaderboards) {
if (!leaderboard.hidden) {
int leaderboardID = leaderboard.id;
viewGroup->Add(new LeaderboardSummaryView(std::move(leaderboard)))->OnClick.Add([=](UI::EventParams &e) -> UI::EventReturn {
screenManager()->push(new RetroAchievementsLeaderboardScreen(gamePath_, leaderboardID));
return UI::EVENT_DONE;
});
}
int leaderboardID = leaderboard->id;
viewGroup->Add(new LeaderboardSummaryView(leaderboard))->OnClick.Add([=](UI::EventParams &e) -> UI::EventReturn {
screenManager()->push(new RetroAchievementsLeaderboardScreen(gamePath_, leaderboardID));
return UI::EVENT_DONE;
});
}
}
@ -98,35 +117,60 @@ void RetroAchievementsListScreen::CreateStatisticsTab(UI::ViewGroup *viewGroup)
viewGroup->Add(new InfoItem(ac->T("Bad memory accesses"), StringFromFormat("%d", stats.badMemoryAccessCount)));
}
RetroAchievementsLeaderboardScreen::~RetroAchievementsLeaderboardScreen() {
if (pendingAsyncCall_) {
rc_client_abort_async(Achievements::GetClient(), pendingAsyncCall_);
}
Poll(); // Gets rid of pendingEntryList_.
if (entryList_) {
rc_client_destroy_leaderboard_entry_list(entryList_);
}
}
RetroAchievementsLeaderboardScreen::RetroAchievementsLeaderboardScreen(const Path &gamePath, int leaderboardID)
: TabbedUIDialogScreenWithGameBackground(gamePath), leaderboardID_(leaderboardID) {
rc_client_begin_fetch_leaderboard_entries(Achievements::GetClient(), leaderboardID_, 0, 20, [](int result, const char *error_message, rc_client_leaderboard_entry_list_t *list, rc_client_t *client, void *userdata) {
if (result != RC_OK) {
g_OSD.Show(OSDType::MESSAGE_ERROR, error_message, 10.0f);
return;
}
RetroAchievementsLeaderboardScreen *thiz = (RetroAchievementsLeaderboardScreen *)userdata;
thiz->pendingEntryList_ = list;
thiz->pendingAsyncCall_ = nullptr;
}, this);
}
void RetroAchievementsLeaderboardScreen::CreateTabs() {
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
const Achievements::Leaderboard *leaderboard = Achievements::GetLeaderboardByID(leaderboardID_);
const rc_client_leaderboard_t *leaderboard = rc_client_get_leaderboard_info(Achievements::GetClient(), leaderboardID_);
using namespace UI;
UI::LinearLayout *layout = AddTab("AchievementsLeaderboard", leaderboard->title.c_str());
UI::LinearLayout *layout = AddTab("AchievementsLeaderboard", leaderboard->title);
layout->Add(new TextView(leaderboard->description));
layout->Add(new ItemHeader(ac->T("Leaderboard")));
Poll();
if (entryList_) {
for (uint32_t i = 0; i < entryList_->num_entries; i++) {
bool is_self = (i == entryList_->user_index);
// Should highlight somehow.
const rc_client_leaderboard_entry_t &entry = entryList_->entries[i];
// TODO: Make it pretty.
for (auto &entry : entries_) {
layout->Add(new TextView(StringFromFormat(" %d: %s: %s%s", entry.rank, entry.user.c_str(), entry.formatted_score.c_str(), entry.is_self ? " <<<<< " : "")));
char buffer[512];
rc_client_leaderboard_entry_get_user_image_url(&entryList_->entries[i], buffer, sizeof(buffer));
// Can also show entry.submitted, which is a time_t. And maybe highlight recent ones?
layout->Add(new LeaderboardEntryView(&entryList_->entries[i], is_self));
}
}
}
void RetroAchievementsLeaderboardScreen::Poll() {
if (done_)
return;
std::optional<bool> result = Achievements::TryEnumerateLeaderboardEntries(leaderboardID_, [&](const Achievements::LeaderboardEntry &entry) -> bool {
entries_.push_back(entry);
return true;
});
if (result.has_value()) {
done_ = true;
if (pendingEntryList_) {
if (entryList_) {
rc_client_destroy_leaderboard_entry_list(entryList_);
}
entryList_ = pendingEntryList_;
pendingEntryList_ = nullptr;
RecreateViews();
}
}
@ -136,6 +180,8 @@ void RetroAchievementsLeaderboardScreen::update() {
Poll();
}
RetroAchievementsSettingsScreen::~RetroAchievementsSettingsScreen() {}
void RetroAchievementsSettingsScreen::CreateTabs() {
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
auto di = GetI18NCategory(I18NCat::DIALOG);
@ -160,8 +206,17 @@ void RetroAchievementsSettingsScreen::CreateAccountTab(UI::ViewGroup *viewGroup)
using namespace UI;
if (Achievements::IsLoggedIn()) {
viewGroup->Add(new InfoItem(ac->T("Username"), Achievements::GetUsername()));
if (!g_Config.bAchievementsEnable) {
viewGroup->Add(new TextView(ac->T("Achievements are disabled")));
} else if (Achievements::IsLoggedIn()) {
const rc_client_user_t *info = rc_client_get_user_info(Achievements::GetClient());
// In the future, RetroAchievements will support display names. Prepare for that.
if (strcmp(info->display_name, info->username) != 0) {
viewGroup->Add(new InfoItem(ac->T("Name"), info->display_name));
}
viewGroup->Add(new InfoItem(ac->T("Username"), info->username));
// viewGroup->Add(new InfoItem(ac->T("Unread messages"), info.numUnreadMessages));
viewGroup->Add(new Choice(di->T("Log out")))->OnClick.Add([=](UI::EventParams &) -> UI::EventReturn {
Achievements::Logout();
return UI::EVENT_DONE;
@ -212,85 +267,165 @@ void RetroAchievementsSettingsScreen::CreateSettingsTab(UI::ViewGroup *viewGroup
using namespace UI;
viewGroup->Add(new ItemHeader(ac->T("Settings")));
viewGroup->Add(new CheckBox(&g_Config.bAchievementsRichPresence, ac->T("Rich Presence")));
viewGroup->Add(new CheckBox(&g_Config.bAchievementsSoundEffects, ac->T("Sound Effects"))); // not yet implemented
viewGroup->Add(new CheckBox(&g_Config.bAchievementsLogBadMemReads, ac->T("Log bad memory accesses")));
// Not yet fully implemented
// viewGroup->Add(new CheckBox(&g_Config.bAchievementsChallengeMode, ac->T("Challenge Mode (no savestates)")));
viewGroup->Add(new CheckBox(&g_Config.bAchievementsEnable, ac->T("Achievements enabled")))->OnClick.Add([&](UI::EventParams &e) -> UI::EventReturn {
Achievements::UpdateSettings();
RecreateViews();
return UI::EVENT_DONE;
});
viewGroup->Add(new CheckBox(&g_Config.bAchievementsChallengeMode, ac->T("Challenge Mode (no savestates)")));
viewGroup->Add(new CheckBox(&g_Config.bAchievementsEncoreMode, ac->T("Encore Mode")));
viewGroup->Add(new CheckBox(&g_Config.bAchievementsUnofficial, ac->T("Unofficial achievements")));
// viewGroup->Add(new CheckBox(&g_Config.bAchievementsSoundEffects, ac->T("Sound Effects")))->SetEnabledPtr(&g_Config.bAchievementsEnable); // not yet implemented
viewGroup->Add(new CheckBox(&g_Config.bAchievementsLogBadMemReads, ac->T("Log bad memory accesses")))->SetEnabledPtr(&g_Config.bAchievementsEnable);
// TODO: What are these for?
// viewGroup->Add(new CheckBox(&g_Config.bAchievementsTestMode, ac->T("Test Mode")));
// viewGroup->Add(new CheckBox(&g_Config.bAchievementsUnofficialTestMode, ac->T("Unofficial Test Mode")));
}
void MeasureAchievement(const UIContext &dc, const Achievements::Achievement &achievement, float *w, float *h) {
void MeasureAchievement(const UIContext &dc, const rc_client_achievement_t *achievement, AchievementRenderStyle style, float *w, float *h) {
*w = 0.0f;
switch (style) {
case AchievementRenderStyle::PROGRESS_INDICATOR:
dc.MeasureText(dc.theme->uiFont, 1.0f, 1.0f, achievement->measured_progress, w, h);
*w += 44.0f + 4.0f * 3.0f;
*h = 44.0f;
break;
case AchievementRenderStyle::CHALLENGE_INDICATOR:
// ONLY the icon.
*w = 60.0f;
*h = 60.0f;
break;
default:
*h = 72.0f;
break;
}
}
void MeasureGameAchievementSummary(const UIContext &dc, float *w, float *h) {
std::string description = Achievements::GetGameAchievementSummary();
float tw, th;
dc.MeasureText(dc.theme->uiFont, 1.0f, 1.0f, "Wg", &tw, &th);
dc.MeasureText(dc.theme->uiFont, 0.66f, 0.66f, description.c_str(), w, h);
*h += 12.0f + th;
*w += 8.0f;
}
void MeasureLeaderboardSummary(const UIContext &dc, const rc_client_leaderboard_t *leaderboard, float *w, float *h) {
*w = 0.0f;
*h = 72.0f;
}
void MeasureGameAchievementSummary(const UIContext &dc, int gameID, float *w, float *h) {
*w = 0.0f;
*h = 72.0f;
}
void MeasureLeaderboardSummary(const UIContext &dc, const Achievements::Leaderboard &achievement, float *w, float *h) {
void MeasureLeaderboardEntry(const UIContext &dc, const rc_client_leaderboard_entry_t *entry, float *w, float *h) {
*w = 0.0f;
*h = 72.0f;
}
// Graphical
void RenderAchievement(UIContext &dc, const Achievements::Achievement &achievement, AchievementRenderStyle style, const Bounds &bounds, float alpha, float startTime, float time_s) {
void RenderAchievement(UIContext &dc, const rc_client_achievement_t *achievement, AchievementRenderStyle style, const Bounds &bounds, float alpha, float startTime, float time_s) {
using namespace UI;
UI::Drawable background = UI::Drawable(dc.theme->backgroundColor);
if (achievement.locked) {
background.color = 0x706060;
// Set some alpha, if displayed in list.
if (style == AchievementRenderStyle::LISTED) {
background.color = colorAlpha(background.color, 0.6f);
}
background.color = colorAlpha(background.color, alpha);
uint32_t fgColor = colorAlpha(dc.theme->itemStyle.fgColor, alpha);
if (!achievement->unlocked) {
// Make the background color gray.
// TODO: Different colors in challenge mode, or even in the "re-take achievements" mode when we add that?
background.color = (background.color & 0xFF000000) | 0x706060;
}
int iconState = achievement->state;
background.color = alphaMul(background.color, alpha);
uint32_t fgColor = alphaMul(dc.theme->itemStyle.fgColor, alpha);
if (style == AchievementRenderStyle::UNLOCKED) {
float mixWhite = pow(Clamp((float)(1.0f - (time_s - startTime)), 0.0f, 1.0f), 3.0f);
background.color = colorBlend(0xFFE0FFFF, background.color, mixWhite);
}
float iconSpace = 64.0f;
float padding = 4.0f;
float iconSpace = bounds.h - padding * 2.0f;
dc.Flush();
dc.RebindTexture();
dc.Begin();
if (style != AchievementRenderStyle::LISTED) {
dc.DrawRectDropShadow(bounds, 12.0f, 0.7f * alpha);
}
dc.FillRect(background, bounds);
dc.Flush();
dc.Begin();
dc.SetFontStyle(dc.theme->uiFont);
dc.SetFontScale(1.0f, 1.0f);
dc.DrawTextRect(achievement.title.c_str(), bounds.Inset(iconSpace + 12.0f, 2.0f, 5.0f, 5.0f), fgColor, ALIGN_TOPLEFT);
char temp[512];
dc.SetFontScale(0.66f, 0.66f);
dc.DrawTextRect(achievement.description.c_str(), bounds.Inset(iconSpace + 12.0f, 39.0f, 5.0f, 5.0f), fgColor, ALIGN_TOPLEFT);
switch (style) {
case AchievementRenderStyle::LISTED:
case AchievementRenderStyle::UNLOCKED:
{
dc.SetFontScale(1.0f, 1.0f);
dc.DrawTextRect(achievement->title, bounds.Inset(iconSpace + 12.0f, 2.0f, padding, padding), fgColor, ALIGN_TOPLEFT);
char temp[64];
snprintf(temp, sizeof(temp), "%d", achievement.points);
dc.SetFontScale(0.66f, 0.66f);
dc.DrawTextRect(achievement->description, bounds.Inset(iconSpace + 12.0f, 39.0f, padding, padding), fgColor, ALIGN_TOPLEFT);
dc.SetFontScale(1.5f, 1.5f);
dc.DrawTextRect(temp, bounds.Expand(-5.0f, -5.0f), fgColor, ALIGN_RIGHT | ALIGN_VCENTER);
if (style == AchievementRenderStyle::LISTED && strlen(achievement->measured_progress) > 0) {
dc.SetFontScale(1.0f, 1.0f);
dc.DrawTextRect(achievement->measured_progress, bounds.Inset(iconSpace + 12.0f, padding, padding + 100.0f, padding), fgColor, ALIGN_VCENTER | ALIGN_RIGHT);
}
dc.SetFontScale(1.0f, 1.0f);
dc.Flush();
// TODO: Draw measured_progress / measured_percent in a cute way
snprintf(temp, sizeof(temp), "%d", achievement->points);
std::string name = Achievements::GetAchievementBadgePath(achievement);
if (g_iconCache.BindIconTexture(&dc, name)) {
dc.Draw()->DrawTexRect(Bounds(bounds.x + 4.0f, bounds.y + 4.0f, iconSpace, iconSpace), 0.0f, 0.0f, 1.0f, 1.0f, whiteAlpha(alpha));
dc.SetFontScale(1.5f, 1.5f);
dc.DrawTextRect(temp, bounds.Expand(-5.0f, -5.0f), fgColor, ALIGN_RIGHT | ALIGN_VCENTER);
dc.SetFontScale(1.0f, 1.0f);
dc.Flush();
break;
}
case AchievementRenderStyle::PROGRESS_INDICATOR:
// TODO: Also render a progress bar.
dc.SetFontScale(1.0f, 1.0f);
dc.DrawTextRect(achievement->measured_progress, bounds.Inset(iconSpace + padding * 2.0f, padding, padding, padding), fgColor, ALIGN_LEFT | ALIGN_VCENTER);
// Show the unlocked icon.
iconState = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
break;
case AchievementRenderStyle::CHALLENGE_INDICATOR:
// Nothing but the icon, unlocked.
iconState = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
break;
}
dc.Flush();
dc.RebindTexture();
// Download and display the image.
char cacheKey[256];
snprintf(cacheKey, sizeof(cacheKey), "ai:%s:%s", achievement->badge_name, iconState == RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED ? "unlocked" : "locked");
if (RC_OK == rc_client_achievement_get_image_url(achievement, iconState, temp, sizeof(temp))) {
Achievements::DownloadImageIfMissing(cacheKey, std::move(std::string(temp)));
if (g_iconCache.BindIconTexture(&dc, cacheKey)) {
dc.Draw()->DrawTexRect(Bounds(bounds.x + padding, bounds.y + padding, iconSpace, iconSpace), 0.0f, 0.0f, 1.0f, 1.0f, whiteAlpha(alpha));
}
dc.Flush();
dc.RebindTexture();
}
}
void RenderGameAchievementSummary(UIContext &dc, int gameID, const Bounds &bounds, float alpha) {
void RenderGameAchievementSummary(UIContext &dc, const Bounds &bounds, float alpha) {
using namespace UI;
UI::Drawable background = dc.theme->itemStyle.background;
background.color = colorAlpha(background.color, alpha);
background.color = alphaMul(background.color, alpha);
uint32_t fgColor = colorAlpha(dc.theme->itemStyle.fgColor, alpha);
float iconSpace = 64.0f;
@ -301,11 +436,12 @@ void RenderGameAchievementSummary(UIContext &dc, int gameID, const Bounds &bound
dc.SetFontStyle(dc.theme->uiFont);
const rc_client_game_t *gameInfo = rc_client_get_game_info(Achievements::GetClient());
dc.SetFontScale(1.0f, 1.0f);
dc.DrawTextRect(Achievements::GetGameTitle().c_str(), bounds.Inset(iconSpace + 5.0f, 2.0f, 5.0f, 5.0f), fgColor, ALIGN_TOPLEFT);
dc.DrawTextRect(gameInfo->title, bounds.Inset(iconSpace + 5.0f, 2.0f, 5.0f, 5.0f), fgColor, ALIGN_TOPLEFT);
std::string description = Achievements::GetGameAchievementSummary();
std::string icon = Achievements::GetGameIcon();
dc.SetFontScale(0.66f, 0.66f);
dc.DrawTextRect(description.c_str(), bounds.Inset(iconSpace + 5.0f, 38.0f, 5.0f, 5.0f), fgColor, ALIGN_TOPLEFT);
@ -313,16 +449,21 @@ void RenderGameAchievementSummary(UIContext &dc, int gameID, const Bounds &bound
dc.SetFontScale(1.0f, 1.0f);
dc.Flush();
std::string name = icon;
if (g_iconCache.BindIconTexture(&dc, name)) {
dc.Draw()->DrawTexRect(Bounds(bounds.x, bounds.y, iconSpace, iconSpace), 0.0f, 0.0f, 1.0f, 1.0f, whiteAlpha(alpha));
char url[512];
char cacheKey[256];
snprintf(cacheKey, sizeof(cacheKey), "gi:%s", gameInfo->badge_name);
if (RC_OK == rc_client_game_get_image_url(gameInfo, url, sizeof(url))) {
Achievements::DownloadImageIfMissing(cacheKey, std::move(std::string(url)));
if (g_iconCache.BindIconTexture(&dc, cacheKey)) {
dc.Draw()->DrawTexRect(Bounds(bounds.x, bounds.y, iconSpace, iconSpace), 0.0f, 0.0f, 1.0f, 1.0f, whiteAlpha(alpha));
}
}
dc.Flush();
dc.RebindTexture();
}
void RenderLeaderboardSummary(UIContext &dc, const Achievements::Leaderboard &leaderboard, AchievementRenderStyle style, const Bounds &bounds, float alpha, float startTime, float time_s) {
void RenderLeaderboardSummary(UIContext &dc, const rc_client_leaderboard_t *leaderboard, AchievementRenderStyle style, const Bounds &bounds, float alpha, float startTime, float time_s) {
using namespace UI;
UI::Drawable background = UI::Drawable(dc.theme->backgroundColor);
background.color = colorAlpha(background.color, alpha);
@ -333,7 +474,6 @@ void RenderLeaderboardSummary(UIContext &dc, const Achievements::Leaderboard &le
background.color = colorBlend(0xFFE0FFFF, background.color, mixWhite);
}
float iconSpace = 64.0f;
dc.Flush();
dc.Begin();
@ -342,10 +482,10 @@ void RenderLeaderboardSummary(UIContext &dc, const Achievements::Leaderboard &le
dc.SetFontStyle(dc.theme->uiFont);
dc.SetFontScale(1.0f, 1.0f);
dc.DrawTextRect(leaderboard.title.c_str(), bounds.Inset(iconSpace + 12.0f, 2.0f, 5.0f, 5.0f), fgColor, ALIGN_TOPLEFT);
dc.DrawTextRect(leaderboard->title, bounds.Inset(12.0f, 2.0f, 5.0f, 5.0f), fgColor, ALIGN_TOPLEFT);
dc.SetFontScale(0.66f, 0.66f);
dc.DrawTextRect(leaderboard.description.c_str(), bounds.Inset(iconSpace + 12.0f, 39.0f, 5.0f, 5.0f), fgColor, ALIGN_TOPLEFT);
dc.DrawTextRect(leaderboard->description, bounds.Inset(12.0f, 39.0f, 5.0f, 5.0f), fgColor, ALIGN_TOPLEFT);
/*
char temp[64];
@ -362,21 +502,84 @@ void RenderLeaderboardSummary(UIContext &dc, const Achievements::Leaderboard &le
dc.RebindTexture();
}
void RenderLeaderboardEntry(UIContext &dc, const rc_client_leaderboard_entry_t *entry, const Bounds &bounds, float alpha) {
using namespace UI;
UI::Drawable background = dc.theme->itemStyle.background;
background.color = alphaMul(background.color, alpha);
uint32_t fgColor = alphaMul(dc.theme->itemStyle.fgColor, alpha);
float iconSize = 64.0f;
float numberSpace = 128.0f;
float iconLeft = numberSpace + 5.0f;
float iconSpace = numberSpace + 5.0f + iconSize;
dc.Flush();
dc.Begin();
dc.FillRect(background, bounds);
dc.SetFontStyle(dc.theme->uiFont);
dc.SetFontScale(1.5f, 1.5f);
dc.DrawTextRect(StringFromFormat("%d", entry->rank).c_str(), Bounds(bounds.x + 4.0f, bounds.y + 4.0f, numberSpace - 10.0f, bounds.h - 4.0f * 2.0f), fgColor, ALIGN_TOPRIGHT);
dc.SetFontScale(1.0f, 1.0f);
dc.DrawTextRect(entry->user, bounds.Inset(iconSpace + 5.0f, 2.0f, 5.0f, 5.0f), fgColor, ALIGN_TOPLEFT);
dc.SetFontScale(0.66f, 0.66f);
dc.DrawTextRect(entry->display, bounds.Inset(iconSpace + 5.0f, 38.0f, 5.0f, 5.0f), fgColor, ALIGN_TOPLEFT);
dc.SetFontScale(1.0f, 1.0f);
dc.Flush();
// Come up with a unique name for the icon entry.
char cacheKey[256];
snprintf(cacheKey, sizeof(cacheKey), "lbe:%s", entry->user);
char temp[512];
if (RC_OK == rc_client_leaderboard_entry_get_user_image_url(entry, temp, sizeof(temp))) {
Achievements::DownloadImageIfMissing(cacheKey, std::move(std::string(temp)));
if (g_iconCache.BindIconTexture(&dc, cacheKey)) {
dc.Draw()->DrawTexRect(Bounds(bounds.x + iconLeft, bounds.y + 4.0f, 64.0f, 64.0f), 0.0f, 0.0f, 1.0f, 1.0f, whiteAlpha(alpha));
}
}
dc.Flush();
dc.RebindTexture();
}
void AchievementView::Draw(UIContext &dc) {
RenderAchievement(dc, achievement_, AchievementRenderStyle::LISTED, bounds_, 1.0f, 0.0f, 0.0f);
}
void AchievementView::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
MeasureAchievement(dc, achievement_, &w, &h);
MeasureAchievement(dc, achievement_, AchievementRenderStyle::LISTED, &w, &h);
}
void AchievementView::Click() {
// In debug builds, clicking achievements will show them being unlocked (which may be a lie).
#ifdef _DEBUG
g_OSD.ShowAchievementUnlocked(achievement_.id);
static int type = 0;
type++;
type = type % 4;
switch (type) {
case 0: g_OSD.ShowAchievementUnlocked(achievement_->id); break;
case 1: g_OSD.ShowAchievementProgress(achievement_->id, 2.0f); break;
case 2: g_OSD.ShowChallengeIndicator(achievement_->id, true); break;
case 3: g_OSD.ShowChallengeIndicator(achievement_->id, false); break;
}
#endif
}
void GameAchievementSummaryView::Draw(UIContext &dc) {
RenderGameAchievementSummary(dc, bounds_, 1.0f);
}
void GameAchievementSummaryView::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
MeasureGameAchievementSummary(dc, &w, &h);
}
void LeaderboardSummaryView::Draw(UIContext &dc) {
RenderLeaderboardSummary(dc, leaderboard_, AchievementRenderStyle::LISTED, bounds_, 1.0f, 0.0f, 0.0f);
}
@ -385,10 +588,11 @@ void LeaderboardSummaryView::GetContentDimensions(const UIContext &dc, float &w,
MeasureLeaderboardSummary(dc, leaderboard_, &w, &h);
}
void GameAchievementSummaryView::Draw(UIContext &dc) {
RenderGameAchievementSummary(dc, gameID_, bounds_, 1.0f);
void LeaderboardEntryView::Draw(UIContext &dc) {
RenderLeaderboardEntry(dc, entry_, bounds_, 1.0f);
}
void GameAchievementSummaryView::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
MeasureGameAchievementSummary(dc, gameID_, &w, &h);
void LeaderboardEntryView::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
MeasureLeaderboardEntry(dc, entry_, &w, &h);
}

View file

@ -1,5 +1,7 @@
#pragma once
#include <cstdint>
#include "Common/File/Path.h"
#include "Common/UI/View.h"
#include "Common/UI/UIScreen.h"
@ -29,6 +31,7 @@ private:
class RetroAchievementsSettingsScreen : public TabbedUIDialogScreenWithGameBackground {
public:
RetroAchievementsSettingsScreen(const Path &gamePath) : TabbedUIDialogScreenWithGameBackground(gamePath) {}
~RetroAchievementsSettingsScreen();
const char *tag() const override { return "RetroAchievementsSettingsScreen"; }
void CreateTabs() override;
@ -47,20 +50,29 @@ private:
class RetroAchievementsLeaderboardScreen : public TabbedUIDialogScreenWithGameBackground {
public:
RetroAchievementsLeaderboardScreen(const Path &gamePath, int leaderboardID) : TabbedUIDialogScreenWithGameBackground(gamePath), leaderboardID_(leaderboardID) {}
RetroAchievementsLeaderboardScreen(const Path &gamePath, int leaderboardID);
~RetroAchievementsLeaderboardScreen();
const char *tag() const override { return "RetroAchievementsLeaderboardScreen"; }
void CreateTabs() override;
void update() override;
protected:
bool ShowSearchControls() const override { return false; }
private:
void Poll();
int leaderboardID_;
bool done_ = false;
std::vector<Achievements::LeaderboardEntry> entries_;
// Keep the fetched list alive and destroy in destructor.
rc_client_leaderboard_entry_list_t *entryList_ = nullptr;
rc_client_leaderboard_entry_list_t *pendingEntryList_ = nullptr;
rc_client_async_handle_t *pendingAsyncCall_ = nullptr;
};
class UIContext;
@ -68,41 +80,66 @@ class UIContext;
enum class AchievementRenderStyle {
LISTED,
UNLOCKED,
PROGRESS_INDICATOR,
CHALLENGE_INDICATOR,
};
void MeasureAchievement(const UIContext &dc, const Achievements::Achievement &achievement, float *w, float *h);
void RenderAchievement(UIContext &dc, const Achievements::Achievement &achievement, AchievementRenderStyle style, const Bounds &bounds, float alpha, float startTime, float time_s);
void MeasureGameAchievementSummary(const UIContext &dc, int gameID, float *w, float *h);
void RenderGameAchievementSummary(UIContext &dc, int gameID, const Bounds &bounds, float alpha);
void MeasureAchievement(const UIContext &dc, const rc_client_achievement_t *achievement, AchievementRenderStyle style, float *w, float *h);
void RenderAchievement(UIContext &dc, const rc_client_achievement_t *achievement, AchievementRenderStyle style, const Bounds &bounds, float alpha, float startTime, float time_s);
void MeasureGameAchievementSummary(const UIContext &dc, float *w, float *h);
void RenderGameAchievementSummary(UIContext &dc, const Bounds &bounds, float alpha);
void MeasureLeaderboardEntry(const UIContext &dc, const rc_client_leaderboard_entry_t *entry, float *w, float *h);
void RenderLeaderboardEntry(UIContext &dc, const rc_client_leaderboard_entry_t *entry, const Bounds &bounds, float alpha);
class AchievementView : public UI::ClickableItem {
public:
AchievementView(const Achievements::Achievement &&achievement, UI::LayoutParams *layoutParams = nullptr) : UI::ClickableItem(layoutParams), achievement_(achievement) {}
AchievementView(const rc_client_achievement_t *achievement, UI::LayoutParams *layoutParams = nullptr) : UI::ClickableItem(layoutParams), achievement_(achievement) {
layoutParams_->height = UI::WRAP_CONTENT; // Override the standard Item fixed height.
}
void Click() override;
void Draw(UIContext &dc) override;
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
private:
Achievements::Achievement achievement_;
};
class LeaderboardSummaryView : public UI::ClickableItem {
public:
LeaderboardSummaryView(const Achievements::Leaderboard &&leaderboard, UI::LayoutParams *layoutParams = nullptr) : UI::ClickableItem(layoutParams), leaderboard_(leaderboard) {}
void Draw(UIContext &dc) override;
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
private:
Achievements::Leaderboard leaderboard_;
const rc_client_achievement_t *achievement_;
};
class GameAchievementSummaryView : public UI::Item {
public:
GameAchievementSummaryView(int gameID, UI::LayoutParams *layoutParams = nullptr) : UI::Item(layoutParams), gameID_(gameID) {}
GameAchievementSummaryView(UI::LayoutParams *layoutParams = nullptr) : UI::Item(layoutParams) {
layoutParams_->height = UI::WRAP_CONTENT; // Override the standard Item fixed height.
}
void Draw(UIContext &dc) override;
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
private:
int gameID_;
};
class LeaderboardSummaryView : public UI::ClickableItem {
public:
LeaderboardSummaryView(const rc_client_leaderboard_t *leaderboard, UI::LayoutParams *layoutParams = nullptr) : UI::ClickableItem(layoutParams), leaderboard_(leaderboard) {
layoutParams_->height = UI::WRAP_CONTENT; // Override the standard Item fixed height.
}
void Draw(UIContext &dc) override;
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
private:
const rc_client_leaderboard_t *leaderboard_;
};
class LeaderboardEntryView : public UI::Item {
public:
LeaderboardEntryView(const rc_client_leaderboard_entry_t *entry, bool isCurrentUser, UI::LayoutParams *layoutParams = nullptr)
: UI::Item(layoutParams), entry_(entry), isCurrentUser_(isCurrentUser) {
layoutParams_->height = UI::WRAP_CONTENT; // Override the standard Item fixed height.
}
void Draw(UIContext &dc) override;
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
private:
const rc_client_leaderboard_entry_t *entry_;
bool isCurrentUser_;
};

View file

@ -106,6 +106,9 @@
<ClInclude Include="Theme.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Core\Core.vcxproj">
<Project>{533f1d30-d04d-47cc-ad71-20f658907e36}</Project>
</ProjectReference>
<ProjectReference Include="..\ext\discord-rpc-build\discord-rpc.vcxproj">
<Project>{beb0a821-3c7f-410f-a525-63afbc69bf8f}</Project>
</ProjectReference>

View file

@ -41,6 +41,7 @@
<ClInclude Include="..\..\ext\rcheevos\include\rc_api_request.h" />
<ClInclude Include="..\..\ext\rcheevos\include\rc_api_runtime.h" />
<ClInclude Include="..\..\ext\rcheevos\include\rc_api_user.h" />
<ClInclude Include="..\..\ext\rcheevos\include\rc_client.h" />
<ClInclude Include="..\..\ext\rcheevos\include\rc_consoles.h" />
<ClInclude Include="..\..\ext\rcheevos\include\rc_error.h" />
<ClInclude Include="..\..\ext\rcheevos\include\rc_hash.h" />
@ -48,6 +49,7 @@
<ClInclude Include="..\..\ext\rcheevos\include\rc_runtime_types.h" />
<ClInclude Include="..\..\ext\rcheevos\include\rc_url.h" />
<ClInclude Include="..\..\ext\rcheevos\src\rapi\rc_api_common.h" />
<ClInclude Include="..\..\ext\rcheevos\src\rcheevos\rc_client_internal.h" />
<ClInclude Include="..\..\ext\rcheevos\src\rcheevos\rc_compat.h" />
<ClInclude Include="..\..\ext\rcheevos\src\rcheevos\rc_internal.h" />
<ClInclude Include="..\..\ext\rcheevos\src\rcheevos\rc_validate.h" />
@ -69,6 +71,7 @@
<ClCompile Include="..\..\ext\rcheevos\src\rcheevos\lboard.c" />
<ClCompile Include="..\..\ext\rcheevos\src\rcheevos\memref.c" />
<ClCompile Include="..\..\ext\rcheevos\src\rcheevos\operand.c" />
<ClCompile Include="..\..\ext\rcheevos\src\rcheevos\rc_client.c" />
<ClCompile Include="..\..\ext\rcheevos\src\rcheevos\rc_validate.c" />
<ClCompile Include="..\..\ext\rcheevos\src\rcheevos\richpresence.c" />
<ClCompile Include="..\..\ext\rcheevos\src\rcheevos\runtime.c" />
@ -103,8 +106,10 @@
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings"></ImportGroup>
<ImportGroup Label="Shared"></ImportGroup>
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>

View file

@ -55,6 +55,12 @@
<ClInclude Include="..\..\ext\rcheevos\src\rhash\md5.h">
<Filter>rhash</Filter>
</ClInclude>
<ClInclude Include="..\..\ext\rcheevos\src\rcheevos\rc_client_internal.h">
<Filter>rcheevos</Filter>
</ClInclude>
<ClInclude Include="..\..\ext\rcheevos\include\rc_client.h">
<Filter>include</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="include">
@ -140,5 +146,8 @@
<ClCompile Include="..\..\ext\rcheevos\src\rhash\md5.c">
<Filter>rhash</Filter>
</ClCompile>
<ClCompile Include="..\..\ext\rcheevos\src\rcheevos\rc_client.c">
<Filter>rcheevos</Filter>
</ClCompile>
</ItemGroup>
</Project>

View file

@ -51,6 +51,7 @@
#include "Core/HLE/sceUmd.h"
#include "Core/SaveState.h"
#include "Core/Core.h"
#include "Core/RetroAchievements.h"
extern bool g_TakeScreenshot;
@ -69,14 +70,23 @@ namespace MainWindow {
LRESULT CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM);
void SetIngameMenuItemStates(HMENU menu, const GlobalUIState state) {
UINT menuEnable = state == UISTATE_INGAME || state == UISTATE_EXCEPTION ? MF_ENABLED : MF_GRAYED;
bool menuEnableBool = state == UISTATE_INGAME || state == UISTATE_EXCEPTION;
bool saveStateEnableBool = menuEnableBool;
if (Achievements::ChallengeModeActive()) {
saveStateEnableBool = false;
}
UINT menuEnable = menuEnableBool ? MF_ENABLED : MF_GRAYED;
UINT saveStateEnable = saveStateEnableBool ? MF_ENABLED : MF_GRAYED;
UINT menuInGameEnable = state == UISTATE_INGAME ? MF_ENABLED : MF_GRAYED;
UINT umdSwitchEnable = state == UISTATE_INGAME && getUMDReplacePermit() ? MF_ENABLED : MF_GRAYED;
EnableMenuItem(menu, ID_FILE_SAVESTATEFILE, menuEnable);
EnableMenuItem(menu, ID_FILE_LOADSTATEFILE, menuEnable);
EnableMenuItem(menu, ID_FILE_QUICKSAVESTATE, menuEnable);
EnableMenuItem(menu, ID_FILE_QUICKLOADSTATE, menuEnable);
EnableMenuItem(menu, ID_FILE_SAVESTATE_SLOT_MENU, saveStateEnable);
EnableMenuItem(menu, ID_FILE_SAVESTATEFILE, saveStateEnable);
EnableMenuItem(menu, ID_FILE_LOADSTATEFILE, saveStateEnable);
EnableMenuItem(menu, ID_FILE_QUICKSAVESTATE, saveStateEnable);
EnableMenuItem(menu, ID_FILE_QUICKLOADSTATE, saveStateEnable);
EnableMenuItem(menu, ID_EMULATION_PAUSE, menuEnable);
EnableMenuItem(menu, ID_EMULATION_STOP, menuEnable);
EnableMenuItem(menu, ID_EMULATION_RESET, menuEnable);
@ -517,70 +527,87 @@ namespace MainWindow {
}
break;
case ID_FILE_LOADSTATEFILE:
if (W32Util::BrowseForFileName(true, hWnd, L"Load state", 0, L"Save States (*.ppst)\0*.ppst\0All files\0*.*\0\0", L"ppst", fn)) {
SetCursor(LoadCursor(0, IDC_WAIT));
SaveState::Load(Path(fn), -1, SaveStateActionFinished);
if (!Achievements::WarnUserIfChallengeModeActive()) {
if (W32Util::BrowseForFileName(true, hWnd, L"Load state", 0, L"Save States (*.ppst)\0*.ppst\0All files\0*.*\0\0", L"ppst", fn)) {
SetCursor(LoadCursor(0, IDC_WAIT));
SaveState::Load(Path(fn), -1, SaveStateActionFinished);
}
}
break;
case ID_FILE_SAVESTATEFILE:
if (W32Util::BrowseForFileName(false, hWnd, L"Save state", 0, L"Save States (*.ppst)\0*.ppst\0All files\0*.*\0\0", L"ppst", fn)) {
SetCursor(LoadCursor(0, IDC_WAIT));
SaveState::Save(Path(fn), -1, SaveStateActionFinished);
if (!Achievements::WarnUserIfChallengeModeActive()) {
if (W32Util::BrowseForFileName(false, hWnd, L"Save state", 0, L"Save States (*.ppst)\0*.ppst\0All files\0*.*\0\0", L"ppst", fn)) {
SetCursor(LoadCursor(0, IDC_WAIT));
SaveState::Save(Path(fn), -1, SaveStateActionFinished);
}
}
break;
case ID_FILE_SAVESTATE_NEXT_SLOT:
{
SaveState::NextSlot();
NativeMessageReceived("savestate_displayslot", "");
break;
}
case ID_FILE_SAVESTATE_NEXT_SLOT_HC:
{
if (!KeyMap::PspButtonHasMappings(VIRTKEY_NEXT_SLOT)) {
if (!Achievements::WarnUserIfChallengeModeActive()) {
SaveState::NextSlot();
NativeMessageReceived("savestate_displayslot", "");
}
break;
}
case ID_FILE_SAVESTATE_SLOT_1: g_Config.iCurrentStateSlot = 0; break;
case ID_FILE_SAVESTATE_SLOT_2: g_Config.iCurrentStateSlot = 1; break;
case ID_FILE_SAVESTATE_SLOT_3: g_Config.iCurrentStateSlot = 2; break;
case ID_FILE_SAVESTATE_SLOT_4: g_Config.iCurrentStateSlot = 3; break;
case ID_FILE_SAVESTATE_SLOT_5: g_Config.iCurrentStateSlot = 4; break;
case ID_FILE_QUICKLOADSTATE:
case ID_FILE_SAVESTATE_NEXT_SLOT_HC:
{
SetCursor(LoadCursor(0, IDC_WAIT));
SaveState::LoadSlot(PSP_CoreParameter().fileToStart, g_Config.iCurrentStateSlot, SaveStateActionFinished);
if (!Achievements::WarnUserIfChallengeModeActive()) {
if (!KeyMap::PspButtonHasMappings(VIRTKEY_NEXT_SLOT)) {
SaveState::NextSlot();
NativeMessageReceived("savestate_displayslot", "");
}
}
break;
}
case ID_FILE_QUICKLOADSTATE_HC:
{
if (!KeyMap::PspButtonHasMappings(VIRTKEY_LOAD_STATE))
{
case ID_FILE_SAVESTATE_SLOT_1:
case ID_FILE_SAVESTATE_SLOT_2:
case ID_FILE_SAVESTATE_SLOT_3:
case ID_FILE_SAVESTATE_SLOT_4:
case ID_FILE_SAVESTATE_SLOT_5:
if (!Achievements::WarnUserIfChallengeModeActive()) {
g_Config.iCurrentStateSlot = wmId - ID_FILE_SAVESTATE_SLOT_1;
}
break;
case ID_FILE_QUICKLOADSTATE:
if (!Achievements::WarnUserIfChallengeModeActive()) {
SetCursor(LoadCursor(0, IDC_WAIT));
SaveState::LoadSlot(PSP_CoreParameter().fileToStart, g_Config.iCurrentStateSlot, SaveStateActionFinished);
}
break;
case ID_FILE_QUICKLOADSTATE_HC:
{
if (!Achievements::WarnUserIfChallengeModeActive()) {
if (!KeyMap::PspButtonHasMappings(VIRTKEY_LOAD_STATE)) {
SetCursor(LoadCursor(0, IDC_WAIT));
SaveState::LoadSlot(PSP_CoreParameter().fileToStart, g_Config.iCurrentStateSlot, SaveStateActionFinished);
}
}
break;
}
case ID_FILE_QUICKSAVESTATE:
{
SetCursor(LoadCursor(0, IDC_WAIT));
SaveState::SaveSlot(PSP_CoreParameter().fileToStart, g_Config.iCurrentStateSlot, SaveStateActionFinished);
if (!Achievements::WarnUserIfChallengeModeActive()) {
SetCursor(LoadCursor(0, IDC_WAIT));
SaveState::SaveSlot(PSP_CoreParameter().fileToStart, g_Config.iCurrentStateSlot, SaveStateActionFinished);
}
break;
}
case ID_FILE_QUICKSAVESTATE_HC:
{
if (!KeyMap::PspButtonHasMappings(VIRTKEY_SAVE_STATE))
{
SetCursor(LoadCursor(0, IDC_WAIT));
SaveState::SaveSlot(PSP_CoreParameter().fileToStart, g_Config.iCurrentStateSlot, SaveStateActionFinished);
break;
if (!Achievements::WarnUserIfChallengeModeActive()) {
if (!KeyMap::PspButtonHasMappings(VIRTKEY_SAVE_STATE))
{
SetCursor(LoadCursor(0, IDC_WAIT));
SaveState::SaveSlot(PSP_CoreParameter().fileToStart, g_Config.iCurrentStateSlot, SaveStateActionFinished);
break;
}
}
break;
}

View file

@ -95,6 +95,7 @@ RCHEEVOS_FILES := \
${SRC}/ext/rcheevos/src/rcheevos/lboard.c \
${SRC}/ext/rcheevos/src/rcheevos/memref.c \
${SRC}/ext/rcheevos/src/rcheevos/operand.c \
${SRC}/ext/rcheevos/src/rcheevos/rc_client.c \
${SRC}/ext/rcheevos/src/rcheevos/rc_validate.c \
${SRC}/ext/rcheevos/src/rcheevos/richpresence.c \
${SRC}/ext/rcheevos/src/rcheevos/runtime.c \
@ -103,8 +104,7 @@ RCHEEVOS_FILES := \
${SRC}/ext/rcheevos/src/rcheevos/value.c \
${SRC}/ext/rcheevos/src/rhash/cdreader.c \
${SRC}/ext/rcheevos/src/rhash/hash.c \
${SRC}/ext/rcheevos/src/rhash/md5.c \
${SRC}/ext/rcheevos/src/rurl/url.c
${SRC}/ext/rcheevos/src/rhash/md5.c
VR_FILES := \
$(SRC)/Common/VR/OpenXRLoader.cpp \

View file

@ -24,23 +24,30 @@
[Achievements]
%d achievements, %d points = %d achievements, %d points
%1: Leaderboard attempt started = %1: Leaderboard attempt started
%1: Leaderboard attempt failed = %1: Leaderboard attempt failed
%1: Submitting leaderboard score: %2! = %1: Submitting leaderboard score: %2!
Account = Account
Achievements = Achievements
Achievements are disabled = Achievements are disabled
Challenge Mode = Challenge Mode
Challenge Mode (no savestates) = Challenge Mode (no savestates)
Earned = You have earned %d of %d achievements, and %d of %d points
Encore Mode = Encore Mode
How to use RetroAchievements = How to use RetroAchievements
Leaderboard submission is enabled = Leaderboard submission is enabled
Leaderboards = Leaderboards
Links = Links
Local = Local
Locked achievements = Locked achievements
Log bad memory accesses = Log bad memory accesses
Mastered %1 = Mastered %1
This feature is not available in Challenge Mode = This feature is not available in Challenge Mode
Save states not available in Challenge Mode = Save states not available in Challenge Mode
Register on www.retroachievements.org = Register on www.retroachievements.org
RetroAchievements are not available for this game = RetroAchievements are not available for this game
RetroAchievements website = RetroAchievements website
Rich Presence = Rich Presence
Save state loaded without achievement data = Save state loaded without achievement data
Sound Effects = Sound Effects
Statistics = Statistics
Submitted Score = Submitted Score
@ -49,7 +56,7 @@ Syncing achievements data... = Syncing achievements data...
Test Mode = Test Mode
This game has no achievements = This game has no achievements
Unlocked achievements = Unlocked achievements
Unofficial = Unofficial
Unofficial achievements = Unofficial achievements
[Audio]
Alternate speed volume = Alternate speed volume

@ -1 +1 @@
Subproject commit d176b4bcb42488da84f55991fd10f8ff36183384
Subproject commit 86374e5731bb2f671a535a585b06afb03a17e047

View file

@ -23,6 +23,8 @@ set(ALL_SOURCE_FILES
${SRC_DIR}/rcheevos/memref.c
${SRC_DIR}/rcheevos/operand.c
${SRC_DIR}/rcheevos/rc_compat.h
${SRC_DIR}/rcheevos/rc_client.c
${SRC_DIR}/rcheevos/rc_client_internal.h
${SRC_DIR}/rcheevos/rc_internal.h
${SRC_DIR}/rcheevos/rc_validate.h
${SRC_DIR}/rcheevos/rc_validate.c
@ -37,8 +39,6 @@ set(ALL_SOURCE_FILES
${SRC_DIR}/rhash/hash.c
${SRC_DIR}/rhash/md5.c
${SRC_DIR}/rhash/md5.h
# rurl
${SRC_DIR}/rurl/url.c
)
add_library(rcheevos STATIC ${ALL_SOURCE_FILES})

View file

@ -49,6 +49,7 @@
<ClCompile Include="..\rcheevos\src\rcheevos\lboard.c" />
<ClCompile Include="..\rcheevos\src\rcheevos\memref.c" />
<ClCompile Include="..\rcheevos\src\rcheevos\operand.c" />
<ClCompile Include="..\rcheevos\src\rcheevos\rc_client.c" />
<ClCompile Include="..\rcheevos\src\rcheevos\rc_validate.c" />
<ClCompile Include="..\rcheevos\src\rcheevos\richpresence.c" />
<ClCompile Include="..\rcheevos\src\rcheevos\runtime.c" />
@ -58,7 +59,6 @@
<ClCompile Include="..\rcheevos\src\rhash\cdreader.c" />
<ClCompile Include="..\rcheevos\src\rhash\hash.c" />
<ClCompile Include="..\rcheevos\src\rhash\md5.c" />
<ClCompile Include="..\rcheevos\src\rurl\url.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\rcheevos\include\rcheevos.h" />
@ -67,6 +67,7 @@
<ClInclude Include="..\rcheevos\include\rc_api_request.h" />
<ClInclude Include="..\rcheevos\include\rc_api_runtime.h" />
<ClInclude Include="..\rcheevos\include\rc_api_user.h" />
<ClInclude Include="..\rcheevos\include\rc_client.h" />
<ClInclude Include="..\rcheevos\include\rc_consoles.h" />
<ClInclude Include="..\rcheevos\include\rc_error.h" />
<ClInclude Include="..\rcheevos\include\rc_hash.h" />
@ -74,6 +75,7 @@
<ClInclude Include="..\rcheevos\include\rc_runtime_types.h" />
<ClInclude Include="..\rcheevos\include\rc_url.h" />
<ClInclude Include="..\rcheevos\src\rapi\rc_api_common.h" />
<ClInclude Include="..\rcheevos\src\rcheevos\rc_client_internal.h" />
<ClInclude Include="..\rcheevos\src\rcheevos\rc_compat.h" />
<ClInclude Include="..\rcheevos\src\rcheevos\rc_internal.h" />
<ClInclude Include="..\rcheevos\src\rcheevos\rc_validate.h" />

View file

@ -10,9 +10,6 @@
<Filter Include="rhash">
<UniqueIdentifier>{55ec06aa-6d34-4edf-8b0b-f93f93a064b4}</UniqueIdentifier>
</Filter>
<Filter Include="rurl">
<UniqueIdentifier>{990047ac-f0d4-44f5-8463-54f898557d02}</UniqueIdentifier>
</Filter>
<Filter Include="include">
<UniqueIdentifier>{9e049d1f-4b83-4aa5-89f3-01a42e1773e2}</UniqueIdentifier>
</Filter>
@ -87,8 +84,8 @@
<ClCompile Include="..\rcheevos\src\rhash\md5.c">
<Filter>rhash</Filter>
</ClCompile>
<ClCompile Include="..\rcheevos\src\rurl\url.c">
<Filter>rurl</Filter>
<ClCompile Include="..\rcheevos\src\rcheevos\rc_client.c">
<Filter>rcheevos</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
@ -146,5 +143,11 @@
<ClInclude Include="..\rcheevos\src\rhash\md5.h">
<Filter>rhash</Filter>
</ClInclude>
<ClInclude Include="..\rcheevos\include\rc_client.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="..\rcheevos\src\rcheevos\rc_client_internal.h">
<Filter>rcheevos</Filter>
</ClInclude>
</ItemGroup>
</Project>

View file

@ -143,6 +143,12 @@ void System_AudioGetDebugStats(char *buf, size_t bufSize) { if (buf) buf[0] = '\
void System_AudioClear() {}
void System_AudioPushSamples(const s32 *audio, int numSamples) {}
// TODO: To avoid having to define these here, these should probably be turned into system "requests".
void NativeSaveSecret(const char *nameOfSecret, const std::string &data) {}
std::string NativeLoadSecret(const char *nameOfSecret) {
return "";
}
int printUsage(const char *progname, const char *reason)
{
if (reason != NULL)

View file

@ -508,6 +508,9 @@
<ProjectReference Include="..\ext\libzstd.vcxproj">
<Project>{8bfd8150-94d5-4bf9-8a50-7bd9929a0850}</Project>
</ProjectReference>
<ProjectReference Include="..\ext\rcheevos-build\rcheevos.vcxproj">
<Project>{31694510-a8c0-40f6-b09b-e8df825adefa}</Project>
</ProjectReference>
<ProjectReference Include="..\GPU\GPU.vcxproj">
<Project>{457f45d2-556f-47bc-a31d-aff0d15beaed}</Project>
</ProjectReference>

View file

@ -212,6 +212,7 @@ SOURCES_C += \
$(EXTDIR)/rcheevos/src/rcheevos/lboard.c \
$(EXTDIR)/rcheevos/src/rcheevos/memref.c \
$(EXTDIR)/rcheevos/src/rcheevos/operand.c \
$(EXTDIR)/rcheevos/src/rcheevos/rc_client.c \
$(EXTDIR)/rcheevos/src/rcheevos/rc_validate.c \
$(EXTDIR)/rcheevos/src/rcheevos/richpresence.c \
$(EXTDIR)/rcheevos/src/rcheevos/runtime.c \

View file

@ -103,6 +103,12 @@ void System_AudioGetDebugStats(char *buf, size_t bufSize) { if (buf) buf[0] = '\
void System_AudioClear() {}
void System_AudioPushSamples(const s32 *audio, int numSamples) {}
// TODO: To avoid having to define these here, these should probably be turned into system "requests".
void NativeSaveSecret(const char *nameOfSecret, const std::string &data) {}
std::string NativeLoadSecret(const char *nameOfSecret) {
return "";
}
#if PPSSPP_PLATFORM(ANDROID)
JNIEnv *getEnv() {
return nullptr;

View file

@ -426,15 +426,15 @@
<ProjectReference Include="..\ext\libzstd.vcxproj">
<Project>{8bfd8150-94d5-4bf9-8a50-7bd9929a0850}</Project>
</ProjectReference>
<ProjectReference Include="..\ext\rcheevos-build\rcheevos.vcxproj">
<Project>{31694510-a8c0-40f6-b09b-e8df825adefa}</Project>
</ProjectReference>
<ProjectReference Include="..\ext\zlib\zlib.vcxproj">
<Project>{f761046e-6c38-4428-a5f1-38391a37bb34}</Project>
</ProjectReference>
<ProjectReference Include="..\GPU\GPU.vcxproj">
<Project>{457f45d2-556f-47bc-a31d-aff0d15beaed}</Project>
</ProjectReference>
<ProjectReference Include="..\UI\UI.vcxproj">
<Project>{004b8d11-2be3-4bd9-ab40-2be04cf2096f}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClInclude Include="JitHarness.h" />