Repository: RetroAchievements/RAIntegration
Pull request #973 (2023-02-03 03:58:11)

explicitly force first condition added to a Value to be Measured
When a new condition is added, the current value is captured and included as an equality check (i.e. `0xH1234=8`). When this is processed as a value, the "=8" is discarded, and the type is changed to Measured, but the underlying serialized value is still `0xH1234=8`, which can be saved to the local file or published to the server.

This PR modifies the New Condition behavior to generate the normalized condition (`M:0xH1234`) immediately, instead of when processed.

See also https://discord.com/channels/310192285306454017/936655398725902356/1070810810957447278


Patch:
diff --git a/RAInterface b/RAInterface
index d1443922..f3a14127 160000
--- a/RAInterface
+++ b/RAInterface
@@ -1,1 +1,1 @@
-Subproject commit d1443922a4d5400aadf608a6a34fc988bd7d2a8b
+Subproject commit f3a14127b87c398fc19553bdfe2536e300f4cc48
diff --git a/rcheevos b/rcheevos
index a5dc2b2e..286f2305 160000
--- a/rcheevos
+++ b/rcheevos
@@ -1,1 +1,1 @@
-Subproject commit a5dc2b2e6fbaad0f586161e8cf9d05de0546fa80
+Subproject commit 286f230586004615ac0eb001b3ff352623c94f04
diff --git a/src/data/context/GameContext.cpp b/src/data/context/GameContext.cpp
index 3f5af4ee..7bee3813 100644
--- a/src/data/context/GameContext.cpp
+++ b/src/data/context/GameContext.cpp
@@ -125,6 +125,11 @@ void GameContext::LoadGame(unsigned int nGameId, Mode nMode)
             // GB memory map is just a subset of GBC, and GBC runs GB games, so allow loading a
             // GB game in GBC context.
         }
+        else if (nServerConsoleId == ConsoleID::DS && pConsoleContext.Id() == ConsoleID::DSi)
+        {
+            // DS memory map is just a subset of DSi, and DSi runs DS games, so allow loading a
+            // DS game in DSi context.
+        }
         else if (ra::etoi(nServerConsoleId) == RC_CONSOLE_EVENTS)
         {
             // Hashes for Events are generated by modifying an existing ROM in a very specific manner
diff --git a/src/ui/viewmodels/TriggerViewModel.cpp b/src/ui/viewmodels/TriggerViewModel.cpp
index b19394ef..e8d00c38 100644
--- a/src/ui/viewmodels/TriggerViewModel.cpp
+++ b/src/ui/viewmodels/TriggerViewModel.cpp
@@ -381,26 +381,38 @@ void TriggerViewModel::NewCondition()
     vmCondition.SetSourceSize(nSize);
     vmCondition.SetSourceValue(nAddress);
 
-    // assume the user wants to compare to the current value of the watched memory address
-    vmCondition.SetOperator(TriggerOperatorType::Equals);
-
-    const auto& pEmulatorContext = ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>();
-    const auto nValue = pEmulatorContext.ReadMemory(nAddress, nSize);
+    if (m_bIsValue && m_vConditions.Count() == 1)
+    {
+        // first condition added to a value should be Measured and not have a operator/target.
+        vmCondition.SetType(TriggerConditionType::Measured);
 
-    vmCondition.SetTargetSize(nSize);
-    switch (nSize)
+        // if the operator is None when changing to a non-modifying type, the operator is
+        // automatically changed to Equals. change it back.
+        vmCondition.SetOperator(TriggerOperatorType::None);
+    }
+    else
     {
-        case MemSize::Float:
-        case MemSize::MBF32:
-        case MemSize::MBF32LE:
-            vmCondition.SetTargetType(TriggerOperandType::Float);
-            vmCondition.SetTargetValue(ra::data::U32ToFloat(nValue, nSize));
-            break;
-
-        default:
-            vmCondition.SetTargetType(TriggerOperandType::Value);
-            vmCondition.SetTargetValue(nValue);
-            break;
+        // assume the user wants to compare to the current value of the watched memory address
+        vmCondition.SetOperator(TriggerOperatorType::Equals);
+
+        const auto& pEmulatorContext = ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>();
+        const auto nValue = pEmulatorContext.ReadMemory(nAddress, nSize);
+
+        vmCondition.SetTargetSize(nSize);
+        switch (nSize)
+        {
+            case MemSize::Float:
+            case MemSize::MBF32:
+            case MemSize::MBF32LE:
+                vmCondition.SetTargetType(TriggerOperandType::Float);
+                vmCondition.SetTargetValue(ra::data::U32ToFloat(nValue, nSize));
+                break;
+
+            default:
+                vmCondition.SetTargetType(TriggerOperandType::Value);
+                vmCondition.SetTargetValue(nValue);
+                break;
+        }
     }
 
     vmCondition.SetSelected(true);


Test patch:
diff --git a/tests/data/context/GameContext_Tests.cpp b/tests/data/context/GameContext_Tests.cpp
index fbd98765..a417b7d9 100644
--- a/tests/data/context/GameContext_Tests.cpp
+++ b/tests/data/context/GameContext_Tests.cpp
@@ -317,6 +317,43 @@ public:
         Assert::IsFalse(bDialogShown);
     }
 
+    TEST_METHOD(TestLoadGameTitleConsoleMismatchDSvDSi)
+    {
+        GameContextHarness game;
+        game.mockServer.HandleRequest<ra::api::FetchGameData>([](const ra::api::FetchGameData::Request& request, ra::api::FetchGameData::Response& response)
+        {
+            Assert::AreEqual(1U, request.GameId);
+
+            response.Title = L"Game";
+            response.ConsoleId = ra::etoi(ConsoleID::DS);
+            response.ImageIcon = "9743";
+            return true;
+        });
+
+        game.mockConsoleContext.SetId(ConsoleID::DSi);
+        game.mockConsoleContext.SetName(L"Nintendo DSi");
+
+        bool bDialogShown = false;
+        game.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&bDialogShown](ra::ui::viewmodels::MessageBoxViewModel&)
+        {
+            bDialogShown = true;
+            return ra::ui::DialogResult::OK;
+        });
+
+        game.LoadGame(1U);
+
+        Assert::IsTrue(game.mockAudioSystem.WasAudioFilePlayed(std::wstring(L"Overlay\\info.wav")));
+
+        const auto* pPopup = game.mockOverlayManager.GetMessage(1);
+        Expects(pPopup != nullptr);
+        Assert::IsNotNull(pPopup);
+        Assert::AreEqual(std::wstring(L"Loaded Game"), pPopup->GetTitle());
+        Assert::AreEqual(std::wstring(L"0 achievements, 0 points"), pPopup->GetDescription());
+        Assert::AreEqual(std::string("9743"), pPopup->GetImage().Name());
+
+        Assert::IsFalse(bDialogShown);
+    }
+
     TEST_METHOD(TestLoadGameTitleConsoleMismatchNESvEvent)
     {
         GameContextHarness game;
diff --git a/tests/ui/viewmodels/TriggerViewModel_Tests.cpp b/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
index a5dfe33b..d5cb8b4f 100644
--- a/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
@@ -949,6 +949,38 @@ public:
             vmTrigger.Conditions().GetItemAt(2)->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
     }
 
+    TEST_METHOD(TestNewConditionValue)
+    {
+        std::array<uint8_t, 10> pMemory = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+        TriggerViewModelHarness vmTrigger;
+        vmTrigger.SetIsValue(true);
+        vmTrigger.AddGroup();
+        Assert::AreEqual({ 0U }, vmTrigger.Conditions().Count());
+
+        vmTrigger.InitializeMemory(&pMemory.at(0), pMemory.size());
+        vmTrigger.mockWindowManager.MemoryInspector.Viewer().SetAddress(8);
+        vmTrigger.mockWindowManager.MemoryInspector.Viewer().SetSize(MemSize::EightBit);
+        vmTrigger.NewCondition();
+
+        // first condition added to a value should be Measured without an operator
+        Assert::AreEqual({ 1U }, vmTrigger.Conditions().Count());
+        Assert::IsTrue(vmTrigger.Conditions().GetItemAt(0)->IsSelected());
+        Assert::AreEqual(std::string("M:0xH0008"), vmTrigger.Serialize());
+
+        // second condition should be added normally (as a comparison)
+        vmTrigger.mockWindowManager.MemoryInspector.Viewer().SetAddress(4);
+        vmTrigger.mockWindowManager.MemoryInspector.Viewer().SetSize(MemSize::SixteenBit);
+        vmTrigger.NewCondition();
+
+        Assert::AreEqual({ 2U }, vmTrigger.Conditions().Count());
+
+        // new condition should be selected, any previously selected items should be deselected
+        Assert::IsFalse(vmTrigger.Conditions().GetItemAt(0)->IsSelected());
+        Assert::IsTrue(vmTrigger.Conditions().GetItemAt(1)->IsSelected());
+
+        Assert::AreEqual(std::string("M:0xH0008_0x 0004=1284"), vmTrigger.Serialize());
+    }
+
     TEST_METHOD(TestAddGroupNoAlts)
     {
         TriggerViewModelHarness vmTrigger;