mirror of
https://github.com/RetroAchievements/RAIntegration.git
synced 2026-10-12 02:09:25 +00:00
use rc_client for runtime (#1029)
This commit is contained in:
+22
-18
@@ -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
|
||||
|
||||
+1
-1
Submodule rcheevos updated: df90bf678f...6f02e791aa
+17
-304
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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 ===
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
+1702
-503
File diff suppressed because it is too large
Load Diff
+148
-153
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -115,6 +115,8 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
void InitializeFromAchievementRuntime();
|
||||
|
||||
private:
|
||||
class MockCodeNotesModel : public ra::data::models::CodeNotesModel
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user