use rc_client for runtime (#1029)

This commit is contained in:
Jamiras
2023-09-30 17:24:16 -06:00
committed by GitHub
parent 2fe0b2b77f
commit 0cd8bead9f
69 changed files with 5637 additions and 7575 deletions
+22 -18
View File
@@ -2,16 +2,32 @@
#Some settings are unavailable but should be already taken care of
Language: Cpp
Standard: Cpp11
MaxEmptyLinesToKeep: 1
ColumnLimit: 120 #should between 80-120 so it'll be easier to see diffs
UseTab: Never
IndentWidth: 4
SortIncludes: true
IncludeBlocks: Preserve #Sorts includes separately
SortUsingDeclarations: true
CompactNamespaces: false #Puts em all on the same line
FixNamespaceComments: true
NamespaceIndentation: None
AlignAfterOpenBracket: true
AlignEscapedNewlinesLeft: true
AlignTrailingComments: true
AlignConsecutiveAssignments: false
IndentCaseLabels: true
AllowShortCaseLabelsOnASingleLine: false
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortFunctionsOnASingleLine: All
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: true
@@ -30,25 +46,14 @@ BraceWrapping:
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: AfterColon
ColumnLimit: 120 #should between 80-120 so it'll be easier to see diffs
ConstructorInitializerAllOnOneLineOrOnePerLine: true
Cpp11BracedListStyle: true
DerivePointerAlignment: true #Tries to stay consistent with existing code
ExperimentalAutoDetectBinPacking: true
IndentCaseLabels: true
IndentWidth: 4
IndentWrappedFunctionNames: true
KeepEmptyLinesAtTheStartOfBlocks: false
Language: Cpp
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PointerAlignment: Left
ReflowComments: true
Standard: Cpp11
UseTab: Never
AccessModifierOffset: -4
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
@@ -59,8 +64,7 @@ SpacesInParentheses: false
SpacesInCStyleCastParentheses: false
SpacesInSquareBrackets: false
SortUsingDeclarations: true
SortIncludes: true
IncludeBlocks: Preserve #Sorts includes separately
FixNamespaceComments: true
CompactNamespaces: false #Puts em all on the same line
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: Always
IndentAccessModifiers: false
AccessModifierOffset: -4
+17 -304
View File
@@ -23,7 +23,6 @@
#include "services\IFileSystem.hh"
#include "services\Initialization.hh"
#include "services\PerformanceCounter.hh"
#include "services\RcheevosClient.hh"
#include "services\ServiceLocator.hh"
#include "ui\drawing\gdi\GDISurface.hh"
@@ -108,7 +107,7 @@ static void InitializeOfflineMode()
pSessionTracker.Initialize(sUsername);
}
auto* pClient = ra::services::ServiceLocator::Get<ra::services::RcheevosClient>().GetClient();
auto* pClient = ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>().GetClient();
pClient->user.username = rc_buf_strcpy(&pClient->state.buffer, pUserContext.GetUsername().c_str());
pClient->user.display_name = rc_buf_strcpy(&pClient->state.buffer, pUserContext.GetDisplayName().c_str());
pClient->user.token = rc_buf_strcpy(&pClient->state.buffer, pConfiguration.GetApiToken().c_str());
@@ -325,15 +324,15 @@ API void CCONV _RA_AttemptLogin(int bBlocking)
}
else
{
auto& pClient = ra::services::ServiceLocator::GetMutable<ra::services::RcheevosClient>();
auto& pClient = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
if (bBlocking)
{
ra::services::RcheevosClient::Synchronizer pSynchronizer;
ra::services::AchievementRuntime::Synchronizer pSynchronizer;
pClient.BeginLoginWithToken(pConfiguration.GetUsername(), pConfiguration.GetApiToken(),
[](int nResult, const char* sErrorMessage, rc_client_t*, void* pUserdata) {
auto* pSynchronizer = static_cast<ra::services::RcheevosClient::Synchronizer*>(pUserdata);
auto* pSynchronizer = static_cast<ra::services::AchievementRuntime::Synchronizer*>(pUserdata);
Expects(pSynchronizer != nullptr);
pSynchronizer->CaptureResult(nResult, sErrorMessage);
@@ -443,300 +442,6 @@ API void _RA_NavigateOverlay(const ControllerInput* pInput)
ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>().Update(*pInput);
}
static void ProcessAchievements()
{
auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
if (pRuntime.IsPaused())
return;
auto& pGameContext = ra::services::ServiceLocator::GetMutable<ra::data::context::GameContext>();
TALLY_PERFORMANCE(PerformanceCheckpoint::RuntimeProcess);
std::vector<ra::services::AchievementRuntime::Change> vChanges;
pRuntime.Process(vChanges);
const ra::data::models::AchievementModel* vmMeasuredAchievement = nullptr;
const rc_trigger_t* pMeasuredAchievementTrigger = nullptr;
float fMeasuredAchievementPercent = 0.0;
TALLY_PERFORMANCE(PerformanceCheckpoint::RuntimeEvents);
for (const auto& pChange : vChanges)
{
switch (pChange.nType)
{
case ra::services::AchievementRuntime::ChangeType::AchievementReset:
{
const auto* pAchievement = pGameContext.Assets().FindAchievement(pChange.nId);
if (pAchievement && pAchievement->IsPauseOnReset())
{
auto& pFrameEventQueue = ra::services::ServiceLocator::GetMutable<ra::services::FrameEventQueue>();
pFrameEventQueue.QueuePauseOnReset(pAchievement->GetName());
}
break;
}
case ra::services::AchievementRuntime::ChangeType::AchievementTriggered:
{
// AwardAchievement may detach the trigger, which would result in the state changing to Inactive.
// explicitly set the state to Triggered before the trigger gets detached.
auto* vmAchievement = pGameContext.Assets().FindAchievement(pChange.nId);
if (vmAchievement)
vmAchievement->SetState(ra::data::models::AssetState::Triggered);
pGameContext.AwardAchievement(pChange.nId);
if (vmAchievement && vmAchievement->IsPauseOnTrigger())
{
auto& pFrameEventQueue = ra::services::ServiceLocator::GetMutable<ra::services::FrameEventQueue>();
pFrameEventQueue.QueuePauseOnTrigger(vmAchievement->GetName());
}
break;
}
case ra::services::AchievementRuntime::ChangeType::AchievementDisabled:
{
auto* vmAchievement = pGameContext.Assets().FindAchievement(pChange.nId);
if (vmAchievement)
{
RA_LOG_WARN("Achievement #%u disabled: address %s not supported by current core", pChange.nId, ra::ByteAddressToString(pChange.nValue));
vmAchievement->SetState(ra::data::models::AssetState::Disabled);
}
break;
}
case ra::services::AchievementRuntime::ChangeType::AchievementPrimed:
{
const auto* vmAchievement = pGameContext.Assets().FindAchievement(pChange.nId);
if (vmAchievement)
{
const auto& pConfiguration = ra::services::ServiceLocator::Get<ra::services::IConfiguration>();
if (pConfiguration.GetPopupLocation(ra::ui::viewmodels::Popup::Challenge) != ra::ui::viewmodels::PopupLocation::None)
{
auto& pOverlayManager = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>();
pOverlayManager.AddChallengeIndicator(vmAchievement->GetID(), ra::ui::ImageType::Badge, ra::Narrow(vmAchievement->GetBadge()));
}
}
break;
}
case ra::services::AchievementRuntime::ChangeType::AchievementUnprimed:
{
auto& pOverlayManager = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>();
pOverlayManager.RemoveChallengeIndicator(pChange.nId);
break;
}
case ra::services::AchievementRuntime::ChangeType::LeaderboardStarted:
{
const auto* pLeaderboard = pGameContext.Assets().FindLeaderboard(pChange.nId);
if (pLeaderboard)
{
const auto& pConfiguration = ra::services::ServiceLocator::Get<ra::services::IConfiguration>();
if (pConfiguration.GetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardStarted) != ra::ui::viewmodels::PopupLocation::None)
{
ra::services::ServiceLocator::Get<ra::services::IAudioSystem>().PlayAudioFile(L"Overlay\\lb.wav");
auto& pOverlayManager = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>();
pOverlayManager.QueueMessage(ra::ui::viewmodels::Popup::LeaderboardStarted,
L"Leaderboard attempt started", pLeaderboard->GetName(), pLeaderboard->GetDescription());
}
if (pConfiguration.GetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardTracker) != ra::ui::viewmodels::PopupLocation::None)
{
auto& pOverlayManager = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>();
auto& pScoreTracker = pOverlayManager.AddScoreTracker(pLeaderboard->GetID());
const auto sDisplayText = pLeaderboard->FormatScore(pChange.nValue);
pScoreTracker.SetDisplayText(ra::Widen(sDisplayText));
}
}
break;
}
case ra::services::AchievementRuntime::ChangeType::LeaderboardUpdated:
{
const auto* pLeaderboard = pGameContext.Assets().FindLeaderboard(pChange.nId);
if (pLeaderboard)
{
auto& pOverlayManager = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>();
auto* pScoreTracker = pOverlayManager.GetScoreTracker(pChange.nId);
if (pScoreTracker != nullptr)
{
const auto sDisplayText = pLeaderboard->FormatScore(pChange.nValue);
pScoreTracker->SetDisplayText(ra::Widen(sDisplayText));
}
}
break;
}
case ra::services::AchievementRuntime::ChangeType::LeaderboardCanceled:
{
const auto* pLeaderboard = pGameContext.Assets().FindLeaderboard(pChange.nId);
if (pLeaderboard)
{
const auto& pConfiguration = ra::services::ServiceLocator::Get<ra::services::IConfiguration>();
if (pConfiguration.GetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardCanceled) != ra::ui::viewmodels::PopupLocation::None)
{
ra::services::ServiceLocator::Get<ra::services::IAudioSystem>().PlayAudioFile(L"Overlay\\lbcancel.wav");
auto& pOverlayManager = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>();
pOverlayManager.QueueMessage(ra::ui::viewmodels::Popup::LeaderboardCanceled,
L"Leaderboard attempt failed", pLeaderboard->GetName(), pLeaderboard->GetDescription());
}
auto& pOverlayManager = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>();
pOverlayManager.RemoveScoreTracker(pLeaderboard->GetID());
}
break;
}
case ra::services::AchievementRuntime::ChangeType::LeaderboardTriggered:
{
pGameContext.SubmitLeaderboardEntry(pChange.nId, pChange.nValue); // will show the scoreboard when submission completes
auto& pOverlayManager = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>();
pOverlayManager.RemoveScoreTracker(pChange.nId);
break;
}
case ra::services::AchievementRuntime::ChangeType::LeaderboardStartReset:
{
/* ASSERT: this event is only raised if the leaderboard is watching for it */
const auto* pLeaderboard = pGameContext.Assets().FindLeaderboard(pChange.nId);
if (pLeaderboard)
{
auto& pFrameEventQueue = ra::services::ServiceLocator::GetMutable<ra::services::FrameEventQueue>();
pFrameEventQueue.QueuePauseOnReset(L"Start: " + pLeaderboard->GetName());
}
break;
}
case ra::services::AchievementRuntime::ChangeType::LeaderboardSubmitReset:
{
/* ASSERT: this event is only raised if the leaderboard is watching for it */
const auto* pLeaderboard = pGameContext.Assets().FindLeaderboard(pChange.nId);
if (pLeaderboard)
{
auto& pFrameEventQueue = ra::services::ServiceLocator::GetMutable<ra::services::FrameEventQueue>();
pFrameEventQueue.QueuePauseOnReset(L"Submit: " + pLeaderboard->GetName());
}
break;
}
case ra::services::AchievementRuntime::ChangeType::LeaderboardCancelReset:
{
/* ASSERT: this event is only raised if the leaderboard is watching for it */
const auto* pLeaderboard = pGameContext.Assets().FindLeaderboard(pChange.nId);
if (pLeaderboard)
{
auto& pFrameEventQueue = ra::services::ServiceLocator::GetMutable<ra::services::FrameEventQueue>();
pFrameEventQueue.QueuePauseOnReset(L"Cancel: " + pLeaderboard->GetName());
}
break;
}
case ra::services::AchievementRuntime::ChangeType::LeaderboardValueReset:
{
/* ASSERT: this event is only raised if the leaderboard is watching for it */
const auto* pLeaderboard = pGameContext.Assets().FindLeaderboard(pChange.nId);
if (pLeaderboard)
{
auto& pFrameEventQueue = ra::services::ServiceLocator::GetMutable<ra::services::FrameEventQueue>();
pFrameEventQueue.QueuePauseOnReset(L"Value: " + pLeaderboard->GetName());
}
break;
}
case ra::services::AchievementRuntime::ChangeType::LeaderboardStartTriggered:
{
/* ASSERT: this event is only raised if the leaderboard is watching for it */
const auto* pLeaderboard = pGameContext.Assets().FindLeaderboard(pChange.nId);
if (pLeaderboard)
{
auto& pFrameEventQueue = ra::services::ServiceLocator::GetMutable<ra::services::FrameEventQueue>();
pFrameEventQueue.QueuePauseOnTrigger(L"Start: " + pLeaderboard->GetName());
}
break;
}
case ra::services::AchievementRuntime::ChangeType::LeaderboardSubmitTriggered:
{
/* ASSERT: this event is only raised if the leaderboard is watching for it */
const auto* pLeaderboard = pGameContext.Assets().FindLeaderboard(pChange.nId);
if (pLeaderboard)
{
auto& pFrameEventQueue = ra::services::ServiceLocator::GetMutable<ra::services::FrameEventQueue>();
pFrameEventQueue.QueuePauseOnTrigger(L"Submit: " + pLeaderboard->GetName());
}
break;
}
case ra::services::AchievementRuntime::ChangeType::LeaderboardCancelTriggered:
{
/* ASSERT: this event is only raised if the leaderboard is watching for it */
const auto* pLeaderboard = pGameContext.Assets().FindLeaderboard(pChange.nId);
if (pLeaderboard)
{
auto& pFrameEventQueue = ra::services::ServiceLocator::GetMutable<ra::services::FrameEventQueue>();
pFrameEventQueue.QueuePauseOnTrigger(L"Cancel: " + pLeaderboard->GetName());
}
break;
}
case ra::services::AchievementRuntime::ChangeType::AchievementProgressChanged:
{
const auto* pTrigger = pRuntime.GetAchievementTrigger(pChange.nId);
if (pTrigger != nullptr && pTrigger->measured_target > 0)
{
const auto& pConfiguration = ra::services::ServiceLocator::Get<ra::services::IConfiguration>();
if (pConfiguration.GetPopupLocation(ra::ui::viewmodels::Popup::Progress) != ra::ui::viewmodels::PopupLocation::None)
{
const auto* vmAchievement = pGameContext.Assets().FindAchievement(pChange.nId);
if (vmAchievement != nullptr)
{
const float fProgress = gsl::narrow_cast<float>(pTrigger->measured_value) / gsl::narrow_cast<float>(pTrigger->measured_target);
if (fProgress > fMeasuredAchievementPercent)
{
fMeasuredAchievementPercent = fProgress;
vmMeasuredAchievement = vmAchievement;
pMeasuredAchievementTrigger = pTrigger;
}
}
}
}
break;
}
}
}
if (vmMeasuredAchievement && pMeasuredAchievementTrigger)
{
auto& pOverlayManager = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>();
// use locked badge if available
auto sBadgeName = ra::Narrow(vmMeasuredAchievement->GetBadge());
if (!ra::StringStartsWith(sBadgeName, "local\\"))
sBadgeName += "_lock";
pOverlayManager.UpdateProgressTracker(ra::ui::ImageType::Badge, sBadgeName,
pMeasuredAchievementTrigger->measured_value, pMeasuredAchievementTrigger->measured_target,
pMeasuredAchievementTrigger->measured_as_percent);
}
}
API void CCONV _RA_SetPaused(int bIsPaused)
{
auto& pOverlayManager = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>();
@@ -782,8 +487,11 @@ API void CCONV _RA_DoAchievementsFrame()
ra::ui::win32::bindings::ControlBinding::RepaintGuard guard;
#endif
// make sure we process the achievements _before_ the frozen bookmarks modify the memory
ProcessAchievements();
// make sure we process the achievements _before_ updating the UI.
// the frozen bookmarks may modify the memory
TALLY_PERFORMANCE(PerformanceCheckpoint::RuntimeProcess);
auto& pRcheevosClient = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
pRcheevosClient.DoFrame();
#ifndef RA_UTEST
UpdateUIForFrameChange();
@@ -820,7 +528,9 @@ API void CCONV _RA_OnSaveState(const char* sFilename)
API int CCONV _RA_CaptureState(char* pBuffer, int nBufferSize)
{
return ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>().SaveProgressToBuffer(pBuffer, nBufferSize);
GSL_SUPPRESS_TYPE1
return ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>().
SaveProgressToBuffer(reinterpret_cast<uint8_t*>(pBuffer), nBufferSize);
}
static bool CanRestoreState()
@@ -847,6 +557,7 @@ static bool CanRestoreState()
static void OnStateRestored()
{
// TODO: rc_client should handle the UI - only need to sync assets
const auto& pRuntime = ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>();
auto& pConfiguration = ra::services::ServiceLocator::GetMutable<ra::services::IConfiguration>();
@@ -891,7 +602,7 @@ static void OnStateRestored()
{
auto& pScoreTracker = pOverlayManager.AddScoreTracker(pLeaderboard->GetID());
const auto pDefinition = pRuntime.GetLeaderboardDefinition(pLeaderboard->GetID());
const auto* pDefinition = pRuntime.GetLeaderboardDefinition(pLeaderboard->GetID());
if (pDefinition != nullptr)
{
const auto sDisplayText = pLeaderboard->FormatScore(pDefinition->value.value.value);
@@ -923,7 +634,9 @@ API void CCONV _RA_RestoreState(const char* pBuffer)
{
if (CanRestoreState())
{
ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().LoadProgressFromBuffer(pBuffer);
GSL_SUPPRESS_TYPE1
ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().
LoadProgressFromBuffer(reinterpret_cast<const uint8_t*>(pBuffer));
OnStateRestored();
}
}
+2 -3
View File
@@ -11,7 +11,6 @@
#include "services\IConfiguration.hh"
#include "services\IThreadPool.hh"
#include "services\Initialization.hh"
#include "services\RcheevosClient.hh"
#include "services\ServiceLocator.hh"
#include "ui\IDesktop.hh"
@@ -147,7 +146,7 @@ API int CCONV _RA_Shutdown()
pDesktop.CloseWindow(pWindowManager.PointerFinder);
}
ra::services::ServiceLocator::GetMutable<ra::services::RcheevosClient>().Shutdown();
ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().Shutdown();
ra::services::Initialization::Shutdown();
@@ -214,7 +213,7 @@ API void CCONV _RA_OnReset()
// Temporarily disable achievements while the system is resetting. They will automatically re-enable when
// DoAchievementsFrame is called if the trigger is not active. Prevents most unexpected triggering caused
// by resetting the emulator.
ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().ResetActiveAchievements();
ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().ResetRuntime();
ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>().ClearPopups();
}
-6
View File
@@ -96,7 +96,6 @@
<ClCompile Include="services\impl\WindowsHttpRequester.cpp" />
<ClCompile Include="services\Initialization.cpp" />
<ClCompile Include="services\PerformanceCounter.cpp" />
<ClCompile Include="services\RcheevosClient.cpp" />
<ClCompile Include="services\SearchResults.cpp" />
<ClCompile Include="ui\drawing\gdi\GDIBitmapSurface.cpp" />
<ClCompile Include="ui\drawing\gdi\GDISurface.cpp" />
@@ -175,7 +174,6 @@
<ClInclude Include="..\RAInterface\RA_Emulators.h" />
<ClInclude Include="..\RAInterface\RA_Interface.h" />
<ClInclude Include="api\ApiCall.hh" />
<ClInclude Include="api\AwardAchievement.hh" />
<ClInclude Include="api\DeleteCodeNote.hh" />
<ClInclude Include="api\FetchAchievementInfo.hh" />
<ClInclude Include="api\FetchBadgeIds.hh" />
@@ -189,10 +187,7 @@
<ClInclude Include="api\impl\ServerBase.hh" />
<ClInclude Include="api\IServer.hh" />
<ClInclude Include="api\LatestClient.hh" />
<ClInclude Include="api\Ping.hh" />
<ClInclude Include="api\ResolveHash.hh" />
<ClInclude Include="api\StartSession.hh" />
<ClInclude Include="api\SubmitLeaderboardEntry.hh" />
<ClInclude Include="api\SubmitNewTitle.hh" />
<ClInclude Include="api\SubmitTicket.hh" />
<ClInclude Include="api\UpdateAchievement.hh" />
@@ -265,7 +260,6 @@
<ClInclude Include="services\Initialization.hh" />
<ClInclude Include="services\IThreadPool.hh" />
<ClInclude Include="services\PerformanceCounter.hh" />
<ClInclude Include="services\RcheevosClient.hh" />
<ClInclude Include="services\ServiceLocator.hh" />
<ClInclude Include="services\SearchResults.h" />
<ClInclude Include="services\TextReader.hh" />
-18
View File
@@ -405,9 +405,6 @@
<ClCompile Include="ui\viewmodels\ProgressTrackerViewModel.cpp">
<Filter>UI\ViewModels</Filter>
</ClCompile>
<ClCompile Include="services\RcheevosClient.cpp">
<Filter>Services</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="RA_Resource.h">
@@ -626,9 +623,6 @@
<ClInclude Include="api\StartSession.hh">
<Filter>API</Filter>
</ClInclude>
<ClInclude Include="api\Ping.hh">
<Filter>API</Filter>
</ClInclude>
<ClInclude Include="ui\viewmodels\PopupMessageViewModel.hh">
<Filter>UI\ViewModels</Filter>
</ClInclude>
@@ -647,9 +641,6 @@
<ClInclude Include="services\AchievementRuntime.hh">
<Filter>Services</Filter>
</ClInclude>
<ClInclude Include="api\ResolveHash.hh">
<Filter>API</Filter>
</ClInclude>
<ClInclude Include="ui\viewmodels\LoginViewModel.hh">
<Filter>UI\ViewModels</Filter>
</ClInclude>
@@ -668,9 +659,6 @@
<ClInclude Include="api\LatestClient.hh">
<Filter>API</Filter>
</ClInclude>
<ClInclude Include="api\AwardAchievement.hh">
<Filter>API</Filter>
</ClInclude>
<ClInclude Include="ui\ViewModelCollection.hh">
<Filter>UI</Filter>
</ClInclude>
@@ -683,9 +671,6 @@
<ClInclude Include="api\SubmitNewTitle.hh">
<Filter>API</Filter>
</ClInclude>
<ClInclude Include="api\SubmitLeaderboardEntry.hh">
<Filter>API</Filter>
</ClInclude>
<ClInclude Include="ui\viewmodels\EmulatorViewModel.hh">
<Filter>UI\ViewModels</Filter>
</ClInclude>
@@ -986,9 +971,6 @@
<ClInclude Include="ui\viewmodels\ProgressTrackerViewModel.hh">
<Filter>UI\ViewModels</Filter>
</ClInclude>
<ClInclude Include="services\RcheevosClient.hh">
<Filter>Services</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="RA_Shared.rc">
-3
View File
@@ -15,9 +15,6 @@ static ra::api::IServer& Server()
}
StartSession::Response StartSession::Request::Call() const { return Server().StartSession(*this); }
Ping::Response Ping::Request::Call() const { return Server().Ping(*this); }
AwardAchievement::Response AwardAchievement::Request::Call() const { return Server().AwardAchievement(*this); }
SubmitLeaderboardEntry::Response SubmitLeaderboardEntry::Request::Call() const { return Server().SubmitLeaderboardEntry(*this); }
FetchUserFriends::Response FetchUserFriends::Request::Call() const { return Server().FetchUserFriends(*this); }
ResolveHash::Response ResolveHash::Request::Call() const { return Server().ResolveHash(*this); }
FetchCodeNotes::Response FetchCodeNotes::Request::Call() const { return Server().FetchCodeNotes(*this); }
-43
View File
@@ -29,49 +29,6 @@ protected:
ra::services::ServiceLocator::GetMutable<ra::services::IThreadPool>().RunAsync(
[request, callback = std::move(callback)]{ callback(request.Call()); });
}
template<class TRequest, class TCallback>
static void CallAsyncWithRetry(const TRequest& request, TCallback&& callback)
{
ra::services::ServiceLocator::GetMutable<ra::services::IThreadPool>().RunAsync(
[requestCopy = request, callback]
{
DoAsyncWithRetry(requestCopy, callback, std::chrono::milliseconds(0));
});
}
private:
template<class TRequest, class TCallback>
static void DoAsyncWithRetry(const TRequest& request, TCallback&& callback, std::chrono::milliseconds delay)
{
auto response = request.Call();
if (response.Result != ApiResult::Incomplete)
{
callback(response);
return;
}
if (delay < std::chrono::milliseconds(500))
{
delay = std::chrono::milliseconds(500);
}
else
{
delay += delay;
if (delay > std::chrono::minutes(2))
delay = std::chrono::minutes(2);
}
// TODO: this used to be requestCopy = std::move(request), but the newest analysis tool
// says not to move const items. This is the only solution I could find the satiate it.
// I'm not convinced this is creating a copy of request, but this code will go away after
// AwardAchievement and SubmitLeaderboardEntry are managed by rc_client_.
ra::services::ServiceLocator::GetMutable<ra::services::IThreadPool>().ScheduleAsync(delay,
[requestCopy = request, callback, delay]
{
DoAsyncWithRetry(requestCopy, callback, delay);
});
}
};
struct ApiResponseBase
-46
View File
@@ -1,46 +0,0 @@
#ifndef RA_API_AWARD_ACHIEVEMENT_HH
#define RA_API_AWARD_ACHIEVEMENT_HH
#pragma once
#include "ApiCall.hh"
namespace ra {
namespace api {
class AwardAchievement
{
public:
static constexpr const char* const Name() noexcept { return "AwardAchievement"; }
struct Response : ApiResponseBase
{
unsigned int NewPlayerScore{ 0U };
unsigned int AchievementsRemaining{ 0U };
};
struct Request : ApiRequestBase
{
unsigned int AchievementId{ 0U };
bool Hardcore{ false };
std::string GameHash;
using Callback = std::function<void(const Response& response)>;
Response Call() const;
void CallAsync(Callback&& callback) const
{
ApiRequestBase::CallAsync<Request, Callback>(*this, std::move(callback));
}
void CallAsyncWithRetry(Callback&& callback) const
{
ApiRequestBase::CallAsyncWithRetry<Request, Callback>(*this, std::move(callback));
}
};
};
} // namespace api
} // namespace ra
#endif // !RA_API_AWARD_ACHIEVEMENT_HH
-6
View File
@@ -2,7 +2,6 @@
#define RA_API_ISERVER_HH
#pragma once
#include "api/AwardAchievement.hh"
#include "api/DeleteCodeNote.hh"
#include "api/FetchAchievementInfo.hh"
#include "api/FetchBadgeIds.hh"
@@ -11,10 +10,8 @@
#include "api/FetchLeaderboardInfo.hh"
#include "api/FetchUserFriends.hh"
#include "api/LatestClient.hh"
#include "api/Ping.hh"
#include "api/ResolveHash.hh"
#include "api/StartSession.hh"
#include "api/SubmitLeaderboardEntry.hh"
#include "api/SubmitNewTitle.hh"
#include "api/SubmitTicket.hh"
#include "api/UpdateAchievement.hh"
@@ -32,9 +29,6 @@ public:
// === user functions ===
virtual StartSession::Response StartSession(const StartSession::Request& request) = 0;
virtual Ping::Response Ping(const Ping::Request& request) = 0;
virtual AwardAchievement::Response AwardAchievement(const AwardAchievement::Request& request) = 0;
virtual SubmitLeaderboardEntry::Response SubmitLeaderboardEntry(const SubmitLeaderboardEntry::Request& request) = 0;
virtual FetchUserFriends::Response FetchUserFriends(const FetchUserFriends::Request& request) = 0;
// === game functions ===
-38
View File
@@ -1,38 +0,0 @@
#ifndef RA_API_PING_HH
#define RA_API_PING_HH
#pragma once
#include "ApiCall.hh"
namespace ra {
namespace api {
class Ping
{
public:
static constexpr const char* const Name() noexcept { return "Ping"; }
struct Response : ApiResponseBase
{
};
struct Request : ApiRequestBase
{
unsigned int GameId{};
std::wstring CurrentActivity;
using Callback = std::function<void(const Response& response)>;
Response Call() const;
void CallAsync(Callback&& callback) const
{
ApiRequestBase::CallAsync<Request, Callback>(*this, std::move(callback));
}
};
};
} // namespace api
} // namespace ra
#endif // !RA_API_PING_HH
-57
View File
@@ -1,57 +0,0 @@
#ifndef RA_API_SUBMIT_LEADERBOARD_ENTRY_HH
#define RA_API_SUBMIT_LEADERBOARD_ENTRY_HH
#pragma once
#include "ApiCall.hh"
namespace ra {
namespace api {
class SubmitLeaderboardEntry
{
public:
static constexpr const char* const Name() noexcept { return "SubmitLeaderboardEntry"; }
struct Response : ApiResponseBase
{
int Score{ 0U };
int BestScore{ 0U };
unsigned int NewRank{ 0U };
unsigned int NumEntries{ 0U };
struct Entry
{
unsigned int Rank{ 0U };
std::string User;
int Score{ 0U };
};
std::vector<Entry> TopEntries;
};
struct Request : ApiRequestBase
{
unsigned int LeaderboardId{ 0U };
int Score{ 0U };
std::string GameHash;
using Callback = std::function<void(const Response& response)>;
Response Call() const;
void CallAsync(Callback&& callback) const
{
ApiRequestBase::CallAsync<Request, Callback>(*this, std::move(callback));
}
void CallAsyncWithRetry(Callback&& callback) const
{
ApiRequestBase::CallAsyncWithRetry<Request, Callback>(*this, std::move(callback));
}
};
};
} // namespace api
} // namespace ra
#endif // !RA_API_SUBMIT_LEADERBOARD_ENTRY_HH
-132
View File
@@ -506,138 +506,6 @@ StartSession::Response ConnectedServer::StartSession(const StartSession::Request
return response;
}
Ping::Response ConnectedServer::Ping(const Ping::Request& request)
{
Ping::Response response;
rc_api_ping_request_t api_params;
memset(&api_params, 0, sizeof(api_params));
const auto& pUserContext = ra::services::ServiceLocator::Get<ra::data::context::UserContext>();
api_params.username = pUserContext.GetUsername().c_str();
api_params.api_token = pUserContext.GetApiToken().c_str();
api_params.game_id = request.GameId;
std::string sRichPresence;
if (!request.CurrentActivity.empty())
{
sRichPresence = ra::Narrow(request.CurrentActivity);
api_params.rich_presence = sRichPresence.c_str();
}
rc_api_request_t api_request;
if (rc_api_init_ping_request(&api_request, &api_params) == RC_OK)
{
ra::services::Http::Response httpResponse;
if (DoRequest(api_request, Ping::Name(), httpResponse, response))
{
rc_api_ping_response_t api_response;
const auto nResult = rc_api_process_ping_response(&api_response, httpResponse.Content().c_str());
if (ValidateResponse(nResult, api_response.response, Ping::Name(), httpResponse.StatusCode(), response))
response.Result = ApiResult::Success;
rc_api_destroy_ping_response(&api_response);
}
}
rc_api_destroy_request(&api_request);
return response;
}
AwardAchievement::Response ConnectedServer::AwardAchievement(const AwardAchievement::Request& request)
{
AwardAchievement::Response response;
rc_api_award_achievement_request_t api_params;
memset(&api_params, 0, sizeof(api_params));
const auto& pUserContext = ra::services::ServiceLocator::Get<ra::data::context::UserContext>();
api_params.username = pUserContext.GetUsername().c_str();
api_params.api_token = pUserContext.GetApiToken().c_str();
if (!request.GameHash.empty())
api_params.game_hash = request.GameHash.c_str();
api_params.achievement_id = request.AchievementId;
api_params.hardcore = request.Hardcore ? 1 : 0;
rc_api_request_t api_request;
if (rc_api_init_award_achievement_request(&api_request, &api_params) == RC_OK)
{
ra::services::Http::Response httpResponse;
if (DoRequest(api_request, AwardAchievement::Name(), httpResponse, response))
{
rc_api_award_achievement_response_t api_response;
const auto nResult = rc_api_process_award_achievement_response(&api_response, httpResponse.Content().c_str());
if (ValidateResponse(nResult, api_response.response, AwardAchievement::Name(), httpResponse.StatusCode(), response))
{
response.Result = ApiResult::Success;
response.NewPlayerScore = api_response.new_player_score;
response.AchievementsRemaining = api_response.achievements_remaining;
}
rc_api_destroy_award_achievement_response(&api_response);
}
}
rc_api_destroy_request(&api_request);
return response;
}
SubmitLeaderboardEntry::Response ConnectedServer::SubmitLeaderboardEntry(const SubmitLeaderboardEntry::Request& request)
{
SubmitLeaderboardEntry::Response response;
rc_api_submit_lboard_entry_request_t api_params;
memset(&api_params, 0, sizeof(api_params));
const auto& pUserContext = ra::services::ServiceLocator::Get<ra::data::context::UserContext>();
api_params.username = pUserContext.GetUsername().c_str();
api_params.api_token = pUserContext.GetApiToken().c_str();
api_params.leaderboard_id = request.LeaderboardId;
api_params.score = request.Score;
if (!request.GameHash.empty())
api_params.game_hash = request.GameHash.c_str();
rc_api_request_t api_request;
if (rc_api_init_submit_lboard_entry_request(&api_request, &api_params) == RC_OK)
{
ra::services::Http::Response httpResponse;
if (DoRequest(api_request, SubmitLeaderboardEntry::Name(), httpResponse, response))
{
rc_api_submit_lboard_entry_response_t api_response;
const auto nResult = rc_api_process_submit_lboard_entry_response(&api_response, httpResponse.Content().c_str());
if (ValidateResponse(nResult, api_response.response, SubmitLeaderboardEntry::Name(), httpResponse.StatusCode(), response))
{
response.Result = ApiResult::Success;
response.Score = api_response.submitted_score;
response.BestScore = api_response.best_score;
response.NewRank = api_response.new_rank;
response.NumEntries = api_response.num_entries;
for (unsigned i = 0; i < api_response.num_top_entries; ++i)
{
const auto* pSrcEntry = &api_response.top_entries[i];
auto& entry = response.TopEntries.emplace_back();
entry.Rank = pSrcEntry->rank;
entry.Score = pSrcEntry->score;
entry.User = pSrcEntry->username;
}
}
rc_api_destroy_submit_lboard_entry_response(&api_response);
}
}
rc_api_destroy_request(&api_request);
return response;
}
FetchUserFriends::Response ConnectedServer::FetchUserFriends(const FetchUserFriends::Request&)
{
FetchUserFriends::Response response;
-3
View File
@@ -16,9 +16,6 @@ public:
const char* Name() const noexcept override { return m_sHost.c_str(); }
StartSession::Response StartSession(const StartSession::Request& request) override;
Ping::Response Ping(const Ping::Request& request) override;
AwardAchievement::Response AwardAchievement(const AwardAchievement::Request& request) override;
SubmitLeaderboardEntry::Response SubmitLeaderboardEntry(const SubmitLeaderboardEntry::Request& request) override;
FetchUserFriends::Response FetchUserFriends(const FetchUserFriends::Request& request) override;
ResolveHash::Response ResolveHash(const ResolveHash::Request& request) override;
FetchCodeNotes::Response FetchCodeNotes(const FetchCodeNotes::Request& request) override;
-16
View File
@@ -13,22 +13,6 @@ namespace ra {
namespace api {
namespace impl {
AwardAchievement::Response OfflineServer::AwardAchievement(const AwardAchievement::Request&)
{
AwardAchievement::Response response;
response.Result = ApiResult::Failed;
response.ErrorMessage = "Achievements are not actually unlocked in offline mode";
return response;
}
SubmitLeaderboardEntry::Response OfflineServer::SubmitLeaderboardEntry(const SubmitLeaderboardEntry::Request&)
{
SubmitLeaderboardEntry::Response response;
response.Result = ApiResult::Failed;
response.ErrorMessage = "Leaderboard entries are not actually submitted in offline mode";
return response;
}
FetchCodeNotes::Response OfflineServer::FetchCodeNotes(const FetchCodeNotes::Request& request)
{
FetchCodeNotes::Response response;
-3
View File
@@ -11,9 +11,6 @@ class OfflineServer : public ServerBase
public:
const char* Name() const noexcept override { return "offline client"; }
AwardAchievement::Response AwardAchievement(const AwardAchievement::Request&) override;
SubmitLeaderboardEntry::Response SubmitLeaderboardEntry(const SubmitLeaderboardEntry::Request&) override;
FetchCodeNotes::Response FetchCodeNotes(const FetchCodeNotes::Request& request) override;
LatestClient::Response LatestClient(const LatestClient::Request& request) override;
-15
View File
@@ -22,21 +22,6 @@ public:
return UnsupportedApi<StartSession::Response>(StartSession::Name());
}
Ping::Response Ping(const Ping::Request&) override
{
return UnsupportedApi<Ping::Response>(Ping::Name());
}
AwardAchievement::Response AwardAchievement(const AwardAchievement::Request&) override
{
return UnsupportedApi<AwardAchievement::Response>(AwardAchievement::Name());
}
SubmitLeaderboardEntry::Response SubmitLeaderboardEntry(const SubmitLeaderboardEntry::Request&) override
{
return UnsupportedApi<SubmitLeaderboardEntry::Response>(SubmitLeaderboardEntry::Name());
}
FetchUserFriends::Response FetchUserFriends(const FetchUserFriends::Request&) override
{
return UnsupportedApi<FetchUserFriends::Response>(FetchUserFriends::Name());
+23 -30
View File
@@ -458,8 +458,12 @@ void EmulatorContext::DisableHardcoreMode()
RebuildMenu();
auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
rc_client_set_hardcore_enabled(pRuntime.GetClient(), false);
// GameContext::DoFrame synchronizes the models to the runtime
auto& pGameContext = ra::services::ServiceLocator::GetMutable<ra::data::context::GameContext>();
pGameContext.RefreshUnlocks();
pGameContext.DoFrame();
}
}
@@ -503,17 +507,6 @@ bool EmulatorContext::EnableHardcoreMode(bool bShowWarning)
RA_LOG_INFO("Hardcore enabled");
// Disable any modified assets
for (gsl::index nIndex = 0; nIndex < gsl::narrow_cast<gsl::index>(pGameContext.Assets().Count()); ++nIndex)
{
auto* pAsset = pGameContext.Assets().GetItemAt(nIndex);
if (pAsset != nullptr && pAsset->GetChanges() != ra::data::models::AssetChanges::None)
{
if (pAsset->IsActive())
pAsset->SetState(ra::data::models::AssetState::Inactive);
}
}
// User has agreed to reset the emulator, or no game is loaded. Enabled hardcore!
pConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, true);
@@ -542,15 +535,25 @@ bool EmulatorContext::EnableHardcoreMode(bool bShowWarning)
// update the integration menu
RebuildMenu();
// when enabling hardcore mode, force a system reset
if (m_fResetEmulator)
m_fResetEmulator();
// update the runtime
auto* pClient = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().GetClient();
rc_client_set_hardcore_enabled(pClient, true);
#ifndef RA_UTEST
// emulator reset code will normally call back into _RA_OnReset, but call it explicitly just in case.
_RA_OnReset();
#endif
// GameContext::DoFrame synchronizes the models to the runtime
pGameContext.DoFrame();
// Disable any modified assets
for (gsl::index nIndex = 0; nIndex < gsl::narrow_cast<gsl::index>(pGameContext.Assets().Count()); ++nIndex)
{
auto* pAsset = pGameContext.Assets().GetItemAt(nIndex);
if (pAsset != nullptr && pAsset->GetChanges() != ra::data::models::AssetChanges::None)
{
if (pAsset->IsActive())
pAsset->SetState(ra::data::models::AssetState::Inactive);
}
}
// unfreeze bookmarks so the user doesn't get hit with a modified memory error
auto& vmBookmarks = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::WindowManager>().MemoryBookmarks;
for (gsl::index nIndex = 0; nIndex < ra::to_signed(vmBookmarks.Bookmarks().Count()); ++nIndex)
{
@@ -559,9 +562,6 @@ bool EmulatorContext::EnableHardcoreMode(bool bShowWarning)
pBookmark->SetBehavior(ra::ui::viewmodels::MemoryBookmarksViewModel::BookmarkBehavior::None);
}
pGameContext.ActivateLeaderboards();
pGameContext.RefreshUnlocks();
return true;
}
@@ -704,9 +704,8 @@ bool EmulatorContext::IsValidAddress(ra::ByteAddress nAddress) const noexcept
return false;
}
uint8_t EmulatorContext::ReadMemoryByte(ra::ByteAddress nAddress) const
uint8_t EmulatorContext::ReadMemoryByte(ra::ByteAddress nAddress) const noexcept
{
const ra::ByteAddress nOriginalAddress = nAddress;
for (const auto& pBlock : m_vMemoryBlocks)
{
if (nAddress < pBlock.size)
@@ -720,16 +719,12 @@ uint8_t EmulatorContext::ReadMemoryByte(ra::ByteAddress nAddress) const
nAddress -= gsl::narrow_cast<ra::ByteAddress>(pBlock.size);
}
if (nOriginalAddress < m_nTotalMemorySize)
ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().InvalidateAddress(nOriginalAddress);
return 0;
}
_Use_decl_annotations_
uint32_t EmulatorContext::ReadMemory(ra::ByteAddress nAddress, uint8_t pBuffer[], size_t nCount) const
{
const ra::ByteAddress nOriginalAddress = nAddress;
uint32_t nBytesRead = 0;
Expects(pBuffer != nullptr);
@@ -756,10 +751,8 @@ uint32_t EmulatorContext::ReadMemory(ra::ByteAddress nAddress, uint8_t pBuffer[]
}
else if (!pBlock.read)
{
ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().InvalidateAddress(nOriginalAddress);
memset(pBuffer, 0, nToRead);
pBuffer += nToRead;
nBytesRead += gsl::narrow_cast<uint32_t>(nToRead);
}
else
{
+10 -1
View File
@@ -138,6 +138,15 @@ public:
m_fUnpauseEmulator();
}
/// <summary>
/// Resets the emulator.
/// </summary>
void Reset() const
{
if (m_fResetEmulator)
m_fResetEmulator();
}
/// <summary>
/// Sets a function to call to reset the emulator.
/// </summary>
@@ -215,7 +224,7 @@ public:
/// <summary>
/// Reads memory from the emulator.
/// </summary>
uint8_t ReadMemoryByte(ra::ByteAddress nAddress) const;
uint8_t ReadMemoryByte(ra::ByteAddress nAddress) const noexcept;
/// <summary>
/// Reads memory from the emulator.
+55 -640
View File
@@ -7,9 +7,6 @@
#include "RA_md5factory.h"
#include "RA_StringUtils.h"
#include "api\AwardAchievement.hh"
#include "api\SubmitLeaderboardEntry.hh"
#include "data\context\ConsoleContext.hh"
#include "data\context\EmulatorContext.hh"
#include "data\context\SessionTracker.hh"
@@ -24,7 +21,6 @@
#include "services\IAudioSystem.hh"
#include "services\IConfiguration.hh"
#include "services\ILocalStorage.hh"
#include "services\RcheevosClient.hh"
#include "services\impl\FileTextReader.hh"
#include "services\impl\FileTextWriter.hh"
#include "services\impl\StringTextReader.hh"
@@ -149,17 +145,13 @@ void GameContext::LoadGame(unsigned int nGameId, const std::string& sGameHash, M
}
}
// download the game data
ra::services::RcheevosClient::Synchronizer pSynchronizer;
auto& pClient = ra::services::ServiceLocator::GetMutable<ra::services::RcheevosClient>();
// enable spectator mode to prevent unlocks when testing compatibility
rc_client_set_spectator_mode_enabled(pClient.GetClient(), nMode == Mode::CompatibilityTest);
rc_client_set_spectator_mode_enabled(pRuntime.GetClient(), nMode == Mode::CompatibilityTest);
const bool bWasPaused = pRuntime.IsPaused();
pRuntime.SetPaused(true);
// download the game data
struct LoadGameUserData
{
bool bWasPaused = false;
@@ -169,7 +161,7 @@ void GameContext::LoadGame(unsigned int nGameId, const std::string& sGameHash, M
pLoadGameUserData->bWasPaused = bWasPaused;
pLoadGameUserData->sOldRichPresence = std::move(sOldRichPresence);
pClient.BeginLoadGame(sGameHash, nGameId,
pRuntime.BeginLoadGame(sGameHash, nGameId,
[](int nResult, const char* sErrorMessage, rc_client_t*, void* pUserdata) {
auto& pGameContext = ra::services::ServiceLocator::GetMutable<ra::data::context::GameContext>();
auto* pLoadGameUserData = static_cast<struct LoadGameUserData*>(pUserdata);
@@ -201,7 +193,7 @@ void GameContext::FinishLoadGame(int nResult, const char* sErrorMessage, bool bW
[this]() { EndLoad(); });
m_vAssets.Append(std::move(pCodeNotes));
auto& pClient = ra::services::ServiceLocator::GetMutable<ra::services::RcheevosClient>();
auto& pClient = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
auto* pGame = rc_client_get_game_info(pClient.GetClient());
if (pGame == nullptr || pGame->id == 0)
{
@@ -287,17 +279,17 @@ void GameContext::FinishLoadGame(int nResult, const char* sErrorMessage, bool bW
// finish up
m_vAssets.EndUpdate();
auto& pRcheevosClient = ra::services::ServiceLocator::GetMutable<ra::services::RcheevosClient>();
pRcheevosClient.SyncAssets();
auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
pRuntime.SyncAssets();
EndLoad();
OnActiveGameChanged();
}
void GameContext::InitializeFromRcheevosClient(const std::map<uint32_t, std::string> mAchievementDefinitions,
const std::map<uint32_t, std::string> mLeaderboardDefinitions)
void GameContext::InitializeFromAchievementRuntime(const std::map<uint32_t, std::string> mAchievementDefinitions,
const std::map<uint32_t, std::string> mLeaderboardDefinitions)
{
const auto* pClient = ra::services::ServiceLocator::Get<ra::services::RcheevosClient>().GetClient();
const auto* pClient = ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>().GetClient();
const auto* pGame = rc_client_get_game_info(pClient);
m_sGameTitle = ra::Widen(pGame->title);
@@ -309,107 +301,70 @@ void GameContext::InitializeFromRcheevosClient(const std::map<uint32_t, std::str
{
// achievements
auto* pAchievementData = pSubset->achievements;
const auto* pAchievementStop = pAchievementData + pSubset->public_.num_achievements;
for (; pAchievementData < pAchievementStop; ++pAchievementData)
if (pAchievementData != nullptr)
{
// if the server has provided an unexpected category (usually 0), ignore it.
ra::data::models::AssetCategory nCategory = ra::data::models::AssetCategory::None;
switch (pAchievementData->public_.category)
const auto* pAchievementStop = pAchievementData + pSubset->public_.num_achievements;
for (; pAchievementData < pAchievementStop; ++pAchievementData)
{
case RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE:
nCategory = ra::data::models::AssetCategory::Core;
break;
case RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL:
nCategory = ra::data::models::AssetCategory::Unofficial;
// if the server has provided an unexpected category (usually 0), ignore it.
ra::data::models::AssetCategory nCategory = ra::data::models::AssetCategory::None;
switch (pAchievementData->public_.category)
{
case RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE:
nCategory = ra::data::models::AssetCategory::Core;
break;
// all unofficial achievements should start inactive.
// rc_client automatically activates them.
pAchievementData->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE;
break;
default:
continue;
}
case RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL:
nCategory = ra::data::models::AssetCategory::Unofficial;
auto vmAchievement = std::make_unique<ra::data::models::AchievementModel>();
vmAchievement->SetID(pAchievementData->public_.id);
vmAchievement->SetName(ra::Widen(pAchievementData->public_.title));
vmAchievement->SetDescription(ra::Widen(pAchievementData->public_.description));
vmAchievement->SetCategory(nCategory);
vmAchievement->SetPoints(pAchievementData->public_.points);
vmAchievement->SetAuthor(ra::Widen(pAchievementData->author));
vmAchievement->SetBadge(ra::Widen(pAchievementData->public_.badge_name));
vmAchievement->SetCreationTime(pAchievementData->created_time);
vmAchievement->SetUpdatedTime(pAchievementData->updated_time);
// all unofficial achievements should start inactive.
// rc_client automatically activates them.
pAchievementData->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE;
const auto sDefinition = mAchievementDefinitions.find(pAchievementData->public_.id);
if (sDefinition != mAchievementDefinitions.end())
vmAchievement->SetTrigger(sDefinition->second);
if (pAchievementData->trigger)
pAchievementData->trigger->state = RC_TRIGGER_STATE_INACTIVE;
break;
vmAchievement->CreateServerCheckpoint();
vmAchievement->CreateLocalCheckpoint();
default:
continue;
}
switch (pAchievementData->public_.state)
{
case RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE:
vmAchievement->SetState(ra::data::models::AssetState::Active);
break;
case RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE:
vmAchievement->SetState(ra::data::models::AssetState::Inactive);
break;
case RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED:
vmAchievement->SetState(ra::data::models::AssetState::Triggered);
break;
case RC_CLIENT_ACHIEVEMENT_STATE_DISABLED:
vmAchievement->SetState(ra::data::models::AssetState::Disabled);
break;
}
auto vmAchievement = std::make_unique<ra::data::models::AchievementModel>();
m_vAssets.Append(std::move(vmAchievement));
const auto sDefinition = mAchievementDefinitions.find(pAchievementData->public_.id);
if (sDefinition != mAchievementDefinitions.end())
vmAchievement->Attach(*pAchievementData, nCategory, sDefinition->second);
else
vmAchievement->Attach(*pAchievementData, nCategory, "");
m_vAssets.Append(std::move(vmAchievement));
#ifndef RA_UTEST
// prefetch the achievement image
pImageRepository.FetchImage(ra::ui::ImageType::Badge, pAchievementData->public_.badge_name);
// prefetch the achievement image
pImageRepository.FetchImage(ra::ui::ImageType::Badge, pAchievementData->public_.badge_name);
#endif
}
}
// leaderboards
auto* pLeaderboardData = pSubset->leaderboards;
const auto* pLeaderboardStop = pLeaderboardData + pSubset->public_.num_leaderboards;
for (; pLeaderboardData < pLeaderboardStop; ++pLeaderboardData)
if (pLeaderboardData != nullptr)
{
auto vmLeaderboard = std::make_unique<ra::data::models::LeaderboardModel>();
vmLeaderboard->SetID(pLeaderboardData->public_.id);
vmLeaderboard->SetName(ra::Widen(pLeaderboardData->public_.title));
vmLeaderboard->SetDescription(ra::Widen(pLeaderboardData->public_.description));
vmLeaderboard->SetCategory(ra::data::models::AssetCategory::Core);
vmLeaderboard->SetValueFormat(ra::itoe<ValueFormat>(pLeaderboardData->format));
vmLeaderboard->SetLowerIsBetter(pLeaderboardData->public_.lower_is_better);
vmLeaderboard->SetHidden(pLeaderboardData->hidden);
const auto sDefinition = mLeaderboardDefinitions.find(pLeaderboardData->public_.id);
if (sDefinition != mLeaderboardDefinitions.end())
vmLeaderboard->SetDefinition(sDefinition->second);
vmLeaderboard->CreateServerCheckpoint();
vmLeaderboard->CreateLocalCheckpoint();
switch (pLeaderboardData->public_.state)
const auto* pLeaderboardStop = pLeaderboardData + pSubset->public_.num_leaderboards;
for (; pLeaderboardData < pLeaderboardStop; ++pLeaderboardData)
{
case RC_CLIENT_LEADERBOARD_STATE_ACTIVE:
vmLeaderboard->SetState(ra::data::models::AssetState::Active);
break;
case RC_CLIENT_LEADERBOARD_STATE_INACTIVE:
vmLeaderboard->SetState(ra::data::models::AssetState::Inactive);
break;
case RC_CLIENT_LEADERBOARD_STATE_TRACKING:
vmLeaderboard->SetState(ra::data::models::AssetState::Primed);
break;
case RC_CLIENT_LEADERBOARD_STATE_DISABLED:
vmLeaderboard->SetState(ra::data::models::AssetState::Disabled);
break;
}
auto vmLeaderboard = std::make_unique<ra::data::models::LeaderboardModel>();
m_vAssets.Append(std::move(vmLeaderboard));
const auto nCategory = ra::data::models::AssetCategory::Core; // all published leaderboards are core
const auto sDefinition = mLeaderboardDefinitions.find(pLeaderboardData->public_.id);
if (sDefinition != mLeaderboardDefinitions.end())
vmLeaderboard->Attach(*pLeaderboardData, nCategory, sDefinition->second);
else
vmLeaderboard->Attach(*pLeaderboardData, nCategory, "");
m_vAssets.Append(std::move(vmLeaderboard));
}
}
}
}
@@ -464,117 +419,6 @@ void GameContext::EndLoad()
}
}
void GameContext::RefreshUnlocks(bool bUnpause, int nPopup)
{
std::set<unsigned int> vUnlockedAchievements;
if (m_nGameId != 0 && m_nMode != Mode::CompatibilityTest)
{
const auto& pConfiguration = ra::services::ServiceLocator::Get<ra::services::IConfiguration>();
const bool bHardcore = pConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore);
const uint8_t nFlag = gsl::narrow_cast<uint8_t>(bHardcore ? ra::etoi(RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE)
: ra::etoi(RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE));
const auto* pClient = ra::services::ServiceLocator::Get<ra::services::RcheevosClient>().GetClient();
if (!pClient->game) // game still loading, ignore
return;
for (auto* pSubset = pClient->game->subsets; pSubset; pSubset = pSubset->next)
{
// achievements
auto* pAchievementData = pSubset->achievements;
const auto* pAchievementStop = pAchievementData + pSubset->public_.num_achievements;
for (; pAchievementData < pAchievementStop; ++pAchievementData)
{
if (pAchievementData->public_.unlocked & nFlag)
vUnlockedAchievements.insert(pAchievementData->public_.id);
}
}
}
BeginLoad();
UpdateUnlocks(vUnlockedAchievements, bUnpause, nPopup);
EndLoad();
}
void GameContext::UpdateUnlocks(const std::set<unsigned int>& vUnlockedAchievements, bool bUnpause, int nPopup)
{
// start by identifying all available achievements
std::set<unsigned int> vLockedAchievements;
for (gsl::index nIndex = 0; nIndex < gsl::narrow_cast<gsl::index>(Assets().Count()); ++nIndex)
{
const auto* pAchievement = dynamic_cast<const ra::data::models::AchievementModel*>(Assets().GetItemAt(nIndex));
if (pAchievement != nullptr)
vLockedAchievements.insert(pAchievement->GetID());
}
// deactivate anything the player has already unlocked
size_t nUnlockedCoreAchievements = 0U;
for (const auto nUnlockedAchievement : vUnlockedAchievements)
{
vLockedAchievements.erase(nUnlockedAchievement);
auto* pAchievement = Assets().FindAchievement(nUnlockedAchievement);
// only core achievements will be automatically activated. also, they're all we want to count
if (pAchievement != nullptr && pAchievement->GetCategory() == ra::data::models::AssetCategory::Core)
{
pAchievement->SetState(ra::data::models::AssetState::Inactive);
++nUnlockedCoreAchievements;
}
}
#ifndef RA_UTEST
auto& pImageRepository = ra::services::ServiceLocator::GetMutable<ra::ui::IImageRepository>();
#endif
// activate any core achievements the player hasn't earned and pre-fetch the locked image
for (auto nAchievementId : vLockedAchievements)
{
auto* pAchievement = Assets().FindAchievement(nAchievementId);
if (pAchievement && pAchievement->GetCategory() == ra::data::models::AssetCategory::Core)
{
if (!pAchievement->IsActive() && pAchievement->GetState() != ra::data::models::AssetState::Disabled)
{
// modified assets should start in the inactive state
if (pAchievement->GetChanges() != ra::data::models::AssetChanges::None)
pAchievement->SetState(ra::data::models::AssetState::Inactive);
else
pAchievement->SetState(ra::data::models::AssetState::Waiting);
}
#ifndef RA_UTEST
if (!pAchievement->GetBadge().empty())
pImageRepository.FetchImage(ra::ui::ImageType::Badge, ra::Narrow(pAchievement->GetBadge()) + "_lock");
#endif
}
}
if (bUnpause)
{
ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().SetPaused(false);
#ifndef RA_UTEST
auto& pAssetList = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::WindowManager>().AssetList;
pAssetList.SetProcessingActive(true);
#endif
}
if (nPopup)
{
auto* pPopup = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>().GetMessage(nPopup);
if (pPopup != nullptr)
{
const auto nDisabledAchievements = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().DetectUnsupportedAchievements();
if (nDisabledAchievements != 0)
{
const auto sDescription = ra::StringPrintf(L"%s (%s unsupported)", pPopup->GetDescription(), nDisabledAchievements);
pPopup->SetDescription(sDescription);
}
pPopup->SetDetail(ra::StringPrintf(L"You have earned %u achievements", nUnlockedCoreAchievements));
pPopup->RebuildRenderImage();
}
}
}
void GameContext::DoFrame()
{
for (gsl::index nIndex = 0; nIndex < gsl::narrow_cast<gsl::index>(m_vAssets.Count()); ++nIndex)
@@ -585,435 +429,6 @@ void GameContext::DoFrame()
}
}
void GameContext::AwardAchievement(ra::AchievementID nAchievementId)
{
auto* pAchievement = Assets().FindAchievement(nAchievementId);
if (pAchievement == nullptr)
return;
const auto& pConfiguration = ra::services::ServiceLocator::Get<ra::services::IConfiguration>();
bool bTakeScreenshot = pConfiguration.IsFeatureEnabled(ra::services::Feature::AchievementTriggeredScreenshot);
bool bSubmit = false;
bool bIsError = false;
std::unique_ptr<ra::ui::viewmodels::PopupMessageViewModel> vmPopup(new ra::ui::viewmodels::PopupMessageViewModel);
vmPopup->SetDescription(ra::StringPrintf(L"%s (%u)", pAchievement->GetName(), pAchievement->GetPoints()));
vmPopup->SetDetail(ra::Widen(pAchievement->GetDescription()));
vmPopup->SetImage(ra::ui::ImageType::Badge, ra::Narrow(pAchievement->GetBadge()));
vmPopup->SetPopupType(ra::ui::viewmodels::Popup::AchievementTriggered);
switch (pAchievement->GetCategory())
{
case ra::data::models::AssetCategory::Local:
vmPopup->SetTitle(L"Local Achievement Unlocked");
bSubmit = false;
bTakeScreenshot = false;
break;
case ra::data::models::AssetCategory::Unofficial:
vmPopup->SetTitle(L"Unofficial Achievement Unlocked");
bSubmit = false;
break;
default:
vmPopup->SetTitle(L"Achievement Unlocked");
bSubmit = true;
break;
}
if (m_nMode == Mode::CompatibilityTest)
{
auto sHeader = vmPopup->GetTitle();
sHeader.insert(0, L"Test ");
vmPopup->SetTitle(sHeader);
RA_LOG_INFO("Achievement %u not unlocked - %s", pAchievement->GetID(), "test compatibility mode");
bSubmit = false;
}
if (pAchievement->IsModified() || // actual modifications
(bSubmit && pAchievement->GetChanges() != ra::data::models::AssetChanges::None)) // unpublished changes
{
auto sHeader = vmPopup->GetTitle();
sHeader.insert(0, L"Modified ");
if (pAchievement->GetCategory() != ra::data::models::AssetCategory::Local)
sHeader.append(L" LOCALLY");
vmPopup->SetTitle(sHeader);
RA_LOG_INFO("Achievement %u not unlocked - %s", pAchievement->GetID(), pAchievement->IsModified() ? "modified" : "unpublished");
bIsError = true;
bSubmit = false;
bTakeScreenshot = false;
}
const auto& pEmulatorContext = ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>();
if (bSubmit && pEmulatorContext.WasMemoryModified())
{
vmPopup->SetTitle(L"Achievement Unlocked LOCALLY");
vmPopup->SetErrorDetail(L"Error: RAM tampered with");
RA_LOG_INFO("Achievement %u not unlocked - %s", pAchievement->GetID(), "RAM tampered with");
bIsError = true;
bSubmit = false;
}
if (bSubmit && _RA_HardcoreModeIsActive() && pEmulatorContext.IsMemoryInsecure())
{
vmPopup->SetTitle(L"Achievement Unlocked LOCALLY");
vmPopup->SetErrorDetail(L"Error: RAM insecure");
RA_LOG_INFO("Achievement %u not unlocked - %s", pAchievement->GetID(), "RAM insecure");
bIsError = true;
bSubmit = false;
}
if (bSubmit && pConfiguration.IsFeatureEnabled(ra::services::Feature::Offline))
{
vmPopup->SetTitle(L"Offline Achievement Unlocked");
bSubmit = false;
}
int nPopupId = -1;
ra::services::ServiceLocator::Get<ra::services::IAudioSystem>().PlayAudioFile(
bIsError ? L"Overlay\\acherror.wav" : L"Overlay\\unlock.wav");
if (pConfiguration.GetPopupLocation(ra::ui::viewmodels::Popup::AchievementTriggered) != ra::ui::viewmodels::PopupLocation::None)
{
auto& pOverlayManager = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>();
nPopupId = pOverlayManager.QueueMessage(vmPopup);
if (bTakeScreenshot)
{
std::wstring sPath = ra::StringPrintf(L"%s%u.png", pConfiguration.GetScreenshotDirectory(), nAchievementId);
pOverlayManager.CaptureScreenshot(nPopupId, sPath);
}
}
pAchievement->SetState(ra::data::models::AssetState::Triggered);
if (!bSubmit)
return;
ra::api::AwardAchievement::Request request;
request.AchievementId = nAchievementId;
request.Hardcore = _RA_HardcoreModeIsActive();
request.GameHash = GameHash();
request.CallAsyncWithRetry([this, nPopupId, nAchievementId](const ra::api::AwardAchievement::Response& response)
{
if (response.Succeeded())
{
// TODO: just read score from rc_client->user (when client handles unlocks)
// success! update the player's score
ra::services::ServiceLocator::GetMutable<ra::data::context::UserContext>().SetScore(response.NewPlayerScore);
if (response.AchievementsRemaining == 0)
CheckForMastery();
}
else if (ra::StringStartsWith(response.ErrorMessage, "User already has "))
{
// if server responds with "Error: User already has hardcore and regular achievements awarded." or
// "Error: User already has this achievement awarded.", just ignore the error. There's no reason to
// notify the user that the unlock failed because it had happened previously.
}
else if (nPopupId != -1)
{
// an error occurred, update the popup to display the error message
auto& pOverlayManager = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>();
auto pPopup = pOverlayManager.GetMessage(nPopupId);
if (pPopup != nullptr)
{
pPopup->SetTitle(L"Achievement Unlock FAILED");
pPopup->SetErrorDetail(response.ErrorMessage.empty() ?
L"Error submitting unlock" : ra::Widen(response.ErrorMessage));
pPopup->RebuildRenderImage();
}
else
{
std::unique_ptr<ra::ui::viewmodels::PopupMessageViewModel> vmPopup(new ra::ui::viewmodels::PopupMessageViewModel);
vmPopup->SetTitle(L"Achievement Unlock FAILED");
vmPopup->SetErrorDetail(response.ErrorMessage.empty() ?
L"Error submitting unlock" : ra::Widen(response.ErrorMessage));
vmPopup->SetPopupType(ra::ui::viewmodels::Popup::AchievementTriggered);
const auto& pGameContext = ra::services::ServiceLocator::Get<GameContext>();
const auto* pAchievement = pGameContext.Assets().FindAchievement(nAchievementId);
if (pAchievement != nullptr)
{
vmPopup->SetDescription(ra::StringPrintf(L"%s (%u)", pAchievement->GetName(), pAchievement->GetPoints()));
vmPopup->SetImage(ra::ui::ImageType::Badge, ra::Narrow(pAchievement->GetBadge()));
}
else
{
vmPopup->SetDescription(ra::StringPrintf(L"Achievement %u", nAchievementId));
}
ra::services::ServiceLocator::Get<ra::services::IAudioSystem>().PlayAudioFile(L"Overlay\\acherror.wav");
pOverlayManager.QueueMessage(vmPopup);
}
}
});
}
void GameContext::CheckForMastery()
{
const auto& pConfiguration = ra::services::ServiceLocator::Get<ra::services::IConfiguration>();
if (pConfiguration.GetPopupLocation(ra::ui::viewmodels::Popup::Mastery) == ra::ui::viewmodels::PopupLocation::None)
return;
// if multiple achievements unlock the mastery at the same time, only show the popup once
auto& pOverlayManager = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>();
if (m_nMasteryPopupId != 0 && pOverlayManager.GetMessage(m_nMasteryPopupId))
return;
// validate that all core assets are unlocked
const auto& pAssets = ra::services::ServiceLocator::Get<ra::data::context::GameContext>().Assets();
unsigned nTotalCoreAchievementPoints = 0;
size_t nNumCoreAchievements = -0;
bool bActiveCoreAchievement = false;
for (gsl::index nIndex = 0; nIndex < gsl::narrow_cast<gsl::index>(pAssets.Count()); ++nIndex)
{
const auto* pAchievement = dynamic_cast<const ra::data::models::AchievementModel*>(pAssets.GetItemAt(nIndex));
if (pAchievement != nullptr && pAchievement->GetCategory() == ra::data::models::AssetCategory::Core)
{
++nNumCoreAchievements;
nTotalCoreAchievementPoints += pAchievement->GetPoints();
if (pAchievement->IsActive())
{
bActiveCoreAchievement = true;
break;
}
}
}
// if they are, show the mastery notification
if (!bActiveCoreAchievement && nNumCoreAchievements > 0)
{
const bool bHardcore = pConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore);
const auto nPlayTimeSeconds = ra::services::ServiceLocator::Get<ra::data::context::SessionTracker>().GetTotalPlaytime(m_nGameId);
const auto nPlayTimeMinutes = std::chrono::duration_cast<std::chrono::minutes>(nPlayTimeSeconds).count();
std::unique_ptr<ra::ui::viewmodels::PopupMessageViewModel> vmMessage(new ra::ui::viewmodels::PopupMessageViewModel);
vmMessage->SetTitle(ra::StringPrintf(L"%s %s", bHardcore ? L"Mastered" : L"Completed", m_sGameTitle));
vmMessage->SetDescription(ra::StringPrintf(L"%u achievements, %u points", nNumCoreAchievements, nTotalCoreAchievementPoints));
vmMessage->SetDetail(ra::StringPrintf(L"%s | Play time: %dh%02dm", pConfiguration.GetUsername(), nPlayTimeMinutes / 60, nPlayTimeMinutes % 60));
vmMessage->SetImage(ra::ui::ImageType::Icon, m_sGameImage);
vmMessage->SetPopupType(ra::ui::viewmodels::Popup::Mastery);
// if multiple achievements unlock the mastery at the same time, only show the popup once
if (m_nMasteryPopupId != 0 && pOverlayManager.GetMessage(m_nMasteryPopupId))
return;
ra::services::ServiceLocator::Get<ra::services::IAudioSystem>().PlayAudioFile(L"Overlay\\unlock.wav");
m_nMasteryPopupId = pOverlayManager.QueueMessage(vmMessage);
if (pConfiguration.IsFeatureEnabled(ra::services::Feature::MasteryNotificationScreenshot))
{
std::wstring sPath = ra::StringPrintf(L"%sGame%u.png", pConfiguration.GetScreenshotDirectory(), m_nGameId);
pOverlayManager.CaptureScreenshot(m_nMasteryPopupId, sPath);
}
}
}
void GameContext::DeactivateLeaderboards()
{
for (gsl::index nIndex = 0; nIndex < gsl::narrow_cast<gsl::index>(Assets().Count()); ++nIndex)
{
auto* pLeaderboard = dynamic_cast<ra::data::models::LeaderboardModel*>(Assets().GetItemAt(nIndex));
if (pLeaderboard != nullptr)
pLeaderboard->SetState(ra::data::models::AssetState::Inactive);
}
}
void GameContext::ActivateLeaderboards()
{
const auto& pConfiguration = ra::services::ServiceLocator::Get<ra::services::IConfiguration>();
if (pConfiguration.IsFeatureEnabled(ra::services::Feature::Leaderboards)) // if not, simply ignore them.
{
for (gsl::index nIndex = 0; nIndex < gsl::narrow_cast<gsl::index>(Assets().Count()); ++nIndex)
{
auto* pLeaderboard = dynamic_cast<ra::data::models::LeaderboardModel*>(Assets().GetItemAt(nIndex));
if (pLeaderboard != nullptr && pLeaderboard->GetCategory() == ra::data::models::AssetCategory::Core)
pLeaderboard->SetState(ra::data::models::AssetState::Waiting);
}
}
}
void GameContext::ShowSimplifiedScoreboard(ra::LeaderboardID nLeaderboardId, int nScore) const
{
auto& pConfiguration = ra::services::ServiceLocator::Get<ra::services::IConfiguration>();
if (pConfiguration.GetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardScoreboard) == ra::ui::viewmodels::PopupLocation::None)
return;
const auto* pLeaderboard = Assets().FindLeaderboard(nLeaderboardId);
if (!pLeaderboard)
return;
ra::ui::viewmodels::ScoreboardViewModel vmScoreboard;
vmScoreboard.SetHeaderText(ra::Widen(pLeaderboard->GetName()));
const auto& pUserName = ra::services::ServiceLocator::Get<ra::data::context::UserContext>().GetDisplayName();
auto& pEntryViewModel = vmScoreboard.Entries().Add();
pEntryViewModel.SetRank(0);
pEntryViewModel.SetScore(ra::Widen(pLeaderboard->FormatScore(nScore)));
pEntryViewModel.SetUserName(ra::Widen(pUserName));
pEntryViewModel.SetHighlighted(true);
ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>().QueueScoreboard(
pLeaderboard->GetID(), std::move(vmScoreboard));
}
void GameContext::SubmitLeaderboardEntry(ra::LeaderboardID nLeaderboardId, int nScore) const
{
const auto* pLeaderboard = Assets().FindLeaderboard(nLeaderboardId);
if (pLeaderboard == nullptr)
return;
std::unique_ptr<ra::ui::viewmodels::PopupMessageViewModel> vmPopup(new ra::ui::viewmodels::PopupMessageViewModel);
vmPopup->SetDescription(ra::Widen(pLeaderboard->GetName()));
std::wstring sTitle = L"Leaderboard NOT Submitted";
bool bSubmit = true;
switch (pLeaderboard->GetCategory())
{
case ra::data::models::AssetCategory::Local:
sTitle.insert(0, L"Local ");
bSubmit = false;
break;
case ra::data::models::AssetCategory::Unofficial:
sTitle.insert(0, L"Unofficial ");
bSubmit = false;
break;
default:
break;
}
if (m_nMode == Mode::CompatibilityTest)
{
vmPopup->SetDetail(L"Leaderboards are not submitted in test mode.");
bSubmit = false;
}
if (pLeaderboard->IsModified() || // actual modifications
(bSubmit && pLeaderboard->GetChanges() != ra::data::models::AssetChanges::None)) // unpublished changes
{
sTitle.insert(0, L"Modified ");
bSubmit = false;
}
const auto& pEmulatorContext = ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>();
if (bSubmit && pEmulatorContext.WasMemoryModified())
{
vmPopup->SetErrorDetail(L"Error: RAM tampered with");
bSubmit = false;
}
const auto& pConfiguration = ra::services::ServiceLocator::Get<ra::services::IConfiguration>();
if (bSubmit)
{
if (!pConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore))
{
vmPopup->SetErrorDetail(L"Submission requires Hardcore mode");
bSubmit = false;
}
else if (pEmulatorContext.IsMemoryInsecure())
{
vmPopup->SetErrorDetail(L"Error: RAM insecure");
bSubmit = false;
}
else if (pConfiguration.IsFeatureEnabled(ra::services::Feature::Offline))
{
vmPopup->SetDetail(L"Leaderboards are not submitted in offline mode.");
bSubmit = false;
}
}
if (!bSubmit)
{
vmPopup->SetTitle(sTitle);
ra::services::ServiceLocator::Get<ra::services::IAudioSystem>().PlayAudioFile(L"Overlay\\info.wav");
ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>().QueueMessage(vmPopup);
ShowSimplifiedScoreboard(nLeaderboardId, nScore);
return;
}
ra::api::SubmitLeaderboardEntry::Request request;
request.LeaderboardId = pLeaderboard->GetID();
request.Score = nScore;
request.GameHash = GameHash();
request.CallAsyncWithRetry([this, nLeaderboardId = pLeaderboard->GetID()](const ra::api::SubmitLeaderboardEntry::Response& response)
{
const auto* pLeaderboard = Assets().FindLeaderboard(nLeaderboardId);
if (!response.Succeeded())
{
std::unique_ptr<ra::ui::viewmodels::PopupMessageViewModel> vmPopup(new ra::ui::viewmodels::PopupMessageViewModel);
vmPopup->SetTitle(L"Leaderboard NOT Submitted");
vmPopup->SetDescription(pLeaderboard ? pLeaderboard->GetName() : ra::StringPrintf(L"Leaderboard %u", nLeaderboardId));
vmPopup->SetDetail(!response.ErrorMessage.empty() ? ra::StringPrintf(L"Error: %s", response.ErrorMessage) : L"Error submitting entry");
ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>().QueueMessage(vmPopup);
}
else if (pLeaderboard)
{
auto& pConfiguration = ra::services::ServiceLocator::Get<ra::services::IConfiguration>();
if (pConfiguration.GetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardScoreboard) != ra::ui::viewmodels::PopupLocation::None)
{
ra::ui::viewmodels::ScoreboardViewModel vmScoreboard;
vmScoreboard.SetHeaderText(pLeaderboard->GetName());
const auto& pUserName = ra::services::ServiceLocator::Get<ra::data::context::UserContext>().GetDisplayName();
constexpr int nEntriesDisplayed = 7; // display is currently hard-coded to show 7 entries
bool bSeenPlayer = false;
for (const auto& pEntry : response.TopEntries)
{
auto& pEntryViewModel = vmScoreboard.Entries().Add();
pEntryViewModel.SetRank(pEntry.Rank);
pEntryViewModel.SetScore(ra::Widen(pLeaderboard->FormatScore(pEntry.Score)));
pEntryViewModel.SetUserName(ra::Widen(pEntry.User));
if (pEntry.User == pUserName)
{
bSeenPlayer = true;
pEntryViewModel.SetHighlighted(true);
if (response.BestScore != response.Score)
pEntryViewModel.SetScore(ra::StringPrintf(L"(%s) %s", pLeaderboard->FormatScore(response.Score), pLeaderboard->FormatScore(response.BestScore)));
}
if (vmScoreboard.Entries().Count() == nEntriesDisplayed)
break;
}
if (!bSeenPlayer)
{
auto* pEntryViewModel = vmScoreboard.Entries().GetItemAt(6);
if (pEntryViewModel != nullptr)
{
pEntryViewModel->SetRank(response.NewRank);
if (response.BestScore != response.Score)
pEntryViewModel->SetScore(ra::StringPrintf(L"(%s) %s", pLeaderboard->FormatScore(response.Score), pLeaderboard->FormatScore(response.BestScore)));
else
pEntryViewModel->SetScore(ra::Widen(pLeaderboard->FormatScore(response.BestScore)));
pEntryViewModel->SetUserName(ra::Widen(pUserName));
pEntryViewModel->SetHighlighted(true);
}
}
ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::OverlayManager>().QueueScoreboard(
pLeaderboard->GetID(), std::move(vmScoreboard));
}
}
});
}
void GameContext::OnCodeNoteChanged(ra::ByteAddress nAddress, const std::wstring& sNewNote)
{
if (!m_vNotifyTargets.empty())
+3 -27
View File
@@ -33,10 +33,10 @@ public:
virtual void LoadGame(unsigned int nGameId, const std::string& sGameHash, Mode nMode = Mode::Normal);
/// <summary>
/// Loads the data from the game currently loaded in the RcheevosClient.
/// Loads the data from the game currently loaded in the AchievementRuntime.
/// </summary>
void InitializeFromRcheevosClient(const std::map<uint32_t, std::string> mAchievementDefinitions,
const std::map<uint32_t, std::string> mLeaderboardDefinitions);
void InitializeFromAchievementRuntime(const std::map<uint32_t, std::string> mAchievementDefinitions,
const std::map<uint32_t, std::string> mLeaderboardDefinitions);
/// <summary>
/// Determines whether a game is being loaded.
@@ -93,24 +93,6 @@ public:
GameAssets& Assets() noexcept { return m_vAssets; }
const GameAssets& Assets() const noexcept { return m_vAssets; }
/// <summary>
/// Shows the popup for earning an achievement and notifies the server if legitimate.
/// </summary>
void AwardAchievement(ra::AchievementID nAchievementId);
void DeactivateLeaderboards();
void ActivateLeaderboards();
/// <summary>
/// Submit a new score for the specified leaderboard.
/// </summary>
void SubmitLeaderboardEntry(ra::LeaderboardID nLeaderboardId, int nScore) const;
/// <summary>
/// Updates the set of unlocked achievements from the server.
/// </summary>
void RefreshUnlocks() { RefreshUnlocks(false, 0); }
class NotifyTarget
{
public:
@@ -138,10 +120,6 @@ private:
void FinishLoadGame(int nResult, const char* sErrorMessage, bool bWasPaused, const std::string& sOldRichPresence);
protected:
void RefreshUnlocks(bool bUnpause, int nPopup);
void UpdateUnlocks(const std::set<unsigned int>& vUnlockedAchievements, bool bUnpause, int nPopup);
void ShowSimplifiedScoreboard(ra::LeaderboardID nLeaderboardId, int nScore) const;
void OnBeforeActiveGameChanged();
void OnActiveGameChanged();
void OnCodeNoteChanged(ra::ByteAddress nAddress, const std::wstring& sNewNote);
@@ -155,8 +133,6 @@ protected:
Mode m_nMode{};
private:
void CheckForMastery();
/// <summary>
/// A collection of pointers to other objects. These are not allocated object and do not need to be free'd. It's
/// impossible to create a set of <c>NotifyTarget</c> references.
-7
View File
@@ -5,7 +5,6 @@
#include "RA_md5factory.h"
#include "RA_StringUtils.h"
#include "api\Ping.hh"
#include "api\StartSession.hh"
#include "data\context\GameContext.hh"
@@ -153,12 +152,6 @@ void SessionTracker::UpdateSession(time_t tSessionStart)
if (tSessionStart != m_tSessionStart)
return;
// ping the server (this also updates the player's current activity string)
ra::api::Ping::Request request;
request.GameId = m_nCurrentGameId;
request.CurrentActivity = GetCurrentActivity();
request.CallAsync([](const ra::api::Ping::Response&) {});
// update session duration
const auto& pClock = ra::services::ServiceLocator::Get<ra::services::IClock>();
const auto tSessionDuration = std::chrono::duration_cast<std::chrono::seconds>(pClock.UpTime() - m_tpSessionStart);
+2 -2
View File
@@ -7,8 +7,8 @@
#include "data\context\EmulatorContext.hh"
#include "services\AchievementRuntime.hh"
#include "services\IConfiguration.hh"
#include "services\RcheevosClient.hh"
#include "ui\viewmodels\MessageBoxViewModel.hh"
#include "ui\viewmodels\OverlayManager.hh"
@@ -23,7 +23,7 @@ void UserContext::Logout()
if (!IsLoggedIn())
return;
auto* pClient = ra::services::ServiceLocator::Get<ra::services::RcheevosClient>().GetClient();
auto* pClient = ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>().GetClient();
rc_client_logout(pClient);
m_sUsername.clear();
+234 -50
View File
@@ -9,6 +9,9 @@
#include "services\IClock.hh"
#include "services\ServiceLocator.hh"
#include <rcheevos\src\rcheevos\rc_internal.h>
#include <rcheevos\src\rcheevos\rc_client_internal.h>
namespace ra {
namespace data {
namespace models {
@@ -54,11 +57,13 @@ void AchievementModel::OnValueChanged(const IntModelProperty::ChangeArgs& args)
}
else if (args.Property == TriggerProperty)
{
if (IsActive())
{
auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
pRuntime.ActivateAchievement(GetID(), GetTrigger());
}
if (m_pAchievement)
SyncTrigger();
}
else if (args.Property == PointsProperty)
{
if (m_pAchievement)
SyncPoints();
}
}
@@ -74,7 +79,17 @@ void AchievementModel::OnValueChanged(const StringModelProperty::ChangeArgs& arg
if (IsUpdating())
return;
if (args.Property == BadgeProperty)
if (args.Property == DescriptionProperty)
{
if (m_pAchievement)
SyncDescription();
}
else if (args.Property == NameProperty)
{
if (m_pAchievement)
SyncTitle();
}
else if (args.Property == BadgeProperty)
{
if (ra::StringStartsWith(args.tOldValue, L"local\\"))
{
@@ -97,6 +112,9 @@ void AchievementModel::OnValueChanged(const StringModelProperty::ChangeArgs& arg
pLocalBadges->AddReference(args.tNewValue, bCommitted);
}
}
if (m_pAchievement)
SyncBadge();
}
}
@@ -140,8 +158,10 @@ bool AchievementModel::ValidateAsset(std::wstring& sError)
void AchievementModel::DoFrame()
{
const auto& pRuntime = ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>();
const auto* pTrigger = pRuntime.GetAchievementTrigger(GetID());
if (!m_pAchievement)
return;
const auto* pTrigger = m_pAchievement->trigger;
if (pTrigger == nullptr)
{
if (IsActive())
@@ -178,68 +198,232 @@ void AchievementModel::DoFrame()
void AchievementModel::HandleStateChanged(AssetState nOldState, AssetState nNewState)
{
if (!ra::services::ServiceLocator::Exists<ra::services::AchievementRuntime>())
if (!m_pAchievement)
return;
const bool bWasActive = IsActive(nOldState);
const bool bIsActive = IsActive(nNewState);
if (bWasActive == bIsActive)
return;
auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
auto* pTrigger = pRuntime.GetAchievementTrigger(GetID());
if (bIsActive)
if (!bIsActive && bWasActive)
{
const bool bRuntimeActive = (pTrigger != nullptr);
if (!bRuntimeActive)
{
pRuntime.ActivateAchievement(GetID(), GetTrigger());
pTrigger = pRuntime.GetAchievementTrigger(GetID());
}
Expects(m_pAchievement->trigger != nullptr);
m_pCapturedTriggerHits.Capture(m_pAchievement->trigger, GetTrigger());
}
else if (bIsActive && !bWasActive)
{
if (m_pAchievement->trigger)
rc_reset_trigger(m_pAchievement->trigger);
else
{
rc_reset_trigger(pTrigger);
}
}
else if (pTrigger)
{
m_pCapturedTriggerHits.Capture(pTrigger, GetTrigger());
pRuntime.DeactivateAchievement(GetID());
SyncTrigger();
}
switch (nNewState)
SyncState();
}
void AchievementModel::SyncState()
{
switch (GetState())
{
case AssetState::Waiting:
if (pTrigger)
pTrigger->state = RC_TRIGGER_STATE_WAITING;
break;
case ra::data::models::AssetState::Triggered: {
m_pAchievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
if (m_pAchievement->trigger)
m_pAchievement->trigger->state = RC_TRIGGER_STATE_TRIGGERED;
case AssetState::Active:
if (pTrigger)
pTrigger->state = RC_TRIGGER_STATE_ACTIVE;
break;
case AssetState::Inactive:
if (pTrigger)
pTrigger->state = RC_TRIGGER_STATE_INACTIVE;
break;
case AssetState::Primed:
if (pTrigger)
pTrigger->state = RC_TRIGGER_STATE_PRIMED;
break;
case AssetState::Triggered:
const auto& pClock = ra::services::ServiceLocator::Get<ra::services::IClock>();
m_tUnlock = pClock.Now();
auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
if (pRuntime.HasRichPresence())
SetUnlockRichPresence(pRuntime.GetRichPresenceDisplayString());
break;
}
case ra::data::models::AssetState::Disabled:
m_pAchievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_DISABLED;
m_pAchievement->public_.bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED;
if (m_pAchievement->trigger)
m_pAchievement->trigger->state = RC_TRIGGER_STATE_DISABLED;
break;
case ra::data::models::AssetState::Inactive:
m_pAchievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE;
if (m_pAchievement->trigger)
m_pAchievement->trigger->state = RC_TRIGGER_STATE_INACTIVE;
break;
case ra::data::models::AssetState::Waiting:
m_pAchievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
if (m_pAchievement->trigger)
m_pAchievement->trigger->state = RC_TRIGGER_STATE_WAITING;
break;
case ra::data::models::AssetState::Active:
m_pAchievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
if (m_pAchievement->trigger)
m_pAchievement->trigger->state = RC_TRIGGER_STATE_ACTIVE;
break;
case ra::data::models::AssetState::Primed:
m_pAchievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
if (m_pAchievement->trigger)
m_pAchievement->trigger->state = RC_TRIGGER_STATE_PRIMED;
break;
default:
m_pAchievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
break;
}
}
void AchievementModel::SyncTitle()
{
m_sTitleBuffer = ra::Narrow(GetName());
m_pAchievement->public_.title = m_sTitleBuffer.c_str();
}
void AchievementModel::SyncDescription()
{
m_sDescriptionBuffer = ra::Narrow(GetDescription());
m_pAchievement->public_.description = m_sDescriptionBuffer.c_str();
}
void AchievementModel::SyncBadge()
{
const auto& sBadge = GetBadge();
if (ra::StringStartsWith(sBadge, L"local\\"))
{
// cannot fit "local/md5.png" into 8 byte buffer. also, client may not understand.
// encode a value that we can intercept in rc_client_achievement_get_image_url.
snprintf(m_pAchievement->public_.badge_name, sizeof(m_pAchievement->public_.badge_name), "L%06x",
m_pAchievement->public_.id);
}
else
{
snprintf(m_pAchievement->public_.badge_name, sizeof(m_pAchievement->public_.badge_name), "%s",
ra::Narrow(sBadge).c_str());
}
}
void AchievementModel::SyncPoints()
{
m_pAchievement->public_.points = GetPoints();
}
void AchievementModel::SyncCategory()
{
switch (GetCategory())
{
case ra::data::models::AssetCategory::Core:
case ra::data::models::AssetCategory::Local:
m_pAchievement->public_.category = RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE;
break;
default:
m_pAchievement->public_.category = RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL;
break;
}
}
void AchievementModel::SyncTrigger()
{
if (!IsActive())
{
if (m_pAchievement->trigger)
{
auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
if (pRuntime.DetachMemory(m_pAchievement->trigger))
free(m_pAchievement->trigger);
m_pAchievement->trigger = nullptr;
}
memset(m_pAchievement->md5, 0, sizeof(m_pAchievement->md5));
return;
}
const auto& sTrigger = GetTrigger();
uint8_t md5[16];
rc_runtime_checksum(sTrigger.c_str(), md5);
if (memcmp(m_pAchievement->md5, md5, sizeof(md5)) != 0)
{
memcpy(m_pAchievement->md5, md5, sizeof(md5));
auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
if (m_pAchievement->trigger && pRuntime.DetachMemory(m_pAchievement->trigger))
{
free(m_pAchievement->trigger);
m_pAchievement->trigger = nullptr;
}
const auto nSize = rc_trigger_size(sTrigger.c_str());
if (nSize > 0)
{
void* trigger_buffer = malloc(nSize);
if (trigger_buffer)
{
// populate the item, using the communal memrefs pool
auto* pGame = pRuntime.GetClient()->game;
rc_parse_state_t parse;
rc_init_parse_state(&parse, trigger_buffer, nullptr, 0);
parse.first_memref = &pGame->runtime.memrefs;
parse.variables = &pGame->runtime.variables;
const char* sMemaddr = sTrigger.c_str();
m_pAchievement->trigger = RC_ALLOC(rc_trigger_t, &parse);
rc_parse_trigger_internal(m_pAchievement->trigger, &sMemaddr, &parse);
rc_destroy_parse_state(&parse);
m_pAchievement->trigger->memrefs = nullptr;
pRuntime.AttachMemory(m_pAchievement->trigger);
}
}
}
}
void AchievementModel::Attach(struct rc_client_achievement_info_t& pAchievement,
AssetCategory nCategory, const std::string& sTrigger)
{
SetID(pAchievement.public_.id);
SetName(ra::Widen(pAchievement.public_.title));
SetDescription(ra::Widen(pAchievement.public_.description));
SetCategory(nCategory);
SetPoints(pAchievement.public_.points);
if (pAchievement.author)
SetAuthor(ra::Widen(pAchievement.author));
SetBadge(ra::Widen(pAchievement.public_.badge_name));
SetCreationTime(pAchievement.created_time);
SetUpdatedTime(pAchievement.updated_time);
SetTrigger(sTrigger);
CreateServerCheckpoint();
CreateLocalCheckpoint();
m_pAchievement = &pAchievement;
DoFrame(); // sync state
}
void AchievementModel::ReplaceAttached(struct rc_client_achievement_info_t& pAchievement) noexcept
{
m_pAchievement = &pAchievement;
}
void AchievementModel::AttachAndInitialize(struct rc_client_achievement_info_t& pAchievement)
{
pAchievement.public_.id = GetID();
m_pAchievement = &pAchievement;
SyncTitle();
SyncDescription();
SyncBadge();
SyncPoints();
SyncCategory();
SyncTrigger();
SyncState();
}
void AchievementModel::Serialize(ra::services::TextWriter& pWriter) const
{
WriteQuoted(pWriter, GetLocalAssetDefinition(m_pTrigger));
+23
View File
@@ -6,6 +6,8 @@
#include "CapturedTriggerHits.hh"
struct rc_client_achievement_info_t;
namespace ra {
namespace data {
namespace models {
@@ -127,6 +129,16 @@ public:
void Serialize(ra::services::TextWriter& pWriter) const override;
bool Deserialize(ra::Tokenizer& pTokenizer) override;
// Attaches an rc_client_achievement_info_t and populates the model from it.
void Attach(struct rc_client_achievement_info_t& pAchievement,
AssetCategory nCategory, const std::string& sTrigger);
// Replaces the attached rc_client_achievement_info_t. Does not update model or source.
void ReplaceAttached(struct rc_client_achievement_info_t& pAchievement) noexcept;
// Attaches an rc_client_achievement_info_t and populates it from the model.
void AttachAndInitialize(struct rc_client_achievement_info_t& pAchievement);
// Gets the attached rc_client_achievement_info_t.
const struct rc_client_achievement_info_t* GetAttached() const noexcept { return m_pAchievement; }
const CapturedTriggerHits& GetCapturedHits() const noexcept { return m_pCapturedTriggerHits; }
void ResetCapturedHits() noexcept { m_pCapturedTriggerHits.Reset(); }
@@ -149,10 +161,21 @@ private:
static const std::array<int, 10> s_vValidPoints;
void HandleStateChanged(AssetState nOldState, AssetState nNewState);
void SyncTitle();
void SyncDescription();
void SyncBadge();
void SyncPoints();
void SyncCategory();
void SyncState();
void SyncTrigger();
AssetDefinition m_pTrigger;
std::chrono::system_clock::time_point m_tUnlock;
struct rc_client_achievement_info_t* m_pAchievement = nullptr;
std::string m_sTitleBuffer;
std::string m_sDescriptionBuffer;
CapturedTriggerHits m_pCapturedTriggerHits;
};
+175 -37
View File
@@ -7,6 +7,9 @@
#include "services\AchievementRuntime.hh"
#include "services\ServiceLocator.hh"
#include <rcheevos\src\rcheevos\rc_client_internal.h>
#include <rcheevos\src\rcheevos\rc_internal.h>
namespace ra {
namespace data {
namespace models {
@@ -57,18 +60,34 @@ void LeaderboardModel::OnValueChanged(const IntModelProperty::ChangeArgs& args)
else if (args.Property == StartTriggerProperty || args.Property == SubmitTriggerProperty ||
args.Property == CancelTriggerProperty || args.Property == ValueDefinitionProperty)
{
if (IsActive())
{
const std::string sDefinition = GetDefinition();
auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
pRuntime.ActivateLeaderboard(GetID(), sDefinition);
}
SyncDefinition();
}
}
AssetModelBase::OnValueChanged(args);
}
void LeaderboardModel::OnValueChanged(const StringModelProperty::ChangeArgs& args)
{
// call base first so the transaction gets updated for GetLocalValue to work properly
AssetModelBase::OnValueChanged(args);
// if we're still loading the asset, ignore the event. we'll get recalled once loading finishes
if (IsUpdating())
return;
if (args.Property == DescriptionProperty)
{
if (m_pLeaderboard)
SyncDescription();
}
else if (args.Property == NameProperty)
{
if (m_pLeaderboard)
SyncTitle();
}
}
bool LeaderboardModel::ValidateAsset(std::wstring& sError)
{
const auto& sStartTrigger = GetAssetDefinition(m_pStartTrigger);
@@ -165,56 +184,139 @@ void LeaderboardModel::DoFrame()
void LeaderboardModel::HandleStateChanged(AssetState nOldState, AssetState nNewState)
{
if (!ra::services::ServiceLocator::Exists<ra::services::AchievementRuntime>())
if (!m_pLeaderboard)
return;
const bool bWasActive = IsActive(nOldState);
const bool bIsActive = IsActive(nNewState);
if (bWasActive == bIsActive)
return;
auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
auto* pLeaderboard = pRuntime.GetLeaderboardDefinition(GetID());
if (!bIsActive && bWasActive)
{
const auto* pLeaderboard = m_pLeaderboard->lboard;
Expects(pLeaderboard != nullptr);
if (bIsActive)
{
const bool bRuntimeActive = (pLeaderboard != nullptr);
if (!bRuntimeActive)
{
const std::string sDefinition = GetDefinition();
pRuntime.ActivateLeaderboard(GetID(), sDefinition);
pLeaderboard = pRuntime.GetLeaderboardDefinition(GetID());
}
else
{
rc_reset_lboard(pLeaderboard);
}
}
else if (pLeaderboard)
{
m_pCapturedStartTriggerHits.Capture(&pLeaderboard->start, GetStartTrigger());
m_pCapturedSubmitTriggerHits.Capture(&pLeaderboard->submit, GetSubmitTrigger());
m_pCapturedCancelTriggerHits.Capture(&pLeaderboard->cancel, GetCancelTrigger());
m_pCapturedValueDefinitionHits.Capture(&pLeaderboard->value, GetValueDefinition());
pRuntime.DeactivateLeaderboard(GetID());
if (pLeaderboard->state == RC_LBOARD_STATE_STARTED)
{
auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
pRuntime.ReleaseLeaderboardTracker(m_pLeaderboard->public_.id);
}
}
else if (bIsActive && !bWasActive)
{
if (m_pLeaderboard->lboard)
rc_reset_lboard(m_pLeaderboard->lboard);
else
SyncDefinition();
}
switch (nNewState)
{
case AssetState::Waiting:
if (pLeaderboard)
pLeaderboard->state = RC_LBOARD_STATE_WAITING;
case ra::data::models::AssetState::Disabled:
m_pLeaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_DISABLED;
if (m_pLeaderboard->lboard)
m_pLeaderboard->lboard->state = RC_LBOARD_STATE_DISABLED;
break;
case AssetState::Active:
if (pLeaderboard)
pLeaderboard->state = RC_LBOARD_STATE_ACTIVE;
case ra::data::models::AssetState::Inactive:
m_pLeaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_INACTIVE;
if (m_pLeaderboard->lboard)
m_pLeaderboard->lboard->state = RC_LBOARD_STATE_INACTIVE;
break;
case AssetState::Inactive:
if (pLeaderboard)
pLeaderboard->state = RC_LBOARD_STATE_INACTIVE;
case ra::data::models::AssetState::Primed:
m_pLeaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_TRACKING;
if (m_pLeaderboard->lboard)
m_pLeaderboard->lboard->state = RC_LBOARD_STATE_STARTED;
break;
case ra::data::models::AssetState::Waiting:
m_pLeaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_ACTIVE;
if (m_pLeaderboard->lboard)
m_pLeaderboard->lboard->state = RC_LBOARD_STATE_WAITING;
break;
default:
m_pLeaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_ACTIVE;
if (m_pLeaderboard->lboard)
m_pLeaderboard->lboard->state = RC_LBOARD_STATE_ACTIVE;
break;
}
}
void LeaderboardModel::SyncTitle()
{
m_sTitleBuffer = ra::Narrow(GetName());
m_pLeaderboard->public_.title = m_sTitleBuffer.c_str();
}
void LeaderboardModel::SyncDescription()
{
m_sDescriptionBuffer = ra::Narrow(GetDescription());
m_pLeaderboard->public_.description = m_sDescriptionBuffer.c_str();
}
void LeaderboardModel::SyncDefinition()
{
if (!m_pLeaderboard)
return;
if (!IsActive())
{
if (m_pLeaderboard->lboard)
{
auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
if (pRuntime.DetachMemory(m_pLeaderboard->lboard))
free(m_pLeaderboard->lboard);
m_pLeaderboard->lboard = nullptr;
}
memset(m_pLeaderboard->md5, 0, sizeof(m_pLeaderboard->md5));
return;
}
const auto& sMemAddr = GetDefinition();
uint8_t md5[16];
rc_runtime_checksum(sMemAddr.c_str(), md5);
if (memcmp(m_pLeaderboard->md5, md5, sizeof(md5)) != 0)
{
memcpy(m_pLeaderboard->md5, md5, sizeof(md5));
auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
if (m_pLeaderboard->lboard && pRuntime.DetachMemory(m_pLeaderboard->lboard))
{
free(m_pLeaderboard->lboard);
m_pLeaderboard->lboard = nullptr;
}
const auto nSize = rc_lboard_size(sMemAddr.c_str());
if (nSize > 0)
{
void* lboard_buffer = malloc(nSize);
if (lboard_buffer)
{
/* populate the item, using the communal memrefs pool */
auto* pGame = pRuntime.GetClient()->game;
rc_parse_state_t parse;
rc_init_parse_state(&parse, lboard_buffer, nullptr, 0);
parse.first_memref = &pGame->runtime.memrefs;
parse.variables = &pGame->runtime.variables;
m_pLeaderboard->lboard = RC_ALLOC(rc_lboard_t, &parse);
rc_parse_lboard_internal(m_pLeaderboard->lboard, sMemAddr.c_str(), &parse);
rc_destroy_parse_state(&parse);
m_pLeaderboard->lboard->memrefs = nullptr;
pRuntime.AttachMemory(m_pLeaderboard->lboard);
}
}
}
}
@@ -247,6 +349,42 @@ std::string LeaderboardModel::GetDefinition() const
"::CAN:" + GetCancelTrigger() + "::VAL:" + GetValueDefinition();
}
void LeaderboardModel::Attach(struct rc_client_leaderboard_info_t& pLeaderboard,
AssetCategory nCategory, const std::string& sDefinition)
{
SetID(pLeaderboard.public_.id);
SetName(ra::Widen(pLeaderboard.public_.title));
SetDescription(ra::Widen(pLeaderboard.public_.description));
SetCategory(nCategory);
SetValueFormat(ra::itoe<ValueFormat>(pLeaderboard.format));
SetLowerIsBetter(pLeaderboard.public_.lower_is_better);
SetHidden(pLeaderboard.hidden);
SetDefinition(sDefinition);
CreateServerCheckpoint();
CreateLocalCheckpoint();
m_pLeaderboard = &pLeaderboard;
DoFrame(); // sync state
}
void LeaderboardModel::ReplaceAttached(struct rc_client_leaderboard_info_t& pLeaderboard) noexcept
{
m_pLeaderboard = &pLeaderboard;
}
void LeaderboardModel::AttachAndInitialize(struct rc_client_leaderboard_info_t& pLeaderboard)
{
pLeaderboard.public_.id = GetID();
m_pLeaderboard = &pLeaderboard;
SyncTitle();
SyncDescription();
SyncDefinition();
}
void LeaderboardModel::Serialize(ra::services::TextWriter& pWriter) const
{
WriteQuoted(pWriter, GetLocalAssetDefinition(m_pStartTrigger));
+20
View File
@@ -8,6 +8,8 @@
#include "data\Types.hh"
struct rc_client_leaderboard_info_t;
namespace ra {
namespace data {
namespace models {
@@ -189,6 +191,16 @@ public:
void Serialize(ra::services::TextWriter& pWriter) const override;
bool Deserialize(ra::Tokenizer& pTokenizer) override;
// Attaches an rc_client_leaderboard_info_t and populates the model from it.
void Attach(struct rc_client_leaderboard_info_t& pLeaderboard,
AssetCategory nCategory, const std::string& sDefinition);
// Replaces the attached rc_client_leaderboard_info_t. Does not update model or source.
void ReplaceAttached(struct rc_client_leaderboard_info_t& pLeaderboard) noexcept;
// Attaches an rc_client_leaderboard_info_t and populates it from the model.
void AttachAndInitialize(struct rc_client_leaderboard_info_t& pLeaderboard);
// Gets the attached rc_client_achievement_info_t.
const struct rc_client_leaderboard_info_t* GetAttached() const noexcept { return m_pLeaderboard; }
const CapturedTriggerHits& GetStartCapturedHits() const noexcept { return m_pCapturedStartTriggerHits; }
const CapturedTriggerHits& GetSubmitCapturedHits() const noexcept { return m_pCapturedSubmitTriggerHits; }
const CapturedTriggerHits& GetCancelCapturedHits() const noexcept { return m_pCapturedCancelTriggerHits; }
@@ -205,17 +217,25 @@ public:
protected:
void OnValueChanged(const IntModelProperty::ChangeArgs& args) override;
void OnValueChanged(const StringModelProperty::ChangeArgs& args) override;
bool ValidateAsset(std::wstring& sErrorMessage) override;
private:
void HandleStateChanged(AssetState nOldState, AssetState nNewState);
void SyncTitle();
void SyncDescription();
void SyncDefinition();
AssetDefinition m_pStartTrigger;
AssetDefinition m_pSubmitTrigger;
AssetDefinition m_pCancelTrigger;
AssetDefinition m_pValueDefinition;
struct rc_client_leaderboard_info_t* m_pLeaderboard = nullptr;
std::string m_sTitleBuffer;
std::string m_sDescriptionBuffer;
CapturedTriggerHits m_pCapturedStartTriggerHits;
CapturedTriggerHits m_pCapturedSubmitTriggerHits;
CapturedTriggerHits m_pCapturedCancelTriggerHits;
File diff suppressed because it is too large Load Diff
+148 -153
View File
@@ -6,122 +6,75 @@
#include "data\Types.hh"
#include "data\context\EmulatorContext.hh"
#include "data\models\AchievementModel.hh"
#include "data\models\LeaderboardModel.hh"
#include "services\TextReader.hh"
#include <string>
#include <rcheevos\include\rcheevos.h>
#include <rcheevos\include\rc_client.h>
struct rc_api_fetch_game_data_response_t;
namespace ra {
namespace services {
class AchievementRuntime : protected ra::data::context::EmulatorContext::NotifyTarget
class AchievementRuntime
{
public:
GSL_SUPPRESS_F6 AchievementRuntime() = default;
virtual ~AchievementRuntime()
{
ResetRuntime();
}
GSL_SUPPRESS_F6 AchievementRuntime();
virtual ~AchievementRuntime();
AchievementRuntime(const AchievementRuntime&) noexcept = delete;
AchievementRuntime& operator=(const AchievementRuntime&) noexcept = delete;
AchievementRuntime(AchievementRuntime&&) noexcept = delete;
AchievementRuntime& operator=(AchievementRuntime&&) noexcept = delete;
/// <summary>
/// Shuts down the runtime.
/// </summary>
void Shutdown() noexcept;
/// <summary>
/// Gets the underlying rc_client object for directly calling into rcheevos.
/// </summary>
rc_client_t* GetClient() const noexcept { return m_pClient.get(); }
void BeginLoginWithToken(const std::string& sUsername, const std::string& sApiToken,
rc_client_callback_t fCallback, void* pCallbackData);
void BeginLoginWithPassword(const std::string& sUsername, const std::string& sPassword,
rc_client_callback_t fCallback, void* pCallbackData);
void BeginLoadGame(const std::string& sHash, unsigned id,
rc_client_callback_t fCallback, void* pCallbackData);
/// <summary>
/// Syncs data in Assets to rc_client.
/// </summary>
void SyncAssets();
/// <summary>
/// Clears all active achievements/leaderboards/rich presence from the runtime.
/// </summary>
void ResetRuntime() noexcept;
/// <summary>
/// Adds an achievement to the processing queue.
/// </summary>
int ActivateAchievement(ra::AchievementID nId, const std::string& sTrigger);
/// <summary>
/// Removes an achievement from the processing queue.
/// </summary>
void DeactivateAchievement(ra::AchievementID nId);
/// <summary>
/// Updates any references using the provided ID to a new value
/// Updates any references using the provided ID to a new value.
/// </summary>
void UpdateAchievementId(ra::AchievementID nOldId, ra::AchievementID nNewId);
/// <summary>
/// Gets the raw trigger for the achievement (if active)
/// Gets the raw trigger for the achievement.
/// </summary>
rc_trigger_t* GetAchievementTrigger(ra::AchievementID nId) noexcept
{
if (!m_bInitialized)
return nullptr;
return rc_runtime_get_achievement(&m_pRuntime, nId);
}
rc_trigger_t* GetAchievementTrigger(ra::AchievementID nId) const noexcept;
/// <summary>
/// Gets the raw trigger for the achievement (if active)
/// Gets the raw definition for the leaderboard.
/// </summary>
const rc_trigger_t* GetAchievementTrigger(ra::AchievementID nId) const noexcept
{
if (!m_bInitialized)
return nullptr;
rc_lboard_t* GetLeaderboardDefinition(ra::LeaderboardID nId) const noexcept;
return rc_runtime_get_achievement(&m_pRuntime, nId);
}
/// <summary>
/// Gets the raw trigger for the achievement (if ever active)
/// </summary>
rc_trigger_t* GetAchievementTrigger(ra::AchievementID nId, const std::string& sTrigger);
/// <summary>
/// Gets the raw trigger for the achievement (if active)
/// </summary>
rc_trigger_t* DetachAchievementTrigger(ra::AchievementID nId);
/// <summary>
/// Removes an achievement from the processing queue.
/// </summary>
void ReleaseAchievementTrigger(ra::AchievementID nId, _In_ rc_trigger_t* pTrigger);
/// <summary>
/// Adds a leaderboard to the processing queue.
/// </summary>
int ActivateLeaderboard(unsigned int nId, const std::string& sDefinition);
/// <summary>
/// Removes a leaderboard from the processing queue.
/// </summary>
void DeactivateLeaderboard(unsigned int nId) noexcept
{
if (m_bInitialized)
rc_runtime_deactivate_lboard(&m_pRuntime, nId);
}
/// <summary>
/// Gets the raw definition for the leaderboard (if active)
/// </summary>
rc_lboard_t* GetLeaderboardDefinition(ra::LeaderboardID nId) noexcept
{
if (!m_bInitialized)
return nullptr;
return rc_runtime_get_lboard(&m_pRuntime, nId);
}
/// <summary>
/// Gets the raw definition for the leaderboard (if active)
/// </summary>
const rc_lboard_t* GetLeaderboardDefinition(ra::LeaderboardID nId) const noexcept
{
if (!m_bInitialized)
return nullptr;
return rc_runtime_get_lboard(&m_pRuntime, nId);
}
void ReleaseLeaderboardTracker(ra::LeaderboardID nId) noexcept;
/// <summary>
/// Specifies the rich presence to process each frame.
@@ -133,67 +86,19 @@ public:
/// <summary>
/// Gets whether or not the loaded game has a rich presence script.
/// </summary>
bool HasRichPresence() const noexcept
{
if (!m_bInitialized)
return false;
/* valid rich presence */
if (m_pRuntime.richpresence != nullptr && m_pRuntime.richpresence->richpresence != nullptr)
return true;
/* invalid rich presence */
if (m_nRichPresenceParseResult != RC_OK)
return true;
/* no rich presence */
return false;
}
bool HasRichPresence() const noexcept;
/// <summary>
/// Gets the current rich presence display string.
/// </summary>
std::wstring GetRichPresenceDisplayString() const;
enum class ChangeType
{
None = 0,
AchievementTriggered,
AchievementReset,
AchievementPaused,
LeaderboardStarted,
LeaderboardCanceled,
LeaderboardUpdated,
LeaderboardTriggered,
AchievementDisabled,
LeaderboardDisabled,
AchievementPrimed,
AchievementActivated,
AchievementUnprimed,
AchievementProgressChanged,
// runtime only changes
LeaderboardStartReset,
LeaderboardSubmitReset,
LeaderboardCancelReset,
LeaderboardValueReset,
LeaderboardStartTriggered,
LeaderboardSubmitTriggered,
LeaderboardCancelTriggered,
};
struct Change
{
ChangeType nType;
unsigned int nId;
int nValue;
};
void InvalidateAddress(ra::ByteAddress nAddress) noexcept;
/// <summary>
/// Processes all active achievements for the current frame.
/// </summary>
virtual void Process(_Inout_ std::vector<Change>& changes);
void DoFrame();
/// <summary>
/// Loads HitCount data for active achievements from a save state file.
@@ -207,7 +112,7 @@ public:
/// </summary>
/// <param name="pBuffer">The buffer to read from.</param>
/// <returns><c>true</c> if the achievement HitCounts were modified, <c>false</c> if not.</returns>
bool LoadProgressFromBuffer(const char* pBuffer);
bool LoadProgressFromBuffer(const uint8_t* pBuffer);
/// <summary>
/// Writes HitCount data for active achievements to a save state file.
@@ -225,7 +130,7 @@ public:
/// nBufferSize - in which case the caller should allocate the specified amount
/// and call again.
/// </returns>
int SaveProgressToBuffer(char* pBuffer, int nBufferSize) const;
int SaveProgressToBuffer(uint8_t* pBuffer, int nBufferSize) const;
/// <summary>
/// Gets whether achievement processing is temporarily suspended.
@@ -237,32 +142,122 @@ public:
/// </summary>
void SetPaused(bool bValue) noexcept { m_bPaused = bValue; }
/// <summary>
/// Resets any active achievements and disables them until their triggers are false.
/// </summary>
void ResetActiveAchievements();
class Synchronizer
{
public:
void Wait()
{
#ifdef RA_UTEST
// unit tests are single-threaded. if m_bWaiting is true, it would wait indefinitely.
if (m_bWaiting)
Microsoft::VisualStudio::CppUnitTestFramework::Assert::Fail(L"Sycnhronous request was not handled.");
#else
std::unique_lock<std::mutex> lock(m_pMutex);
if (m_bWaiting)
m_pCondVar.wait(lock);
#endif
}
void InvalidateAddress(ra::ByteAddress nAddress) noexcept;
void Notify() noexcept
{
m_bWaiting = false;
m_pCondVar.notify_all();
}
size_t DetectUnsupportedAchievements();
void CaptureResult(int nResult, const char* sErrorMessage)
{
m_nResult = nResult;
protected:
bool m_bPaused = false;
rc_runtime_t m_pRuntime{};
mutable std::mutex m_pMutex;
if (sErrorMessage)
m_sErrorMessage = sErrorMessage;
else
m_sErrorMessage.clear();
}
// EmulatorContext::NotifyTarget
void OnTotalMemorySizeChanged() override;
int GetResult() const noexcept { return m_nResult; }
const std::string& GetErrorMessage() const noexcept { return m_sErrorMessage; }
private:
std::mutex m_pMutex;
std::condition_variable m_pCondVar;
std::string m_sErrorMessage;
int m_nResult = 0;
bool m_bWaiting = true;
};
void AttachMemory(void* pMemory);
bool DetachMemory(void* pMemory) noexcept;
private:
bool LoadProgressV1(const std::string& sProgress, std::set<unsigned int>& vProcessedAchievementIds);
bool LoadProgressV2(ra::services::TextReader& pFile, std::set<unsigned int>& vProcessedAchievementIds);
bool m_bPaused = false;
std::unique_ptr<rc_client_t> m_pClient;
void EnsureInitialized() noexcept;
class ClientSynchronizer;
std::unique_ptr<ClientSynchronizer> m_pClientSynchronizer;
int m_nRichPresenceParseResult = RC_OK;
int m_nRichPresenceErrorLine = 0;
bool m_bInitialized = false;
static void LogMessage(const char* sMessage, const rc_client_t* pClient);
static uint32_t ReadMemory(uint32_t nAddress, uint8_t* pBuffer, uint32_t nBytes, rc_client_t* pClient);
static void ServerCallAsync(const rc_api_request_t* pRequest, rc_client_server_callback_t fCallback,
void* pCallbackData, rc_client_t* pClient);
static void EventHandler(const rc_client_event_t* pEvent, rc_client_t* pClient);
class CallbackWrapper
{
public:
CallbackWrapper(rc_client_t* client, rc_client_callback_t callback, void* callback_userdata) noexcept :
m_pClient(client), m_fCallback(callback), m_pCallbackUserdata(callback_userdata)
{}
void DoCallback(int nResult, const char* sErrorMessage) noexcept
{
m_fCallback(nResult, sErrorMessage, m_pClient, m_pCallbackUserdata);
}
static void Dispatch(int nResult, const char* sErrorMessage, rc_client_t*, void* pUserdata)
{
auto* pWrapper = static_cast<CallbackWrapper*>(pUserdata);
Expects(pWrapper != nullptr);
pWrapper->DoCallback(nResult, sErrorMessage);
delete pWrapper;
}
private:
rc_client_t* m_pClient;
rc_client_callback_t m_fCallback;
void* m_pCallbackUserdata;
};
friend class AchievementRuntimeExports;
rc_client_async_handle_t* BeginLoginWithPassword(const char* sUsername, const char* sPassword,
CallbackWrapper* pCallbackWrapper) noexcept;
rc_client_async_handle_t* BeginLoginWithToken(const char* sUsername, const char* sApiToken,
CallbackWrapper* pCallbackWrapper) noexcept;
static void LoginCallback(int nResult, const char* sErrorMessage, rc_client_t* pClient, void* pUserdata);
class LoadGameCallbackWrapper : public CallbackWrapper
{
public:
LoadGameCallbackWrapper(rc_client_t* client, rc_client_callback_t callback, void* callback_userdata) noexcept :
CallbackWrapper(client, callback, callback_userdata)
{}
std::map<uint32_t, std::string> m_mAchievementDefinitions;
std::map<uint32_t, std::string> m_mLeaderboardDefinitions;
};
rc_client_async_handle_t* BeginLoadGame(const char* sHash, unsigned id, CallbackWrapper* pCallbackWrapper) noexcept;
static void LoadGameCallback(int nResult, const char* sErrorMessage, rc_client_t* pClient, void* pUserdata);
static void PostProcessGameDataResponse(const rc_api_server_response_t* server_response,
struct rc_api_fetch_game_data_response_t* game_data_response,
rc_client_t* client, void* pUserdata);
};
} // namespace services
-4
View File
@@ -12,7 +12,6 @@
#include "services\FrameEventQueue.hh"
#include "services\GameIdentifier.hh"
#include "services\PerformanceCounter.hh"
#include "services\RcheevosClient.hh"
#include "services\ServiceLocator.hh"
#include "services\impl\Clock.hh"
#include "services\impl\FileLocalStorage.hh"
@@ -138,9 +137,6 @@ void Initialization::RegisterServices(EmulatorID nEmulatorId, const char* sClien
ra::services::ServiceLocator::Provide<ra::services::PerformanceCounter>(std::move(pPerformanceCounter));
#endif
auto pRcheevosClient = std::make_unique<ra::services::RcheevosClient>();
ra::services::ServiceLocator::Provide<ra::services::RcheevosClient>(std::move(pRcheevosClient));
auto pUserContext = std::make_unique<ra::data::context::UserContext>();
ra::services::ServiceLocator::Provide<ra::data::context::UserContext>(std::move(pUserContext));
-876
View File
@@ -1,876 +0,0 @@
#include "RcheevosClient.hh"
#include "data/context/EmulatorContext.hh"
#include "data/context/SessionTracker.hh"
#include "data/context/UserContext.hh"
#include "services/Http.hh"
#include "services/IAudioSystem.hh"
#include "services/ILocalStorage.hh"
#include "services/ServiceLocator.hh"
#include "ui/viewmodels/MessageBoxViewModel.hh"
#include "ui/viewmodels/OverlayManager.hh"
#include "ui/viewmodels/PopupMessageViewModel.hh"
#include "ui/viewmodels/WindowManager.hh"
#include "RA_Log.h"
#include "RA_StringUtils.h"
#include <rcheevos\include\rc_api_runtime.h>
#include <rcheevos\src\rapi\rc_api_common.h> // for parsing cached patchdata response
#include <rcheevos\src\rcheevos\rc_internal.h>
#include <rcheevos\src\rcheevos\rc_client_internal.h>
#undef RC_CLIENT_SUPPORTS_EXTERNAL
namespace ra {
namespace services {
static int CanSubmit()
{
const auto& pGameContext = ra::services::ServiceLocator::Get<ra::data::context::GameContext>();
if (pGameContext.GetMode() == ra::data::context::GameContext::Mode::CompatibilityTest)
return 0;
const auto& pEmulatorContext = ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>();
if (pEmulatorContext.WasMemoryModified())
return 0;
if (pEmulatorContext.IsMemoryInsecure())
return 0;
return 1;
}
static int CanSubmitAchievementUnlock(uint32_t nAchievementId, rc_client_t*)
{
if (!CanSubmit())
return 0;
const auto& pGameContext = ra::services::ServiceLocator::Get<ra::data::context::GameContext>();
const auto* pAchievement = pGameContext.Assets().FindAchievement(nAchievementId);
if (pAchievement == nullptr || pAchievement->GetChanges() != ra::data::models::AssetChanges::None)
return 0;
return 1;
}
static int CanSubmitLeaderboardEntry(uint32_t, rc_client_t*)
{
return CanSubmit();
}
RcheevosClient::RcheevosClient()
{
m_pClient.reset(rc_client_create(RcheevosClient::ReadMemory, RcheevosClient::ServerCallAsync));
m_pClient->callbacks.can_submit_achievement_unlock = CanSubmitAchievementUnlock;
m_pClient->callbacks.can_submit_leaderboard_entry = CanSubmitLeaderboardEntry;
#ifndef RA_UTEST
rc_client_enable_logging(m_pClient.get(), RC_CLIENT_LOG_LEVEL_VERBOSE, RcheevosClient::LogMessage);
#endif
rc_client_set_unofficial_enabled(m_pClient.get(), 1);
}
RcheevosClient::~RcheevosClient()
{
Shutdown();
}
void RcheevosClient::Shutdown() noexcept
{
if (m_pClient != nullptr)
rc_client_destroy(m_pClient.release());
}
void RcheevosClient::LogMessage(const char* sMessage, const rc_client_t*)
{
const auto& pLogger = ra::services::ServiceLocator::Get<ra::services::ILogger>();
if (pLogger.IsEnabled(ra::services::LogLevel::Info))
pLogger.LogMessage(ra::services::LogLevel::Info, sMessage);
}
uint32_t RcheevosClient::ReadMemory(uint32_t nAddress, uint8_t* pBuffer, uint32_t nBytes, rc_client_t*)
{
const auto& pEmulatorContext = ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>();
return pEmulatorContext.ReadMemory(nAddress, pBuffer, nBytes);
}
static void ConvertHttpResponseToApiServerResponse(rc_api_server_response_t& pResponse,
const ra::services::Http::Response& httpResponse) noexcept
{
memset(&pResponse, 0, sizeof(pResponse));
pResponse.http_status_code = ra::etoi(httpResponse.StatusCode());
pResponse.body = httpResponse.Content().c_str();
pResponse.body_length = httpResponse.Content().length();
}
void RcheevosClient::ServerCallAsync(const rc_api_request_t* pRequest,
rc_client_server_callback_t fCallback, void* pCallbackData, rc_client_t*)
{
ra::services::Http::Request httpRequest(pRequest->url);
httpRequest.SetPostData(pRequest->post_data);
httpRequest.SetContentType(pRequest->content_type);
httpRequest.CallAsync([fCallback, pCallbackData](const ra::services::Http::Response& httpResponse) noexcept {
rc_api_server_response_t pResponse;
ConvertHttpResponseToApiServerResponse(pResponse, httpResponse);
fCallback(&pResponse, pCallbackData);
});
}
/* ---- ClientSynchronizer ----- */
class RcheevosClient::ClientSynchronizer
{
public:
ClientSynchronizer() = default;
virtual ~ClientSynchronizer()
{
for (auto* pMemory : m_vAllocatedMemory)
free(pMemory);
}
ClientSynchronizer(const ClientSynchronizer&) noexcept = delete;
ClientSynchronizer& operator=(const ClientSynchronizer&) noexcept = delete;
ClientSynchronizer(ClientSynchronizer&&) noexcept = delete;
ClientSynchronizer& operator=(ClientSynchronizer&&) noexcept = delete;
private:
rc_client_subset_info_t* m_pPublishedSubset = nullptr;
rc_client_t* m_pClient = nullptr;
rc_memref_t** m_pNextMemref = nullptr;
rc_value_t** m_pNextVariable = nullptr;
typedef struct rc_client_subset_wrapper_t
{
rc_client_subset_info_t pCoreSubset{};
rc_client_subset_info_t pLocalSubset{};
rc_api_buffer_t pBuffer{};
std::vector<void*> vAllocatedMemory;
} SubsetWrapper;
std::unique_ptr<SubsetWrapper> m_pSubsetWrapper;
std::vector<void*> m_vAllocatedMemory;
static bool DetachMemory(std::vector<void*>& pAllocatedMemory, void* pMemory) noexcept
{
for (auto pIter = pAllocatedMemory.begin(); pIter != pAllocatedMemory.end(); ++pIter)
{
if (*pIter == pMemory)
{
pAllocatedMemory.erase(pIter);
return true;
}
}
return false;
}
bool AllocatedMemrefs() noexcept
{
bool bAllocatedMemref = false;
/* if at least one memref was allocated within the object, we can't free the buffer when the object is
* deactivated */
if (*m_pNextMemref != nullptr)
{
bAllocatedMemref = true;
/* advance through the new memrefs so we're ready for the next allocation */
do
{
m_pNextMemref = &(*m_pNextMemref)->next;
} while (*m_pNextMemref != nullptr);
}
/* if at least one variable was allocated within the object, we can't free the buffer when the object is
* deactivated */
if (*m_pNextVariable != nullptr)
{
bAllocatedMemref = true;
/* advance through the new variables so we're ready for the next allocation */
do
{
m_pNextVariable = &(*m_pNextVariable)->next;
} while (*m_pNextVariable != nullptr);
}
return bAllocatedMemref;
}
static rc_client_leaderboard_info_t* FindLeaderboard(rc_client_subset_info_t* pSubset, uint32_t nId)
{
Expects(pSubset != nullptr);
auto* pLeaderboard = pSubset->leaderboards;
const auto* pLeaderboardStop = pLeaderboard + pSubset->public_.num_leaderboards;
for (; pLeaderboard < pLeaderboardStop; ++pLeaderboard)
{
if (pLeaderboard->public_.id == nId)
return pLeaderboard;
}
return nullptr;
}
void SyncLeaderboard(rc_client_leaderboard_info_t* pLeaderboard, SubsetWrapper* pSubsetWrapper,
const ra::data::models::LeaderboardModel* vmLeaderboard)
{
Expects(vmLeaderboard != nullptr);
const auto sTitle = ra::Narrow(vmLeaderboard->GetName());
if (!pLeaderboard->public_.title || pLeaderboard->public_.title != sTitle)
pLeaderboard->public_.title = rc_buf_strcpy(&pSubsetWrapper->pBuffer, sTitle.c_str());
const auto sDescription = ra::Narrow(vmLeaderboard->GetDescription());
if (!pLeaderboard->public_.description || pLeaderboard->public_.description != sDescription)
pLeaderboard->public_.description = rc_buf_strcpy(&pSubsetWrapper->pBuffer, sDescription.c_str());
pLeaderboard->public_.id = vmLeaderboard->GetID();
switch (vmLeaderboard->GetState())
{
case ra::data::models::AssetState::Disabled:
pLeaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_DISABLED;
break;
case ra::data::models::AssetState::Inactive:
pLeaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_INACTIVE;
break;
case ra::data::models::AssetState::Primed:
pLeaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_TRACKING;
break;
default:
pLeaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_ACTIVE;
break;
}
const auto& sMemAddr = vmLeaderboard->GetDefinition();
uint8_t md5[16];
rc_runtime_checksum(sMemAddr.c_str(), md5);
if (memcmp(pLeaderboard->md5, md5, sizeof(md5)) != 0)
{
memcpy(pLeaderboard->md5, md5, sizeof(md5));
if (m_pSubsetWrapper && DetachMemory(m_pSubsetWrapper->vAllocatedMemory, pLeaderboard->lboard))
free(pLeaderboard->lboard);
const auto nSize = rc_lboard_size(sMemAddr.c_str());
if (nSize > 0)
{
pLeaderboard->lboard = static_cast<rc_lboard_t*>(malloc(nSize));
if (pLeaderboard->lboard)
{
/* populate the item, using the communal memrefs pool */
rc_parse_state_t parse;
rc_init_parse_state(&parse, pLeaderboard->lboard, nullptr, 0);
parse.first_memref = &m_pClient->game->runtime.memrefs;
parse.variables = &m_pClient->game->runtime.variables;
rc_parse_lboard_internal(pLeaderboard->lboard, sMemAddr.c_str(), &parse);
rc_destroy_parse_state(&parse);
if (AllocatedMemrefs())
{
// if memrefs were allocated, we can't release this memory until the game is unloaded
m_vAllocatedMemory.push_back(pLeaderboard->lboard);
}
else
{
pSubsetWrapper->vAllocatedMemory.push_back(pLeaderboard->lboard);
}
}
}
}
}
static rc_client_achievement_info_t* FindAchievement(rc_client_subset_info_t* pSubset, uint32_t nId)
{
Expects(pSubset != nullptr);
auto* pAchievement = pSubset->achievements;
const auto* pAchievementStop = pAchievement + pSubset->public_.num_achievements;
for (; pAchievement < pAchievementStop; ++pAchievement)
{
if (pAchievement->public_.id == nId)
return pAchievement;
}
return nullptr;
}
void SyncAchievement(rc_client_achievement_info_t* pAchievement, SubsetWrapper* pSubsetWrapper,
const ra::data::models::AchievementModel* vmAchievement)
{
Expects(pAchievement != nullptr);
Expects(pSubsetWrapper != nullptr);
Expects(vmAchievement != nullptr);
const auto sTitle = ra::Narrow(vmAchievement->GetName());
if (!pAchievement->public_.title || pAchievement->public_.title != sTitle)
pAchievement->public_.title = rc_buf_strcpy(&pSubsetWrapper->pBuffer, sTitle.c_str());
const auto sDescription = ra::Narrow(vmAchievement->GetDescription());
if (!pAchievement->public_.description || pAchievement->public_.description != sDescription)
pAchievement->public_.description = rc_buf_strcpy(&pSubsetWrapper->pBuffer, sDescription.c_str());
const auto& sBadge = vmAchievement->GetBadge();
if (ra::StringStartsWith(sBadge, L"local\\"))
{
// cannot fit "local/md5.png" into 8 byte buffer. also, client may not understand.
// encode a value that we can intercept in rc_client_achievement_get_image_url.
snprintf(pAchievement->public_.badge_name, sizeof(pAchievement->public_.badge_name), "L%06x", pAchievement->public_.id);
}
else
{
snprintf(pAchievement->public_.badge_name, sizeof(pAchievement->public_.badge_name), "%s", ra::Narrow(sBadge).c_str());
}
pAchievement->public_.id = vmAchievement->GetID();
pAchievement->public_.points = vmAchievement->GetPoints();
switch (vmAchievement->GetCategory())
{
case ra::data::models::AssetCategory::Core:
case ra::data::models::AssetCategory::Local:
pAchievement->public_.category = RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE;
break;
default:
pAchievement->public_.category = RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL;
break;
}
switch (vmAchievement->GetState())
{
case ra::data::models::AssetState::Triggered:
pAchievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
break;
case ra::data::models::AssetState::Disabled:
pAchievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_DISABLED;
pAchievement->public_.bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED;
break;
case ra::data::models::AssetState::Inactive:
pAchievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE;
break;
default:
pAchievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
break;
}
const auto& sTrigger = vmAchievement->GetTrigger();
uint8_t md5[16];
rc_runtime_checksum(sTrigger.c_str(), md5);
if (memcmp(pAchievement->md5, md5, sizeof(md5)) != 0)
{
memcpy(pAchievement->md5, md5, sizeof(md5));
if (m_pSubsetWrapper && DetachMemory(m_pSubsetWrapper->vAllocatedMemory, pAchievement->trigger))
free(pAchievement->trigger);
const auto nSize = rc_trigger_size(sTrigger.c_str());
if (nSize > 0)
{
pAchievement->trigger = static_cast<rc_trigger_t*>(malloc(nSize));
if (pAchievement->trigger)
{
/* populate the item, using the communal memrefs pool */
rc_parse_state_t parse;
rc_init_parse_state(&parse, pAchievement->trigger, nullptr, 0);
parse.first_memref = &m_pClient->game->runtime.memrefs;
parse.variables = &m_pClient->game->runtime.variables;
const char* sMemaddr = sTrigger.c_str();
rc_parse_trigger_internal(pAchievement->trigger, &sMemaddr, &parse);
rc_destroy_parse_state(&parse);
if (AllocatedMemrefs())
{
// if memrefs were allocated, we can't release this memory until the game is unloaded
m_vAllocatedMemory.push_back(pAchievement->trigger);
}
else
{
pSubsetWrapper->vAllocatedMemory.push_back(pAchievement->trigger);
}
}
}
}
}
void SyncSubset(rc_client_subset_info_t* pSubset, SubsetWrapper* pSubsetWrapper,
std::vector<const ra::data::models::AchievementModel*>& vAchievements,
std::vector<const ra::data::models::LeaderboardModel*>& vLeaderboards)
{
pSubset->active = true;
if (!vAchievements.empty())
{
pSubset->achievements = static_cast<rc_client_achievement_info_t*>(rc_buf_alloc(
&pSubsetWrapper->pBuffer, vAchievements.size() * sizeof(*pSubset->achievements)));
memset(pSubset->achievements, 0, vAchievements.size() * sizeof(*pSubset->achievements));
rc_client_achievement_info_t* pAchievement = pSubset->achievements;
rc_client_achievement_info_t* pSrcAchievement = nullptr;
for (const auto* vmAchievement : vAchievements)
{
Expects(vmAchievement != nullptr);
const auto nAchievementId = vmAchievement->GetID();
if (vmAchievement->GetChanges() == ra::data::models::AssetChanges::None)
{
rc_client_subset_info_t* pPublishedSubset = m_pPublishedSubset;
for (; pPublishedSubset; pPublishedSubset = pPublishedSubset->next)
{
pSrcAchievement = FindAchievement(pPublishedSubset, nAchievementId);
if (pSrcAchievement != nullptr)
{
memcpy(pAchievement++, pSrcAchievement, sizeof(*pAchievement));
break;
}
}
}
else
{
pSrcAchievement = nullptr;
if (m_pSubsetWrapper != nullptr)
{
if (vmAchievement->GetCategory() == ra::data::models::AssetCategory::Local)
pSrcAchievement = FindAchievement(&m_pSubsetWrapper->pLocalSubset, nAchievementId);
else
pSrcAchievement = FindAchievement(&m_pSubsetWrapper->pCoreSubset, nAchievementId);
}
if (pSrcAchievement != nullptr)
{
// transfer the trigger ownership to the new SubsetWrapper
if (pAchievement->trigger && DetachMemory(m_pSubsetWrapper->vAllocatedMemory, pAchievement->trigger))
pSubsetWrapper->vAllocatedMemory.push_back(pAchievement->trigger);
memcpy(pAchievement++, pSrcAchievement, sizeof(*pAchievement));
}
else
{
SyncAchievement(pAchievement++, pSubsetWrapper, vmAchievement);
}
}
}
pSubset->public_.num_achievements = gsl::narrow_cast<uint32_t>(pAchievement - pSubset->achievements);
}
if (!vLeaderboards.empty())
{
pSubset->leaderboards = static_cast<rc_client_leaderboard_info_t*>(rc_buf_alloc(
&pSubsetWrapper->pBuffer, vLeaderboards.size() * sizeof(*pSubset->leaderboards)));
memset(pSubset->leaderboards, 0, vLeaderboards.size() * sizeof(*pSubset->leaderboards));
rc_client_leaderboard_info_t* pLeaderboard = pSubset->leaderboards;
rc_client_leaderboard_info_t* pSrcLeaderboard = nullptr;
for (const auto* vmLeaderboard : vLeaderboards)
{
Expects(vmLeaderboard != nullptr);
const auto nLeaderboardId = vmLeaderboard->GetID();
if (vmLeaderboard->GetChanges() == ra::data::models::AssetChanges::None)
{
rc_client_subset_info_t* pPublishedSubset = m_pPublishedSubset;
for (; pPublishedSubset; pPublishedSubset = pPublishedSubset->next)
{
pSrcLeaderboard = FindLeaderboard(pPublishedSubset, nLeaderboardId);
if (pSrcLeaderboard != nullptr)
{
memcpy(pLeaderboard++, pSrcLeaderboard, sizeof(*pLeaderboard));
break;
}
}
}
else
{
pSrcLeaderboard = nullptr;
if (m_pSubsetWrapper != nullptr)
{
if (vmLeaderboard->GetCategory() == ra::data::models::AssetCategory::Local)
pSrcLeaderboard = FindLeaderboard(&m_pSubsetWrapper->pLocalSubset, nLeaderboardId);
else
pSrcLeaderboard = FindLeaderboard(&m_pSubsetWrapper->pCoreSubset, nLeaderboardId);
}
if (pSrcLeaderboard != nullptr)
{
// transfer the lboard ownership to the new SubsetWrapper
if (pLeaderboard->lboard && DetachMemory(m_pSubsetWrapper->vAllocatedMemory, pLeaderboard->lboard))
pSubsetWrapper->vAllocatedMemory.push_back(pLeaderboard->lboard);
memcpy(pLeaderboard++, pSrcLeaderboard, sizeof(*pLeaderboard));
}
else
{
SyncLeaderboard(pLeaderboard++, pSubsetWrapper, vmLeaderboard);
}
}
}
pSubset->public_.num_leaderboards = gsl::narrow_cast<uint32_t>(pLeaderboard - pSubset->leaderboards);
}
}
public:
void SyncAssets(rc_client_t* pClient)
{
const auto& pGameContext = ra::services::ServiceLocator::Get<ra::data::context::GameContext>();
const auto& vAssets = pGameContext.Assets();
m_pClient = pClient;
m_pNextMemref = &pClient->game->runtime.memrefs;
m_pNextVariable = &pClient->game->runtime.variables;
AllocatedMemrefs(); // advance pointers
if (m_pPublishedSubset == nullptr)
m_pPublishedSubset = pClient->game->subsets;
std::vector<const ra::data::models::AchievementModel*> vCoreAchievements;
std::vector<const ra::data::models::AchievementModel*> vLocalAchievements;
std::vector<const ra::data::models::LeaderboardModel*> vCoreLeaderboards;
std::vector<const ra::data::models::LeaderboardModel*> vLocalLeaderboards;
for (gsl::index nIndex = 0; nIndex < gsl::narrow_cast<gsl::index>(vAssets.Count()); ++nIndex)
{
const auto* pAsset = vAssets.GetItemAt(nIndex);
Expects(pAsset != nullptr);
if (pAsset->GetChanges() == ra::data::models::AssetChanges::Deleted)
continue;
switch (pAsset->GetType())
{
case ra::data::models::AssetType::Achievement:
if (pAsset->GetCategory() == ra::data::models::AssetCategory::Local)
vLocalAchievements.push_back(dynamic_cast<const ra::data::models::AchievementModel*>(pAsset));
else
vCoreAchievements.push_back(dynamic_cast<const ra::data::models::AchievementModel*>(pAsset));
break;
case ra::data::models::AssetType::Leaderboard:
if (pAsset->GetCategory() == ra::data::models::AssetCategory::Local)
vLocalLeaderboards.push_back(dynamic_cast<const ra::data::models::LeaderboardModel*>(pAsset));
else
vCoreLeaderboards.push_back(dynamic_cast<const ra::data::models::LeaderboardModel*>(pAsset));
break;
default:
break;
}
}
std::unique_ptr<SubsetWrapper> pSubsetWrapper;
pSubsetWrapper.reset(new SubsetWrapper);
memset(&pSubsetWrapper->pCoreSubset, 0, sizeof(pSubsetWrapper->pCoreSubset));
memset(&pSubsetWrapper->pLocalSubset, 0, sizeof(pSubsetWrapper->pLocalSubset));
rc_buf_init(&pSubsetWrapper->pBuffer);
pSubsetWrapper->pCoreSubset.public_.id = pClient->game->public_.id;
pSubsetWrapper->pCoreSubset.public_.title = pClient->game->public_.title;
snprintf(pSubsetWrapper->pCoreSubset.public_.badge_name, sizeof(pSubsetWrapper->pCoreSubset.public_.badge_name),
"%s", pClient->game->public_.badge_name);
SyncSubset(&pSubsetWrapper->pCoreSubset, pSubsetWrapper.get(), vCoreAchievements, vCoreLeaderboards);
if (!vLocalAchievements.empty() || !vLocalLeaderboards.empty())
{
pSubsetWrapper->pLocalSubset.public_.id = ra::data::context::GameAssets::LocalSubsetId;
pSubsetWrapper->pLocalSubset.public_.title = "Local";
snprintf(pSubsetWrapper->pLocalSubset.public_.badge_name,
sizeof(pSubsetWrapper->pLocalSubset.public_.badge_name), "%s", pClient->game->public_.badge_name);
SyncSubset(&pSubsetWrapper->pLocalSubset, pSubsetWrapper.get(), vLocalAchievements, vLocalLeaderboards);
pSubsetWrapper->pCoreSubset.next = &pSubsetWrapper->pLocalSubset;
}
std::swap(pSubsetWrapper, m_pSubsetWrapper);
if (pSubsetWrapper != nullptr)
{
for (auto* pMemory : pSubsetWrapper->vAllocatedMemory)
free(pMemory);
}
rc_mutex_lock(&pClient->state.mutex);
pClient->game->subsets = &m_pSubsetWrapper->pCoreSubset;
rc_mutex_unlock(&pClient->state.mutex);
}
};
void RcheevosClient::SyncAssets()
{
if (m_pClientSynchronizer == nullptr)
m_pClientSynchronizer.reset(new ClientSynchronizer());
m_pClientSynchronizer->SyncAssets(GetClient());
}
/* ---- Login ----- */
void RcheevosClient::BeginLoginWithPassword(const std::string& sUsername, const std::string& sPassword,
rc_client_callback_t fCallback, void* pCallbackData)
{
GSL_SUPPRESS_R3
auto* pCallbackWrapper = new CallbackWrapper(m_pClient.get(), fCallback, pCallbackData);
BeginLoginWithPassword(sUsername.c_str(), sPassword.c_str(), pCallbackWrapper);
}
rc_client_async_handle_t* RcheevosClient::BeginLoginWithPassword(const char* sUsername, const char* sPassword,
CallbackWrapper* pCallbackWrapper) noexcept
{
return rc_client_begin_login_with_password(GetClient(), sUsername, sPassword,
RcheevosClient::LoginCallback, pCallbackWrapper);
}
void RcheevosClient::BeginLoginWithToken(const std::string& sUsername, const std::string& sApiToken,
rc_client_callback_t fCallback, void* pCallbackData)
{
GSL_SUPPRESS_R3
auto* pCallbackWrapper = new CallbackWrapper(m_pClient.get(), fCallback, pCallbackData);
BeginLoginWithToken(sUsername.c_str(), sApiToken.c_str(), pCallbackWrapper);
}
rc_client_async_handle_t* RcheevosClient::BeginLoginWithToken(const char* sUsername, const char* sApiToken,
CallbackWrapper* pCallbackWrapper) noexcept
{
return rc_client_begin_login_with_token(GetClient(), sUsername, sApiToken,
RcheevosClient::LoginCallback, pCallbackWrapper);
}
GSL_SUPPRESS_CON3
void RcheevosClient::LoginCallback(int nResult, const char* sErrorMessage,
rc_client_t* pClient, void* pUserdata)
{
if (nResult == RC_OK)
{
const auto* user = rc_client_get_user_info(pClient);
if (!user)
{
nResult = RC_INVALID_STATE;
sErrorMessage = "User data not available.";
}
else
{
// initialize the user context
auto& pUserContext = ra::services::ServiceLocator::GetMutable<ra::data::context::UserContext>();
if (pUserContext.IsLoginDisabled())
{
nResult = RC_INVALID_STATE;
sErrorMessage = "Login has been disabled.";
}
else
{
pUserContext.Initialize(user->username, user->display_name, user->token);
pUserContext.SetScore(user->score);
}
}
}
auto* wrapper = static_cast<CallbackWrapper*>(pUserdata);
Expects(wrapper != nullptr);
wrapper->DoCallback(nResult, sErrorMessage);
delete wrapper;
}
/* ---- Load Game ----- */
void RcheevosClient::BeginLoadGame(const std::string& sHash, unsigned id,
rc_client_callback_t fCallback, void* pCallbackData)
{
GSL_SUPPRESS_R3
auto* pCallbackWrapper = new LoadGameCallbackWrapper(m_pClient.get(), fCallback, pCallbackData);
BeginLoadGame(sHash.c_str(), id, pCallbackWrapper);
}
static void ExtractPatchData(const rc_api_server_response_t* server_response, uint32_t nGameId)
{
// extract the PatchData and store a copy in the cache for offline mode
rc_api_response_t api_response{};
GSL_SUPPRESS_ES47
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("PatchData")
};
if (rc_json_parse_server_response(&api_response, server_response, fields, sizeof(fields) / sizeof(fields[0])) == RC_OK &&
fields[2].value_start && fields[2].value_end)
{
auto& pLocalStorage = ra::services::ServiceLocator::GetMutable<ra::services::ILocalStorage>();
auto pData = pLocalStorage.WriteText(ra::services::StorageItemType::GameData, std::to_wstring(nGameId));
if (pData != nullptr)
{
std::string sPatchData;
sPatchData.append(fields[2].value_start, fields[2].value_end - fields[2].value_start);
pData->Write(sPatchData);
}
}
}
GSL_SUPPRESS_CON3
void RcheevosClient::PostProcessGameDataResponse(const rc_api_server_response_t* server_response,
struct rc_api_fetch_game_data_response_t* game_data_response,
rc_client_t* client, void* pUserdata)
{
auto* wrapper = static_cast<LoadGameCallbackWrapper*>(pUserdata);
Expects(wrapper != nullptr);
auto& pGameContext = ra::services::ServiceLocator::GetMutable<ra::data::context::GameContext>();
auto pRichPresence = std::make_unique<ra::data::models::RichPresenceModel>();
pRichPresence->SetScript(game_data_response->rich_presence_script);
pRichPresence->CreateServerCheckpoint();
pRichPresence->CreateLocalCheckpoint();
pGameContext.Assets().Append(std::move(pRichPresence));
#ifndef RA_UTEST
// prefetch the game icon
if (client->game)
{
auto& pImageRepository = ra::services::ServiceLocator::GetMutable<ra::ui::IImageRepository>();
pImageRepository.FetchImage(ra::ui::ImageType::Icon, client->game->public_.badge_name);
}
#else
(void*)client;
#endif
const rc_api_achievement_definition_t* pAchievement = game_data_response->achievements;
const rc_api_achievement_definition_t* pAchievementStop = pAchievement + game_data_response->num_achievements;
for (; pAchievement < pAchievementStop; ++pAchievement)
wrapper->m_mAchievementDefinitions[pAchievement->id] = pAchievement->definition;
const rc_api_leaderboard_definition_t* pLeaderboard = game_data_response->leaderboards;
const rc_api_leaderboard_definition_t* pLeaderboardStop = pLeaderboard + game_data_response->num_leaderboards;
for (; pLeaderboard < pLeaderboardStop; ++pLeaderboard)
wrapper->m_mLeaderboardDefinitions[pLeaderboard->id] = pLeaderboard->definition;
ExtractPatchData(server_response, game_data_response->id);
}
rc_client_async_handle_t* RcheevosClient::BeginLoadGame(const char* sHash, unsigned id,
CallbackWrapper* pCallbackWrapper) noexcept
{
auto* client = GetClient();
// unload the game and free any additional memory we allocated for local changes
rc_client_unload_game(client);
m_pClientSynchronizer.reset();
// if an ID was provided, store the hash->id mapping
if (id != 0)
{
auto* client_hash = rc_client_find_game_hash(client, sHash);
if (ra::to_signed(client_hash->game_id) < 0)
client_hash->game_id = id;
}
// start the load process
client->callbacks.post_process_game_data_response = PostProcessGameDataResponse;
return rc_client_begin_load_game(client, sHash, RcheevosClient::LoadGameCallback, pCallbackWrapper);
}
GSL_SUPPRESS_CON3
void RcheevosClient::LoadGameCallback(int nResult, const char* sErrorMessage, rc_client_t*, void* pUserdata)
{
auto* wrapper = static_cast<LoadGameCallbackWrapper*>(pUserdata);
Expects(wrapper != nullptr);
if (nResult == RC_OK || nResult == RC_NO_GAME_LOADED)
{
// initialize the game context
auto& pGameContext = ra::services::ServiceLocator::GetMutable<ra::data::context::GameContext>();
pGameContext.InitializeFromRcheevosClient(wrapper->m_mAchievementDefinitions,
wrapper->m_mLeaderboardDefinitions);
}
else
{
}
wrapper->DoCallback(nResult, sErrorMessage);
delete wrapper;
}
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
class RcheevosClientExports : private RcheevosClient
{
public:
static rc_client_async_handle_t* begin_login_with_password(rc_client_t* client,
const char* username, const char* password,
rc_client_callback_t callback, void* callback_userdata)
{
auto* pCallbackData = new CallbackWrapper(client, callback, callback_userdata);
auto& pClient = ra::services::ServiceLocator::GetMutable<ra::services::RcheevosClient>();
return pClient.BeginLoginWithPassword(username, password, pCallbackData);
}
static rc_client_async_handle_t* begin_login_with_token(rc_client_t* client,
const char* username, const char* token,
rc_client_callback_t callback, void* callback_userdata)
{
auto* pCallbackData = new CallbackWrapper(client, callback, callback_userdata);
auto& pClient = ra::services::ServiceLocator::GetMutable<ra::services::RcheevosClient>();
return pClient.BeginLoginWithToken(username, token, pCallbackData);
}
static const rc_client_user_t* get_user_info()
{
auto& pClient = ra::services::ServiceLocator::GetMutable<ra::services::RcheevosClient>();
return rc_client_get_user_info(pClient.GetClient());
}
static void logout()
{
auto& pClient = ra::services::ServiceLocator::GetMutable<ra::services::RcheevosClient>();
return rc_client_logout(pClient.GetClient());
}
};
#endif RC_CLIENT_SUPPORTS_EXTERNAL
} // namespace services
} // namespace ra
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
#include "Exports.hh"
#include "rcheevos/src/rcheevos/rc_client_external.h"
#ifdef __cplusplus
extern "C" {
#endif
API int CCONV _Rcheevos_GetExternalClient(rc_client_external_t* pClient, int nVersion)
{
switch (nVersion)
{
default:
RA_LOG_WARN("Unknown rc_client_external interface version: %s", nVersion);
__fallthrough;
case 1:
pClient->begin_login_with_password = ra::services::RcheevosClientExports::begin_login_with_password;
pClient->begin_login_with_token = ra::services::RcheevosClientExports::begin_login_with_token;
pClient->logout = ra::services::RcheevosClientExports::logout;
pClient->get_user_info = ra::services::RcheevosClientExports::get_user_info;
break;
}
return 1;
}
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* RC_CLIENT_SUPPORTS_EXTERNAL */
-160
View File
@@ -1,160 +0,0 @@
#ifndef RA_SERVICES_RCHEEVOS_CLIENT_HH
#define RA_SERVICES_RCHEEVOS_CLIENT_HH
#pragma once
#include "ra_fwd.h"
#include "data\Types.hh"
#include "data\context\EmulatorContext.hh"
#include "services\TextReader.hh"
#include <string>
#include <rcheevos\include\rc_client.h>
struct rc_api_fetch_game_data_response_t;
namespace ra {
namespace services {
class RcheevosClient
{
public:
GSL_SUPPRESS_F6 RcheevosClient();
virtual ~RcheevosClient();
RcheevosClient(const RcheevosClient&) noexcept = delete;
RcheevosClient& operator=(const RcheevosClient&) noexcept = delete;
RcheevosClient(RcheevosClient&&) noexcept = delete;
RcheevosClient& operator=(RcheevosClient&&) noexcept = delete;
void Shutdown() noexcept;
rc_client_t* GetClient() const noexcept { return m_pClient.get(); }
void SyncAssets();
void BeginLoginWithToken(const std::string& sUsername, const std::string& sApiToken,
rc_client_callback_t fCallback, void* pCallbackData);
void BeginLoginWithPassword(const std::string& sUsername, const std::string& sPassword,
rc_client_callback_t fCallback, void* pCallbackData);
void BeginLoadGame(const std::string& sHash, unsigned id,
rc_client_callback_t fCallback, void* pCallbackData);
class Synchronizer
{
public:
void Wait()
{
#ifdef RA_UTEST
// unit tests are single-threaded. if m_bWaiting is true, it would wait indefinitely.
if (m_bWaiting)
Microsoft::VisualStudio::CppUnitTestFramework::Assert::Fail(L"Sycnhronous request was not handled.");
#else
std::unique_lock<std::mutex> lock(m_pMutex);
if (m_bWaiting)
m_pCondVar.wait(lock);
#endif
}
void Notify() noexcept
{
m_bWaiting = false;
m_pCondVar.notify_all();
}
void CaptureResult(int nResult, const char* sErrorMessage)
{
m_nResult = nResult;
if (sErrorMessage)
m_sErrorMessage = sErrorMessage;
else
m_sErrorMessage.clear();
}
int GetResult() const noexcept { return m_nResult; }
const std::string& GetErrorMessage() const noexcept { return m_sErrorMessage; }
private:
std::mutex m_pMutex;
std::condition_variable m_pCondVar;
std::string m_sErrorMessage;
int m_nResult = 0;
bool m_bWaiting = true;
};
private:
std::unique_ptr<rc_client_t> m_pClient;
class ClientSynchronizer;
std::unique_ptr<ClientSynchronizer> m_pClientSynchronizer;
static void LogMessage(const char* sMessage, const rc_client_t* pClient);
static uint32_t ReadMemory(uint32_t nAddress, uint8_t* pBuffer, uint32_t nBytes, rc_client_t* pClient);
static void ServerCallAsync(const rc_api_request_t* pRequest, rc_client_server_callback_t fCallback,
void* pCallbackData, rc_client_t* pClient);
class CallbackWrapper
{
public:
CallbackWrapper(rc_client_t* client, rc_client_callback_t callback, void* callback_userdata) noexcept :
m_pClient(client), m_fCallback(callback), m_pCallbackUserdata(callback_userdata)
{}
void DoCallback(int nResult, const char* sErrorMessage) noexcept
{
m_fCallback(nResult, sErrorMessage, m_pClient, m_pCallbackUserdata);
}
static void Dispatch(int nResult, const char* sErrorMessage, rc_client_t*, void* pUserdata)
{
auto* pWrapper = static_cast<CallbackWrapper*>(pUserdata);
Expects(pWrapper != nullptr);
pWrapper->DoCallback(nResult, sErrorMessage);
delete pWrapper;
}
private:
rc_client_t* m_pClient;
rc_client_callback_t m_fCallback;
void* m_pCallbackUserdata;
};
friend class RcheevosClientExports;
rc_client_async_handle_t* BeginLoginWithPassword(const char* sUsername, const char* sPassword,
CallbackWrapper* pCallbackWrapper) noexcept;
rc_client_async_handle_t* BeginLoginWithToken(const char* sUsername, const char* sApiToken,
CallbackWrapper* pCallbackWrapper) noexcept;
static void LoginCallback(int nResult, const char* sErrorMessage, rc_client_t* pClient, void* pUserdata);
class LoadGameCallbackWrapper : public CallbackWrapper
{
public:
LoadGameCallbackWrapper(rc_client_t* client, rc_client_callback_t callback, void* callback_userdata) noexcept :
CallbackWrapper(client, callback, callback_userdata)
{}
std::map<uint32_t, std::string> m_mAchievementDefinitions;
std::map<uint32_t, std::string> m_mLeaderboardDefinitions;
};
rc_client_async_handle_t* BeginLoadGame(const char* sHash, unsigned id, CallbackWrapper* pCallbackWrapper) noexcept;
static void LoadGameCallback(int nResult, const char* sErrorMessage, rc_client_t* pClient, void* pUserdata);
static void PostProcessGameDataResponse(const rc_api_server_response_t* server_response,
struct rc_api_fetch_game_data_response_t* game_data_response,
rc_client_t* client, void* pUserdata);
};
} // namespace services
} // namespace ra
#endif // !RA_SERVICES_RCHEEVOS_CLIENT_HH
+1 -1
View File
@@ -985,7 +985,7 @@ void AssetEditorViewModel::UpdateMeasuredValue()
const auto* pLeaderboard = dynamic_cast<ra::data::models::LeaderboardModel*>(m_pAsset);
if (pLeaderboard != nullptr)
{
auto* pLeaderboardDefinition = ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>().GetLeaderboardDefinition(pLeaderboard->GetID());
const auto* pLeaderboardDefinition = ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>().GetLeaderboardDefinition(pLeaderboard->GetID());
if (pLeaderboardDefinition)
{
const unsigned nValue = pLeaderboardDefinition->value.value.value;
+3
View File
@@ -1477,6 +1477,9 @@ void AssetListViewModel::ResetSelected()
pGameContext.Assets().EndUpdate();
auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
pRuntime.SyncAssets();
// if any achievements were deleted, disable their challenge indicators
for (auto nId : vPrimedAchievementsBefore)
{
@@ -246,16 +246,15 @@ void IntegrationMenuViewModel::ToggleLeaderboards()
const bool bLeaderboardsActive = !pConfiguration.IsFeatureEnabled(ra::services::Feature::Leaderboards);
pConfiguration.SetFeatureEnabled(ra::services::Feature::Leaderboards, bLeaderboardsActive);
auto& pGameContext = ra::services::ServiceLocator::GetMutable<ra::data::context::GameContext>();
if (!bLeaderboardsActive)
{
pGameContext.DeactivateLeaderboards();
// TODO: disallow disabling leaderboards
//pGameContext.DeactivateLeaderboards();
ra::ui::viewmodels::MessageBoxViewModel::ShowMessage(L"Leaderboards are now disabled.");
}
else
{
pGameContext.ActivateLeaderboards();
//pGameContext.ActivateLeaderboards();
ra::ui::viewmodels::MessageBoxViewModel::ShowMessage(L"Leaderboards are now enabled.");
}
@@ -385,7 +384,6 @@ void IntegrationMenuViewModel::ShowGameHash()
Expects(!vmUnknownGame.GetTestMode());
pGameContext.SetMode(ra::data::context::GameContext::Mode::Normal);
pGameContext.RefreshUnlocks();
}
}
else
+5 -5
View File
@@ -10,8 +10,8 @@
#include "data\context\SessionTracker.hh"
#include "data\context\UserContext.hh"
#include "services\AchievementRuntime.hh"
#include "services\IConfiguration.hh"
#include "services\RcheevosClient.hh"
#include "ui\viewmodels\MessageBoxViewModel.hh"
#include "ui\viewmodels\WindowManager.hh"
@@ -49,12 +49,12 @@ bool LoginViewModel::Login() const
return false;
}
ra::services::RcheevosClient::Synchronizer pSynchronizer;
ra::services::AchievementRuntime::Synchronizer pSynchronizer;
auto& pClient = ra::services::ServiceLocator::GetMutable<ra::services::RcheevosClient>();
pClient.BeginLoginWithPassword(ra::Narrow(GetUsername()), ra::Narrow(GetPassword()),
auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
pRuntime.BeginLoginWithPassword(ra::Narrow(GetUsername()), ra::Narrow(GetPassword()),
[](int nResult, const char* sErrorMessage, rc_client_t*, void* pUserdata) {
auto* pSynchronizer = static_cast<ra::services::RcheevosClient::Synchronizer*>(pUserdata);
auto* pSynchronizer = static_cast<ra::services::AchievementRuntime::Synchronizer*>(pUserdata);
Expects(pSynchronizer != nullptr);
pSynchronizer->CaptureResult(nResult, sErrorMessage);
@@ -8,8 +8,8 @@
#include "data\context\SessionTracker.hh"
#include "data\context\UserContext.hh"
#include "services\AchievementRuntime.hh"
#include "services\IClock.hh"
#include "services\RcheevosClient.hh"
#include "ui\OverlayTheme.hh"
@@ -104,7 +104,7 @@ void OverlayAchievementsPageViewModel::Refresh()
SetListTitle(pGameContext.GameTitle());
// achievement list
const auto& pClient = ra::services::ServiceLocator::Get<ra::services::RcheevosClient>().GetClient();
const auto& pClient = ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>().GetClient();
std::vector<rc_client_subset_info_t*> vDeactivatedSubsets;
int nCategory = RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL;
@@ -314,7 +314,7 @@ void OverlayAchievementsPageViewModel::FetchItemDetail(ItemViewModel& vmItem)
if (m_vAchievementDetails.find(vmItem.GetId()) != m_vAchievementDetails.end()) // already populated
return;
const auto& pClient = ra::services::ServiceLocator::Get<ra::services::RcheevosClient>().GetClient();
const auto& pClient = ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>().GetClient();
const auto nAchievementID = vmItem.GetId();
GSL_SUPPRESS_TYPE1 const auto* pAchievement =
reinterpret_cast<const rc_client_achievement_info_t*>(rc_client_get_achievement_info(pClient, nAchievementID));
@@ -7,7 +7,7 @@
#include "data\context\GameContext.hh"
#include "data\context\UserContext.hh"
#include "services\RcheevosClient.hh"
#include "services\AchievementRuntime.hh"
#include "ui\OverlayTheme.hh"
@@ -66,7 +66,7 @@ void OverlayLeaderboardsPageViewModel::Refresh()
SetListTitle(pGameContext.GameTitle());
// leaderboard list
const auto& pClient = ra::services::ServiceLocator::Get<ra::services::RcheevosClient>().GetClient();
const auto& pClient = ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>().GetClient();
std::vector<rc_client_subset_info_t*> vDeactivatedSubsets;
const auto& pAssetList = ra::services::ServiceLocator::Get<ra::ui::viewmodels::WindowManager>().AssetList;
@@ -203,7 +203,7 @@ void OverlayLeaderboardsPageViewModel::FetchItemDetail(ItemViewModel& vmItem)
if (m_vLeaderboardRanks.find(vmItem.GetId()) != m_vLeaderboardRanks.end()) // already populated
return;
const auto& pClient = ra::services::ServiceLocator::Get<ra::services::RcheevosClient>().GetClient();
const auto& pClient = ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>().GetClient();
const auto nLeaderboardId = vmItem.GetId();
GSL_SUPPRESS_TYPE1 const auto* pLeaderboard =
reinterpret_cast<const rc_client_leaderboard_info_t*>(rc_client_get_leaderboard_info(pClient, nLeaderboardId));
+20 -13
View File
@@ -239,13 +239,13 @@ void OverlayManager::Render(ra::ui::drawing::ISurface& pSurface, bool bRedrawAll
}
}
ScoreTrackerViewModel& OverlayManager::AddScoreTracker(ra::LeaderboardID nLeaderboardId)
ScoreTrackerViewModel& OverlayManager::AddScoreTracker(uint32_t nTrackerId)
{
ScoreTrackerViewModel* vmTracker;
{
std::lock_guard<std::mutex> pGuard(m_pPopupQueueMutex);
auto pScoreTracker = std::make_unique<ScoreTrackerViewModel>();
pScoreTracker->SetPopupId(nLeaderboardId);
pScoreTracker->SetPopupId(nTrackerId);
pScoreTracker->UpdateRenderImage(0.0);
vmTracker = m_vScoreTrackers.emplace_back(std::move(pScoreTracker)).get();
}
@@ -255,14 +255,14 @@ ScoreTrackerViewModel& OverlayManager::AddScoreTracker(ra::LeaderboardID nLeader
return *vmTracker;
}
void OverlayManager::RemoveScoreTracker(ra::LeaderboardID nLeaderboardId)
void OverlayManager::RemoveScoreTracker(uint32_t nTrackerId)
{
bool bRequestRender = false;
{
std::lock_guard<std::mutex> pGuard(m_pPopupQueueMutex);
for (auto pIter = m_vScoreTrackers.begin(); pIter != m_vScoreTrackers.end(); ++pIter)
{
if (ra::to_unsigned((*pIter)->GetPopupId()) == nLeaderboardId && !(*pIter)->IsDestroyPending())
if (ra::to_unsigned((*pIter)->GetPopupId()) == nTrackerId && !(*pIter)->IsDestroyPending())
{
(*pIter)->SetDestroyPending();
bRequestRender = true;
@@ -335,14 +335,24 @@ void OverlayManager::RemoveChallengeIndicator(ra::AchievementID nAchievementId)
RequestRender();
}
void OverlayManager::UpdateProgressTracker(ra::ui::ImageType imageType, const std::string& sImageName, unsigned nValue, unsigned nTarget, bool bAsPercent)
void OverlayManager::UpdateProgressTracker(ra::ui::ImageType imageType, const std::string& sImageName, const std::wstring& sProgress)
{
if (m_vmProgressTracker == nullptr)
m_vmProgressTracker.reset(new ProgressTrackerViewModel());
if (sProgress.empty())
{
if (m_vmProgressTracker == nullptr)
return;
m_vmProgressTracker->SetImage(imageType, sImageName);
m_vmProgressTracker->SetProgress(nValue, nTarget, bAsPercent);
m_vmProgressTracker->BeginAnimation();
m_vmProgressTracker->SetDestroyPending();
}
else
{
if (m_vmProgressTracker == nullptr)
m_vmProgressTracker.reset(new ProgressTrackerViewModel());
m_vmProgressTracker->SetImage(imageType, sImageName);
m_vmProgressTracker->SetProgress(sProgress);
m_vmProgressTracker->BeginAnimation();
}
RequestRender();
}
@@ -705,9 +715,6 @@ void OverlayManager::UpdateProgressTracker(ra::ui::drawing::ISurface& pSurface,
UpdatePopup(pSurface, pPopupLocations, fElapsed, *m_vmProgressTracker);
if (m_vmProgressTracker->IsAnimationComplete())
m_vmProgressTracker->SetDestroyPending();
if (m_vmProgressTracker->IsDestroyPending())
{
// call UpdatePopup again to force it to "unpaint"
+8 -8
View File
@@ -201,27 +201,27 @@ public:
void AdvanceFrame() noexcept { ++m_nFrameId; }
/// <summary>
/// Adds a score tracker for a leaderboard.
/// Adds a score tracker.
/// </summary>
ScoreTrackerViewModel& AddScoreTracker(ra::LeaderboardID nLeaderboardId);
ScoreTrackerViewModel& AddScoreTracker(uint32_t nTrackerId);
/// <summary>
/// Removes the score tracker associated to the specified leaderboard.
/// Removes the specified score tracker.
/// </summary>
/// <param name="nLeaderboardId">The unique identifier of the leaderboard associated to the tracker.</param>
void RemoveScoreTracker(ra::LeaderboardID nLeaderboardId);
/// <param name="nTrackerId">The unique identifier of the leaderboard tracker.</param>
void RemoveScoreTracker(uint32_t nTrackerId);
/// <summary>
/// Gets the score tracker associated to the specified leaderboard.
/// </summary>
/// <param name="nLeaderboardId">The unique identifier of the leaderboard associated to the tracker.</param>
/// <returns>Requested score tracker view model, <c>nullptr</c> if not found.</returns>
ScoreTrackerViewModel* GetScoreTracker(ra::LeaderboardID nLeaderboardId)
ScoreTrackerViewModel* GetScoreTracker(uint32_t nTrackerId)
{
std::lock_guard<std::mutex> pGuard(m_pPopupQueueMutex);
for (auto& pTracker : m_vScoreTrackers)
{
if (ra::to_unsigned(pTracker->GetPopupId()) == nLeaderboardId)
if (ra::to_unsigned(pTracker->GetPopupId()) == nTrackerId)
return pTracker.get();
}
@@ -281,7 +281,7 @@ public:
/// Updates the progress tracker indicator
/// </summary>
void UpdateProgressTracker(ra::ui::ImageType imageType, const std::string& sImageName,
unsigned nValue, unsigned nTarget, bool bAsPercent);
const std::wstring& sProgress);
/// <summary>
/// Clears all popups.
+1 -19
View File
@@ -17,26 +17,8 @@ ProgressTrackerViewModel::ProgressTrackerViewModel() noexcept
GSL_SUPPRESS_F6 SetVerticalOffset(10);
}
void ProgressTrackerViewModel::SetProgress(unsigned nValue, unsigned nTarget, bool bAsPercent)
bool ProgressTrackerViewModel::UpdateRenderImage(double)
{
if (bAsPercent)
{
const auto nProgressPercent = gsl::narrow_cast<int>(static_cast<long long>(nValue) * 100 / nTarget);
m_sProgress = ra::StringPrintf(L"%d%%", nProgressPercent);
}
else
{
m_sProgress = ra::StringPrintf(L"%u/%u", nValue, nTarget);
}
}
bool ProgressTrackerViewModel::UpdateRenderImage(double fElapsed)
{
if (m_fAnimationProgress >= TOTAL_ANIMATION_TIME)
return false;
m_fAnimationProgress += fElapsed;
bool bUpdated = false;
if (m_pSurface == nullptr || m_bRegenerate)
@@ -26,7 +26,7 @@ public:
/// <summary>
/// Sets the percentage of the image to highlight
/// </summary>
void SetProgress(unsigned nValue, unsigned nTarget, bool bAsPercent);
void SetProgress(const std::wstring& sProgress) { m_sProgress = sProgress; }
/// <summary>
/// Gets the image to display.
@@ -43,13 +43,9 @@ public:
/// </summary>
bool UpdateRenderImage(double fElapsed) override;
void BeginAnimation() noexcept override { m_fAnimationProgress = 0.0; }
void BeginAnimation() noexcept override { }
bool IsAnimationStarted() const noexcept override { return true; }
bool IsAnimationComplete() const noexcept override
{
return m_fAnimationProgress >= TOTAL_ANIMATION_TIME;
}
bool IsAnimationComplete() const noexcept override { return false; }
private:
ra::ui::ImageReference m_hImage;
+3 -671
View File
@@ -6,6 +6,7 @@
#include "RA_Emulators.h"
#include "RA_Resource.h"
#include "tests\mocks\MockAchievementRuntime.hh"
#include "tests\mocks\MockAudioSystem.hh"
#include "tests\mocks\MockClock.hh"
#include "tests\mocks\MockConfiguration.hh"
@@ -15,7 +16,6 @@
#include "tests\mocks\MockGameContext.hh"
#include "tests\mocks\MockOverlayManager.hh"
#include "tests\mocks\MockOverlayTheme.hh"
#include "tests\mocks\MockRcheevosClient.hh"
#include "tests\mocks\MockServer.hh"
#include "tests\mocks\MockSessionTracker.hh"
#include "tests\mocks\MockSurface.hh"
@@ -37,11 +37,11 @@ using ra::data::context::mocks::MockEmulatorContext;
using ra::data::context::mocks::MockGameContext;
using ra::data::context::mocks::MockSessionTracker;
using ra::data::context::mocks::MockUserContext;
using ra::services::mocks::MockAchievementRuntime;
using ra::services::mocks::MockAudioSystem;
using ra::services::mocks::MockClock;
using ra::services::mocks::MockConfiguration;
using ra::services::mocks::MockFrameEventQueue;
using ra::services::mocks::MockRcheevosClient;
using ra::services::mocks::MockThreadPool;
using ra::ui::mocks::MockDesktop;
using ra::ui::mocks::MockOverlayTheme;
@@ -101,7 +101,7 @@ private:
MockUserContext mockUserContext;
MockSessionTracker mockSessionTracker;
MockConfiguration mockConfiguration;
MockRcheevosClient mockRcheevosClient;
MockAchievementRuntime mockRcheevosClient;
MockAudioSystem mockAudioSystem;
MockServer mockServer;
MockOverlayManager mockOverlayManager;
@@ -383,28 +383,6 @@ public:
}
private:
class MockAchievementRuntime : public ra::services::AchievementRuntime
{
public:
MockAchievementRuntime() noexcept : m_Override(this) {}
void QueueChange(ChangeType nType, unsigned int nId, int nValue = 0)
{
m_vChanges.emplace_back(Change{ nType, nId, nValue });
}
void Process(_Inout_ std::vector<Change>& changes) noexcept override
{
GSL_SUPPRESS_F6
changes.insert(changes.begin(), m_vChanges.begin(), m_vChanges.end());
m_vChanges.clear();
}
private:
std::vector<ra::services::AchievementRuntime::Change> m_vChanges;
ra::services::ServiceLocator::ServiceOverride<ra::services::AchievementRuntime> m_Override;
};
class DoAchievementsFrameHarness
{
public:
@@ -421,27 +399,6 @@ private:
MockWindowManager mockWindowManager;
MockClock mockClock;
DoAchievementsFrameHarness() noexcept
{
GSL_SUPPRESS_F6 mockServer.HandleRequest<ra::api::AwardAchievement>([this]
(const ra::api::AwardAchievement::Request& request, ra::api::AwardAchievement::Response& response)
{
m_vUnlockedAchievements.insert(request.AchievementId);
response.Result = ra::api::ApiResult::Success;
return true;
});
GSL_SUPPRESS_F6 mockServer.HandleRequest<ra::api::SubmitLeaderboardEntry>([this]
(const ra::api::SubmitLeaderboardEntry::Request& request, ra::api::SubmitLeaderboardEntry::Response& response)
{
m_vSubmittedLeaderboardEntries.insert_or_assign(request.LeaderboardId, request.Score);
response.Result = ra::api::ApiResult::Success;
return true;
});
}
ra::data::models::AchievementModel& MockAchievement(unsigned int nId)
{
auto& pAch = mockGameContext.Assets().NewAchievement();
@@ -453,637 +410,12 @@ private:
return pAch;
}
bool WasUnlocked(unsigned int nId)
{
mockThreadPool.ExecuteNextTask();
return m_vUnlockedAchievements.find(nId) != m_vUnlockedAchievements.end();
}
unsigned int GetSubmittedScore(ra::LeaderboardID nId)
{
mockThreadPool.ExecuteNextTask();
const auto pIter = m_vSubmittedLeaderboardEntries.find(nId);
return (pIter != m_vSubmittedLeaderboardEntries.end()) ? pIter->second : 0U;
}
private:
MockThreadPool mockThreadPool;
MockServer mockServer;
MockUserContext mockUserContext;
std::set<unsigned int> m_vUnlockedAchievements;
std::map<ra::LeaderboardID, unsigned int> m_vSubmittedLeaderboardEntries;
};
public:
TEST_METHOD(TestDoAchievementsFrameAchievementTriggered)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U);
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::AchievementTriggered, 1U);
harness.mockGameContext.SetRichPresenceDisplayString(L"Titles");
harness.mockGameContext.Assets().FindRichPresence()->Activate();
_RA_DoAchievementsFrame();
Assert::IsTrue(harness.WasUnlocked(1U));
Assert::IsFalse(harness.mockDesktop.WasDialogShown());
Assert::AreEqual(std::wstring(L"Titles"), harness.mockGameContext.Assets().FindAchievement(1U)->GetUnlockRichPresence());
}
TEST_METHOD(TestDoAchievementsFrameAchievementTriggeredNoRichPresence)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U);
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::AchievementTriggered, 1U);
_RA_DoAchievementsFrame();
Assert::IsTrue(harness.WasUnlocked(1U));
Assert::IsFalse(harness.mockDesktop.WasDialogShown());
Assert::AreEqual(std::wstring(), harness.mockGameContext.Assets().FindAchievement(1U)->GetUnlockRichPresence());
}
TEST_METHOD(TestDoAchievementsFrameAchievementPrimed)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U).SetBadge(L"55223");
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::Challenge, ra::ui::viewmodels::PopupLocation::BottomRight);
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::AchievementPrimed, 1U);
_RA_DoAchievementsFrame();
const auto* pIndicator = harness.mockOverlayManager.GetChallengeIndicator(1U);
Assert::IsNotNull(pIndicator);
Ensures(pIndicator != nullptr);
Assert::AreEqual(ra::ui::ImageType::Badge, pIndicator->GetImage().Type());
Assert::AreEqual(std::string("55223"), pIndicator->GetImage().Name());
}
TEST_METHOD(TestDoAchievementsFrameAchievementPrimedNoTracker)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U).SetBadge(L"55223");
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::Challenge, ra::ui::viewmodels::PopupLocation::None);
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::AchievementPrimed, 1U);
_RA_DoAchievementsFrame();
const auto* pIndicator = harness.mockOverlayManager.GetChallengeIndicator(1U);
Assert::IsNull(pIndicator);
}
TEST_METHOD(TestDoAchievementsFrameAchievementUnprimed)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U).SetBadge(L"55223");
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::Challenge, ra::ui::viewmodels::PopupLocation::BottomRight);
harness.mockOverlayManager.AddChallengeIndicator(1U, ra::ui::ImageType::Badge, "55223");
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::AchievementUnprimed, 1U);
_RA_DoAchievementsFrame();
const auto* pIndicator = harness.mockOverlayManager.GetChallengeIndicator(1U);
Assert::IsNotNull(pIndicator);
Ensures(pIndicator != nullptr);
Assert::IsTrue(pIndicator->IsDestroyPending());
}
TEST_METHOD(TestDoAchievementsFrameAchievementProgress)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U).SetBadge(L"55223");
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::Progress, ra::ui::viewmodels::PopupLocation::BottomRight);
harness.mockRuntime.ActivateAchievement(1U, "M:0xH0001=10");
auto* pTrigger = harness.mockRuntime.GetAchievementTrigger(1U);
pTrigger->measured_value = 5;
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::AchievementProgressChanged, 1U, pTrigger->measured_value);
_RA_DoAchievementsFrame();
const auto* pIndicator = harness.mockOverlayManager.GetProgressTracker();
Assert::IsNotNull(pIndicator);
Ensures(pIndicator != nullptr);
Assert::AreEqual(ra::ui::ImageType::Badge, pIndicator->GetImage().Type());
Assert::AreEqual(std::string("55223_lock"), pIndicator->GetImage().Name());
Assert::AreEqual(std::wstring(L"5/10"), pIndicator->GetText());
}
TEST_METHOD(TestDoAchievementsFrameAchievementProgressNoTracker)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U).SetBadge(L"55223");
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::Progress, ra::ui::viewmodels::PopupLocation::None);
harness.mockRuntime.ActivateAchievement(1U, "M:0xH0001=10");
auto* pTrigger = harness.mockRuntime.GetAchievementTrigger(1U);
pTrigger->measured_value = 5;
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::AchievementProgressChanged, 1U, pTrigger->measured_value);
_RA_DoAchievementsFrame();
const auto* pIndicator = harness.mockOverlayManager.GetProgressTracker();
Assert::IsNull(pIndicator);
}
TEST_METHOD(TestDoAchievementsFrameAchievementProgressLocalBadge)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U).SetBadge(L"local\\GUID");
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::Progress, ra::ui::viewmodels::PopupLocation::BottomRight);
harness.mockRuntime.ActivateAchievement(1U, "M:0xH0001=10");
auto* pTrigger = harness.mockRuntime.GetAchievementTrigger(1U);
pTrigger->measured_value = 5;
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::AchievementProgressChanged, 1U, pTrigger->measured_value);
_RA_DoAchievementsFrame();
const auto* pIndicator = harness.mockOverlayManager.GetProgressTracker();
Assert::IsNotNull(pIndicator);
Ensures(pIndicator != nullptr);
Assert::AreEqual(ra::ui::ImageType::Badge, pIndicator->GetImage().Type());
Assert::AreEqual(std::string("local\\GUID"), pIndicator->GetImage().Name()); // no "_lock"
Assert::AreEqual(std::wstring(L"5/10"), pIndicator->GetText());
}
TEST_METHOD(TestDoAchievementsFrameAchievementProgressHighestPercent)
{
DoAchievementsFrameHarness harness;
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::Progress, ra::ui::viewmodels::PopupLocation::BottomRight);
harness.MockAchievement(1U).SetBadge(L"55221");
harness.mockRuntime.ActivateAchievement(1U, "M:0xH0001=10");
harness.MockAchievement(2U).SetBadge(L"55222");
harness.mockRuntime.ActivateAchievement(2U, "M:0xH0001=10");
harness.MockAchievement(3U).SetBadge(L"55223");
harness.mockRuntime.ActivateAchievement(3U, "M:0xH0001=10");
harness.mockRuntime.GetAchievementTrigger(1U)->measured_value = 5;
harness.mockRuntime.GetAchievementTrigger(2U)->measured_value = 7;
harness.mockRuntime.GetAchievementTrigger(3U)->measured_value = 3;
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::AchievementProgressChanged, 1U, 5);
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::AchievementProgressChanged, 2U, 7);
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::AchievementProgressChanged, 3U, 3);
_RA_DoAchievementsFrame();
const auto* pIndicator = harness.mockOverlayManager.GetProgressTracker();
Assert::IsNotNull(pIndicator);
Ensures(pIndicator != nullptr);
Assert::AreEqual(ra::ui::ImageType::Badge, pIndicator->GetImage().Type());
Assert::AreEqual(std::string("55222_lock"), pIndicator->GetImage().Name());
Assert::AreEqual(std::wstring(L"7/10"), pIndicator->GetText());
}
TEST_METHOD(TestDoAchievementsFrameAchievementProgressAsPercent)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U).SetBadge(L"55223");
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::Progress, ra::ui::viewmodels::PopupLocation::BottomRight);
harness.mockRuntime.ActivateAchievement(1U, "G:0xH0001=10");
auto* pTrigger = harness.mockRuntime.GetAchievementTrigger(1U);
pTrigger->measured_value = 5;
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::AchievementProgressChanged, 1U, pTrigger->measured_value);
_RA_DoAchievementsFrame();
const auto* pIndicator = harness.mockOverlayManager.GetProgressTracker();
Assert::IsNotNull(pIndicator);
Ensures(pIndicator != nullptr);
Assert::AreEqual(ra::ui::ImageType::Badge, pIndicator->GetImage().Type());
Assert::AreEqual(std::string("55223_lock"), pIndicator->GetImage().Name());
Assert::AreEqual(std::wstring(L"50%"), pIndicator->GetText());
}
TEST_METHOD(TestDoAchievementsFramePaused)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U);
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::AchievementTriggered, 1U);
harness.mockRuntime.SetPaused(true);
_RA_DoAchievementsFrame();
Assert::IsFalse(harness.WasUnlocked(1U));
harness.mockRuntime.SetPaused(false);
_RA_DoAchievementsFrame();
Assert::IsTrue(harness.WasUnlocked(1U));
}
TEST_METHOD(TestDoAchievementsFramePauseOnTrigger)
{
DoAchievementsFrameHarness harness;
auto& pAch = harness.MockAchievement(1U);
pAch.SetName(L"AchievementTitle");
pAch.UpdateServerCheckpoint();
pAch.SetPauseOnTrigger(true);
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::AchievementTriggered, 1U);
bool bWasPaused = false;
harness.mockEmulatorContext.SetPauseFunction([&bWasPaused] { bWasPaused = true; });
bool bMessageSeen = false;
harness.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&bMessageSeen](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
{
bMessageSeen = true;
Assert::AreEqual(std::wstring(L"The following triggers have triggered:\n* AchievementTitle"), vmMessageBox.GetMessage());
return ra::ui::DialogResult::OK;
});
_RA_DoAchievementsFrame();
harness.mockFrameEventQueue.DoFrame(); // UpdateUIForFrameChange isn't called in the unit test build
Assert::IsTrue(harness.WasUnlocked(1U));
Assert::IsTrue(bMessageSeen);
Assert::IsTrue(bWasPaused);
}
TEST_METHOD(TestDoAchievementsFramePauseOnReset)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U);
harness.mockGameContext.Assets().FindAchievement(1U)->SetPauseOnReset(true);
harness.mockGameContext.Assets().FindAchievement(1U)->SetName(L"AchievementTitle");
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::AchievementReset, 1U);
bool bWasPaused = false;
harness.mockEmulatorContext.SetPauseFunction([&bWasPaused] { bWasPaused = true; });
bool bMessageSeen = false;
harness.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&bMessageSeen](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
{
bMessageSeen = true;
Assert::AreEqual(std::wstring(L"The following triggers have been reset:\n* AchievementTitle"), vmMessageBox.GetMessage());
return ra::ui::DialogResult::OK;
});
_RA_DoAchievementsFrame();
harness.mockFrameEventQueue.DoFrame(); // UpdateUIForFrameChange isn't called in the unit test build
Assert::IsTrue(bMessageSeen);
Assert::IsTrue(bWasPaused);
}
TEST_METHOD(TestDoAchievementsFrameLeaderboardStart)
{
DoAchievementsFrameHarness harness;
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardStarted, ra::ui::viewmodels::PopupLocation::BottomLeft);
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardTracker, ra::ui::viewmodels::PopupLocation::BottomRight);
auto& pLeaderboard = harness.mockGameContext.Assets().NewLeaderboard();
pLeaderboard.SetID(1U);
pLeaderboard.SetName(L"Title");
pLeaderboard.SetDescription(L"Description");
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::LeaderboardStarted, 1U, 1234U);
_RA_DoAchievementsFrame();
const auto* pPopup = harness.mockOverlayManager.GetMessage(1);
Assert::IsNotNull(pPopup);
Ensures(pPopup != nullptr);
Assert::IsTrue(harness.mockAudioSystem.WasAudioFilePlayed(L"Overlay\\lb.wav"));
Assert::AreEqual(std::wstring(L"Leaderboard attempt started"), pPopup->GetTitle());
Assert::AreEqual(std::wstring(L"Title"), pPopup->GetDescription());
Assert::AreEqual(std::wstring(L"Description"), pPopup->GetDetail());
const auto* pScore = harness.mockOverlayManager.GetScoreTracker(1U);
Assert::IsNotNull(pScore);
Ensures(pScore != nullptr);
Assert::AreEqual(std::wstring(L"1234"), pScore->GetDisplayText());
}
TEST_METHOD(TestDoAchievementsFrameLeaderboardStartNoPopups)
{
DoAchievementsFrameHarness harness;
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardStarted, ra::ui::viewmodels::PopupLocation::None);
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardTracker, ra::ui::viewmodels::PopupLocation::None);
auto& pLeaderboard = harness.mockGameContext.Assets().NewLeaderboard();
pLeaderboard.SetID(1U);
pLeaderboard.SetName(L"Title");
pLeaderboard.SetDescription(L"Description");
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::LeaderboardStarted, 1U, 1234U);
_RA_DoAchievementsFrame();
const auto* pPopup = harness.mockOverlayManager.GetMessage(1);
Assert::IsNull(pPopup);
const auto* pScore = harness.mockOverlayManager.GetScoreTracker(1U);
Assert::IsNull(pScore);
}
TEST_METHOD(TestDoAchievementsFrameLeaderboardStartPopupDisabled)
{
DoAchievementsFrameHarness harness;
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardStarted, ra::ui::viewmodels::PopupLocation::None);
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardTracker, ra::ui::viewmodels::PopupLocation::BottomRight);
auto& pLeaderboard = harness.mockGameContext.Assets().NewLeaderboard();
pLeaderboard.SetID(1U);
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::LeaderboardStarted, 1U, 1234U);
_RA_DoAchievementsFrame();
const auto* pPopup = harness.mockOverlayManager.GetMessage(1);
Assert::IsNull(pPopup);
const auto* pScore = harness.mockOverlayManager.GetScoreTracker(1U);
Assert::IsNotNull(pScore);
Ensures(pScore != nullptr);
Assert::AreEqual(std::wstring(L"1234"), pScore->GetDisplayText());
}
TEST_METHOD(TestDoAchievementsFrameLeaderboardCanceled)
{
DoAchievementsFrameHarness harness;
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardCanceled, ra::ui::viewmodels::PopupLocation::BottomLeft);
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardTracker, ra::ui::viewmodels::PopupLocation::BottomRight);
auto& pLeaderboard = harness.mockGameContext.Assets().NewLeaderboard();
pLeaderboard.SetID(1U);
pLeaderboard.SetName(L"Title");
pLeaderboard.SetDescription(L"Description");
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::LeaderboardCanceled, 1U);
_RA_DoAchievementsFrame();
const auto* pPopup = harness.mockOverlayManager.GetMessage(1);
Assert::IsNotNull(pPopup);
Ensures(pPopup != nullptr);
Assert::IsTrue(harness.mockAudioSystem.WasAudioFilePlayed(L"Overlay\\lbcancel.wav"));
Assert::AreEqual(std::wstring(L"Leaderboard attempt failed"), pPopup->GetTitle());
Assert::AreEqual(std::wstring(L"Title"), pPopup->GetDescription());
const auto* pScore = harness.mockOverlayManager.GetScoreTracker(1U);
Assert::IsNull(pScore);
}
TEST_METHOD(TestDoAchievementsFrameLeaderboardCaneledPopupDisabled)
{
DoAchievementsFrameHarness harness;
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardCanceled, ra::ui::viewmodels::PopupLocation::None);
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardTracker, ra::ui::viewmodels::PopupLocation::BottomRight);
auto& pLeaderboard = harness.mockGameContext.Assets().NewLeaderboard();
pLeaderboard.SetID(1U);
pLeaderboard.SetName(L"Title");
pLeaderboard.SetDescription(L"Description");
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::LeaderboardCanceled, 1U);
_RA_DoAchievementsFrame();
const auto* pPopup = harness.mockOverlayManager.GetMessage(1);
Assert::IsNull(pPopup);
const auto* pScore = harness.mockOverlayManager.GetScoreTracker(1U);
Assert::IsNull(pScore);
}
TEST_METHOD(TestDoAchievementsFrameLeaderboardUpdated)
{
DoAchievementsFrameHarness harness;
auto& pLeaderboard = harness.mockGameContext.Assets().NewLeaderboard();
pLeaderboard.SetID(1U);
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardTracker, ra::ui::viewmodels::PopupLocation::BottomRight);
harness.mockOverlayManager.AddScoreTracker(1U);
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::LeaderboardUpdated, 1U, 1235U);
_RA_DoAchievementsFrame();
const auto* pScore = harness.mockOverlayManager.GetScoreTracker(1U);
Assert::IsNotNull(pScore);
Ensures(pScore != nullptr);
Assert::AreEqual(std::wstring(L"1235"), pScore->GetDisplayText());
}
TEST_METHOD(TestDoAchievementsFrameLeaderboardTriggered)
{
DoAchievementsFrameHarness harness;
harness.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, true);
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardTracker, ra::ui::viewmodels::PopupLocation::BottomRight);
auto& pLeaderboard = harness.mockGameContext.Assets().NewLeaderboard();
pLeaderboard.SetID(1U);
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::LeaderboardTriggered, 1U, 1236U);
_RA_DoAchievementsFrame();
Assert::AreEqual(1236U, harness.GetSubmittedScore(1U));
const auto* pScore = harness.mockOverlayManager.GetScoreTracker(1U);
Assert::IsNull(pScore);
}
TEST_METHOD(TestDoAchievementsFrameLeaderboardStartPauseOnReset)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U);
auto& pLeaderboard = harness.mockGameContext.Assets().NewLeaderboard();
pLeaderboard.SetID(1U);
pLeaderboard.SetName(L"Title");
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::LeaderboardStartReset, 1U);
bool bWasPaused = false;
harness.mockEmulatorContext.SetPauseFunction([&bWasPaused] { bWasPaused = true; });
bool bMessageSeen = false;
harness.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&bMessageSeen](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
{
bMessageSeen = true;
Assert::AreEqual(std::wstring(L"The following triggers have been reset:\n* Start: Title"), vmMessageBox.GetMessage());
return ra::ui::DialogResult::OK;
});
_RA_DoAchievementsFrame();
harness.mockFrameEventQueue.DoFrame(); // UpdateUIForFrameChange isn't called in the unit test build
Assert::IsTrue(bMessageSeen);
Assert::IsTrue(bWasPaused);
}
TEST_METHOD(TestDoAchievementsFrameLeaderboardSubmitPauseOnReset)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U);
auto& pLeaderboard = harness.mockGameContext.Assets().NewLeaderboard();
pLeaderboard.SetID(1U);
pLeaderboard.SetName(L"Title");
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::LeaderboardSubmitReset, 1U);
bool bWasPaused = false;
harness.mockEmulatorContext.SetPauseFunction([&bWasPaused] { bWasPaused = true; });
bool bMessageSeen = false;
harness.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&bMessageSeen](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
{
bMessageSeen = true;
Assert::AreEqual(std::wstring(L"The following triggers have been reset:\n* Submit: Title"), vmMessageBox.GetMessage());
return ra::ui::DialogResult::OK;
});
_RA_DoAchievementsFrame();
harness.mockFrameEventQueue.DoFrame(); // UpdateUIForFrameChange isn't called in the unit test build
Assert::IsTrue(bMessageSeen);
Assert::IsTrue(bWasPaused);
}
TEST_METHOD(TestDoAchievementsFrameLeaderboardCancelPauseOnReset)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U);
auto& pLeaderboard = harness.mockGameContext.Assets().NewLeaderboard();
pLeaderboard.SetID(1U);
pLeaderboard.SetName(L"Title");
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::LeaderboardCancelReset, 1U);
bool bWasPaused = false;
harness.mockEmulatorContext.SetPauseFunction([&bWasPaused] { bWasPaused = true; });
bool bMessageSeen = false;
harness.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&bMessageSeen](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
{
bMessageSeen = true;
Assert::AreEqual(std::wstring(L"The following triggers have been reset:\n* Cancel: Title"), vmMessageBox.GetMessage());
return ra::ui::DialogResult::OK;
});
_RA_DoAchievementsFrame();
harness.mockFrameEventQueue.DoFrame(); // UpdateUIForFrameChange isn't called in the unit test build
Assert::IsTrue(bMessageSeen);
Assert::IsTrue(bWasPaused);
}
TEST_METHOD(TestDoAchievementsFrameLeaderboardValuePauseOnReset)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U);
auto& pLeaderboard = harness.mockGameContext.Assets().NewLeaderboard();
pLeaderboard.SetID(1U);
pLeaderboard.SetName(L"Title");
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::LeaderboardValueReset, 1U);
bool bWasPaused = false;
harness.mockEmulatorContext.SetPauseFunction([&bWasPaused] { bWasPaused = true; });
bool bMessageSeen = false;
harness.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&bMessageSeen](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
{
bMessageSeen = true;
Assert::AreEqual(std::wstring(L"The following triggers have been reset:\n* Value: Title"), vmMessageBox.GetMessage());
return ra::ui::DialogResult::OK;
});
_RA_DoAchievementsFrame();
harness.mockFrameEventQueue.DoFrame(); // UpdateUIForFrameChange isn't called in the unit test build
Assert::IsTrue(bMessageSeen);
Assert::IsTrue(bWasPaused);
}
TEST_METHOD(TestDoAchievementsFrameLeaderboardStartPauseOnTriggered)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U);
auto& pLeaderboard = harness.mockGameContext.Assets().NewLeaderboard();
pLeaderboard.SetID(1U);
pLeaderboard.SetName(L"Title");
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::LeaderboardStartTriggered, 1U);
bool bWasPaused = false;
harness.mockEmulatorContext.SetPauseFunction([&bWasPaused] { bWasPaused = true; });
bool bMessageSeen = false;
harness.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&bMessageSeen](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
{
bMessageSeen = true;
Assert::AreEqual(std::wstring(L"The following triggers have triggered:\n* Start: Title"), vmMessageBox.GetMessage());
return ra::ui::DialogResult::OK;
});
_RA_DoAchievementsFrame();
harness.mockFrameEventQueue.DoFrame(); // UpdateUIForFrameChange isn't called in the unit test build
Assert::IsTrue(bMessageSeen);
Assert::IsTrue(bWasPaused);
}
TEST_METHOD(TestDoAchievementsFrameLeaderboardSubmitPauseOnTriggered)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U);
auto& pLeaderboard = harness.mockGameContext.Assets().NewLeaderboard();
pLeaderboard.SetID(1U);
pLeaderboard.SetName(L"Title");
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::LeaderboardSubmitTriggered, 1U);
bool bWasPaused = false;
harness.mockEmulatorContext.SetPauseFunction([&bWasPaused] { bWasPaused = true; });
bool bMessageSeen = false;
harness.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&bMessageSeen](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
{
bMessageSeen = true;
Assert::AreEqual(std::wstring(L"The following triggers have triggered:\n* Submit: Title"), vmMessageBox.GetMessage());
return ra::ui::DialogResult::OK;
});
_RA_DoAchievementsFrame();
harness.mockFrameEventQueue.DoFrame(); // UpdateUIForFrameChange isn't called in the unit test build
Assert::IsTrue(bMessageSeen);
Assert::IsTrue(bWasPaused);
}
TEST_METHOD(TestDoAchievementsFrameLeaderboardCancelPauseOnTriggered)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U);
auto& pLeaderboard = harness.mockGameContext.Assets().NewLeaderboard();
pLeaderboard.SetID(1U);
pLeaderboard.SetName(L"Title");
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::LeaderboardCancelTriggered, 1U);
bool bWasPaused = false;
harness.mockEmulatorContext.SetPauseFunction([&bWasPaused] { bWasPaused = true; });
bool bMessageSeen = false;
harness.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&bMessageSeen](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
{
bMessageSeen = true;
Assert::AreEqual(std::wstring(L"The following triggers have triggered:\n* Cancel: Title"), vmMessageBox.GetMessage());
return ra::ui::DialogResult::OK;
});
_RA_DoAchievementsFrame();
harness.mockFrameEventQueue.DoFrame(); // UpdateUIForFrameChange isn't called in the unit test build
Assert::IsTrue(bMessageSeen);
Assert::IsTrue(bWasPaused);
}
TEST_METHOD(TestDoAchievementsFrameMultiple)
{
DoAchievementsFrameHarness harness;
harness.MockAchievement(1U);
harness.MockAchievement(2U);
harness.mockGameContext.Assets().NewLeaderboard().SetID(3U);
harness.mockGameContext.Assets().NewLeaderboard().SetID(4U);
harness.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::LeaderboardTracker, ra::ui::viewmodels::PopupLocation::BottomRight);
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::LeaderboardStarted, 3U, 1234U);
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::AchievementTriggered, 1U);
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::AchievementTriggered, 2U);
harness.mockRuntime.QueueChange(ra::services::AchievementRuntime::ChangeType::LeaderboardStarted, 4U, 5678U);
_RA_DoAchievementsFrame();
Assert::IsTrue(harness.WasUnlocked(1U));
Assert::IsTrue(harness.WasUnlocked(2U));
const auto* pScore3 = harness.mockOverlayManager.GetScoreTracker(3U);
Assert::IsNotNull(pScore3);
Ensures(pScore3 != nullptr);
Assert::AreEqual(std::wstring(L"1234"), pScore3->GetDisplayText());
const auto* pScore4 = harness.mockOverlayManager.GetScoreTracker(4U);
Assert::IsNotNull(pScore4);
Ensures(pScore4 != nullptr);
Assert::AreEqual(std::wstring(L"5678"), pScore4->GetDisplayText());
}
private:
void AssertMenuItem(const RA_MenuItem* pItem, LPARAM nId, const wchar_t* sLabel, bool bChecked = false)
{
Assert::AreEqual((int)nId, (int)pItem->nID);
+1 -3
View File
@@ -318,7 +318,6 @@
<ClCompile Include="..\src\services\Http.cpp" />
<ClCompile Include="..\src\services\impl\FileLocalStorage.cpp" />
<ClCompile Include="..\src\services\impl\JsonFileConfiguration.cpp" />
<ClCompile Include="..\src\services\RcheevosClient.cpp" />
<ClCompile Include="..\src\services\SearchResults.cpp" />
<ClCompile Include="..\src\ui\Theme.cpp" />
<ClCompile Include="..\src\ui\TransactionalViewModelBase.cpp" />
@@ -382,7 +381,7 @@
<ClCompile Include="data\models\RichPresenceModel_Tests.cpp" />
<ClCompile Include="data\models\TriggerValidation_Tests.cpp" />
<ClCompile Include="Exports_Tests.cpp" />
<ClCompile Include="mocks\MockRcheevosClient.cpp" />
<ClCompile Include="mocks\MockAchievementRuntime.cpp" />
<ClCompile Include="services\AchievementRuntime_Tests.cpp" />
<ClCompile Include="services\FileLocalStorage_Tests.cpp" />
<ClCompile Include="services\FrameEventQueue_Tests.cpp" />
@@ -445,7 +444,6 @@
<ClInclude Include="mocks\MockLocalStorage.hh" />
<ClInclude Include="mocks\MockOverlayManager.hh" />
<ClInclude Include="mocks\MockOverlayTheme.hh" />
<ClInclude Include="mocks\MockRcheevosClient.hh" />
<ClInclude Include="mocks\MockServer.hh" />
<ClInclude Include="mocks\MockSessionTracker.hh" />
<ClInclude Include="mocks\MockSurface.hh" />
+1 -7
View File
@@ -459,10 +459,7 @@
<ClCompile Include="data\models\CapturedTriggerHits_Tests.cpp">
<Filter>Tests\Data\Models</Filter>
</ClCompile>
<ClCompile Include="..\src\services\RcheevosClient.cpp">
<Filter>Code</Filter>
</ClCompile>
<ClCompile Include="mocks\MockRcheevosClient.cpp">
<ClCompile Include="mocks\MockAchievementRuntime.cpp">
<Filter>Mocks</Filter>
</ClCompile>
</ItemGroup>
@@ -560,8 +557,5 @@
<ClInclude Include="..\src\ui\viewmodels\PointerFinderViewModel.hh">
<Filter>Code</Filter>
</ClInclude>
<ClInclude Include="mocks\MockRcheevosClient.hh">
<Filter>Mocks</Filter>
</ClInclude>
</ItemGroup>
</Project>
-220
View File
@@ -31,97 +31,6 @@ TEST_CLASS(ConnectedServer_Tests)
public:
// ConnectedServer manages converting the Http error codes to Incomplete responses, this just tests the handling
// of Incomplete responses by the ApiRequestBase.
TEST_METHOD(TestCallAsyncWithRetry)
{
MockThreadPool mockThreadPool;
MockServer mockServer;
bool bHandlerCalled = false;
mockServer.HandleRequest<ra::api::AwardAchievement>([&bHandlerCalled](const ra::api::AwardAchievement::Request& request, ra::api::AwardAchievement::Response& response)
{
bHandlerCalled = true;
Assert::AreEqual(1234U, request.AchievementId);
Assert::AreEqual(std::string("HASH"), request.GameHash);
Assert::IsTrue(request.Hardcore);
response.Result = ra::api::ApiResult::Incomplete;
return true;
});
AwardAchievement::Request request;
request.GameHash = "HASH";
request.AchievementId = 1234U;
request.Hardcore = true;
bool bCallbackCalled = false;
request.CallAsyncWithRetry([&bCallbackCalled](const ra::api::AwardAchievement::Response& response)
{
Assert::AreEqual(ra::api::ApiResult::Success, response.Result);
bCallbackCalled = true;
});
// call should be async
Assert::IsFalse(bCallbackCalled);
Assert::IsFalse(bHandlerCalled);
Assert::AreEqual({ 1U }, mockThreadPool.PendingTasks());
// async call should be incomplete and requeued
mockThreadPool.ExecuteNextTask();
Assert::IsFalse(bCallbackCalled);
Assert::IsTrue(bHandlerCalled);
Assert::AreEqual({ 1U }, mockThreadPool.PendingTasks());
Assert::AreEqual(std::chrono::milliseconds(500), mockThreadPool.NextTaskDelay());
// callback called again, still incomplete, requeue with longer delay
bHandlerCalled = false;
mockThreadPool.AdvanceTime(std::chrono::milliseconds(500));
Assert::IsFalse(bCallbackCalled);
Assert::IsTrue(bHandlerCalled);
Assert::AreEqual({ 1U }, mockThreadPool.PendingTasks());
Assert::AreEqual(std::chrono::milliseconds(1000), mockThreadPool.NextTaskDelay());
// response succeeded, should complete transaction
mockServer.HandleRequest<ra::api::AwardAchievement>([&bHandlerCalled](const ra::api::AwardAchievement::Request& request, ra::api::AwardAchievement::Response& response)
{
bHandlerCalled = true;
Assert::AreEqual(1234U, request.AchievementId);
Assert::AreEqual(std::string("HASH"), request.GameHash);
Assert::IsTrue(request.Hardcore);
response.Result = ra::api::ApiResult::Success;
return true;
});
bHandlerCalled = false;
mockThreadPool.AdvanceTime(std::chrono::milliseconds(1000));
Assert::IsTrue(bCallbackCalled);
Assert::IsTrue(bHandlerCalled);
Assert::AreEqual({ 0U }, mockThreadPool.PendingTasks());
}
TEST_METHOD(TestCallAsyncWithRetryMaxDelay)
{
MockThreadPool mockThreadPool;
MockServer mockServer;
mockServer.HandleRequest<ra::api::AwardAchievement>([](const ra::api::AwardAchievement::Request&, ra::api::AwardAchievement::Response& response)
{
response.Result = ra::api::ApiResult::Incomplete;
return true;
});
AwardAchievement::Request request;
request.GameHash = "HASH";
request.AchievementId = 1234U;
request.Hardcore = true;
request.CallAsyncWithRetry([](const ra::api::AwardAchievement::Response&) {});
// call should be async
mockThreadPool.ExecuteNextTask();
for (int i = 0; i < 100; i++)
mockThreadPool.AdvanceTime(mockThreadPool.NextTaskDelay());
Assert::AreEqual(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::minutes(2)), mockThreadPool.NextTaskDelay());
}
// ====================================================
// These tests validate the generic handling of errors - they just happen to be written using the LatestClient API
@@ -207,106 +116,6 @@ public:
Assert::AreEqual(std::string(""), response.MinimumVersion);
}
TEST_METHOD(TestAwardAchievementFailed403)
{
MockUserContext mockUserContext;
mockUserContext.Initialize("Username", "ApiToken");
MockHttpRequester mockHttp([]([[maybe_unused]] const Http::Request& /*request*/)
{
return Http::Response(Http::StatusCode::Forbidden, "");
});
ConnectedServer server("host.com");
AwardAchievement::Request request;
request.GameHash = "HASH";
request.AchievementId = 1234U;
request.Hardcore = true;
auto response = server.AwardAchievement(request);
Assert::AreEqual(ApiResult::Error, response.Result);
Assert::AreEqual(std::string("HTTP error code: 403 (err403)"), response.ErrorMessage);
}
TEST_METHOD(TestAwardAchievementFailed403WithMessage)
{
MockUserContext mockUserContext;
mockUserContext.Initialize("Username", "ApiToken");
MockHttpRequester mockHttp([]([[maybe_unused]] const Http::Request& /*request*/)
{
return Http::Response(Http::StatusCode::Forbidden, "{\"Success\":false, \"Error\":\"Invalid API token\"}");
});
ConnectedServer server("host.com");
AwardAchievement::Request request;
request.GameHash = "HASH";
request.AchievementId = 1234U;
request.Hardcore = true;
auto response = server.AwardAchievement(request);
Assert::AreEqual(ApiResult::Error, response.Result);
Assert::AreEqual(std::string("Invalid API token"), response.ErrorMessage);
}
TEST_METHOD(TestAwardAchievementFailed429)
{
MockUserContext mockUserContext;
mockUserContext.Initialize("Username", "ApiToken");
MockHttpRequester mockHttp([]([[maybe_unused]] const Http::Request& /*request*/)
{
return Http::Response(Http::StatusCode::TooManyRequests,
"<html>\n"
"<head><title>429 Too Many Requests</title></head>\n"
"<body>\n"
"<center><h1>429 Too Many Requests</h1></center>\n"
"<hr><center>nginx</center>\n"
"</body>\n"
"</html>");
});
ConnectedServer server("host.com");
AwardAchievement::Request request;
request.GameHash = "HASH";
request.AchievementId = 1234U;
request.Hardcore = true;
auto response = server.AwardAchievement(request);
Assert::AreEqual(ApiResult::Error, response.Result);
Assert::AreEqual(std::string("HTTP error code: 429 (err429)"), response.ErrorMessage);
}
TEST_METHOD(TestAwardAchievementFailed429Json)
{
MockUserContext mockUserContext;
mockUserContext.Initialize("Username", "ApiToken");
MockHttpRequester mockHttp([]([[maybe_unused]] const Http::Request& /*request*/)
{
return Http::Response(Http::StatusCode::TooManyRequests,
"{\"Success\": false,\"Error\": \"Too Many Attempts\"}");
});
ConnectedServer server("host.com");
AwardAchievement::Request request;
request.GameHash = "HASH";
request.AchievementId = 1234U;
request.Hardcore = true;
auto response = server.AwardAchievement(request);
Assert::AreEqual(ApiResult::Error, response.Result);
Assert::AreEqual(std::string("Too Many Attempts"), response.ErrorMessage);
}
TEST_METHOD(TestLatestClientUnknownServer)
{
MockHttpRequester mockHttp([]([[maybe_unused]] const Http::Request& /*request*/)
@@ -410,35 +219,6 @@ public:
// End of generic validation tests
// ====================================================
// ====================================================
// SubmitLeaderboardEntry
TEST_METHOD(TestSubmitLeaderboardEntrySigned)
{
MockUserContext mockUserContext;
mockUserContext.Initialize("Username", "ApiToken");
MockHttpRequester mockHttp([](const Http::Request&)
{
return Http::Response(Http::StatusCode::OK,
"{\"Success\":true,\"Response\":{\"Score\":1234,\"BestScore\":2345,"
"\"TopEntries\":[{\"User\":\"Player1\",\"Score\":8765,\"Rank\":1},{\"User\":\"Player2\",\"Score\":7654,\"Rank\":2}],"
"\"RankInfo\":{\"Rank\":5,\"NumEntries\":\"17\"}}}");
});
ra::services::ServiceLocator::ServiceOverride<ra::api::IServer> serviceOverride(new ConnectedServer("host.com"), true);
auto& server = ra::services::ServiceLocator::GetMutable<ra::api::IServer>();
SubmitLeaderboardEntry::Request request;
request.LeaderboardId = 234;
request.Score = -55667788;
auto response = server.SubmitLeaderboardEntry(request);
Assert::AreEqual(ApiResult::Success, response.Result);
Assert::AreEqual(std::string(), response.ErrorMessage);
}
// ====================================================
// FetchCodeNotes
+145 -131
View File
@@ -14,7 +14,6 @@
#include "tests\mocks\MockHttpRequester.hh"
#include "tests\mocks\MockLocalStorage.hh"
#include "tests\mocks\MockOverlayManager.hh"
#include "tests\mocks\MockRcheevosClient.hh"
#include "tests\mocks\MockServer.hh"
#include "tests\mocks\MockThreadPool.hh"
#include "tests\mocks\MockUserContext.hh"
@@ -42,15 +41,15 @@ private:
ra::services::mocks::MockFileSystem mockFileSystem;
ra::services::mocks::MockHttpRequester mockHttpRequester;
ra::services::mocks::MockThreadPool mockThreadPool;
ra::services::mocks::MockRcheevosClient mockRcheevosClient;
ra::ui::mocks::MockDesktop mockDesktop;
ra::ui::viewmodels::mocks::MockOverlayManager mockOverlayManager;
ra::ui::viewmodels::mocks::MockWindowManager mockWindowManager;
GSL_SUPPRESS_F6 EmulatorContextHarness() noexcept
: mockHttpRequester([](const ra::services::Http::Request&) { return ra::services::Http::Response(ra::services::Http::StatusCode::OK, ""); })
: mockHttpRequester([](const ra::services::Http::Request&) { return ra::services::Http::Response(ra::services::Http::StatusCode::OK, ""); }),
m_Override(this)
{
mockRcheevosClient.MockGame();
mockAchievementRuntime.MockGame();
}
void MockVersions(const std::string& sClientVersion, const std::string& sServerVersion, const std::string& sMinimumVersion)
@@ -83,6 +82,9 @@ private:
pAch.UpdateServerCheckpoint();
return pAch;
}
private:
ra::services::ServiceLocator::ServiceOverride<EmulatorContext> m_Override;
};
class EmulatorContextNotifyHarness : public EmulatorContext::NotifyTarget
@@ -655,44 +657,43 @@ public:
TEST_METHOD(TestDisableHardcoreMode)
{
EmulatorContextHarness emulator;
emulator.mockGameContext.SetGameId(1234U);
emulator.mockAchievementRuntime.MockGame();
emulator.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, true);
bool bWasReset = false;
emulator.SetResetFunction([&bWasReset]() { bWasReset = true; });
auto& pLeaderboard = emulator.mockGameContext.Assets().NewLeaderboard();
pLeaderboard.SetID(32U);
pLeaderboard.SetState(ra::data::models::AssetState::Active);
Assert::IsTrue(pLeaderboard.IsActive());
emulator.mockAchievementRuntime.MockLeaderboardWithLboard(32U);
emulator.mockAchievementRuntime.MockAchievementWithTrigger(44U);
auto* pAchievement45 = emulator.mockAchievementRuntime.MockAchievementWithTrigger(45U);
emulator.mockAchievementRuntime.UnlockAchievement(pAchievement45, RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE);
auto* pAchievement46 = emulator.mockAchievementRuntime.MockAchievementWithTrigger(46U);
emulator.mockAchievementRuntime.UnlockAchievement(pAchievement46, RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH);
emulator.mockGameContext.InitializeFromAchievementRuntime();
emulator.mockRcheevosClient.MockAchievement(44U)->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_NONE;
emulator.mockRcheevosClient.MockAchievement(45U)->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE;
emulator.mockRcheevosClient.MockAchievement(46U)->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH;
auto* vmAchievement44 = emulator.mockGameContext.Assets().FindAchievement(44U);
auto* vmAchievement45 = emulator.mockGameContext.Assets().FindAchievement(45U);
auto* vmAchievement46 = emulator.mockGameContext.Assets().FindAchievement(46U);
auto* vmLeaderboard32 = emulator.mockGameContext.Assets().FindLeaderboard(32U);
Expects(vmAchievement44 && vmAchievement45 && vmAchievement46 && vmLeaderboard32);
auto& pAchievement44 = emulator.mockGameContext.Assets().NewAchievement();
pAchievement44.SetID(44U);
pAchievement44.SetState(ra::data::models::AssetState::Active);
auto& pAchievement45 = emulator.mockGameContext.Assets().NewAchievement();
pAchievement45.SetID(45U);
auto& pAchievement46 = emulator.mockGameContext.Assets().NewAchievement();
pAchievement46.SetID(46U);
Assert::IsTrue(pAchievement44.IsActive());
Assert::IsFalse(pAchievement45.IsActive());
Assert::IsFalse(pAchievement46.IsActive());
Assert::IsTrue(vmAchievement44->IsActive());
Assert::IsTrue(vmAchievement45->IsActive());
Assert::IsFalse(vmAchievement46->IsActive());
Assert::IsTrue(vmLeaderboard32->IsActive());
emulator.DisableHardcoreMode();
// ensure mode was deactivated
Assert::IsFalse(emulator.mockConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore));
Assert::IsFalse(rc_client_get_hardcore_enabled(emulator.mockAchievementRuntime.GetClient()));
// deactivating hardcore mode should not disable leaderboards
Assert::IsTrue(pLeaderboard.IsActive());
Assert::IsTrue(vmLeaderboard32->IsActive());
// deactivating hardcore mode should update active achievements
Assert::IsTrue(pAchievement44.IsActive());
Assert::IsFalse(pAchievement45.IsActive());
Assert::IsFalse(pAchievement46.IsActive());
Assert::IsTrue(vmAchievement44->IsActive());
Assert::IsFalse(vmAchievement45->IsActive());
Assert::IsFalse(vmAchievement46->IsActive());
// deactivating hardcore mode should not reset the emulator
Assert::IsFalse(bWasReset);
@@ -709,7 +710,7 @@ public:
Assert::IsTrue(emulator.mockConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore));
Assert::IsFalse(emulator.mockDesktop.WasDialogShown());
Assert::IsTrue(bWasReset);
Assert::IsFalse(bWasReset); // if no game loaded, rc_client won't raise a reset event
}
TEST_METHOD(TestEnableHardcoreModeAlreadyEnabled)
@@ -734,28 +735,36 @@ public:
emulator.MockVersions("0.57", "0.57");
emulator.mockGameContext.SetGameId(1U);
emulator.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Leaderboards, true);
emulator.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&emulator](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
{
Assert::IsFalse(emulator.mockConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore));
Assert::AreEqual(std::wstring(L"Enable hardcore mode?"), vmMessageBox.GetHeader());
Assert::AreEqual(std::wstring(L"Enabling hardcore mode will reset the emulator. You will lose any progress that has not been saved through the game."), vmMessageBox.GetMessage());
return ra::ui::DialogResult::Yes;
});
emulator.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
emulator.mockAchievementRuntime.GetClient()->state.hardcore = 0;
emulator.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>(
[&emulator](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox) {
Assert::IsFalse(emulator.mockConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore));
Assert::AreEqual(std::wstring(L"Enable hardcore mode?"), vmMessageBox.GetHeader());
Assert::AreEqual(std::wstring(L"Enabling hardcore mode will reset the emulator. You will lose any "
L"progress that has not been saved through the game."),
vmMessageBox.GetMessage());
return ra::ui::DialogResult::Yes;
});
emulator.mockRcheevosClient.MockAchievement(44U)->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_NONE;
emulator.mockRcheevosClient.MockAchievement(45U)->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE;
emulator.mockRcheevosClient.MockAchievement(46U)->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH;
emulator.mockAchievementRuntime.MockLeaderboardWithLboard(32U);
emulator.mockAchievementRuntime.MockAchievementWithTrigger(44U);
auto* pAchievement45 = emulator.mockAchievementRuntime.MockAchievementWithTrigger(45U);
emulator.mockAchievementRuntime.UnlockAchievement(pAchievement45, RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE);
auto* pAchievement46 = emulator.mockAchievementRuntime.MockAchievementWithTrigger(46U);
emulator.mockAchievementRuntime.UnlockAchievement(pAchievement46, RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH);
emulator.mockGameContext.InitializeFromAchievementRuntime();
auto& pAchievement44 = emulator.mockGameContext.Assets().NewAchievement();
pAchievement44.SetID(44U);
auto& pAchievement45 = emulator.mockGameContext.Assets().NewAchievement();
pAchievement45.SetID(45U);
auto& pAchievement46 = emulator.mockGameContext.Assets().NewAchievement();
pAchievement46.SetID(46U);
auto* vmAchievement44 = emulator.mockGameContext.Assets().FindAchievement(44U);
auto* vmAchievement45 = emulator.mockGameContext.Assets().FindAchievement(45U);
auto* vmAchievement46 = emulator.mockGameContext.Assets().FindAchievement(46U);
auto* vmLeaderboard32 = emulator.mockGameContext.Assets().FindLeaderboard(32U);
Expects(vmAchievement44 && vmAchievement45 && vmAchievement46 && vmLeaderboard32);
auto& pLeaderboard = emulator.mockGameContext.Assets().NewLeaderboard();
pLeaderboard.SetID(32U);
Assert::IsFalse(pLeaderboard.IsActive());
Assert::IsTrue(vmAchievement44->IsActive());
Assert::IsFalse(vmAchievement45->IsActive());
Assert::IsFalse(vmAchievement46->IsActive());
Assert::IsFalse(vmLeaderboard32->IsActive());
bool bWasReset = false;
emulator.SetResetFunction([&bWasReset]() { bWasReset = true; });
@@ -764,17 +773,18 @@ public:
// ensure hardcore mode was enabled
Assert::IsTrue(emulator.mockConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore));
Assert::IsTrue(rc_client_get_hardcore_enabled(emulator.mockAchievementRuntime.GetClient()) != 0);
// ensure prompt was shown
Assert::IsTrue(emulator.mockDesktop.WasDialogShown());
// enabling hardcore mode should enable leaderboards
Assert::IsTrue(pLeaderboard.IsActive());
Assert::IsTrue(vmLeaderboard32->IsActive());
// enabling hardcore mode should activate achievements not unlocked in hardcore
Assert::IsTrue(pAchievement44.IsActive());
Assert::IsTrue(pAchievement45.IsActive());
Assert::IsFalse(pAchievement46.IsActive());
Assert::IsTrue(vmAchievement44->IsActive());
Assert::IsTrue(vmAchievement45->IsActive());
Assert::IsFalse(vmAchievement46->IsActive());
// enabling hardcore mode should reset the emulator
Assert::IsTrue(bWasReset);
@@ -785,7 +795,10 @@ public:
EmulatorContextHarness emulator;
emulator.MockVersions("0.57", "0.57");
emulator.mockGameContext.SetGameId(1U);
emulator.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&emulator](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
emulator.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
emulator.mockAchievementRuntime.GetClient()->state.hardcore = 0;
emulator.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>(
[&emulator](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
{
Assert::IsFalse(emulator.mockConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore));
Assert::AreEqual(std::wstring(L"Enable hardcore mode?"), vmMessageBox.GetHeader());
@@ -799,6 +812,7 @@ public:
Assert::IsFalse(emulator.EnableHardcoreMode());
Assert::IsFalse(emulator.mockConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore));
Assert::IsFalse(rc_client_get_hardcore_enabled(emulator.mockAchievementRuntime.GetClient()) != 0);
Assert::IsTrue(emulator.mockDesktop.WasDialogShown());
Assert::IsFalse(bWasReset);
}
@@ -808,7 +822,10 @@ public:
EmulatorContextHarness emulator;
emulator.MockVersions("0.58", "0.57");
emulator.mockGameContext.SetGameId(1U);
emulator.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&emulator](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
emulator.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
emulator.mockAchievementRuntime.GetClient()->state.hardcore = 0;
emulator.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>(
[&emulator](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
{
Assert::IsFalse(emulator.mockConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore));
Assert::AreEqual(std::wstring(L"Enable hardcore mode?"), vmMessageBox.GetHeader());
@@ -821,6 +838,7 @@ public:
Assert::IsTrue(emulator.EnableHardcoreMode());
Assert::IsTrue(emulator.mockConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore));
Assert::IsTrue(rc_client_get_hardcore_enabled(emulator.mockAchievementRuntime.GetClient()) != 0);
Assert::IsTrue(emulator.mockDesktop.WasDialogShown());
Assert::IsTrue(bWasReset);
}
@@ -832,8 +850,11 @@ public:
emulator.mockConfiguration.SetHostName("host");
emulator.mockUserContext.Initialize("User", "Token");
emulator.mockGameContext.SetGameId(1U);
emulator.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
emulator.mockAchievementRuntime.GetClient()->state.hardcore = 0;
emulator.MockVersions("0.57.0.0", "0.58.0.0");
emulator.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&emulator](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
emulator.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>(
[&emulator](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
{
Assert::IsFalse(emulator.mockConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore));
Assert::AreEqual(std::wstring(L"A newer client is required for hardcore mode."), vmMessageBox.GetHeader());
@@ -847,6 +868,7 @@ public:
Assert::IsFalse(emulator.EnableHardcoreMode());
Assert::IsFalse(emulator.mockConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore));
Assert::IsFalse(rc_client_get_hardcore_enabled(emulator.mockAchievementRuntime.GetClient()) != 0);
Assert::IsTrue(emulator.mockDesktop.WasDialogShown());
Assert::AreEqual(std::string("http://host/download.php"), emulator.mockDesktop.LastOpenedUrl());
@@ -862,8 +884,11 @@ public:
emulator.mockConfiguration.SetHostName("host");
emulator.mockUserContext.Initialize("User", "Token");
emulator.mockGameContext.SetGameId(1U);
emulator.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
emulator.mockAchievementRuntime.GetClient()->state.hardcore = 0;
emulator.MockVersions("0.57.0.0", "0.58.0.0");
emulator.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&emulator](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
emulator.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>(
[&emulator](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
{
Assert::IsFalse(emulator.mockConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore));
Assert::AreEqual(std::wstring(L"A newer client is required for hardcore mode."), vmMessageBox.GetHeader());
@@ -877,6 +902,7 @@ public:
Assert::IsFalse(emulator.EnableHardcoreMode());
Assert::IsFalse(emulator.mockConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore));
Assert::IsFalse(rc_client_get_hardcore_enabled(emulator.mockAchievementRuntime.GetClient()) != 0);
Assert::IsTrue(emulator.mockDesktop.WasDialogShown());
Assert::AreEqual(std::string(""), emulator.mockDesktop.LastOpenedUrl());
Assert::IsTrue(emulator.mockUserContext.IsLoggedIn());
@@ -889,21 +915,27 @@ public:
emulator.MockVersions("0.57", "0.57");
emulator.mockGameContext.SetGameId(1U);
emulator.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Leaderboards, true);
emulator.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
emulator.mockAchievementRuntime.GetClient()->state.hardcore = 0;
emulator.mockRcheevosClient.MockAchievement(44U)->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_NONE;
emulator.mockRcheevosClient.MockAchievement(45U)->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE;
emulator.mockRcheevosClient.MockAchievement(46U)->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH;
emulator.mockAchievementRuntime.MockLeaderboardWithLboard(32U);
emulator.mockAchievementRuntime.MockAchievementWithTrigger(44U);
auto* pAchievement45 = emulator.mockAchievementRuntime.MockAchievementWithTrigger(45U);
emulator.mockAchievementRuntime.UnlockAchievement(pAchievement45, RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE);
auto* pAchievement46 = emulator.mockAchievementRuntime.MockAchievementWithTrigger(46U);
emulator.mockAchievementRuntime.UnlockAchievement(pAchievement46, RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH);
emulator.mockGameContext.InitializeFromAchievementRuntime();
auto& pAchievement44 = emulator.mockGameContext.Assets().NewAchievement();
pAchievement44.SetID(44U);
auto& pAchievement45 = emulator.mockGameContext.Assets().NewAchievement();
pAchievement45.SetID(45U);
auto& pAchievement46 = emulator.mockGameContext.Assets().NewAchievement();
pAchievement46.SetID(46U);
auto* vmAchievement44 = emulator.mockGameContext.Assets().FindAchievement(44U);
auto* vmAchievement45 = emulator.mockGameContext.Assets().FindAchievement(45U);
auto* vmAchievement46 = emulator.mockGameContext.Assets().FindAchievement(46U);
auto* vmLeaderboard32 = emulator.mockGameContext.Assets().FindLeaderboard(32U);
Expects(vmAchievement44 && vmAchievement45 && vmAchievement46 && vmLeaderboard32);
auto& pLeaderboard = emulator.mockGameContext.Assets().NewLeaderboard();
pLeaderboard.SetID(32U);
Assert::IsFalse(pLeaderboard.IsActive());
Assert::IsTrue(vmAchievement44->IsActive());
Assert::IsFalse(vmAchievement45->IsActive());
Assert::IsFalse(vmAchievement46->IsActive());
Assert::IsFalse(vmLeaderboard32->IsActive());
bool bWasReset = false;
emulator.SetResetFunction([&bWasReset]() { bWasReset = true; });
@@ -912,17 +944,18 @@ public:
// ensure hardcore mode was enabled
Assert::IsTrue(emulator.mockConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore));
Assert::IsTrue(rc_client_get_hardcore_enabled(emulator.mockAchievementRuntime.GetClient()) != 0);
// ensure prompt was not shown
Assert::IsFalse(emulator.mockDesktop.WasDialogShown());
// enabling hardcore mode should enable leaderboards
Assert::IsTrue(pLeaderboard.IsActive());
Assert::IsTrue(vmLeaderboard32->IsActive());
// enabling hardcore mode should activate achievements not unlocked in hardcore
Assert::IsTrue(pAchievement44.IsActive());
Assert::IsTrue(pAchievement45.IsActive());
Assert::IsFalse(pAchievement46.IsActive());
Assert::IsTrue(vmAchievement44->IsActive());
Assert::IsTrue(vmAchievement45->IsActive());
Assert::IsFalse(vmAchievement46->IsActive());
// enabling hardcore mode should reset the emulator
Assert::IsTrue(bWasReset);
@@ -935,8 +968,11 @@ public:
emulator.mockConfiguration.SetHostName("host");
emulator.mockUserContext.Initialize("User", "Token");
emulator.mockGameContext.SetGameId(1U);
emulator.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
emulator.mockAchievementRuntime.GetClient()->state.hardcore = 0;
emulator.MockVersions("0.57.0.0", "0.58.0.0");
emulator.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&emulator](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
emulator.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>(
[&emulator](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
{
Assert::IsFalse(emulator.mockConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore));
Assert::AreEqual(std::wstring(L"A newer client is required for hardcore mode."), vmMessageBox.GetHeader());
@@ -950,6 +986,7 @@ public:
Assert::IsFalse(emulator.EnableHardcoreMode(false));
Assert::IsFalse(emulator.mockConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore));
Assert::IsFalse(rc_client_get_hardcore_enabled(emulator.mockAchievementRuntime.GetClient()) != 0);
Assert::IsTrue(emulator.mockDesktop.WasDialogShown());
Assert::AreEqual(std::string("http://host/download.php"), emulator.mockDesktop.LastOpenedUrl());
@@ -1317,36 +1354,6 @@ public:
Assert::AreEqual(gsl::at(buffer, 1), memory.at(1));
}
TEST_METHOD(TestReadMemoryByteInvalidAddressDisablesAchievement)
{
memory.at(4) = 0xA8;
memory.at(5) = 0x00;
memory.at(6) = 0x37;
memory.at(7) = 0x2E;
EmulatorContextHarness emulator;
emulator.mockAchievementRuntime.ActivateAchievement(1, "0xH0004=1_0xH0044=1");
Assert::IsTrue(emulator.mockAchievementRuntime.IsAchievementSupported(1));
// memory has not yet been registered. achievement should not be disabled [client may register memory later]
Assert::AreEqual(0x00, static_cast<int>(emulator.ReadMemory(4U, MemSize::EightBit)));
Assert::IsTrue(emulator.mockAchievementRuntime.IsAchievementSupported(1));
// valid memory registered. achievement should not be disabled
emulator.AddMemoryBlock(0, 20, &ReadMemory0, &WriteMemory0);
Assert::AreEqual(0xA8, static_cast<int>(emulator.ReadMemory(4U, MemSize::EightBit)));
Assert::IsTrue(emulator.mockAchievementRuntime.IsAchievementSupported(1));
// address not in supported memory range, achievement should not be disabled [indirect memory address]
Assert::AreEqual(0x00, static_cast<int>(emulator.ReadMemory(68U, MemSize::EightBit)));
Assert::IsTrue(emulator.mockAchievementRuntime.IsAchievementSupported(1));
// invalid memory region registered, address not supported. achievement should be disabled
emulator.AddMemoryBlock(1, 80, nullptr, nullptr);
Assert::AreEqual(0x00, static_cast<int>(emulator.ReadMemory(68U, MemSize::EightBit)));
Assert::IsFalse(emulator.mockAchievementRuntime.IsAchievementSupported(1));
}
TEST_METHOD(TestReadMemoryBuffer)
{
InitializeMemory();
@@ -1688,45 +1695,52 @@ public:
emulator.MockVersions("0.57", "0.57");
emulator.mockGameContext.SetGameId(1U);
emulator.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Leaderboards, true);
emulator.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&emulator](ra::ui::viewmodels::MessageBoxViewModel&)
emulator.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
emulator.mockAchievementRuntime.GetClient()->state.hardcore = 0;
emulator.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>(
[&emulator](ra::ui::viewmodels::MessageBoxViewModel&)
{
return ra::ui::DialogResult::Yes;
});
auto& ach0 = emulator.MockAchievement(); // 0 = Unmodified
ach0.SetState(ra::data::models::AssetState::Active);
auto& ach1 = emulator.MockAchievement(); // 1 = Modified
ach1.SetName(L"New Name");
ach1.SetState(ra::data::models::AssetState::Active);
auto& ach2 = emulator.MockAchievement(); // 2 = New
ach2.SetNew();
ach2.SetState(ra::data::models::AssetState::Active);
auto& ach3 = emulator.MockAchievement(); // 3 = Unpublished
ach3.SetName(L"Newer Name");
ach3.UpdateLocalCheckpoint();
ach3.SetState(ra::data::models::AssetState::Active);
emulator.mockAchievementRuntime.MockAchievementWithTrigger(44U);
emulator.mockAchievementRuntime.MockAchievementWithTrigger(45U);
emulator.mockAchievementRuntime.MockAchievementWithTrigger(46U);
emulator.mockAchievementRuntime.MockAchievementWithTrigger(47U);
emulator.mockGameContext.InitializeFromAchievementRuntime();
Assert::AreEqual(ra::data::models::AssetChanges::None, emulator.mockGameContext.Assets().GetItemAt(0)->GetChanges());
Assert::AreEqual(ra::data::models::AssetChanges::Modified, emulator.mockGameContext.Assets().GetItemAt(1)->GetChanges());
Assert::AreEqual(ra::data::models::AssetChanges::New, emulator.mockGameContext.Assets().GetItemAt(2)->GetChanges());
Assert::AreEqual(ra::data::models::AssetChanges::Unpublished, emulator.mockGameContext.Assets().GetItemAt(3)->GetChanges());
auto* vmAchievement44 = emulator.mockGameContext.Assets().FindAchievement(44U);
auto* vmAchievement45 = emulator.mockGameContext.Assets().FindAchievement(45U);
vmAchievement45->SetName(L"New Name"); // modified
auto* vmAchievement46 = emulator.mockGameContext.Assets().FindAchievement(46U);
vmAchievement46->SetNew(); // new
auto* vmAchievement47 = emulator.mockGameContext.Assets().FindAchievement(47U);
vmAchievement47->SetName(L"New Name");
vmAchievement47->UpdateLocalCheckpoint(); // unpublished
Assert::AreEqual(ra::data::models::AssetState::Active, emulator.mockGameContext.Assets().GetItemAt(0)->GetState());
Assert::AreEqual(ra::data::models::AssetState::Active, emulator.mockGameContext.Assets().GetItemAt(1)->GetState());
Assert::AreEqual(ra::data::models::AssetState::Active, emulator.mockGameContext.Assets().GetItemAt(2)->GetState());
Assert::AreEqual(ra::data::models::AssetState::Active, emulator.mockGameContext.Assets().GetItemAt(3)->GetState());
Assert::AreEqual(ra::data::models::AssetChanges::None, vmAchievement44->GetChanges());
Assert::AreEqual(ra::data::models::AssetChanges::Modified, vmAchievement45->GetChanges());
Assert::AreEqual(ra::data::models::AssetChanges::New, vmAchievement46->GetChanges());
Assert::AreEqual(ra::data::models::AssetChanges::Unpublished, vmAchievement47->GetChanges());
Assert::AreEqual(ra::data::models::AssetState::Active, vmAchievement44->GetState());
Assert::AreEqual(ra::data::models::AssetState::Active, vmAchievement45->GetState());
Assert::AreEqual(ra::data::models::AssetState::Active, vmAchievement46->GetState());
Assert::AreEqual(ra::data::models::AssetState::Active, vmAchievement47->GetState());
Assert::IsTrue(emulator.EnableHardcoreMode());
Assert::AreEqual(ra::data::models::AssetChanges::None, emulator.mockGameContext.Assets().GetItemAt(0)->GetChanges());
Assert::AreEqual(ra::data::models::AssetChanges::Modified, emulator.mockGameContext.Assets().GetItemAt(1)->GetChanges());
Assert::AreEqual(ra::data::models::AssetChanges::New, emulator.mockGameContext.Assets().GetItemAt(2)->GetChanges());
Assert::AreEqual(ra::data::models::AssetChanges::Unpublished, emulator.mockGameContext.Assets().GetItemAt(3)->GetChanges());
Assert::AreEqual(ra::data::models::AssetChanges::None, vmAchievement44->GetChanges());
Assert::AreEqual(ra::data::models::AssetChanges::Modified, vmAchievement45->GetChanges());
Assert::AreEqual(ra::data::models::AssetChanges::New, vmAchievement46->GetChanges());
Assert::AreEqual(ra::data::models::AssetChanges::Unpublished, vmAchievement47->GetChanges());
Assert::AreEqual(ra::data::models::AssetState::Active, emulator.mockGameContext.Assets().GetItemAt(0)->GetState());
Assert::AreEqual(ra::data::models::AssetState::Inactive, emulator.mockGameContext.Assets().GetItemAt(1)->GetState());
Assert::AreEqual(ra::data::models::AssetState::Inactive, emulator.mockGameContext.Assets().GetItemAt(2)->GetState());
Assert::AreEqual(ra::data::models::AssetState::Inactive, emulator.mockGameContext.Assets().GetItemAt(3)->GetState());
// enabling hardcore does not change the asset state to waiting. it issues a reset request
// and resetting the runtime changes the asset state to waiting.
Assert::AreEqual(ra::data::models::AssetState::Active, vmAchievement44->GetState());
Assert::AreEqual(ra::data::models::AssetState::Inactive, vmAchievement45->GetState());
Assert::AreEqual(ra::data::models::AssetState::Inactive, vmAchievement46->GetState());
Assert::AreEqual(ra::data::models::AssetState::Inactive, vmAchievement47->GetState());
}
TEST_METHOD(TestFormatAddress)
+1
View File
@@ -35,6 +35,7 @@ public:
GameAssetsHarness() noexcept
{
GSL_SUPPRESS_F6 mockGameContext.SetGameTitle(L"GameName");
mockRuntime.MockGame();
}
void SaveAllAssets()
File diff suppressed because it is too large Load Diff
+1 -91
View File
@@ -32,9 +32,9 @@ public:
GSL_SUPPRESS_F6 SessionTrackerHarness(const char* sUsername = "User") noexcept
: m_sUsername(sUsername), m_sUsernameWide(ra::Widen(sUsername))
{
mockRuntime.MockGame();
}
ra::api::mocks::MockServer mockServer;
ra::data::context::mocks::MockGameContext mockGameContext;
ra::services::mocks::MockAchievementRuntime mockRuntime;
@@ -245,96 +245,6 @@ public:
Assert::AreEqual(std::string("1234:1534000000:1732:f5\n1234:1534889323:173:bb\n9999:1534889496:169:14\n"), tracker.GetStoredData());
}
TEST_METHOD(TestPing)
{
SessionTrackerHarness tracker;
tracker.Initialize("User");
int nPings = 0;
tracker.mockServer.HandleRequest<ra::api::Ping>([&nPings](const ra::api::Ping::Request& request, _UNUSED ra::api::Ping::Response& /*response*/)
{
Assert::AreEqual(1234U, request.GameId);
Assert::AreEqual(std::wstring(L"Playing Banana"), request.CurrentActivity);
++nPings;
return false;
});
// BeginSession should begin the session, but not write to the file
tracker.mockGameContext.SetGameId(1234U);
tracker.mockGameContext.SetGameTitle(L"Banana");
tracker.BeginSession(1234U);
tracker.mockThreadPool.ExecuteNextTask(); // execute async server call StartSession
Assert::AreEqual(0, nPings);
// after 30 seconds, the callback will be called, and the first ping should occur
tracker.mockClock.AdvanceTime(std::chrono::seconds(30));
tracker.mockThreadPool.AdvanceTime(std::chrono::seconds(30));
tracker.mockThreadPool.ExecuteNextTask(); // execute async server call
Assert::AreEqual({ 1U }, tracker.mockThreadPool.PendingTasks());
Assert::AreEqual(1, nPings);
// after two minutes, the callback will be called again, and the second ping should occur
tracker.mockClock.AdvanceTime(std::chrono::seconds(120));
tracker.mockThreadPool.AdvanceTime(std::chrono::seconds(120));
tracker.mockThreadPool.ExecuteNextTask(); // execute async server call
Assert::AreEqual(2, nPings);
// after two more minutes, the callback will be called again, and the third ping should occur
tracker.mockClock.AdvanceTime(std::chrono::seconds(120));
tracker.mockThreadPool.AdvanceTime(std::chrono::seconds(120));
tracker.mockThreadPool.ExecuteNextTask(); // execute async server call
Assert::AreEqual(3, nPings);
// once the session is ended, pinging should stop
tracker.EndSession();
tracker.mockClock.AdvanceTime(std::chrono::seconds(120));
tracker.mockThreadPool.AdvanceTime(std::chrono::seconds(120));
tracker.mockThreadPool.ExecuteNextTask(); // execute async server call
Assert::AreEqual(3, nPings);
}
TEST_METHOD(TestPingUnknownGame)
{
SessionTrackerHarness tracker;
tracker.Initialize("User");
tracker.mockGameContext.SetGameId(0U);
tracker.mockGameContext.SetGameTitle(L"FileUnknown");
int nPings = 0;
tracker.mockServer.HandleRequest<ra::api::Ping>([&nPings](const ra::api::Ping::Request& request, _UNUSED ra::api::Ping::Response& /*response*/)
{
Assert::AreEqual(0U, request.GameId);
Assert::AreEqual(std::wstring(L"Playing FileUnknown"), request.CurrentActivity);
++nPings;
return false;
});
tracker.mockServer.ExpectUncalled<ra::api::StartSession>();
// BeginSession should begin the session, but not send a start session
tracker.BeginSession(0U);
tracker.mockThreadPool.ExecuteNextTask(); // StartSession should not be queued
Assert::AreEqual(0, nPings);
// after 30 seconds, the callback will be called, and the first ping should occur
tracker.mockClock.AdvanceTime(std::chrono::seconds(30));
tracker.mockThreadPool.AdvanceTime(std::chrono::seconds(30));
tracker.mockThreadPool.ExecuteNextTask(); // execute async server call
Assert::AreEqual({ 1U }, tracker.mockThreadPool.PendingTasks());
Assert::AreEqual(1, nPings);
// after two minutes, the callback will be called again, and the second ping should occur
tracker.mockClock.AdvanceTime(std::chrono::seconds(120));
tracker.mockThreadPool.AdvanceTime(std::chrono::seconds(120));
tracker.mockThreadPool.ExecuteNextTask(); // execute async server call
Assert::AreEqual(2, nPings);
// total playtime should still be tallied, but not written to the file
Assert::AreEqual(150, gsl::narrow<int>(tracker.GetTotalPlaytime(0U).count()));
Assert::AreEqual(std::string(""), tracker.GetStoredData());
}
TEST_METHOD(TestCurrentActivityNoRichPresence)
{
SessionTrackerHarness tracker;
+5 -135
View File
@@ -25,6 +25,11 @@ private:
class AchievementModelHarness : public AchievementModel
{
public:
AchievementModelHarness()
{
mockRuntime.MockGame();
}
ra::data::context::mocks::MockGameContext mockGameContext;
ra::services::impl::StringTextWriter textWriter;
ra::services::mocks::MockAchievementRuntime mockRuntime;
@@ -63,141 +68,6 @@ public:
Assert::AreEqual(std::string(""), achievement.GetTrigger());
}
TEST_METHOD(TestActivateDeactivate)
{
AchievementModelHarness achievement;
achievement.SetID(1U);
achievement.SetTrigger("0xH1234=1");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
Assert::AreEqual(AssetState::Inactive, achievement.GetState());
Assert::IsNull(achievement.mockRuntime.GetAchievementTrigger(1U));
achievement.Activate();
Assert::AreEqual(AssetState::Waiting, achievement.GetState());
const auto* pTrigger = achievement.mockRuntime.GetAchievementTrigger(1U);
Assert::IsNotNull(pTrigger);
Assert::AreEqual((int)RC_TRIGGER_STATE_WAITING, (int)pTrigger->state);
achievement.Deactivate();
Assert::AreEqual(AssetState::Inactive, achievement.GetState());
pTrigger = achievement.mockRuntime.GetAchievementTrigger(1U);
Assert::IsNull(pTrigger);
}
TEST_METHOD(TestActivateDeactivateActive)
{
AchievementModelHarness achievement;
achievement.SetID(1U);
achievement.SetTrigger("0xH1234=1");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Activate();
auto* pTrigger = achievement.mockRuntime.GetAchievementTrigger(1U);
Assert::IsNotNull(pTrigger);
Expects(pTrigger != nullptr);
pTrigger->state = RC_TRIGGER_STATE_ACTIVE;
achievement.DoFrame();
Assert::AreEqual(AssetState::Active, achievement.GetState());
achievement.Activate();
Assert::AreEqual(AssetState::Active, achievement.GetState());
pTrigger = achievement.mockRuntime.GetAchievementTrigger(1U);
Assert::IsNotNull(pTrigger);
Assert::AreEqual((int)RC_TRIGGER_STATE_ACTIVE, (int)pTrigger->state);
achievement.Deactivate();
Assert::AreEqual(AssetState::Inactive, achievement.GetState());
pTrigger = achievement.mockRuntime.GetAchievementTrigger(1U);
Assert::IsNull(pTrigger);
}
TEST_METHOD(TestActivateDeactivatePaused)
{
AchievementModelHarness achievement;
achievement.SetID(1U);
achievement.SetTrigger("0xH1234=1");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Activate();
auto* pTrigger = achievement.mockRuntime.GetAchievementTrigger(1U);
Assert::IsNotNull(pTrigger);
Expects(pTrigger != nullptr);
pTrigger->state = RC_TRIGGER_STATE_PAUSED;
achievement.DoFrame();
Assert::AreEqual(AssetState::Paused, achievement.GetState());
achievement.Activate();
Assert::AreEqual(AssetState::Paused, achievement.GetState());
pTrigger = achievement.mockRuntime.GetAchievementTrigger(1U);
Assert::IsNotNull(pTrigger);
Assert::AreEqual((int)RC_TRIGGER_STATE_PAUSED, (int)pTrigger->state);
achievement.Deactivate();
Assert::AreEqual(AssetState::Inactive, achievement.GetState());
pTrigger = achievement.mockRuntime.GetAchievementTrigger(1U);
Assert::IsNull(pTrigger);
Assert::AreEqual(std::chrono::system_clock::duration::zero().count(), achievement.GetUnlockTime().time_since_epoch().count());
}
TEST_METHOD(TestActivateDeactivateTriggered)
{
AchievementModelHarness achievement;
achievement.SetID(1U);
achievement.SetTrigger("0xH1234=1");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Activate();
auto* pTrigger = achievement.mockRuntime.GetAchievementTrigger(1U);
Assert::IsNotNull(pTrigger);
Expects(pTrigger != nullptr);
pTrigger->state = RC_TRIGGER_STATE_TRIGGERED;
// first DoFrame will set State to Triggered, which deactivates the achievement
achievement.DoFrame();
Assert::AreEqual(AssetState::Triggered, achievement.GetState());
pTrigger = achievement.mockRuntime.GetAchievementTrigger(1U);
Assert::IsNull(pTrigger);
Assert::AreNotEqual(std::chrono::system_clock::duration::zero().count(), achievement.GetUnlockTime().time_since_epoch().count());
// second DoFrame won't be able to find the trigger, but should not modify the State
achievement.DoFrame();
Assert::AreEqual(AssetState::Triggered, achievement.GetState());
pTrigger = achievement.mockRuntime.GetAchievementTrigger(1U);
Assert::IsNull(pTrigger);
// reactivating the achievement will set it back to waiting, but not reset the unlock time
achievement.Activate();
Assert::AreEqual(AssetState::Waiting, achievement.GetState());
pTrigger = achievement.mockRuntime.GetAchievementTrigger(1U);
Assert::IsNotNull(pTrigger);
Assert::AreEqual((int)RC_TRIGGER_STATE_WAITING, (int)pTrigger->state);
Assert::AreNotEqual(std::chrono::system_clock::duration::zero().count(), achievement.GetUnlockTime().time_since_epoch().count());
}
TEST_METHOD(TestStatePrimed)
{
AchievementModelHarness achievement;
achievement.SetID(1U);
achievement.SetTrigger("0xH1234=1_T:0xH1234=2");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Activate();
auto* pTrigger = achievement.mockRuntime.GetAchievementTrigger(1U);
Assert::IsNotNull(pTrigger);
Expects(pTrigger != nullptr);
pTrigger->state = RC_TRIGGER_STATE_PRIMED;
achievement.DoFrame();
Assert::AreEqual(AssetState::Primed, achievement.GetState());
}
TEST_METHOD(TestSetBadgeNameLocal)
{
AchievementModelHarness achievement;
@@ -46,152 +46,6 @@ public:
Assert::AreEqual(std::string(""), leaderboard.GetValueDefinition());
}
TEST_METHOD(TestActivateDeactivate)
{
LeaderboardModelHarness leaderboard;
leaderboard.SetID(1U);
leaderboard.SetDefinition("STA:0xH1234=1::SUB:0xH1234=2::CAN:0xH1234=3::VAL:0xH1234");
leaderboard.CreateServerCheckpoint();
leaderboard.CreateLocalCheckpoint();
Assert::AreEqual(std::string("0xH1234=1"), leaderboard.GetStartTrigger());
Assert::AreEqual(std::string("0xH1234=2"), leaderboard.GetSubmitTrigger());
Assert::AreEqual(std::string("0xH1234=3"), leaderboard.GetCancelTrigger());
Assert::AreEqual(std::string("0xH1234"), leaderboard.GetValueDefinition());
Assert::AreEqual(AssetState::Inactive, leaderboard.GetState());
Assert::IsNull(leaderboard.mockRuntime.GetLeaderboardDefinition(1U));
leaderboard.Activate();
Assert::AreEqual(AssetState::Waiting, leaderboard.GetState());
const auto* pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Assert::IsNotNull(pLeaderboard);
Assert::AreEqual((int)RC_LBOARD_STATE_WAITING, (int)pLeaderboard->state);
Assert::AreEqual(1U, pLeaderboard->start.requirement->conditions->operand2.value.num);
Assert::AreEqual(2U, pLeaderboard->submit.requirement->conditions->operand2.value.num);
Assert::AreEqual(3U, pLeaderboard->cancel.requirement->conditions->operand2.value.num);
leaderboard.Deactivate();
Assert::AreEqual(AssetState::Inactive, leaderboard.GetState());
pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Assert::IsNull(pLeaderboard);
}
TEST_METHOD(TestActivateDeactivateActive)
{
LeaderboardModelHarness leaderboard;
leaderboard.SetID(1U);
leaderboard.SetDefinition("STA:0xH1234=1::SUB:0xH1234=2::CAN:0xH1234=3::VAL:0xH1234");
leaderboard.CreateServerCheckpoint();
leaderboard.CreateLocalCheckpoint();
leaderboard.Activate();
auto* pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Expects(pLeaderboard != nullptr);
pLeaderboard->state = RC_LBOARD_STATE_ACTIVE;
leaderboard.DoFrame();
Assert::AreEqual(AssetState::Active, leaderboard.GetState());
leaderboard.Activate();
Assert::AreEqual(AssetState::Active, leaderboard.GetState());
pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Assert::IsNotNull(pLeaderboard);
Assert::AreEqual((int)RC_LBOARD_STATE_ACTIVE, (int)pLeaderboard->state);
leaderboard.Deactivate();
Assert::AreEqual(AssetState::Inactive, leaderboard.GetState());
pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Assert::IsNull(pLeaderboard);
}
TEST_METHOD(TestActivateDeactivateCanceled)
{
LeaderboardModelHarness leaderboard;
leaderboard.SetID(1U);
leaderboard.SetDefinition("STA:0xH1234=1::SUB:0xH1234=2::CAN:0xH1234=3::VAL:0xH1234");
leaderboard.CreateServerCheckpoint();
leaderboard.CreateLocalCheckpoint();
leaderboard.Activate();
// canceled means the leaderboard was primed, but was canceled before submitting.
// it should still be active, waiting for the start condition to become false.
auto* pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Expects(pLeaderboard != nullptr);
pLeaderboard->state = RC_LBOARD_STATE_CANCELED;
leaderboard.DoFrame();
Assert::AreEqual(AssetState::Waiting, leaderboard.GetState());
leaderboard.Activate();
Assert::AreEqual(AssetState::Waiting, leaderboard.GetState());
pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Assert::IsNotNull(pLeaderboard);
Assert::AreEqual((int)RC_LBOARD_STATE_CANCELED, (int)pLeaderboard->state);
leaderboard.Deactivate();
Assert::AreEqual(AssetState::Inactive, leaderboard.GetState());
pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Assert::IsNull(pLeaderboard);
}
TEST_METHOD(TestActivateDeactivateTriggered)
{
LeaderboardModelHarness leaderboard;
leaderboard.SetID(1U);
leaderboard.SetDefinition("STA:0xH1234=1::SUB:0xH1234=2::CAN:0xH1234=3::VAL:0xH1234");
leaderboard.CreateServerCheckpoint();
leaderboard.CreateLocalCheckpoint();
leaderboard.Activate();
// triggered means the leaderboard was submitted.
// it should still be active, waiting for the start condition to become false
auto* pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Expects(pLeaderboard != nullptr);
pLeaderboard->state = RC_LBOARD_STATE_TRIGGERED;
leaderboard.DoFrame();
Assert::AreEqual(AssetState::Waiting, leaderboard.GetState());
leaderboard.Activate();
Assert::AreEqual(AssetState::Waiting, leaderboard.GetState());
pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Assert::IsNotNull(pLeaderboard);
Assert::AreEqual((int)RC_LBOARD_STATE_TRIGGERED, (int)pLeaderboard->state);
leaderboard.Deactivate();
Assert::AreEqual(AssetState::Inactive, leaderboard.GetState());
pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Assert::IsNull(pLeaderboard);
}
TEST_METHOD(TestActivateDeactivatePrimed)
{
LeaderboardModelHarness leaderboard;
leaderboard.SetID(1U);
leaderboard.SetDefinition("STA:0xH1234=1::SUB:0xH1234=2::CAN:0xH1234=3::VAL:0xH1234");
leaderboard.CreateServerCheckpoint();
leaderboard.CreateLocalCheckpoint();
leaderboard.Activate();
// started means the leaderboard is engaged (showing on screen)
auto* pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Expects(pLeaderboard != nullptr);
pLeaderboard->state = RC_LBOARD_STATE_STARTED;
leaderboard.DoFrame();
Assert::AreEqual(AssetState::Primed, leaderboard.GetState());
leaderboard.Activate();
Assert::AreEqual(AssetState::Primed, leaderboard.GetState());
pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Assert::IsNotNull(pLeaderboard);
Assert::AreEqual((int)RC_LBOARD_STATE_STARTED, (int)pLeaderboard->state);
leaderboard.Deactivate();
Assert::AreEqual(AssetState::Inactive, leaderboard.GetState());
pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Assert::IsNull(pLeaderboard);
}
TEST_METHOD(TestDeserialize)
{
LeaderboardModelHarness leaderboard;
@@ -336,106 +190,6 @@ public:
Assert::AreEqual(AssetChanges::None, leaderboard.GetChanges());
}
TEST_METHOD(TestSetStartTrigger)
{
LeaderboardModelHarness leaderboard;
leaderboard.SetID(1U);
leaderboard.SetName(L"Title");
leaderboard.SetDescription(L"Desc");
leaderboard.SetDefinition("STA:0xH1234=1::SUB:0xH1234=2::CAN:0xH1234=3::VAL:0xH1234");
leaderboard.SetValueFormat(ValueFormat::Value);
leaderboard.SetLowerIsBetter(true);
leaderboard.CreateServerCheckpoint();
leaderboard.CreateLocalCheckpoint();
leaderboard.Activate();
Assert::AreEqual(AssetChanges::None, leaderboard.GetChanges());
// modify
leaderboard.SetStartTrigger("0xH5555=55");
Assert::AreEqual(AssetChanges::Modified, leaderboard.GetChanges());
// ensure runtime is updated
const auto* pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Assert::IsNotNull(pLeaderboard);
Ensures(pLeaderboard != nullptr);
Assert::AreEqual(0x5555U, pLeaderboard->start.requirement->conditions->operand1.value.memref->address);
}
TEST_METHOD(TestSetSubmitTrigger)
{
LeaderboardModelHarness leaderboard;
leaderboard.SetID(1U);
leaderboard.SetName(L"Title");
leaderboard.SetDescription(L"Desc");
leaderboard.SetDefinition("STA:0xH1234=1::SUB:0xH1234=2::CAN:0xH1234=3::VAL:0xH1234");
leaderboard.SetValueFormat(ValueFormat::Value);
leaderboard.SetLowerIsBetter(true);
leaderboard.CreateServerCheckpoint();
leaderboard.CreateLocalCheckpoint();
leaderboard.Activate();
Assert::AreEqual(AssetChanges::None, leaderboard.GetChanges());
// modify
leaderboard.SetSubmitTrigger("0xH5555=55");
Assert::AreEqual(AssetChanges::Modified, leaderboard.GetChanges());
// ensure runtime is updated
const auto* pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Assert::IsNotNull(pLeaderboard);
Ensures(pLeaderboard != nullptr);
Assert::AreEqual(0x5555U, pLeaderboard->submit.requirement->conditions->operand1.value.memref->address);
}
TEST_METHOD(TestSetCancelTrigger)
{
LeaderboardModelHarness leaderboard;
leaderboard.SetID(1U);
leaderboard.SetName(L"Title");
leaderboard.SetDescription(L"Desc");
leaderboard.SetDefinition("STA:0xH1234=1::SUB:0xH1234=2::CAN:0xH1234=3::VAL:0xH1234");
leaderboard.SetValueFormat(ValueFormat::Value);
leaderboard.SetLowerIsBetter(true);
leaderboard.CreateServerCheckpoint();
leaderboard.CreateLocalCheckpoint();
leaderboard.Activate();
Assert::AreEqual(AssetChanges::None, leaderboard.GetChanges());
// modify
leaderboard.SetCancelTrigger("0xH5555=55");
Assert::AreEqual(AssetChanges::Modified, leaderboard.GetChanges());
// ensure runtime is updated
const auto* pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Assert::IsNotNull(pLeaderboard);
Ensures(pLeaderboard != nullptr);
Assert::AreEqual(0x5555U, pLeaderboard->cancel.requirement->conditions->operand1.value.memref->address);
}
TEST_METHOD(TestSetValueDefinition)
{
LeaderboardModelHarness leaderboard;
leaderboard.SetID(1U);
leaderboard.SetName(L"Title");
leaderboard.SetDescription(L"Desc");
leaderboard.SetDefinition("STA:0xH1234=1::SUB:0xH1234=2::CAN:0xH1234=3::VAL:0xH1234");
leaderboard.SetValueFormat(ValueFormat::Value);
leaderboard.SetLowerIsBetter(true);
leaderboard.CreateServerCheckpoint();
leaderboard.CreateLocalCheckpoint();
leaderboard.Activate();
Assert::AreEqual(AssetChanges::None, leaderboard.GetChanges());
// modify
leaderboard.SetValueDefinition("M:0xH5555");
Assert::AreEqual(AssetChanges::Modified, leaderboard.GetChanges());
// ensure runtime is updated
const auto* pLeaderboard = leaderboard.mockRuntime.GetLeaderboardDefinition(1U);
Assert::IsNotNull(pLeaderboard);
Ensures(pLeaderboard != nullptr);
Assert::AreEqual(0x5555U, pLeaderboard->value.conditions->conditions->operand1.value.memref->address);
}
TEST_METHOD(TestValidateMissingField)
{
LeaderboardModelHarness leaderboard;
+5 -63
View File
@@ -24,6 +24,11 @@ private:
class RichPresenceModelHarness : public RichPresenceModel
{
public:
RichPresenceModelHarness()
{
mockRuntime.MockGame();
}
ra::data::context::mocks::MockGameContext mockGameContext;
ra::services::mocks::MockLocalStorage mockLocalStorage;
ra::services::impl::StringTextWriter textWriter;
@@ -45,69 +50,6 @@ public:
Assert::AreEqual(std::string(""), richPresence.GetScript());
}
TEST_METHOD(TestActivateDeactivate)
{
RichPresenceModelHarness richPresence;
richPresence.SetScript("Display:\nTest\n");
richPresence.CreateServerCheckpoint();
richPresence.CreateLocalCheckpoint();
Assert::AreEqual(std::string("Display:\nTest\n"), richPresence.GetScript());
Assert::AreEqual(AssetState::Inactive, richPresence.GetState());
Assert::AreEqual(std::wstring(L"No Rich Presence defined."), richPresence.mockRuntime.GetRichPresenceDisplayString());
richPresence.Activate();
Assert::AreEqual(AssetState::Active, richPresence.GetState());
Assert::AreEqual(std::wstring(L"Test"), richPresence.mockRuntime.GetRichPresenceDisplayString());
richPresence.Deactivate();
Assert::AreEqual(AssetState::Inactive, richPresence.GetState());
Assert::AreEqual(std::wstring(L"No Rich Presence defined."), richPresence.mockRuntime.GetRichPresenceDisplayString());
}
TEST_METHOD(TestChangeWhileActive)
{
RichPresenceModelHarness richPresence;
richPresence.SetScript("Display:\nTest\n");
richPresence.CreateServerCheckpoint();
richPresence.CreateLocalCheckpoint();
richPresence.Activate();
Assert::AreEqual(AssetState::Active, richPresence.GetState());
Assert::AreEqual(std::wstring(L"Test"), richPresence.mockRuntime.GetRichPresenceDisplayString());
richPresence.SetScript("Display:\nNew test\n");
Assert::AreEqual(AssetState::Active, richPresence.GetState());
Assert::AreEqual(std::wstring(L"New test"), richPresence.mockRuntime.GetRichPresenceDisplayString());
}
TEST_METHOD(TestActivateInvalid)
{
RichPresenceModelHarness richPresence;
richPresence.SetScript("Test\n");
richPresence.CreateServerCheckpoint();
richPresence.CreateLocalCheckpoint();
Assert::AreEqual(std::string("Test\n"), richPresence.GetScript());
Assert::AreEqual(AssetState::Inactive, richPresence.GetState());
Assert::AreEqual(std::wstring(L"No Rich Presence defined."), richPresence.mockRuntime.GetRichPresenceDisplayString());
richPresence.Activate();
Assert::AreEqual(AssetState::Disabled, richPresence.GetState());
Assert::AreEqual(std::wstring(L"Parse error -18 (line 1): Missing display string"), richPresence.mockRuntime.GetRichPresenceDisplayString());
richPresence.SetScript("Display:\nTest\n");
Assert::AreEqual(AssetState::Active, richPresence.GetState());
Assert::AreEqual(std::wstring(L"Test"), richPresence.mockRuntime.GetRichPresenceDisplayString());
}
TEST_METHOD(TestNormalizeScript)
{
RichPresenceModelHarness richPresence;
@@ -1,4 +1,7 @@
#include "MockRcheevosClient.hh"
#include "MockAchievementRuntime.hh"
#include "MockEmulatorContext.hh"
#include "MockGameContext.hh"
#include "services\ServiceLocator.hh"
#include "CppUnitTest.h"
@@ -13,7 +16,7 @@ namespace ra {
namespace services {
namespace mocks {
void MockRcheevosClient::OnBeforeResponse(const std::string& sRequestParams, std::function<void()>&& fHandler)
void MockAchievementRuntime::OnBeforeResponse(const std::string& sRequestParams, std::function<void()>&& fHandler)
{
for (auto& pResponse : m_vResponses)
{
@@ -28,7 +31,7 @@ void MockRcheevosClient::OnBeforeResponse(const std::string& sRequestParams, std
ra::StringPrintf(L"No response registered for: %s", sRequestParams).c_str());
}
void MockRcheevosClient::MockUser(const std::string& sUsername, const std::string& sApiToken)
void MockAchievementRuntime::MockUser(const std::string& sUsername, const std::string& sApiToken)
{
m_sUsername = sUsername;
m_sApiToken = sApiToken;
@@ -38,7 +41,7 @@ void MockRcheevosClient::MockUser(const std::string& sUsername, const std::strin
GetClient()->state.user = RC_CLIENT_USER_STATE_LOGGED_IN;
}
void MockRcheevosClient::MockGame()
void MockAchievementRuntime::MockGame()
{
rc_client_game_info_t* game = (rc_client_game_info_t*)calloc(1, sizeof(rc_client_game_info_t));
Expects(game != nullptr);
@@ -126,28 +129,72 @@ static rc_client_achievement_info_t* AddAchievement(rc_client_game_info_t* game,
achievement->public_.category = RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE;
achievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
achievement->public_.points = 5;
achievement->author = "Author";
return achievement;
}
rc_client_achievement_info_t* MockRcheevosClient::MockAchievement(uint32_t nId, const char* sTitle)
rc_client_achievement_info_t* MockAchievementRuntime::MockAchievement(uint32_t nId, const char* sTitle)
{
rc_client_game_info_t* game = GetClient()->game;
return AddAchievement(game, GetCoreSubset(game), nId, sTitle);
}
rc_client_achievement_info_t* MockRcheevosClient::MockAchievementWithTrigger(uint32_t nId, const char* sTitle)
rc_client_achievement_info_t* MockAchievementRuntime::MockAchievementWithTrigger(uint32_t nId, const char* sTitle)
{
rc_client_game_info_t* game = GetClient()->game;
rc_client_achievement_info_t* achievement = AddAchievement(game, GetCoreSubset(game), nId, sTitle);
achievement->trigger = (rc_trigger_t*)rc_buf_alloc(&game->buffer, sizeof(rc_trigger_t));
memset(achievement->trigger, 0, sizeof(*achievement->trigger));
achievement->trigger->state = RC_TRIGGER_STATE_ACTIVE;
return achievement;
}
rc_client_achievement_info_t* MockRcheevosClient::MockUnofficialAchievement(uint32_t nId, const char* sTitle)
rc_client_achievement_info_t* MockAchievementRuntime::ActivateAchievement(uint32_t nId, const std::string& sTrigger)
{
rc_client_game_info_t* game = GetClient()->game;
rc_client_achievement_info_t* achievement = AddAchievement(game, GetCoreSubset(game), nId, "Achievement");
m_mAchievementDefinitions[nId] = sTrigger;
auto nSize = rc_trigger_size(sTrigger.c_str());
void* trigger_buffer = rc_buf_alloc(&game->buffer, nSize);
achievement->trigger = rc_parse_trigger(trigger_buffer, sTrigger.c_str(), nullptr, 0);
achievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
return achievement;
}
void MockAchievementRuntime::UnlockAchievement(rc_client_achievement_info_t* pAchievement, int nMode)
{
pAchievement->public_.unlocked |= nMode;
if (pAchievement->unlock_time_softcore == 0)
pAchievement->unlock_time_softcore = time(nullptr);
if (nMode == RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH)
{
if (pAchievement->unlock_time_hardcore == 0)
pAchievement->unlock_time_hardcore = time(nullptr);
pAchievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
if (pAchievement->trigger)
pAchievement->trigger->state = RC_TRIGGER_STATE_TRIGGERED;
}
else if (nMode == RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE &&
!rc_client_get_hardcore_enabled(GetClient()))
{
pAchievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
if (pAchievement->trigger)
pAchievement->trigger->state = RC_TRIGGER_STATE_TRIGGERED;
}
}
rc_client_achievement_info_t* MockAchievementRuntime::MockUnofficialAchievement(uint32_t nId, const char* sTitle)
{
rc_client_game_info_t* game = GetClient()->game;
rc_client_achievement_info_t* achievement = AddAchievement(game, GetCoreSubset(game), nId, sTitle);
@@ -155,14 +202,14 @@ rc_client_achievement_info_t* MockRcheevosClient::MockUnofficialAchievement(uint
return achievement;
}
rc_client_achievement_info_t* MockRcheevosClient::MockLocalAchievement(uint32_t nId, const char* sTitle)
rc_client_achievement_info_t* MockAchievementRuntime::MockLocalAchievement(uint32_t nId, const char* sTitle)
{
rc_client_game_info_t* game = GetClient()->game;
return AddAchievement(game, GetLocalSubset(game), nId, sTitle);
}
static rc_client_leaderboard_info_t* AddLeaderboard(rc_client_game_info_t* game, rc_client_subset_info_t* subset,
uint32_t nId, const char* sTitle)
static rc_client_leaderboard_info_t* AddLeaderboard(rc_client_t* client, rc_client_game_info_t* game,
rc_client_subset_info_t* subset, uint32_t nId, const char* sTitle)
{
if (subset->public_.num_leaderboards % 8 == 0)
{
@@ -196,24 +243,53 @@ static rc_client_leaderboard_info_t* AddLeaderboard(rc_client_game_info_t* game,
const std::string sGeneratedDescripton = ra::StringPrintf("Description %u", nId);
leaderboard->public_.description = rc_buf_strcpy(&game->buffer, sGeneratedDescripton.c_str());
leaderboard->public_.state = static_cast<uint8_t>(rc_client_get_hardcore_enabled(client) ?
RC_CLIENT_LEADERBOARD_STATE_ACTIVE : RC_CLIENT_LEADERBOARD_STATE_INACTIVE);
return leaderboard;
}
rc_client_leaderboard_info_t* MockAchievementRuntime::MockLeaderboard(uint32_t nId, const char* sTitle)
{
rc_client_game_info_t* game = GetClient()->game;
return AddLeaderboard(GetClient(), game, GetCoreSubset(game), nId, sTitle);
}
rc_client_leaderboard_info_t* MockAchievementRuntime::MockLeaderboardWithLboard(uint32_t nId, const char* sTitle)
{
rc_client_game_info_t* game = GetClient()->game;
rc_client_leaderboard_info_t* leaderboard = AddLeaderboard(GetClient(), game, GetCoreSubset(game), nId, sTitle);
leaderboard->lboard = (rc_lboard_t*)rc_buf_alloc(&game->buffer, sizeof(rc_lboard_t));
memset(leaderboard->lboard, 0, sizeof(*leaderboard->lboard));
leaderboard->lboard->state = static_cast<uint8_t>(
rc_client_get_hardcore_enabled(GetClient()) ? RC_LBOARD_STATE_ACTIVE : RC_LBOARD_STATE_INACTIVE);
return leaderboard;
}
rc_client_leaderboard_info_t* MockAchievementRuntime::MockLocalLeaderboard(uint32_t nId, const char* sTitle)
{
rc_client_game_info_t* game = GetClient()->game;
return AddLeaderboard(GetClient(), game, GetLocalSubset(game), nId, sTitle);
}
rc_client_leaderboard_info_t* MockAchievementRuntime::ActivateLeaderboard(uint32_t nId, const std::string& sDefinition)
{
rc_client_game_info_t* game = GetClient()->game;
rc_client_leaderboard_info_t* leaderboard = AddLeaderboard(GetClient(), game, GetCoreSubset(game), nId, "Leaderboard");
m_mLeaderboardDefinitions[nId] = sDefinition;
auto nSize = rc_lboard_size(sDefinition.c_str());
void* trigger_buffer = rc_buf_alloc(&game->buffer, nSize);
leaderboard->lboard = rc_parse_lboard(trigger_buffer, sDefinition.c_str(), nullptr, 0);
leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_ACTIVE;
return leaderboard;
}
rc_client_leaderboard_info_t* MockRcheevosClient::MockLeaderboard(uint32_t nId, const char* sTitle)
{
rc_client_game_info_t* game = GetClient()->game;
return AddLeaderboard(game, GetCoreSubset(game), nId, sTitle);
}
rc_client_leaderboard_info_t* MockRcheevosClient::MockLocalLeaderboard(uint32_t nId, const char* sTitle)
{
rc_client_game_info_t* game = GetClient()->game;
return AddLeaderboard(game, GetLocalSubset(game), nId, sTitle);
}
void MockRcheevosClient::AssertCalled(const std::string& sRequestParams) const
void MockAchievementRuntime::AssertCalled(const std::string& sRequestParams) const
{
for (auto& pResponse : m_vResponses)
{
@@ -228,7 +304,7 @@ void MockRcheevosClient::AssertCalled(const std::string& sRequestParams) const
ra::StringPrintf(L"Could not find mock response for %s", sRequestParams).c_str());
}
void MockRcheevosClient::AssertNoPendingRequests() const
void MockAchievementRuntime::AssertNoPendingRequests() const
{
for (auto& pResponse : m_vResponses)
{
@@ -241,10 +317,10 @@ void MockRcheevosClient::AssertNoPendingRequests() const
}
}
void MockRcheevosClient::MockServerCall(const rc_api_request_t* pRequest,
void MockAchievementRuntime::MockServerCall(const rc_api_request_t* pRequest,
rc_client_server_callback_t fCallback, void* pCallbackData, rc_client_t*)
{
auto* pClient = dynamic_cast<MockRcheevosClient*>(&ra::services::ServiceLocator::GetMutable<ra::services::RcheevosClient>());
auto* pClient = dynamic_cast<MockAchievementRuntime*>(&ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>());
std::string sRequestParams = pRequest->post_data;
for (auto& pResponse : pClient->m_vResponses)
@@ -275,4 +351,41 @@ void MockRcheevosClient::MockServerCall(const rc_api_request_t* pRequest,
} // namespace mocks
} // namespace services
namespace data {
namespace context {
namespace mocks {
void MockEmulatorContext::SetRuntimeMemorySize(size_t nBytes)
{
if (ra::services::ServiceLocator::Exists<ra::services::AchievementRuntime>())
{
auto* pGame = ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>().GetClient()->game;
if (pGame && pGame->max_valid_address == 0U)
pGame->max_valid_address = gsl::narrow_cast<uint32_t>(nBytes);
}
}
void MockGameContext::InitializeFromAchievementRuntime()
{
auto* pMockRuntime = dynamic_cast<ra::services::mocks::MockAchievementRuntime*>(
&ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>());
if (pMockRuntime != nullptr)
{
GameContext::InitializeFromAchievementRuntime(pMockRuntime->GetAchievementDefinitions(),
pMockRuntime->GetLeaderboardDefinitions());
}
else
{
std::map<uint32_t, std::string> mAchievementDefinitions;
std::map<uint32_t, std::string> mLeaderboardDefinitions;
GameContext::InitializeFromAchievementRuntime(mAchievementDefinitions, mLeaderboardDefinitions);
}
}
} // namespace mocks
} // namespace context
} // namespace data
} // namespace ra
+66 -11
View File
@@ -5,6 +5,8 @@
#include "services\AchievementRuntime.hh"
#include "services\ServiceLocator.hh"
#include <rcheevos\src\rcheevos\rc_client_internal.h>
namespace ra {
namespace services {
namespace mocks {
@@ -14,25 +16,78 @@ class MockAchievementRuntime : public AchievementRuntime
public:
MockAchievementRuntime() noexcept : m_Override(this)
{
/* intercept server requests */
GetClient()->callbacks.server_call = MockServerCall;
/* disable logging */
rc_client_enable_logging(GetClient(), RC_CLIENT_LOG_LEVEL_NONE, nullptr);
}
bool IsAchievementSupported(ra::AchievementID nAchievement) noexcept
{
for (unsigned i = 0; i < m_pRuntime.trigger_count; ++i)
{
if (m_pRuntime.triggers[i].id == nAchievement)
{
const auto* pTrigger = m_pRuntime.triggers[i].trigger;
if (pTrigger)
return pTrigger->state != RC_TRIGGER_STATE_DISABLED && !m_pRuntime.triggers[i].invalid_memref;
}
}
return false;
const auto* pAchievement = GetAchievementTrigger(nAchievement);
return (pAchievement != nullptr && pAchievement->state != RC_TRIGGER_STATE_DISABLED);
}
void MockResponse(const std::string& sRequestParams, const std::string& sResponseBody)
{
auto& pResponse = m_vResponses.emplace_back();
pResponse.sRequestParams = sRequestParams;
pResponse.sResponseBody = sResponseBody;
pResponse.nHttpStatus = 200;
}
void OnBeforeResponse(const std::string& sRequestParams, std::function<void()>&& fHandler);
void MockUser(const std::string& sUsername, const std::string& sApiToken);
void MockGame();
rc_client_achievement_info_t* MockAchievement(uint32_t nId, const char* sTitle = nullptr);
rc_client_achievement_info_t* MockAchievementWithTrigger(uint32_t nId, const char* sTitle = nullptr);
rc_client_achievement_info_t* MockUnofficialAchievement(uint32_t nId, const char* sTitle = nullptr);
rc_client_achievement_info_t* MockLocalAchievement(uint32_t nId, const char* sTitle = nullptr);
rc_client_achievement_info_t* ActivateAchievement(uint32_t nId, const std::string& sTrigger);
void UnlockAchievement(rc_client_achievement_info_t* pAchievement, int nMode);
rc_client_leaderboard_info_t* MockLeaderboard(uint32_t nId, const char* sTitle = nullptr);
rc_client_leaderboard_info_t* MockLeaderboardWithLboard(uint32_t nId, const char* sTitle = nullptr);
rc_client_leaderboard_info_t* MockLocalLeaderboard(uint32_t nId, const char* sTitle = nullptr);
rc_client_leaderboard_info_t* ActivateLeaderboard(uint32_t nId, const std::string& sDefinition);
void AssertCalled(const std::string& sRequestParams) const;
void AssertNoPendingRequests() const;
std::map<uint32_t, std::string>& GetAchievementDefinitions() { return m_mAchievementDefinitions; }
std::map<uint32_t, std::string>& GetLeaderboardDefinitions() { return m_mLeaderboardDefinitions; }
private:
static void MockServerCall(const rc_api_request_t* pRequest, rc_client_server_callback_t fCallback,
void* pCallbackData, rc_client_t*);
ra::services::ServiceLocator::ServiceOverride<ra::services::AchievementRuntime> m_Override;
typedef struct MockApiResponse
{
std::string sRequestParams;
std::string sResponseBody;
int nHttpStatus = 0;
bool bSeen = 0;
rc_client_server_callback_t fAsyncCallback = nullptr;
void* pAsyncCallbackData = nullptr;
std::function<void()> fBeforeResponse = nullptr;
} MockApiResponse;
std::vector<MockApiResponse> m_vResponses;
std::map<uint32_t, std::string> m_mAchievementDefinitions;
std::map<uint32_t, std::string> m_mLeaderboardDefinitions;
std::string m_sUsername;
std::string m_sApiToken;
};
} // namespace mocks
+6
View File
@@ -87,6 +87,8 @@ public:
ClearMemoryBlocks();
AddMemoryBlock(0, s_pMemory.size_bytes(), ReadMemoryHelper, WriteMemoryHelper);
SetRuntimeMemorySize(s_pMemory.size_bytes());
}
void MockMemory(unsigned char pMemory[], size_t nBytes)
@@ -95,6 +97,8 @@ public:
ClearMemoryBlocks();
AddMemoryBlock(0, nBytes, ReadMemoryHelper, WriteMemoryHelper);
SetRuntimeMemorySize(nBytes);
}
void MockMemoryModified(bool bModified) noexcept
@@ -122,6 +126,8 @@ private:
s_pMemory.at(nAddress) = nValue;
}
static void SetRuntimeMemorySize(size_t nBytes);
bool m_bInvalid = false;
std::string m_sWarnDisableHardcoreActivity;
+2
View File
@@ -115,6 +115,8 @@ public:
return true;
}
void InitializeFromAchievementRuntime();
private:
class MockCodeNotesModel : public ra::data::models::CodeNotesModel
{
-78
View File
@@ -1,78 +0,0 @@
#ifndef RA_SERVICES_MOCK_RCHEEVOS_CLIENT_HH
#define RA_SERVICES_MOCK_RCHEEVOS_CLIENT_HH
#pragma once
#include "services\RcheevosClient.hh"
#include "services\ServiceLocator.hh"
#include <rcheevos\src\rcheevos\rc_client_internal.h>
namespace ra {
namespace services {
namespace mocks {
class MockRcheevosClient : public RcheevosClient
{
public:
MockRcheevosClient() noexcept : m_Override(this)
{
/* intercept server requests */
GetClient()->callbacks.server_call = MockRcheevosClient::MockServerCall;
/* disable logging */
rc_client_enable_logging(GetClient(), RC_CLIENT_LOG_LEVEL_NONE, nullptr);
}
void MockResponse(const std::string& sRequestParams, const std::string& sResponseBody)
{
auto& pResponse = m_vResponses.emplace_back();
pResponse.sRequestParams = sRequestParams;
pResponse.sResponseBody = sResponseBody;
pResponse.nHttpStatus = 200;
}
void OnBeforeResponse(const std::string& sRequestParams, std::function<void()>&& fHandler);
void MockUser(const std::string& sUsername, const std::string& sApiToken);
void MockGame();
rc_client_achievement_info_t* MockAchievement(uint32_t nId, const char* sTitle = nullptr);
rc_client_achievement_info_t* MockAchievementWithTrigger(uint32_t nId, const char* sTitle = nullptr);
rc_client_achievement_info_t* MockUnofficialAchievement(uint32_t nId, const char* sTitle = nullptr);
rc_client_achievement_info_t* MockLocalAchievement(uint32_t nId, const char* sTitle = nullptr);
rc_client_leaderboard_info_t* MockLeaderboard(uint32_t nId, const char* sTitle = nullptr);
rc_client_leaderboard_info_t* MockLocalLeaderboard(uint32_t nId, const char* sTitle = nullptr);
void AssertCalled(const std::string& sRequestParams) const;
void AssertNoPendingRequests() const;
private:
static void MockServerCall(const rc_api_request_t* pRequest, rc_client_server_callback_t fCallback,
void* pCallbackData, rc_client_t*);
ra::services::ServiceLocator::ServiceOverride<ra::services::RcheevosClient> m_Override;
typedef struct MockApiResponse
{
std::string sRequestParams;
std::string sResponseBody;
int nHttpStatus = 0;
bool bSeen = 0;
rc_client_server_callback_t fAsyncCallback = nullptr;
void* pAsyncCallbackData = nullptr;
std::function<void()> fBeforeResponse = nullptr;
} MockApiResponse;
std::vector<MockApiResponse> m_vResponses;
std::string m_sUsername;
std::string m_sApiToken;
};
} // namespace mocks
} // namespace services
} // namespace ra
#endif // !RA_SERVICES_MOCK_RCHEEVOS_CLIENT_HH
-15
View File
@@ -63,21 +63,6 @@ public:
return HandleRequest<ra::api::StartSession>(request);
}
Ping::Response Ping(const Ping::Request& request) override
{
return HandleRequest<ra::api::Ping>(request);
}
AwardAchievement::Response AwardAchievement(const AwardAchievement::Request& request) override
{
return HandleRequest<ra::api::AwardAchievement>(request);
}
SubmitLeaderboardEntry::Response SubmitLeaderboardEntry(const SubmitLeaderboardEntry::Request& request) override
{
return HandleRequest<ra::api::SubmitLeaderboardEntry>(request);
}
FetchUserFriends::Response FetchUserFriends(const FetchUserFriends::Request& request) override
{
return HandleRequest<ra::api::FetchUserFriends>(request);
File diff suppressed because it is too large Load Diff
+31
View File
@@ -5,6 +5,7 @@
#include "ui\ImageReference.hh"
#include "ui\WindowViewModelBase.hh"
#include "ui\viewmodels\MessageBoxViewModel.hh"
#include "ui\viewmodels\PopupViewModelBase.hh"
namespace Microsoft {
namespace VisualStudio {
@@ -90,6 +91,36 @@ template<> std::wstring ToString<ra::ui::ImageType>(const ra::ui::ImageType& typ
}
}
template<>
std::wstring ToString<ra::ui::viewmodels::Popup>(const ra::ui::viewmodels::Popup& type)
{
switch (type)
{
case ra::ui::viewmodels::Popup::None:
return L"None";
case ra::ui::viewmodels::Popup::Message:
return L"Message";
case ra::ui::viewmodels::Popup::AchievementTriggered:
return L"AchievementTriggered";
case ra::ui::viewmodels::Popup::Mastery:
return L"Mastery";
case ra::ui::viewmodels::Popup::LeaderboardStarted:
return L"LeaderboardStarted";
case ra::ui::viewmodels::Popup::LeaderboardCanceled:
return L"LeaderboardCanceled";
case ra::ui::viewmodels::Popup::LeaderboardTracker:
return L"LeaderboardTracker";
case ra::ui::viewmodels::Popup::LeaderboardScoreboard:
return L"LeaderboardScoreboard";
case ra::ui::viewmodels::Popup::Challenge:
return L"Challenge";
case ra::ui::viewmodels::Popup::Progress:
return L"Progress";
default:
return std::to_wstring(ra::etoi(type));
}
}
#pragma warning(pop)
} // namespace CppUnitTestFramework
+136 -190
View File
@@ -44,6 +44,8 @@ private:
{
// DoFrame tests expect the dialog to be visible
GSL_SUPPRESS_F6 SetIsVisible(true);
mockRuntime.MockGame();
}
~AssetEditorViewModelHarness()
@@ -853,62 +855,64 @@ public:
TEST_METHOD(TestSyncState)
{
AssetEditorViewModelHarness editor;
AchievementModel achievement;
achievement.SetState(AssetState::Active);
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
editor.mockRuntime.ActivateAchievement(1234U, "0xH1234=1");
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmAch = editor.mockGameContext.Assets().FindAchievement(1234U);
editor.mockRuntime.SyncAssets();
vmAch->SetState(AssetState::Active);
editor.LoadAsset(&achievement);
editor.LoadAsset(vmAch);
Assert::AreEqual(AssetState::Active, editor.GetState());
Assert::AreEqual(AssetState::Active, achievement.GetState());
Assert::AreEqual(AssetState::Active, vmAch->GetState());
Assert::AreEqual(std::wstring(L"Active"), editor.GetWaitingLabel());
editor.SetState(AssetState::Paused);
Assert::AreEqual(AssetState::Paused, editor.GetState());
Assert::AreEqual(AssetState::Paused, achievement.GetState());
Assert::AreEqual(AssetState::Paused, vmAch->GetState());
Assert::AreEqual(std::wstring(L"Active"), editor.GetWaitingLabel());
achievement.SetState(AssetState::Primed);
vmAch->SetState(AssetState::Primed);
Assert::AreEqual(AssetState::Primed, editor.GetState());
Assert::AreEqual(AssetState::Primed, achievement.GetState());
Assert::AreEqual(AssetState::Primed, vmAch->GetState());
Assert::AreEqual(std::wstring(L"Active"), editor.GetWaitingLabel());
editor.SetState(AssetState::Triggered);
Assert::AreEqual(AssetState::Triggered, editor.GetState());
Assert::AreEqual(AssetState::Triggered, achievement.GetState());
Assert::AreEqual(AssetState::Triggered, vmAch->GetState());
Assert::AreEqual(std::wstring(L"Active"), editor.GetWaitingLabel());
achievement.SetState(AssetState::Waiting);
vmAch->SetState(AssetState::Waiting);
Assert::AreEqual(AssetState::Waiting, editor.GetState());
Assert::AreEqual(AssetState::Waiting, achievement.GetState());
Assert::AreEqual(AssetState::Waiting, vmAch->GetState());
Assert::AreEqual(std::wstring(L"Waiting"), editor.GetWaitingLabel());
achievement.SetState(AssetState::Active);
vmAch->SetState(AssetState::Active);
Assert::AreEqual(AssetState::Active, editor.GetState());
Assert::AreEqual(AssetState::Active, achievement.GetState());
Assert::AreEqual(AssetState::Active, vmAch->GetState());
Assert::AreEqual(std::wstring(L"Active"), editor.GetWaitingLabel());
}
TEST_METHOD(TestSyncStateMeasured)
{
AssetEditorViewModelHarness editor;
AchievementModel achievement;
achievement.SetTrigger("M:0xH1234=6.11.");
achievement.SetState(AssetState::Inactive);
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
editor.LoadAsset(&achievement);
editor.mockRuntime.ActivateAchievement(1234U, "M:0xH1234=6.11.");
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmAch = editor.mockGameContext.Assets().FindAchievement(1234U);
editor.mockRuntime.SyncAssets();
vmAch->SetState(AssetState::Inactive);
editor.LoadAsset(vmAch);
Assert::IsTrue(editor.HasMeasured());
Assert::AreEqual(std::wstring(L"[Not Active]"), editor.GetMeasuredValue());
achievement.SetState(AssetState::Waiting);
vmAch->SetState(AssetState::Waiting);
Assert::AreEqual(std::wstring(L"0/11"), editor.GetMeasuredValue());
achievement.SetState(AssetState::Active);
vmAch->SetState(AssetState::Active);
Assert::AreEqual(std::wstring(L"0/11"), editor.GetMeasuredValue());
achievement.SetState(AssetState::Triggered);
vmAch->SetState(AssetState::Triggered);
Assert::AreEqual(std::wstring(L"[Not Active]"), editor.GetMeasuredValue());
}
@@ -1225,14 +1229,12 @@ public:
{
AssetEditorViewModelHarness editor;
editor.mockConfiguration.SetFeatureEnabled(ra::services::Feature::PreferDecimal, true);
AchievementModel achievement;
achievement.SetID(1234U);
achievement.SetTrigger("0xH1234=1");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Activate();
editor.mockRuntime.ActivateAchievement(1234U, "0xH1234=1");
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmAch = editor.mockGameContext.Assets().FindAchievement(1234U);
editor.mockRuntime.SyncAssets();
editor.LoadAsset(&achievement);
editor.LoadAsset(vmAch);
// make sure the record got loaded into the runtime
const auto* pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
@@ -1253,7 +1255,7 @@ public:
pCondition->SetTargetValue(2U);
// make sure the trigger definition got updated
Assert::AreEqual(std::string("0xH1234=2"), achievement.GetTrigger());
Assert::AreEqual(std::string("0xH1234=2"), vmAch->GetTrigger());
// make sure the runtime record got updated
pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
@@ -1277,18 +1279,13 @@ public:
{
AssetEditorViewModelHarness editor;
editor.mockConfiguration.SetFeatureEnabled(ra::services::Feature::PreferDecimal, true);
AchievementModel achievement;
achievement.SetID(1234U);
achievement.SetTrigger("0xH1234=1");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Deactivate();
editor.mockRuntime.ActivateAchievement(1234U, "0xH1234=1");
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmAch = editor.mockGameContext.Assets().FindAchievement(1234U);
editor.mockRuntime.SyncAssets();
vmAch->Deactivate();
editor.LoadAsset(&achievement);
// make sure the record did not get loaded into the runtime
const auto* pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
Assert::IsNull(pTrigger);
editor.LoadAsset(vmAch);
Assert::AreEqual({ 1U }, editor.Trigger().Conditions().Count());
auto* pCondition = editor.Trigger().Conditions().GetItemAt(0);
@@ -1304,11 +1301,7 @@ public:
pCondition->SetTargetValue(2U);
// make sure the trigger definition got updated
Assert::AreEqual(std::string("0xH1234=2"), achievement.GetTrigger());
// make sure the record did not get loaded into the runtime
pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
Assert::IsNull(pTrigger);
Assert::AreEqual(std::string("0xH1234=2"), vmAch->GetTrigger());
// make sure the UI kept the updated value
Assert::AreEqual({ 1U }, editor.Trigger().Conditions().Count());
@@ -1327,14 +1320,12 @@ public:
{
AssetEditorViewModelHarness editor;
editor.mockConfiguration.SetFeatureEnabled(ra::services::Feature::PreferDecimal, true);
AchievementModel achievement;
achievement.SetID(1234U);
achievement.SetTrigger("0xH1234=1");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Activate();
editor.mockRuntime.ActivateAchievement(1234U, "0xH1234=1");
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmAch = editor.mockGameContext.Assets().FindAchievement(1234U);
editor.mockRuntime.SyncAssets();
editor.LoadAsset(&achievement);
editor.LoadAsset(vmAch);
// make sure the record got loaded into the runtime
const auto* pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
@@ -1352,10 +1343,10 @@ public:
Assert::AreEqual((int)TriggerOperandType::Value, (int)pCondition->GetTargetType());
Assert::AreEqual(std::wstring(L"1"), pCondition->GetTargetValue());
achievement.SetTrigger("0xH1234=2");
vmAch->SetTrigger("0xH1234=2");
// make sure the trigger definition got updated
Assert::AreEqual(std::string("0xH1234=2"), achievement.GetTrigger());
Assert::AreEqual(std::string("0xH1234=2"), vmAch->GetTrigger());
// make sure the runtime record got updated
pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
@@ -1379,14 +1370,12 @@ public:
{
AssetEditorViewModelHarness editor;
editor.mockConfiguration.SetFeatureEnabled(ra::services::Feature::PreferDecimal, true);
AchievementModel achievement;
achievement.SetID(1234U);
achievement.SetTrigger("M:0xH1234=1.1._0xH2345=1.2.");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Activate();
editor.mockRuntime.ActivateAchievement(1234U, "M:0xH1234=1.1._0xH2345=1.2.");
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmAch = editor.mockGameContext.Assets().FindAchievement(1234U);
editor.mockRuntime.SyncAssets();
editor.LoadAsset(&achievement);
editor.LoadAsset(vmAch);
// make sure the record got loaded into the runtime
const auto* pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
@@ -1396,16 +1385,14 @@ public:
editor.Trigger().Conditions().GetItemAt(1)->SetType(ra::ui::viewmodels::TriggerConditionType::Measured);
// make sure the trigger definition got updated
Assert::AreEqual(std::string("M:0xH1234=1.1._M:0xH2345=1.2."), achievement.GetTrigger());
Assert::AreEqual(std::string("M:0xH1234=1.1._M:0xH2345=1.2."), vmAch->GetTrigger());
// make sure the error got set
Assert::AreEqual(std::wstring(L"Multiple measured targets"), editor.GetAssetValidationError());
Assert::IsTrue(editor.HasAssetValidationError());
// make sure the achievement was disabled
Assert::AreEqual(AssetState::Inactive, achievement.GetState());
pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
Assert::IsNull(pTrigger);
Assert::AreEqual(AssetState::Inactive, vmAch->GetState());
// make sure the UI kept the change
Assert::AreEqual({ 2U }, editor.Trigger().Conditions().Count());
@@ -1417,16 +1404,14 @@ public:
editor.Trigger().Conditions().GetItemAt(0)->SetType(ra::ui::viewmodels::TriggerConditionType::Standard);
// make sure the trigger definition got updated
Assert::AreEqual(std::string("0xH1234=1.1._M:0xH2345=1.2."), achievement.GetTrigger());
Assert::AreEqual(std::string("0xH1234=1.1._M:0xH2345=1.2."), vmAch->GetTrigger());
// make sure the error got cleared
Assert::AreEqual(std::wstring(L""), editor.GetAssetValidationError());
Assert::IsFalse(editor.HasAssetValidationError());
// the achievement should have remained disabled
Assert::AreEqual(AssetState::Inactive, achievement.GetState());
pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
Assert::IsNull(pTrigger);
Assert::AreEqual(AssetState::Inactive, vmAch->GetState());
// make sure the UI kept the change
Assert::AreEqual({ 2U }, editor.Trigger().Conditions().Count());
@@ -1441,14 +1426,12 @@ public:
TEST_METHOD(TestTriggerUpdatedInvalidAlts)
{
AssetEditorViewModelHarness editor;
AchievementModel achievement;
achievement.SetID(1234U);
achievement.SetTrigger("0=0SM:0xH1234=1.1.S0xH2345=1.2.");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Activate();
editor.mockRuntime.ActivateAchievement(1234U, "0=0SM:0xH1234=1.1.S0xH2345=1.2.");
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmAch = editor.mockGameContext.Assets().FindAchievement(1234U);
editor.mockRuntime.SyncAssets();
editor.LoadAsset(&achievement);
editor.LoadAsset(vmAch);
// make sure the record got loaded into the runtime
const auto* pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
@@ -1459,16 +1442,14 @@ public:
editor.Trigger().Conditions().GetItemAt(0)->SetType(ra::ui::viewmodels::TriggerConditionType::Measured);
// make sure the trigger definition got updated
Assert::AreEqual(std::string("0=0SM:0xH1234=1.1.SM:0xH2345=1.2."), achievement.GetTrigger());
Assert::AreEqual(std::string("0=0SM:0xH1234=1.1.SM:0xH2345=1.2."), vmAch->GetTrigger());
// make sure the error got set
Assert::AreEqual(std::wstring(L"Multiple measured targets"), editor.GetAssetValidationError());
Assert::IsTrue(editor.HasAssetValidationError());
// make sure the achievement was disabled
Assert::AreEqual(AssetState::Inactive, achievement.GetState());
pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
Assert::IsNull(pTrigger);
Assert::AreEqual(AssetState::Inactive, vmAch->GetState());
// make sure the UI kept the change
Assert::AreEqual({ 1U }, editor.Trigger().Conditions().Count());
@@ -1504,16 +1485,14 @@ public:
pCondition->SetRequiredHits(2);
// make sure the trigger definition got updated
Assert::AreEqual(std::string("0=0SM:0xH1234=1.2.SM:0xH2345=1.2."), achievement.GetTrigger());
Assert::AreEqual(std::string("0=0SM:0xH1234=1.2.SM:0xH2345=1.2."), vmAch->GetTrigger());
// make sure the error got cleared
Assert::AreEqual(std::wstring(L""), editor.GetAssetValidationError());
Assert::IsFalse(editor.HasAssetValidationError());
// the achievement should have remained disabled
Assert::AreEqual(AssetState::Inactive, achievement.GetState());
pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
Assert::IsNull(pTrigger);
Assert::AreEqual(AssetState::Inactive, vmAch->GetState());
// make sure the UI kept the change
pCondition = editor.Trigger().Conditions().GetItemAt(0);
@@ -1535,14 +1514,12 @@ public:
AssetEditorViewModelHarness editor;
editor.mockConfiguration.SetFeatureEnabled(ra::services::Feature::PreferDecimal, true);
AchievementModel achievement;
achievement.SetID(1234U);
achievement.SetTrigger("0xH1234=1");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Activate();
editor.mockRuntime.ActivateAchievement(1234U, "0xH1234=1");
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmAch = editor.mockGameContext.Assets().FindAchievement(1234U);
editor.mockRuntime.SyncAssets();
editor.LoadAsset(&achievement);
editor.LoadAsset(vmAch);
editor.Trigger().SetSelectedGroupIndex(0);
auto& condition = *editor.Trigger().Conditions().GetItemAt(0);
@@ -1552,13 +1529,13 @@ public:
// change to address switches size to match source
condition.SetTargetType(TriggerOperandType::Delta);
Assert::AreEqual(std::string("0xH1234=d0xH0001"), achievement.GetTrigger());
Assert::AreEqual(std::string("0xH1234=d0xH0001"), vmAch->GetTrigger());
Assert::AreEqual(initialVersion + 1, editor.GetTriggerVersion());
Assert::IsNotNull(group.m_pConditionSet);
// change to value sets size back to 32-bit
condition.SetTargetType(TriggerOperandType::Value);
Assert::AreEqual(std::string("0xH1234=1"), achievement.GetTrigger());
Assert::AreEqual(std::string("0xH1234=1"), vmAch->GetTrigger());
Assert::AreEqual(initialVersion + 2, editor.GetTriggerVersion());
Assert::IsNotNull(group.m_pConditionSet);
}
@@ -1567,14 +1544,12 @@ public:
{
AssetEditorViewModelHarness editor;
editor.mockConfiguration.SetFeatureEnabled(ra::services::Feature::PreferDecimal, true);
AchievementModel achievement;
achievement.SetID(1234U);
achievement.SetTrigger("0xH1234=1_0xH1235=2");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Activate();
editor.mockRuntime.ActivateAchievement(1234U, "0xH1234=1_0xH1235=2");
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmAch = editor.mockGameContext.Assets().FindAchievement(1234U);
editor.mockRuntime.SyncAssets();
editor.LoadAsset(&achievement);
editor.LoadAsset(vmAch);
// make sure the record got loaded into the runtime
const auto* pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
@@ -1585,7 +1560,7 @@ public:
editor.Trigger().MoveSelectedConditionsUp();
// make sure the trigger definition got updated
Assert::AreEqual(std::string("0xH1235=2_0xH1234=1"), achievement.GetTrigger());
Assert::AreEqual(std::string("0xH1235=2_0xH1234=1"), vmAch->GetTrigger());
Assert::IsNotNull(editor.Trigger().Groups().GetItemAt(0)->m_pConditionSet);
// make sure the UI picked up the updated value
@@ -1605,7 +1580,7 @@ public:
editor.Trigger().MoveSelectedConditionsUp();
// make sure the trigger definition did not update
Assert::AreEqual(std::string("0xH1235=2_0xH1234=1"), achievement.GetTrigger());
Assert::AreEqual(std::string("0xH1235=2_0xH1234=1"), vmAch->GetTrigger());
Assert::IsNotNull(editor.Trigger().Groups().GetItemAt(0)->m_pConditionSet);
// make sure the UI did not update
@@ -1623,17 +1598,14 @@ public:
TEST_METHOD(TestValueUpdated)
{
AssetEditorViewModelHarness editor;
LeaderboardModel leaderboard;
leaderboard.SetID(1234U);
leaderboard.SetStartTrigger("0x8000=0");
leaderboard.SetSubmitTrigger("0x8000=1");
leaderboard.SetCancelTrigger("0x8000=2");
leaderboard.SetValueDefinition("M:0xH1234");
leaderboard.CreateServerCheckpoint();
leaderboard.CreateLocalCheckpoint();
leaderboard.Activate();
const std::string sDefinition = "STA:0x8000=0::SUB:0x8000=1::CAN:0x8000=2::VAL:M:0xH1234";
editor.mockRuntime.ActivateLeaderboard(1234U, sDefinition);
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmLbd = editor.mockGameContext.Assets().FindLeaderboard(1234U);
vmLbd->SetDefinition(sDefinition);
editor.mockRuntime.SyncAssets();
editor.LoadAsset(&leaderboard);
editor.LoadAsset(vmLbd);
editor.SetSelectedLeaderboardPart(AssetEditorViewModel::LeaderboardPart::Value);
// make sure the record got loaded into the runtime
@@ -1648,10 +1620,10 @@ public:
Assert::AreEqual(std::wstring(L"0x1234"), pCondition->GetSourceValue());
Assert::AreEqual((int)TriggerOperatorType::None, (int)pCondition->GetOperator());
leaderboard.SetValueDefinition("M:0xH2222");
vmLbd->SetValueDefinition("M:0xH2222");
// make sure the value definition got updated
Assert::AreEqual(std::string("M:0xH2222"), leaderboard.GetValueDefinition());
Assert::AreEqual(std::string("M:0xH2222"), vmLbd->GetValueDefinition());
// make sure the runtime record got updated
pLeaderboard = editor.mockRuntime.GetLeaderboardDefinition(1234U);
@@ -1671,7 +1643,7 @@ public:
pCondition->SetSourceValue(0x3333U);
// make sure the value definition got updated
Assert::AreEqual(std::string("M:0xH3333"), leaderboard.GetValueDefinition());
Assert::AreEqual(std::string("M:0xH3333"), vmLbd->GetValueDefinition());
// make sure the runtime record got updated
pLeaderboard = editor.mockRuntime.GetLeaderboardDefinition(1234U);
@@ -1682,14 +1654,12 @@ public:
TEST_METHOD(TestDoFrameUpdatesHitsFromActiveAchievement)
{
AssetEditorViewModelHarness editor;
AchievementModel achievement;
achievement.SetID(1234U);
achievement.SetTrigger("0xH1234=1");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Activate();
editor.mockRuntime.ActivateAchievement(1234U, "0xH1234=1");
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmAch = editor.mockGameContext.Assets().FindAchievement(1234U);
editor.mockRuntime.SyncAssets();
editor.LoadAsset(&achievement);
editor.LoadAsset(vmAch);
Assert::AreEqual({ 1U }, editor.Trigger().Conditions().Count());
auto* pCondition = editor.Trigger().Conditions().GetItemAt(0);
@@ -1708,15 +1678,13 @@ public:
TEST_METHOD(TestDoFrameUpdatesHitsFromActiveAchievementWhenHidden)
{
AssetEditorViewModelHarness editor;
AchievementModel achievement;
achievement.SetID(1234U);
achievement.SetTrigger("0xH1234=1");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Activate();
editor.mockRuntime.ActivateAchievement(1234U, "0xH1234=1");
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmAch = editor.mockGameContext.Assets().FindAchievement(1234U);
editor.mockRuntime.SyncAssets();
editor.SetIsVisible(false);
editor.LoadAsset(&achievement);
editor.LoadAsset(vmAch);
Assert::AreEqual({ 1U }, editor.Trigger().Conditions().Count());
auto* pCondition = editor.Trigger().Conditions().GetItemAt(0);
@@ -1742,14 +1710,12 @@ public:
TEST_METHOD(TestDoFrameUpdatesMeasuredFromActiveAchievement)
{
AssetEditorViewModelHarness editor;
AchievementModel achievement;
achievement.SetID(1234U);
achievement.SetTrigger("M:0xH1234=1.99.");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Activate();
editor.mockRuntime.ActivateAchievement(1234U, "M:0xH1234=1.99.");
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmAch = editor.mockGameContext.Assets().FindAchievement(1234U);
editor.mockRuntime.SyncAssets();
editor.LoadAsset(&achievement);
editor.LoadAsset(vmAch);
auto* pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
Expects(pTrigger != nullptr);
@@ -1763,18 +1729,12 @@ public:
TEST_METHOD(TestDoFrameUpdatesMeasuredFromActiveLeaderboard)
{
AssetEditorViewModelHarness editor;
LeaderboardModel leaderboard;
leaderboard.SetID(1234U);
leaderboard.SetStartTrigger("1=1");
leaderboard.SetSubmitTrigger("1=0");
leaderboard.SetCancelTrigger("0=1");
leaderboard.SetValueDefinition("M:0xH1234");
leaderboard.SetValueFormat(ra::data::ValueFormat::Centiseconds);
leaderboard.CreateServerCheckpoint();
leaderboard.CreateLocalCheckpoint();
leaderboard.Activate();
editor.mockRuntime.ActivateLeaderboard(1234U, "STA:1=1::SUB:1=0::CAN:0=1::VAL:M:0xH1234")->format = RC_FORMAT_CENTISECS;
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmLbd = editor.mockGameContext.Assets().FindLeaderboard(1234U);
editor.mockRuntime.SyncAssets();
editor.LoadAsset(&leaderboard);
editor.LoadAsset(vmLbd);
auto* pDefinition = editor.mockRuntime.GetLeaderboardDefinition(1234U);
Expects(pDefinition != nullptr);
@@ -1831,18 +1791,16 @@ public:
TEST_METHOD(TestDecimalPreferredUpdatesConditions)
{
AssetEditorViewModelHarness editor;
AchievementModel achievement;
achievement.SetID(1234U);
achievement.SetTrigger("0xH1234=1_fF2222=f2.3");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Activate();
editor.mockRuntime.ActivateAchievement(1234U, "0xH1234=1_fF2222=f2.3");
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmAch = editor.mockGameContext.Assets().FindAchievement(1234U);
editor.mockRuntime.SyncAssets();
const auto* pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
Expects(pTrigger != nullptr);
pTrigger->requirement->conditions->current_hits = 6U;
editor.LoadAsset(&achievement);
editor.LoadAsset(vmAch);
const auto* pCondition1 = editor.Trigger().Conditions().GetItemAt(0);
Expects(pCondition1 != nullptr);
const auto* pCondition2 = editor.Trigger().Conditions().GetItemAt(1);
@@ -1872,18 +1830,16 @@ public:
TEST_METHOD(TestCaptureRestoreHits)
{
AssetEditorViewModelHarness editor;
AchievementModel achievement;
achievement.SetID(1234U);
achievement.SetTrigger("0xH1234=1");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Activate();
editor.mockRuntime.ActivateAchievement(1234U, "0xH1234=1");
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmAch = editor.mockGameContext.Assets().FindAchievement(1234U);
editor.mockRuntime.SyncAssets();
const auto* pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
Expects(pTrigger != nullptr);
pTrigger->requirement->conditions->current_hits = 6U;
editor.LoadAsset(&achievement);
editor.LoadAsset(vmAch);
Assert::AreEqual({ 1U }, editor.Trigger().Conditions().Count());
auto* pCondition = editor.Trigger().Conditions().GetItemAt(0);
@@ -1897,7 +1853,7 @@ public:
editor.LoadAsset(nullptr);
Assert::AreEqual({ 0U }, editor.Trigger().Conditions().Count());
editor.LoadAsset(&achievement);
editor.LoadAsset(vmAch);
Assert::AreEqual({ 1U }, editor.Trigger().Conditions().Count());
pCondition = editor.Trigger().Conditions().GetItemAt(0);
Expects(pCondition != nullptr);
@@ -1907,20 +1863,17 @@ public:
TEST_METHOD(TestCaptureRestoreHitsTriggered)
{
AssetEditorViewModelHarness editor;
AchievementModel achievement;
achievement.SetID(1234U);
achievement.SetTrigger("0xH1234=1");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Activate();
Assert::AreEqual(ra::data::models::AssetState::Waiting, achievement.GetState());
editor.mockRuntime.ActivateAchievement(1234U, "0xH1234=1");
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmAch = editor.mockGameContext.Assets().FindAchievement(1234U);
editor.mockRuntime.SyncAssets();
auto* pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
Expects(pTrigger != nullptr);
pTrigger->requirement->conditions->current_hits = 6U;
pTrigger->has_hits = 1;
editor.LoadAsset(&achievement);
editor.LoadAsset(vmAch);
Assert::AreEqual({ 1U }, editor.Trigger().Conditions().Count());
auto* pCondition = editor.Trigger().Conditions().GetItemAt(0);
@@ -1929,17 +1882,15 @@ public:
pTrigger->requirement->conditions->current_hits = 7U;
pTrigger->state = RC_TRIGGER_STATE_TRIGGERED;
achievement.SetState(ra::data::models::AssetState::Triggered);
// triggered achievement is removed from runtime
Assert::IsNull(editor.mockRuntime.GetAchievementTrigger(1234U));
vmAch->SetState(ra::data::models::AssetState::Triggered);
// but the updated state should still be reflected in the UI
// the updated state should still be reflected in the UI
Assert::AreEqual(7U, pCondition->GetCurrentHits());
editor.LoadAsset(nullptr);
Assert::AreEqual({ 0U }, editor.Trigger().Conditions().Count());
editor.LoadAsset(&achievement);
editor.LoadAsset(vmAch);
Assert::AreEqual({ 1U }, editor.Trigger().Conditions().Count());
pCondition = editor.Trigger().Conditions().GetItemAt(0);
Expects(pCondition != nullptr);
@@ -1950,20 +1901,17 @@ public:
TEST_METHOD(TestCaptureRestoreHitsTriggeredChanged)
{
AssetEditorViewModelHarness editor;
AchievementModel achievement;
achievement.SetID(1234U);
achievement.SetTrigger("0xH1234=1");
achievement.CreateServerCheckpoint();
achievement.CreateLocalCheckpoint();
achievement.Activate();
Assert::AreEqual(ra::data::models::AssetState::Waiting, achievement.GetState());
editor.mockRuntime.ActivateAchievement(1234U, "0xH1234=1");
editor.mockGameContext.InitializeFromAchievementRuntime();
auto* vmAch = editor.mockGameContext.Assets().FindAchievement(1234U);
editor.mockRuntime.SyncAssets();
auto* pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
Expects(pTrigger != nullptr);
pTrigger->requirement->conditions->current_hits = 6U;
pTrigger->has_hits = 1;
editor.LoadAsset(&achievement);
editor.LoadAsset(vmAch);
Assert::AreEqual({ 1U }, editor.Trigger().Conditions().Count());
auto* pCondition = editor.Trigger().Conditions().GetItemAt(0);
@@ -1972,9 +1920,7 @@ public:
pTrigger->requirement->conditions->current_hits = 7U;
pTrigger->state = RC_TRIGGER_STATE_TRIGGERED;
achievement.SetState(ra::data::models::AssetState::Triggered);
// triggered achievement is removed from runtime
Assert::IsNull(editor.mockRuntime.GetAchievementTrigger(1234U));
vmAch->SetState(ra::data::models::AssetState::Triggered);
// but the updated state should still be reflected in the UI
Assert::AreEqual(7U, pCondition->GetCurrentHits());
@@ -1982,8 +1928,8 @@ public:
editor.LoadAsset(nullptr);
Assert::AreEqual({ 0U }, editor.Trigger().Conditions().Count());
achievement.SetTrigger("0xH1234=2");
editor.LoadAsset(&achievement);
vmAch->SetTrigger("0xH1234=2");
editor.LoadAsset(vmAch);
Assert::AreEqual({ 1U }, editor.Trigger().Conditions().Count());
pCondition = editor.Trigger().Conditions().GetItemAt(0);
Expects(pCondition != nullptr);
@@ -172,6 +172,8 @@ private:
AssetListViewModelHarness() noexcept
{
mockRuntime.MockGame();
GSL_SUPPRESS_F6 InitializeNotifyTargets();
}
@@ -387,20 +389,21 @@ private:
void AddAchievement(AssetCategory nCategory, unsigned nPoints, const std::wstring& sTitle)
{
auto vmAchievement = std::make_unique<ra::data::models::AchievementModel>();
auto vmAchievement = std::make_unique<MockAchievementModel>();
vmAchievement->SetID(gsl::narrow_cast<unsigned int>(mockGameContext.Assets().Count() + 1));
vmAchievement->SetCategory(nCategory);
vmAchievement->SetPoints(nPoints);
vmAchievement->SetName(sTitle);
vmAchievement->CreateServerCheckpoint();
vmAchievement->CreateLocalCheckpoint();
vmAchievement->AttachAndInitialize(*vmAchievement->m_pInfo);
mockGameContext.Assets().Append(std::move(vmAchievement));
}
void AddAchievement(AssetCategory nCategory, unsigned nPoints, const std::wstring& sTitle,
const std::wstring& sDescription, const std::wstring& sBadge, const std::string& sTrigger)
{
auto vmAchievement = std::make_unique<ra::data::models::AchievementModel>();
auto vmAchievement = std::make_unique<MockAchievementModel>();
vmAchievement->SetID(gsl::narrow_cast<unsigned int>(mockGameContext.Assets().Count() + 1));
vmAchievement->SetCategory(nCategory);
vmAchievement->SetPoints(nPoints);
@@ -410,13 +413,14 @@ private:
vmAchievement->SetTrigger(sTrigger);
vmAchievement->CreateServerCheckpoint();
vmAchievement->CreateLocalCheckpoint();
vmAchievement->AttachAndInitialize(*vmAchievement->m_pInfo);
mockGameContext.Assets().Append(std::move(vmAchievement));
}
void AddNewAchievement(unsigned nPoints, const std::wstring& sTitle,
const std::wstring& sDescription, const std::wstring& sBadge, const std::string& sTrigger)
{
auto vmAchievement = std::make_unique<ra::data::models::AchievementModel>();
auto vmAchievement = std::make_unique<MockAchievementModel>();
vmAchievement->CreateServerCheckpoint();
vmAchievement->SetID(gsl::narrow_cast<unsigned int>(mockGameContext.Assets().Count() + 1));
vmAchievement->SetCategory(AssetCategory::Local);
@@ -427,6 +431,7 @@ private:
vmAchievement->SetTrigger(sTrigger);
vmAchievement->CreateLocalCheckpoint();
vmAchievement->SetNew();
vmAchievement->AttachAndInitialize(*vmAchievement->m_pInfo);
mockGameContext.Assets().Append(std::move(vmAchievement));
}
@@ -561,8 +566,18 @@ private:
return dynamic_cast<ra::data::models::LocalBadgesModel*>(mockGameContext.Assets().FindAsset(ra::data::models::AssetType::LocalBadges, 0));
}
class MockAchievementModel : public ra::data::models::AchievementModel
{
public:
MockAchievementModel()
{
m_pInfo = std::make_unique<rc_client_achievement_info_t>();
memset(m_pInfo.get(), 0, sizeof(rc_client_achievement_info_t));
}
std::unique_ptr<rc_client_achievement_info_t> m_pInfo;
};
private:
std::map<ra::AchievementID, std::wstring> m_mValidationErrors;
std::map<ra::AchievementID, std::string> m_mPublishServerErrors;
};
@@ -1250,6 +1265,7 @@ public:
vmAssetList.SetSpecialFilter(AssetListViewModel::SpecialFilter::Active);
vmAssetList.AddAchievement(AssetCategory::Core, 5, L"Ach1");
Assert::AreEqual({ 1U }, vmAssetList.mockGameContext.Assets().Count());
auto* pAsset = dynamic_cast<ra::data::models::AchievementModel*>(vmAssetList.mockGameContext.Assets().GetItemAt(0));
Expects(pAsset != nullptr);
@@ -3101,12 +3117,13 @@ public:
vmAssetList.AddAchievement(AssetCategory::Core, 5, L"Test1", L"Desc1", L"12345", "0xH1234=1");
vmAssetList.AddAchievement(AssetCategory::Core, 7, L"Test2", L"Desc2", L"11111", "0xH1111=1");
auto* pAchievement = vmAssetList.mockGameContext.Assets().FindAchievement(1U);
auto* pAchievement = dynamic_cast<AssetListViewModelHarness::MockAchievementModel*>(
vmAssetList.mockGameContext.Assets().FindAchievement(1U));
Expects(pAchievement != nullptr);
pAchievement->Activate();
Assert::AreEqual(AssetState::Waiting, pAchievement->GetState());
auto* pTrigger = vmAssetList.mockRuntime.GetAchievementTrigger(1U);
auto* pTrigger = pAchievement->m_pInfo->trigger;
Expects(pTrigger != nullptr);
pTrigger->state = RC_TRIGGER_STATE_ACTIVE;
Assert::AreEqual(AssetState::Waiting, pAchievement->GetState());
@@ -24,7 +24,6 @@
#include "tests\mocks\MockEmulatorContext.hh"
#include "tests\mocks\MockGameContext.hh"
#include "tests\mocks\MockLocalStorage.hh"
#include "tests\mocks\MockRcheevosClient.hh"
#include "tests\mocks\MockServer.hh"
#include "tests\mocks\MockThreadPool.hh"
#include "tests\mocks\MockUserContext.hh"
@@ -51,14 +50,13 @@ private:
ra::services::mocks::MockAchievementRuntime mockAchievementRuntime;
ra::services::mocks::MockConfiguration mockConfiguration;
ra::services::mocks::MockLocalStorage mockLocalStorage;
ra::services::mocks::MockRcheevosClient mockRcheevosClient;
ra::services::mocks::MockThreadPool mockThreadPool;
ra::ui::mocks::MockDesktop mockDesktop;
ra::ui::viewmodels::mocks::MockWindowManager mockWindowManager;
IntegrationMenuViewModelHarness()
{
mockRcheevosClient.MockGame();
mockAchievementRuntime.MockGame();
}
void BuildMenu()
+9 -9
View File
@@ -3,10 +3,10 @@
#include "ui\viewmodels\LoginViewModel.hh"
#include "tests\ui\UIAsserts.hh"
#include "tests\mocks\MockAchievementRuntime.hh"
#include "tests\mocks\MockConfiguration.hh"
#include "tests\mocks\MockDesktop.hh"
#include "tests\mocks\MockEmulatorContext.hh"
#include "tests\mocks\MockRcheevosClient.hh"
#include "tests\mocks\MockServer.hh"
#include "tests\mocks\MockSessionTracker.hh"
#include "tests\mocks\MockUserContext.hh"
@@ -18,7 +18,7 @@ using ra::data::context::mocks::MockEmulatorContext;
using ra::data::context::mocks::MockSessionTracker;
using ra::data::context::mocks::MockUserContext;
using ra::services::mocks::MockConfiguration;
using ra::services::mocks::MockRcheevosClient;
using ra::services::mocks::MockAchievementRuntime;
using ra::ui::mocks::MockDesktop;
@@ -36,7 +36,7 @@ private:
GSL_SUPPRESS_F6 LoginViewModelHarness() : LoginViewModel(L"User") {}
MockConfiguration mockConfiguration;
MockRcheevosClient mockRcheevosClient;
MockAchievementRuntime mockAchievementRuntime;
MockDesktop mockDesktop;
MockServer mockServer;
MockUserContext mockUserContext;
@@ -70,7 +70,7 @@ public:
vmLogin.SetPassword(L"Pa$$w0rd");
Assert::IsFalse(vmLogin.Login());
Assert::IsTrue(vmLogin.mockDesktop.WasDialogShown());
vmLogin.mockRcheevosClient.AssertNoPendingRequests();
vmLogin.mockAchievementRuntime.AssertNoPendingRequests();
}
TEST_METHOD(TestLoginNoPassword)
@@ -87,13 +87,13 @@ public:
vmLogin.SetPassword(L"");
Assert::IsFalse(vmLogin.Login());
Assert::IsTrue(vmLogin.mockDesktop.WasDialogShown());
vmLogin.mockRcheevosClient.AssertNoPendingRequests();
vmLogin.mockAchievementRuntime.AssertNoPendingRequests();
}
TEST_METHOD(TestLoginSuccessful)
{
LoginViewModelHarness vmLogin;
vmLogin.mockRcheevosClient.MockResponse("r=login&u=user&p=Pa%24%24w0rd",
vmLogin.mockAchievementRuntime.MockResponse("r=login&u=user&p=Pa%24%24w0rd",
"{\"Success\":true,\"User\":\"User\",\"DisplayName\":\"UserDisplay\","
"\"Token\":\"ApiToken\",\"Score\":12345,\"SoftcoreScore\":123,"
"\"Messages\":0,\"Permissions\":1,\"AccountType\":\"Registered\"}");
@@ -136,7 +136,7 @@ public:
TEST_METHOD(TestLoginSuccessfulAppTitle)
{
LoginViewModelHarness vmLogin;
vmLogin.mockRcheevosClient.MockResponse("r=login&u=user&p=Pa%24%24w0rd",
vmLogin.mockAchievementRuntime.MockResponse("r=login&u=user&p=Pa%24%24w0rd",
"{\"Success\":true,\"User\":\"User\",\"DisplayName\":\"UserDisplay\","
"\"Token\":\"ApiToken\",\"Score\":12345,\"SoftcoreScore\":123,"
"\"Messages\":0,\"Permissions\":1,\"AccountType\":\"Registered\"}");
@@ -181,7 +181,7 @@ public:
TEST_METHOD(TestLoginInvalidPassword)
{
LoginViewModelHarness vmLogin;
vmLogin.mockRcheevosClient.MockResponse("r=login&u=User&p=Pa%24%24w0rd",
vmLogin.mockAchievementRuntime.MockResponse("r=login&u=User&p=Pa%24%24w0rd",
"{\"Success\":false,\"Error\":\"Invalid User/Password combination. Please try again\"}");
vmLogin.mockDesktop.ExpectWindow<MessageBoxViewModel>([](MessageBoxViewModel& vmMessageBox)
@@ -201,7 +201,7 @@ public:
TEST_METHOD(TestLoginSuccessfulRememberPassword)
{
LoginViewModelHarness vmLogin;
vmLogin.mockRcheevosClient.MockResponse("r=login&u=user&p=Pa%24%24w0rd",
vmLogin.mockAchievementRuntime.MockResponse("r=login&u=user&p=Pa%24%24w0rd",
"{\"Success\":true,\"User\":\"User\",\"DisplayName\":\"UserDisplay\","
"\"Token\":\"ApiToken\",\"Score\":12345,\"SoftcoreScore\":123,"
"\"Messages\":0,\"Permissions\":1,\"AccountType\":\"Registered\"}");
@@ -7,7 +7,6 @@
#include "tests\mocks\MockGameContext.hh"
#include "tests\mocks\MockImageRepository.hh"
#include "tests\mocks\MockOverlayManager.hh"
#include "tests\mocks\MockRcheevosClient.hh"
#include "tests\mocks\MockServer.hh"
#include "tests\mocks\MockSessionTracker.hh"
#include "tests\mocks\MockThreadPool.hh"
@@ -37,7 +36,6 @@ private:
ra::data::context::mocks::MockUserContext mockUserContext;
ra::services::mocks::MockAchievementRuntime mockAchievementRuntime;
ra::services::mocks::MockClock mockClock;
ra::services::mocks::MockRcheevosClient mockRcheevosClient;
ra::services::mocks::MockThreadPool mockThreadPool;
ra::ui::mocks::MockImageRepository mockImageRepository;
ra::ui::viewmodels::mocks::MockOverlayManager mockOverlayManager;
@@ -160,9 +158,9 @@ public:
TEST_METHOD(TestRefreshUnlockedAchievement)
{
OverlayAchievementsPageViewModelHarness achievementsPage;
achievementsPage.mockRcheevosClient.MockGame();
achievementsPage.mockAchievementRuntime.MockGame();
auto* pAch1 = achievementsPage.mockRcheevosClient.MockAchievement(1, "AchievementTitle");
auto* pAch1 = achievementsPage.mockAchievementRuntime.MockAchievement(1, "AchievementTitle");
pAch1->public_.description = "Trigger this";
pAch1->public_.points = 5;
snprintf(pAch1->public_.badge_name, sizeof(pAch1->public_.badge_name), "BADGE");
@@ -183,9 +181,9 @@ public:
TEST_METHOD(TestRefreshActiveAchievement)
{
OverlayAchievementsPageViewModelHarness achievementsPage;
achievementsPage.mockRcheevosClient.MockGame();
achievementsPage.mockAchievementRuntime.MockGame();
auto* pAch1 = achievementsPage.mockRcheevosClient.MockAchievement(1, "AchievementTitle");
auto* pAch1 = achievementsPage.mockAchievementRuntime.MockAchievement(1, "AchievementTitle");
pAch1->public_.description = "Trigger this";
pAch1->public_.points = 5;
snprintf(pAch1->public_.badge_name, sizeof(pAch1->public_.badge_name), "BADGE");
@@ -204,9 +202,9 @@ public:
TEST_METHOD(TestRefreshOnePointAchievement)
{
OverlayAchievementsPageViewModelHarness achievementsPage;
achievementsPage.mockRcheevosClient.MockGame();
achievementsPage.mockAchievementRuntime.MockGame();
auto* pAch1 = achievementsPage.mockRcheevosClient.MockAchievement(1, "AchievementTitle");
auto* pAch1 = achievementsPage.mockAchievementRuntime.MockAchievement(1, "AchievementTitle");
pAch1->public_.description = "Trigger this";
pAch1->public_.points = 1; // should say "1 point" instead of "1 points"
snprintf(pAch1->public_.badge_name, sizeof(pAch1->public_.badge_name), "BADGE");
@@ -231,21 +229,21 @@ public:
TEST_METHOD(TestRefreshActiveAndInactiveAchievements)
{
OverlayAchievementsPageViewModelHarness achievementsPage;
achievementsPage.mockRcheevosClient.MockGame();
achievementsPage.mockAchievementRuntime.MockGame();
auto* pAch1 = achievementsPage.mockRcheevosClient.MockAchievement(1);
auto* pAch1 = achievementsPage.mockAchievementRuntime.MockAchievement(1);
pAch1->public_.points = 1;
pAch1->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE; // Waiting appears as active at this level
auto* pAch2 = achievementsPage.mockRcheevosClient.MockAchievement(2);
auto* pAch2 = achievementsPage.mockAchievementRuntime.MockAchievement(2);
pAch2->public_.points = 2;
pAch2->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE;
auto* pAch3 = achievementsPage.mockRcheevosClient.MockAchievement(3);
auto* pAch3 = achievementsPage.mockAchievementRuntime.MockAchievement(3);
pAch3->public_.points = 3;
pAch3->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
auto* pAch4 = achievementsPage.mockRcheevosClient.MockAchievement(4);
auto* pAch4 = achievementsPage.mockAchievementRuntime.MockAchievement(4);
pAch4->public_.points = 4;
pAch4->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
pAch4->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH;
@@ -268,22 +266,22 @@ public:
TEST_METHOD(TestRefreshActiveAndDisabledAchievements)
{
OverlayAchievementsPageViewModelHarness achievementsPage;
achievementsPage.mockRcheevosClient.MockGame();
achievementsPage.mockAchievementRuntime.MockGame();
auto* pAch1 = achievementsPage.mockRcheevosClient.MockAchievement(1);
auto* pAch1 = achievementsPage.mockAchievementRuntime.MockAchievement(1);
pAch1->public_.points = 1;
pAch1->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE; // Waiting appears as active at this level
auto* pAch2 = achievementsPage.mockRcheevosClient.MockAchievement(2);
auto* pAch2 = achievementsPage.mockAchievementRuntime.MockAchievement(2);
pAch2->public_.points = 2;
pAch2->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_DISABLED;
pAch2->public_.bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED;
auto* pAch3 = achievementsPage.mockRcheevosClient.MockAchievement(3);
auto* pAch3 = achievementsPage.mockAchievementRuntime.MockAchievement(3);
pAch3->public_.points = 3;
pAch3->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
auto* pAch4 = achievementsPage.mockRcheevosClient.MockAchievement(4);
auto* pAch4 = achievementsPage.mockAchievementRuntime.MockAchievement(4);
pAch4->public_.points = 4;
pAch4->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_DISABLED; // will appear as locked, even though it was unlocked
pAch4->public_.bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED;
@@ -339,23 +337,23 @@ public:
TEST_METHOD(TestRefreshInactiveAndDisabledAchievements)
{
OverlayAchievementsPageViewModelHarness achievementsPage;
achievementsPage.mockRcheevosClient.MockGame();
achievementsPage.mockAchievementRuntime.MockGame();
auto* pAch1 = achievementsPage.mockRcheevosClient.MockAchievement(1);
auto* pAch1 = achievementsPage.mockAchievementRuntime.MockAchievement(1);
pAch1->public_.points = 1;
pAch1->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE;
auto* pAch2 = achievementsPage.mockRcheevosClient.MockAchievement(2);
auto* pAch2 = achievementsPage.mockAchievementRuntime.MockAchievement(2);
pAch2->public_.points = 2;
pAch2->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_DISABLED;
pAch2->public_.bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED;
auto* pAch3 = achievementsPage.mockRcheevosClient.MockAchievement(3);
auto* pAch3 = achievementsPage.mockAchievementRuntime.MockAchievement(3);
pAch3->public_.points = 3;
pAch3->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
pAch3->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH;
auto* pAch4 = achievementsPage.mockRcheevosClient.MockAchievement(4);
auto* pAch4 = achievementsPage.mockAchievementRuntime.MockAchievement(4);
pAch4->public_.points = 4;
pAch4->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_DISABLED; // will appear as locked, even though it was unlocked
pAch4->public_.bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED;
@@ -380,23 +378,23 @@ public:
TEST_METHOD(TestRefreshInactiveAndDisabledAchievementsInitialCollapseState)
{
OverlayAchievementsPageViewModelHarness achievementsPage;
achievementsPage.mockRcheevosClient.MockGame();
achievementsPage.mockAchievementRuntime.MockGame();
auto* pAch1 = achievementsPage.mockRcheevosClient.MockAchievement(1);
auto* pAch1 = achievementsPage.mockAchievementRuntime.MockAchievement(1);
pAch1->public_.points = 1;
pAch1->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE;
auto* pAch2 = achievementsPage.mockRcheevosClient.MockAchievement(2);
auto* pAch2 = achievementsPage.mockAchievementRuntime.MockAchievement(2);
pAch2->public_.points = 2;
pAch2->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_DISABLED;
pAch2->public_.bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED;
auto* pAch3 = achievementsPage.mockRcheevosClient.MockAchievement(3);
auto* pAch3 = achievementsPage.mockAchievementRuntime.MockAchievement(3);
pAch3->public_.points = 3;
pAch3->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
pAch3->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH;
auto* pAch4 = achievementsPage.mockRcheevosClient.MockAchievement(4);
auto* pAch4 = achievementsPage.mockAchievementRuntime.MockAchievement(4);
pAch4->public_.points = 4;
pAch4->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_DISABLED;
pAch4->public_.bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED;
@@ -420,18 +418,18 @@ public:
TEST_METHOD(TestRefreshActiveAndPrimedAchievements)
{
OverlayAchievementsPageViewModelHarness achievementsPage;
achievementsPage.mockRcheevosClient.MockGame();
achievementsPage.mockAchievementRuntime.MockGame();
auto* pAch1 = achievementsPage.mockRcheevosClient.MockAchievement(1);
auto* pAch1 = achievementsPage.mockAchievementRuntime.MockAchievement(1);
pAch1->public_.points = 1;
pAch1->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
auto* pAch2 = achievementsPage.mockRcheevosClient.MockAchievementWithTrigger(2);
auto* pAch2 = achievementsPage.mockAchievementRuntime.MockAchievementWithTrigger(2);
pAch2->public_.points = 2;
pAch2->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
pAch2->trigger->state = RC_TRIGGER_STATE_PRIMED;
auto* pAch3 = achievementsPage.mockRcheevosClient.MockAchievement(3);
auto* pAch3 = achievementsPage.mockAchievementRuntime.MockAchievement(3);
pAch3->public_.points = 3;
pAch3->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
@@ -452,21 +450,21 @@ public:
TEST_METHOD(TestRefreshCategoriesCoreOnly)
{
OverlayAchievementsPageViewModelHarness achievementsPage;
achievementsPage.mockRcheevosClient.MockGame();
achievementsPage.mockAchievementRuntime.MockGame();
auto* pAch1 = achievementsPage.mockRcheevosClient.MockAchievement(1);
auto* pAch1 = achievementsPage.mockAchievementRuntime.MockAchievement(1);
pAch1->public_.points = 1;
pAch1->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
auto* pAch2 = achievementsPage.mockRcheevosClient.MockUnofficialAchievement(2);
auto* pAch2 = achievementsPage.mockAchievementRuntime.MockUnofficialAchievement(2);
pAch2->public_.points = 2;
pAch2->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
auto* pAch3 = achievementsPage.mockRcheevosClient.MockLocalAchievement(3);
auto* pAch3 = achievementsPage.mockAchievementRuntime.MockLocalAchievement(3);
pAch3->public_.points = 3;
pAch3->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
auto* pAch4 = achievementsPage.mockRcheevosClient.MockAchievement(4);
auto* pAch4 = achievementsPage.mockAchievementRuntime.MockAchievement(4);
pAch4->public_.points = 4;
pAch4->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
pAch4->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH;
@@ -490,17 +488,17 @@ public:
TEST_METHOD(TestRefreshCategoriesWithLocal)
{
OverlayAchievementsPageViewModelHarness achievementsPage;
achievementsPage.mockRcheevosClient.MockGame();
achievementsPage.mockAchievementRuntime.MockGame();
auto* pAch1 = achievementsPage.mockRcheevosClient.MockAchievement(1);
auto* pAch1 = achievementsPage.mockAchievementRuntime.MockAchievement(1);
pAch1->public_.points = 1;
pAch1->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
auto* pAch2 = achievementsPage.mockRcheevosClient.MockUnofficialAchievement(2);
auto* pAch2 = achievementsPage.mockAchievementRuntime.MockUnofficialAchievement(2);
pAch2->public_.points = 2;
pAch2->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
auto* pAch3 = achievementsPage.mockRcheevosClient.MockLocalAchievement(3);
auto* pAch3 = achievementsPage.mockAchievementRuntime.MockLocalAchievement(3);
pAch3->public_.points = 3;
snprintf(pAch3->public_.badge_name, sizeof(pAch3->public_.badge_name), "L000003");
pAch3->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
@@ -509,7 +507,7 @@ public:
vmAch3.SetID(3);
vmAch3.SetBadge(L"local\\1-abcdef0123456789.png");
auto* pAch4 = achievementsPage.mockRcheevosClient.MockAchievement(4);
auto* pAch4 = achievementsPage.mockAchievementRuntime.MockAchievement(4);
pAch4->public_.points = 4;
pAch4->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
pAch4->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH;
@@ -547,23 +545,23 @@ public:
TEST_METHOD(TestRefreshProgressAchievements)
{
OverlayAchievementsPageViewModelHarness achievementsPage;
achievementsPage.mockRcheevosClient.MockGame();
achievementsPage.mockAchievementRuntime.MockGame();
auto* pAch1 = achievementsPage.mockRcheevosClient.MockAchievement(1);
auto* pAch1 = achievementsPage.mockAchievementRuntime.MockAchievement(1);
pAch1->public_.points = 1;
pAch1->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
auto* pAch2 = achievementsPage.mockRcheevosClient.MockAchievementWithTrigger(2);
auto* pAch2 = achievementsPage.mockAchievementRuntime.MockAchievementWithTrigger(2);
pAch2->public_.points = 2;
pAch2->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
pAch2->trigger->measured_target = 10;
pAch2->trigger->measured_value = 5;
auto* pAch3 = achievementsPage.mockRcheevosClient.MockAchievement(3);
auto* pAch3 = achievementsPage.mockAchievementRuntime.MockAchievement(3);
pAch3->public_.points = 3;
pAch3->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
auto* pAch4 = achievementsPage.mockRcheevosClient.MockAchievementWithTrigger(4);
auto* pAch4 = achievementsPage.mockAchievementRuntime.MockAchievementWithTrigger(4);
pAch4->public_.points = 4;
pAch4->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
pAch4->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH;
@@ -594,28 +592,28 @@ public:
TEST_METHOD(TestRefreshAlmostThereAchievements)
{
OverlayAchievementsPageViewModelHarness achievementsPage;
achievementsPage.mockRcheevosClient.MockGame();
achievementsPage.mockAchievementRuntime.MockGame();
auto* pAch1 = achievementsPage.mockRcheevosClient.MockAchievementWithTrigger(1);
auto* pAch1 = achievementsPage.mockAchievementRuntime.MockAchievementWithTrigger(1);
pAch1->public_.points = 1;
pAch1->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
pAch1->trigger->measured_target = 10;
pAch1->trigger->measured_value = 1;
auto* pAch2 = achievementsPage.mockRcheevosClient.MockAchievementWithTrigger(2);
auto* pAch2 = achievementsPage.mockAchievementRuntime.MockAchievementWithTrigger(2);
pAch2->public_.points = 2;
pAch2->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
pAch2->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH;
pAch2->trigger->measured_target = 10;
pAch2->trigger->measured_value = 9;
auto* pAch3 = achievementsPage.mockRcheevosClient.MockAchievementWithTrigger(3);
auto* pAch3 = achievementsPage.mockAchievementRuntime.MockAchievementWithTrigger(3);
pAch3->public_.points = 3;
pAch3->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
pAch3->trigger->measured_target = 10;
pAch3->trigger->measured_value = 9;
auto* pAch4 = achievementsPage.mockRcheevosClient.MockAchievement(4);
auto* pAch4 = achievementsPage.mockAchievementRuntime.MockAchievement(4);
pAch4->public_.points = 4;
pAch4->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
@@ -644,27 +642,27 @@ public:
TEST_METHOD(TestRefreshAlmostThereAchievementsSorting)
{
OverlayAchievementsPageViewModelHarness achievementsPage;
achievementsPage.mockRcheevosClient.MockGame();
achievementsPage.mockAchievementRuntime.MockGame();
auto* pAch1 = achievementsPage.mockRcheevosClient.MockAchievementWithTrigger(1);
auto* pAch1 = achievementsPage.mockAchievementRuntime.MockAchievementWithTrigger(1);
pAch1->public_.points = 1;
pAch1->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
pAch1->trigger->measured_target = 100;
pAch1->trigger->measured_value = 90;
auto* pAch2 = achievementsPage.mockRcheevosClient.MockAchievementWithTrigger(2);
auto* pAch2 = achievementsPage.mockAchievementRuntime.MockAchievementWithTrigger(2);
pAch2->public_.points = 2;
pAch2->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
pAch2->trigger->measured_target = 100;
pAch2->trigger->measured_value = 85;
auto* pAch3 = achievementsPage.mockRcheevosClient.MockAchievementWithTrigger(3);
auto* pAch3 = achievementsPage.mockAchievementRuntime.MockAchievementWithTrigger(3);
pAch3->public_.points = 3;
pAch3->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
pAch3->trigger->measured_target = 100;
pAch3->trigger->measured_value = 95;
auto* pAch4 = achievementsPage.mockRcheevosClient.MockAchievementWithTrigger(4);
auto* pAch4 = achievementsPage.mockAchievementRuntime.MockAchievementWithTrigger(4);
pAch4->public_.points = 4;
pAch4->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE;
pAch4->trigger->measured_target = 100;
@@ -688,10 +686,10 @@ public:
{
OverlayAchievementsPageViewModelHarness achievementsPage;
achievementsPage.mockGameContext.SetGameId(1U);
achievementsPage.mockRcheevosClient.MockGame();
achievementsPage.mockAchievementRuntime.MockGame();
achievementsPage.mockSessionTracker.MockSession(1U, 1234567879, std::chrono::minutes(347));
auto* pAch1 = achievementsPage.mockRcheevosClient.MockAchievement(1);
auto* pAch1 = achievementsPage.mockAchievementRuntime.MockAchievement(1);
pAch1->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
pAch1->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH;
@@ -705,10 +703,10 @@ public:
{
OverlayAchievementsPageViewModelHarness achievementsPage;
achievementsPage.mockGameContext.SetGameId(1U);
achievementsPage.mockRcheevosClient.MockGame();
achievementsPage.mockAchievementRuntime.MockGame();
achievementsPage.mockSessionTracker.MockSession(1U, 1234567879, std::chrono::seconds(17 * 60 + 12));
auto* pAch1 = achievementsPage.mockRcheevosClient.MockAchievement(1);
auto* pAch1 = achievementsPage.mockAchievementRuntime.MockAchievement(1);
pAch1->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
pAch1->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH;
@@ -733,8 +731,8 @@ public:
{
OverlayAchievementsPageViewModelHarness achievementsPage;
achievementsPage.mockWindowManager.AssetList.SetFilterCategory(AssetListViewModel::FilterCategory::Local);
achievementsPage.mockRcheevosClient.MockGame();
const auto* pAch1 = achievementsPage.mockRcheevosClient.MockAchievement(ra::data::context::GameAssets::FirstLocalId);
achievementsPage.mockAchievementRuntime.MockGame();
const auto* pAch1 = achievementsPage.mockAchievementRuntime.MockAchievement(ra::data::context::GameAssets::FirstLocalId);
achievementsPage.mockServer.ExpectUncalled<ra::api::FetchAchievementInfo>();
@@ -752,8 +750,8 @@ public:
TEST_METHOD(TestFetchItemDetail)
{
OverlayAchievementsPageViewModelHarness achievementsPage;
achievementsPage.mockRcheevosClient.MockGame();
auto* pAch1 = achievementsPage.mockRcheevosClient.MockAchievement(1234);
achievementsPage.mockAchievementRuntime.MockGame();
auto* pAch1 = achievementsPage.mockAchievementRuntime.MockAchievement(1234);
pAch1->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED;
pAch1->public_.unlocked = RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH;
@@ -2,11 +2,11 @@
#include "ui\viewmodels\OverlayLeaderboardsPageViewModel.hh"
#include "tests\mocks\MockAchievementRuntime.hh"
#include "tests\mocks\MockGameContext.hh"
#include "tests\mocks\MockImageRepository.hh"
#include "tests\mocks\MockOverlayManager.hh"
#include "tests\mocks\MockServer.hh"
#include "tests\mocks\MockRcheevosClient.hh"
#include "tests\mocks\MockThreadPool.hh"
#include "tests\mocks\MockUserContext.hh"
#include "tests\mocks\MockWindowManager.hh"
@@ -28,7 +28,7 @@ private:
ra::api::mocks::MockServer mockServer;
ra::data::context::mocks::MockGameContext mockGameContext;
ra::data::context::mocks::MockUserContext mockUserContext;
ra::services::mocks::MockRcheevosClient mockRcheevosClient;
ra::services::mocks::MockAchievementRuntime mockAchievementRuntime;
ra::services::mocks::MockThreadPool mockThreadPool;
ra::ui::mocks::MockImageRepository mockImageRepository;
ra::ui::viewmodels::mocks::MockOverlayManager mockOverlayManager;
@@ -102,10 +102,10 @@ public:
TEST_METHOD(TestRefreshLeaderboards)
{
OverlayLeaderboardsPageViewModelHarness leaderboardsPage;
leaderboardsPage.mockRcheevosClient.MockGame();
leaderboardsPage.mockAchievementRuntime.MockGame();
auto* pLbd1 = leaderboardsPage.mockRcheevosClient.MockLeaderboard(1);
auto* pLbd2 = leaderboardsPage.mockRcheevosClient.MockLeaderboard(2);
auto* pLbd1 = leaderboardsPage.mockAchievementRuntime.MockLeaderboard(1);
auto* pLbd2 = leaderboardsPage.mockAchievementRuntime.MockLeaderboard(2);
leaderboardsPage.SetCanCollapseHeaders(false);
leaderboardsPage.Refresh();
@@ -121,12 +121,12 @@ public:
TEST_METHOD(TestRefreshCoreOnly)
{
OverlayLeaderboardsPageViewModelHarness leaderboardsPage;
leaderboardsPage.mockRcheevosClient.MockGame();
leaderboardsPage.mockAchievementRuntime.MockGame();
// only 1 and 3 will be visible - 2 is not core, and filtered out by asset list
auto* pLbd1 = leaderboardsPage.mockRcheevosClient.MockLeaderboard(1);
leaderboardsPage.mockRcheevosClient.MockLocalLeaderboard(2);
auto* pLbd3 = leaderboardsPage.mockRcheevosClient.MockLeaderboard(3);
auto* pLbd1 = leaderboardsPage.mockAchievementRuntime.MockLeaderboard(1);
leaderboardsPage.mockAchievementRuntime.MockLocalLeaderboard(2);
auto* pLbd3 = leaderboardsPage.mockAchievementRuntime.MockLeaderboard(3);
leaderboardsPage.SetCanCollapseHeaders(false);
leaderboardsPage.Refresh();
@@ -143,11 +143,11 @@ public:
TEST_METHOD(TestRefreshCoreAndLocal)
{
OverlayLeaderboardsPageViewModelHarness leaderboardsPage;
leaderboardsPage.mockRcheevosClient.MockGame();
leaderboardsPage.mockAchievementRuntime.MockGame();
auto* pLbd1 = leaderboardsPage.mockRcheevosClient.MockLeaderboard(1);
auto* pLbd2 = leaderboardsPage.mockRcheevosClient.MockLocalLeaderboard(2);
auto* pLbd3 = leaderboardsPage.mockRcheevosClient.MockLeaderboard(3);
auto* pLbd1 = leaderboardsPage.mockAchievementRuntime.MockLeaderboard(1);
auto* pLbd2 = leaderboardsPage.mockAchievementRuntime.MockLocalLeaderboard(2);
auto* pLbd3 = leaderboardsPage.mockAchievementRuntime.MockLeaderboard(3);
leaderboardsPage.SetAssetFilter(ra::ui::viewmodels::AssetListViewModel::FilterCategory::Local);
leaderboardsPage.SetCanCollapseHeaders(false);
@@ -167,13 +167,13 @@ public:
TEST_METHOD(TestRefreshWithHidden)
{
OverlayLeaderboardsPageViewModelHarness leaderboardsPage;
leaderboardsPage.mockRcheevosClient.MockGame();
leaderboardsPage.mockAchievementRuntime.MockGame();
// only 1 and 3 will be visible - 2 is hidden
auto* pLbd1 = leaderboardsPage.mockRcheevosClient.MockLeaderboard(1);
auto* pLbd2 = leaderboardsPage.mockRcheevosClient.MockLocalLeaderboard(2);
auto* pLbd1 = leaderboardsPage.mockAchievementRuntime.MockLeaderboard(1);
auto* pLbd2 = leaderboardsPage.mockAchievementRuntime.MockLocalLeaderboard(2);
pLbd2->hidden = 1;
auto* pLbd3 = leaderboardsPage.mockRcheevosClient.MockLeaderboard(3);
auto* pLbd3 = leaderboardsPage.mockAchievementRuntime.MockLeaderboard(3);
leaderboardsPage.SetCanCollapseHeaders(false);
leaderboardsPage.Refresh();
@@ -190,13 +190,13 @@ public:
TEST_METHOD(TestRefreshWithActive)
{
OverlayLeaderboardsPageViewModelHarness leaderboardsPage;
leaderboardsPage.mockRcheevosClient.MockGame();
leaderboardsPage.mockAchievementRuntime.MockGame();
auto* pLbd1 = leaderboardsPage.mockRcheevosClient.MockLeaderboard(1);
auto* pLbd2 = leaderboardsPage.mockRcheevosClient.MockLeaderboard(2);
auto* pLbd1 = leaderboardsPage.mockAchievementRuntime.MockLeaderboard(1);
auto* pLbd2 = leaderboardsPage.mockAchievementRuntime.MockLeaderboard(2);
pLbd2->public_.state = RC_CLIENT_LEADERBOARD_STATE_TRACKING;
pLbd2->public_.tracker_value = "1:23.00";
auto* pLbd3 = leaderboardsPage.mockRcheevosClient.MockLeaderboard(3);
auto* pLbd3 = leaderboardsPage.mockAchievementRuntime.MockLeaderboard(3);
leaderboardsPage.SetCanCollapseHeaders(false);
leaderboardsPage.Refresh();
@@ -215,15 +215,15 @@ public:
TEST_METHOD(TestRefreshWithHiddenActive)
{
OverlayLeaderboardsPageViewModelHarness leaderboardsPage;
leaderboardsPage.mockRcheevosClient.MockGame();
leaderboardsPage.mockAchievementRuntime.MockGame();
// only 1 and 3 will be visible - 2 is hidden (TODO: should it be?)
auto* pLbd1 = leaderboardsPage.mockRcheevosClient.MockLeaderboard(1);
auto* pLbd2 = leaderboardsPage.mockRcheevosClient.MockLeaderboard(2);
auto* pLbd1 = leaderboardsPage.mockAchievementRuntime.MockLeaderboard(1);
auto* pLbd2 = leaderboardsPage.mockAchievementRuntime.MockLeaderboard(2);
pLbd2->hidden = 1;
pLbd2->public_.state = RC_CLIENT_LEADERBOARD_STATE_TRACKING;
pLbd2->public_.tracker_value = "1:23.00";
auto* pLbd3 = leaderboardsPage.mockRcheevosClient.MockLeaderboard(3);
auto* pLbd3 = leaderboardsPage.mockAchievementRuntime.MockLeaderboard(3);
leaderboardsPage.SetCanCollapseHeaders(false);
leaderboardsPage.Refresh();
@@ -240,12 +240,12 @@ public:
TEST_METHOD(TestRefreshWithDisabled)
{
OverlayLeaderboardsPageViewModelHarness leaderboardsPage;
leaderboardsPage.mockRcheevosClient.MockGame();
leaderboardsPage.mockAchievementRuntime.MockGame();
auto* pLbd1 = leaderboardsPage.mockRcheevosClient.MockLeaderboard(1);
auto* pLbd2 = leaderboardsPage.mockRcheevosClient.MockLeaderboard(2);
auto* pLbd1 = leaderboardsPage.mockAchievementRuntime.MockLeaderboard(1);
auto* pLbd2 = leaderboardsPage.mockAchievementRuntime.MockLeaderboard(2);
pLbd2->public_.state = RC_CLIENT_LEADERBOARD_STATE_DISABLED;
auto* pLbd3 = leaderboardsPage.mockRcheevosClient.MockLeaderboard(3);
auto* pLbd3 = leaderboardsPage.mockAchievementRuntime.MockLeaderboard(3);
leaderboardsPage.SetAssetFilter(ra::ui::viewmodels::AssetListViewModel::FilterCategory::Local);
leaderboardsPage.SetCanCollapseHeaders(false);
@@ -265,8 +265,8 @@ public:
TEST_METHOD(TestFetchItemDetail)
{
OverlayLeaderboardsPageViewModelHarness leaderboardsPage;
leaderboardsPage.mockRcheevosClient.MockGame();
leaderboardsPage.mockRcheevosClient.MockLeaderboard(1);
leaderboardsPage.mockAchievementRuntime.MockGame();
leaderboardsPage.mockAchievementRuntime.MockLeaderboard(1);
leaderboardsPage.mockUserContext.Initialize("user2", "User2", "ApiToken");
leaderboardsPage.mockServer.HandleRequest<ra::api::FetchLeaderboardInfo>([](const ra::api::FetchLeaderboardInfo::Request& request, ra::api::FetchLeaderboardInfo::Response& response)
+2 -4
View File
@@ -12,7 +12,6 @@
#include "tests\mocks\MockGameContext.hh"
#include "tests\mocks\MockImageRepository.hh"
#include "tests\mocks\MockOverlayTheme.hh"
#include "tests\mocks\MockRcheevosClient.hh"
#include "tests\mocks\MockSurface.hh"
#include "tests\mocks\MockThreadPool.hh"
#include "tests\mocks\MockUserContext.hh"
@@ -39,7 +38,6 @@ private:
ra::services::mocks::MockAchievementRuntime mockAchievementRuntime;
ra::services::mocks::MockClock mockClock;
ra::services::mocks::MockConfiguration mockConfiguration;
ra::services::mocks::MockRcheevosClient mockRcheevosClient;
ra::services::mocks::MockThreadPool mockThreadPool;
ra::ui::mocks::MockDesktop mockDesktop;
ra::ui::mocks::MockImageRepository mockImageRepository;
@@ -609,7 +607,7 @@ public:
OverlayManagerHarness overlay;
overlay.mockConfiguration.SetPopupLocation(ra::ui::viewmodels::Popup::Progress, ra::ui::viewmodels::PopupLocation::BottomRight);
overlay.UpdateProgressTracker(ra::ui::ImageType::Badge, "12345_lock", 3, 7, false);
overlay.UpdateProgressTracker(ra::ui::ImageType::Badge, "12345_lock", L"3/7");
auto* pTracker = overlay.GetProgressTracker();
Assert::IsNotNull(pTracker);
Ensures(pTracker != nullptr);
@@ -625,7 +623,7 @@ public:
overlay.Render(mockSurface, false);
Assert::AreEqual(10, pTracker->GetVerticalOffset());
overlay.mockClock.AdvanceTime(std::chrono::seconds(3));
overlay.UpdateProgressTracker(ra::ui::ImageType::None, "", L"");
overlay.Render(mockSurface, false);
Assert::IsTrue(overlay.WasRenderRequested()); // destroy pending
Assert::IsTrue(pTracker->IsDestroyPending());
@@ -28,6 +28,8 @@ TEST_CLASS(RichPresenceMonitorViewModel_Tests)
public:
RichPresenceMonitorViewModelHarness() noexcept
{
mockRuntime.MockGame();
GSL_SUPPRESS_F6 InitializeNotifyTargets();
}