Repository: RetroAchievements/RAIntegration
Pull request #701 (2021-04-18 19:18:01)

add totals for hit chains
When a condition is the last part of an AddHits chain, it will now display the total hits in brackets (along with the condition-specific hits in parenthesis). Additionally, a tooltip for the hits field will show where the hits are coming from:

![image](https://user-images.githubusercontent.com/32680403/115157788-60f9b880-a048-11eb-8f12-643678ee29d7.png)



Patch:
diff --git a/src/ui/viewmodels/TriggerConditionViewModel.cpp b/src/ui/viewmodels/TriggerConditionViewModel.cpp
index 31a9d1ce..fa5ee7af 100644
--- a/src/ui/viewmodels/TriggerConditionViewModel.cpp
+++ b/src/ui/viewmodels/TriggerConditionViewModel.cpp
@@ -29,6 +29,7 @@ const IntModelProperty TriggerConditionViewModel::TargetSizeProperty("TriggerCon
 const IntModelProperty TriggerConditionViewModel::TargetValueProperty("TriggerConditionViewModel", "TargetValue", 0);
 const IntModelProperty TriggerConditionViewModel::CurrentHitsProperty("TriggerConditionViewModel", "CurrentHits", 0);
 const IntModelProperty TriggerConditionViewModel::RequiredHitsProperty("TriggerConditionViewModel", "RequiredHits", 0);
+const IntModelProperty TriggerConditionViewModel::TotalHitsProperty("TriggerConditionViewModel", "TotalHits", 0);
 const BoolModelProperty TriggerConditionViewModel::IsSelectedProperty("TriggerConditionViewModel", "IsSelected", false);
 const BoolModelProperty TriggerConditionViewModel::IsIndirectProperty("TriggerConditionViewModel", "IsIndirect", false);
 const BoolModelProperty TriggerConditionViewModel::HasSourceSizeProperty("TriggerConditionViewModel", "HasSourceSize", true);
diff --git a/src/ui/viewmodels/TriggerConditionViewModel.hh b/src/ui/viewmodels/TriggerConditionViewModel.hh
index 858f73d2..83a760d0 100644
--- a/src/ui/viewmodels/TriggerConditionViewModel.hh
+++ b/src/ui/viewmodels/TriggerConditionViewModel.hh
@@ -107,6 +107,10 @@ public:
     unsigned int GetRequiredHits() const { return ra::to_unsigned(GetValue(RequiredHitsProperty)); }
     void SetRequiredHits(unsigned int nValue) { SetValue(RequiredHitsProperty, ra::to_signed(nValue)); }
 
+    static const IntModelProperty TotalHitsProperty;
+    unsigned int GetTotalHits() const { return ra::to_unsigned(GetValue(TotalHitsProperty)); }
+    void SetTotalHits(unsigned int nValue) { SetValue(TotalHitsProperty, ra::to_signed(nValue)); }
+
     static const BoolModelProperty IsIndirectProperty;
     bool IsIndirect() const { return GetValue(IsIndirectProperty); }
     void SetIndirect(bool bValue) { SetValue(IsIndirectProperty, bValue); }
diff --git a/src/ui/viewmodels/TriggerViewModel.cpp b/src/ui/viewmodels/TriggerViewModel.cpp
index 62de04e3..121194a5 100644
--- a/src/ui/viewmodels/TriggerViewModel.cpp
+++ b/src/ui/viewmodels/TriggerViewModel.cpp
@@ -497,8 +497,12 @@ void TriggerViewModel::OnViewModelBoolValueChanged(gsl::index, const BoolModelPr
 
 void TriggerViewModel::ConditionsMonitor::OnViewModelIntValueChanged(gsl::index, const IntModelProperty::ChangeArgs& args)
 {
-    if (args.Property != TriggerConditionViewModel::CurrentHitsProperty && args.Property != TriggerConditionViewModel::RowColorProperty)
+    if (args.Property != TriggerConditionViewModel::CurrentHitsProperty &&
+        args.Property != TriggerConditionViewModel::TotalHitsProperty &&
+        args.Property != TriggerConditionViewModel::RowColorProperty)
+    {
         UpdateCurrentGroup();
+    }
 }
 
 void TriggerViewModel::ConditionsMonitor::OnViewModelAdded(gsl::index)
@@ -569,6 +573,7 @@ void TriggerViewModel::UpdateConditions(const GroupViewModel* pGroup)
     m_vConditions.BeginUpdate();
 
     int nIndex = 0;
+    m_bHasHitChain = false;
 
     if (pGroup != nullptr && pGroup->m_pConditionSet)
     {
@@ -587,9 +592,12 @@ void TriggerViewModel::UpdateConditions(const GroupViewModel* pGroup)
             vmCondition->SetIndex(++nIndex);
             vmCondition->InitializeFrom(*pCondition);
             vmCondition->SetCurrentHits(pCondition->current_hits);
+            vmCondition->SetTotalHits(0);
 
             vmCondition->SetIndirect(bIsIndirect);
             bIsIndirect = (pCondition->type == RC_CONDITION_ADD_ADDRESS);
+
+            m_bHasHitChain |= (pCondition->type == RC_CONDITION_ADD_HITS || pCondition->type == RC_CONDITION_SUB_HITS);
         }
     }
 
@@ -675,6 +683,9 @@ void TriggerViewModel::DoFrame()
         m_vConditions.RemoveNotifyTarget(m_pConditionsMonitor);
         m_vConditions.BeginUpdate();
 
+        unsigned int nHits = 0;
+        bool bIsHitsChain = false;
+
         gsl::index nConditionIndex = 0;
         rc_condition_t* pCondition = pGroup->m_pConditionSet->conditions;
         for (; pCondition != nullptr; pCondition = pCondition->next)
@@ -683,6 +694,40 @@ void TriggerViewModel::DoFrame()
             Expects(vmCondition != nullptr);
 
             vmCondition->SetCurrentHits(pCondition->current_hits);
+
+            if (m_bHasHitChain)
+            {
+                switch (vmCondition->GetType())
+                {
+                    case ra::ui::viewmodels::TriggerConditionType::AddHits:
+                        bIsHitsChain = true;
+                        nHits += vmCondition->GetCurrentHits();
+                        break;
+
+                    case ra::ui::viewmodels::TriggerConditionType::SubHits:
+                        bIsHitsChain = true;
+                        nHits -= vmCondition->GetCurrentHits();
+                        break;
+
+                    case ra::ui::viewmodels::TriggerConditionType::AddAddress:
+                    case ra::ui::viewmodels::TriggerConditionType::AddSource:
+                    case ra::ui::viewmodels::TriggerConditionType::SubSource:
+                    case ra::ui::viewmodels::TriggerConditionType::AndNext:
+                    case ra::ui::viewmodels::TriggerConditionType::OrNext:
+                    case ra::ui::viewmodels::TriggerConditionType::ResetNextIf:
+                        break;
+
+                    default:
+                        if (bIsHitsChain)
+                        {
+                            nHits += vmCondition->GetCurrentHits();
+                            vmCondition->SetTotalHits(nHits);
+                            bIsHitsChain = false;
+                        }
+                        nHits = 0;
+                        break;
+                }
+            }
         }
 
         m_vConditions.EndUpdate();
@@ -690,6 +735,76 @@ void TriggerViewModel::DoFrame()
     }
 }
 
+bool TriggerViewModel::BuildHitChainTooltip(std::wstring& sTooltip,
+    const ViewModelCollection<TriggerConditionViewModel>& vmConditions, gsl::index nIndex)
+{
+    unsigned int nHits = 0, nConditionHits = 0;
+    bool bIsHitsChain = false;
+    sTooltip.clear();
+
+    for (gsl::index nScan = 0; nScan < gsl::narrow_cast<gsl::index>(vmConditions.Count()); ++nScan)
+    {
+        const auto* vmCondition = vmConditions.GetItemAt(nScan);
+        Expects(vmCondition != nullptr);
+
+        switch (vmCondition->GetType())
+        {
+            case ra::ui::viewmodels::TriggerConditionType::AddHits:
+                bIsHitsChain = true;
+                nConditionHits = vmCondition->GetCurrentHits();
+                if (nConditionHits != 0)
+                {
+                    sTooltip.append(ra::StringPrintf(L"+%u (condition %d)\n", nConditionHits, vmCondition->GetIndex()));
+                    nHits += nConditionHits;
+                }
+                break;
+
+            case ra::ui::viewmodels::TriggerConditionType::SubHits:
+                bIsHitsChain = true;
+                nConditionHits = vmCondition->GetCurrentHits();
+                if (nConditionHits != 0)
+                {
+                    sTooltip.append(ra::StringPrintf(L"-%u (condition %d)\n", nConditionHits, vmCondition->GetIndex()));
+                    nHits -= nConditionHits;
+                }
+                break;
+
+            case ra::ui::viewmodels::TriggerConditionType::AddAddress:
+            case ra::ui::viewmodels::TriggerConditionType::AddSource:
+            case ra::ui::viewmodels::TriggerConditionType::SubSource:
+            case ra::ui::viewmodels::TriggerConditionType::AndNext:
+            case ra::ui::viewmodels::TriggerConditionType::OrNext:
+            case ra::ui::viewmodels::TriggerConditionType::ResetNextIf:
+                break;
+
+            default:
+                if (nScan >= nIndex)
+                {
+                    if (bIsHitsChain)
+                    {
+                        nConditionHits = vmCondition->GetCurrentHits();
+                        if (nConditionHits != 0)
+                        {
+                            sTooltip.append(ra::StringPrintf(L"+%u (condition %d)\n", nConditionHits, vmCondition->GetIndex()));
+                            nHits += nConditionHits;
+                        }
+
+                        sTooltip.append(ra::StringPrintf(L"%d/%u total", ra::to_signed(nHits), vmCondition->GetRequiredHits()));
+                    }
+
+                    return true;
+                }
+
+                bIsHitsChain = false;
+                nHits = 0;
+                sTooltip.clear();
+                break;
+        }
+    }
+
+    return false;
+}
+
 void TriggerViewModel::UpdateColors(const rc_trigger_t* pTrigger)
 {
     UpdateGroupColors(pTrigger);
diff --git a/src/ui/viewmodels/TriggerViewModel.hh b/src/ui/viewmodels/TriggerViewModel.hh
index 685c359c..5d530eaa 100644
--- a/src/ui/viewmodels/TriggerViewModel.hh
+++ b/src/ui/viewmodels/TriggerViewModel.hh
@@ -132,6 +132,8 @@ public:
     void DoFrame();
     void UpdateColors(const rc_trigger_t* pTrigger);
 
+    static bool BuildHitChainTooltip(std::wstring& sTooltip, const ViewModelCollection<TriggerConditionViewModel>& vmConditions, gsl::index nIndex);
+
 protected:
     // ViewModelCollectionBase::NotifyTarget
     void OnViewModelBoolValueChanged(gsl::index nIndex, const BoolModelProperty::ChangeArgs& args) override;
@@ -151,6 +153,7 @@ private:
 
     ViewModelCollection<GroupViewModel> m_vGroups;
     ViewModelCollection<TriggerConditionViewModel> m_vConditions;
+    bool m_bHasHitChain = false;
 
     class ConditionsMonitor : public ViewModelCollectionBase::NotifyTarget
     {
diff --git a/src/ui/win32/AssetEditorDialog.cpp b/src/ui/win32/AssetEditorDialog.cpp
index bc3faadc..a86baf47 100644
--- a/src/ui/win32/AssetEditorDialog.cpp
+++ b/src/ui/win32/AssetEditorDialog.cpp
@@ -281,6 +281,10 @@ public:
             return std::to_wstring(nRequiredHits);
         }
 
+        const auto nTotalHits = vmItems.GetItemValue(nIndex, TriggerConditionViewModel::TotalHitsProperty);
+        if (nTotalHits > 0)
+            return ra::StringPrintf(L"%d (%d) [%d]", nRequiredHits, nCurrentHits, nTotalHits);
+
         return ra::StringPrintf(L"%d (%d)", nRequiredHits, nCurrentHits);
     }
 
@@ -290,10 +294,24 @@ public:
             return true;
         if (pProperty == TriggerConditionViewModel::HasHitsProperty)
             return true;
+        if (pProperty == TriggerConditionViewModel::TotalHitsProperty)
+            return true;
 
         return GridNumberColumnBinding::DependsOn(pProperty);
     }
 
+    std::wstring GetTooltip(const ra::ui::ViewModelCollectionBase& vmItems, gsl::index nIndex) const override
+    {
+        const auto* vmConditions = dynamic_cast<const ViewModelCollection<TriggerConditionViewModel>*>(&vmItems);
+        Expects(vmConditions != nullptr);
+
+        std::wstring sTooltip;
+        if (!TriggerViewModel::BuildHitChainTooltip(sTooltip, *vmConditions, nIndex))
+            sTooltip.clear();
+
+        return sTooltip;
+    }
+
     HWND CreateInPlaceEditor(HWND hParent, InPlaceEditorInfo& pInfo) override
     {
         auto* pGridBinding = static_cast<ra::ui::win32::bindings::GridBinding*>(pInfo.pGridBinding);


Test patch:
diff --git a/tests/ui/viewmodels/TriggerViewModel_Tests.cpp b/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
index 214d258d..3182e633 100644
--- a/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
@@ -49,6 +49,13 @@ private:
         {
             return GetValue(VersionProperty);
         }
+
+        std::wstring GetHitChainTooltip(gsl::index nIndex)
+        {
+            std::wstring sTooltip;
+            BuildHitChainTooltip(sTooltip, Conditions(), nIndex);
+            return sTooltip;
+        }
     };
 
     void Parse(TriggerViewModel& vmTrigger, const std::string& sInput)
@@ -911,18 +918,22 @@ public:
         vmTrigger.Groups().GetItemAt(0)->m_pConditionSet->conditions->current_hits = 2;
         vmTrigger.DoFrame();
         Assert::AreEqual(2U, vmTrigger.Conditions().GetItemAt(0)->GetCurrentHits());
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(0)->GetTotalHits()); // total hits only applies to AddHits groups
 
         vmTrigger.Groups().GetItemAt(0)->m_pConditionSet->conditions->current_hits++;
         vmTrigger.DoFrame();
         Assert::AreEqual(3U, vmTrigger.Conditions().GetItemAt(0)->GetCurrentHits());
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(0)->GetTotalHits()); // total hits only applies to AddHits groups
 
         vmTrigger.Groups().GetItemAt(0)->m_pConditionSet->conditions->current_hits++;
         vmTrigger.DoFrame();
         Assert::AreEqual(4U, vmTrigger.Conditions().GetItemAt(0)->GetCurrentHits());
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(0)->GetTotalHits()); // total hits only applies to AddHits groups
 
         vmTrigger.Groups().GetItemAt(0)->m_pConditionSet->conditions->current_hits = 0;
         vmTrigger.DoFrame();
         Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(0)->GetCurrentHits());
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(0)->GetTotalHits()); // total hits only applies to AddHits groups
     }
 
     TEST_METHOD(TestDoFrameDoesNotUpdateTrigger)
@@ -943,6 +954,122 @@ public:
         Assert::AreEqual(std::string("1=1.10."), vmTrigger.Serialize());
         Assert::AreEqual(0, vmTrigger.GetVersion());
     }
+
+    TEST_METHOD(TestDoFrameHitsChain)
+    {
+        TriggerViewModelHarness vmTrigger;
+        Parse(vmTrigger, "0=0.5._C:0=0.10._0=0.20._0=0.30._C:0=0.50._D:0=0.60._0=0.70.");
+        Assert::AreEqual({ 1U }, vmTrigger.Groups().Count());
+
+        auto* cond = vmTrigger.Groups().GetItemAt(0)->m_pConditionSet->conditions;
+        cond->current_hits = 2; cond = cond->next;  //         0=0 (5)
+        cond->current_hits = 3; cond = cond->next;  // AddHits 0=0 (10)
+        cond->current_hits = 6; cond = cond->next;  //         0=0 (20)
+        cond->current_hits = 12; cond = cond->next; //         0=0 (30)
+        cond->current_hits = 24; cond = cond->next; // AddHits 0=0 (50)
+        cond->current_hits = 1; cond = cond->next;  // SubHits 0=0 (60)
+        cond->current_hits = 48;                    //         0=0 (70)
+        vmTrigger.DoFrame();
+        Assert::AreEqual(2U, vmTrigger.Conditions().GetItemAt(0)->GetCurrentHits());
+        Assert::AreEqual(3U, vmTrigger.Conditions().GetItemAt(1)->GetCurrentHits());
+        Assert::AreEqual(6U, vmTrigger.Conditions().GetItemAt(2)->GetCurrentHits());
+        Assert::AreEqual(12U, vmTrigger.Conditions().GetItemAt(3)->GetCurrentHits());
+        Assert::AreEqual(24U, vmTrigger.Conditions().GetItemAt(4)->GetCurrentHits());
+        Assert::AreEqual(1U, vmTrigger.Conditions().GetItemAt(5)->GetCurrentHits());
+        Assert::AreEqual(48U, vmTrigger.Conditions().GetItemAt(6)->GetCurrentHits());
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(0)->GetTotalHits()); // non hit-chain
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(1)->GetTotalHits()); // middle of hit-chain
+        Assert::AreEqual(9U, vmTrigger.Conditions().GetItemAt(2)->GetTotalHits()); // end of hit-chain (3+6)
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(3)->GetTotalHits()); // new non hit-chain
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(4)->GetTotalHits()); // middle of new hit-chain
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(5)->GetTotalHits()); // middle of new hit-chain
+        Assert::AreEqual(71U, vmTrigger.Conditions().GetItemAt(6)->GetTotalHits()); // end of new hit-chain (24-1+48)
+    }
+
+    TEST_METHOD(TestDoFrameHitsChainWithModifiers)
+    {
+        TriggerViewModelHarness vmTrigger;
+        Parse(vmTrigger, "C:0=0.5._I:0_A:0_C:0=0.50._N:0=0_O:0=0_P:0=0.70.");
+        Assert::AreEqual({ 1U }, vmTrigger.Groups().Count());
+
+        auto* cond = vmTrigger.Groups().GetItemAt(0)->m_pConditionSet->conditions;
+        cond->current_hits = 2; cond = cond->next;  // AddHits    0=0 (5)
+        cond->current_hits = 3; cond = cond->next;  // AddAddress 0
+        cond->current_hits = 6; cond = cond->next;  // AddSource  0
+        cond->current_hits = 12; cond = cond->next; // AddHits    0=0 (50)
+        cond->current_hits = 24; cond = cond->next; // AndNext    0=0
+        cond->current_hits = 1; cond = cond->next;  // OrNext     0=0
+        cond->current_hits = 48;                    // PauseIf    0=0 (70)
+        vmTrigger.DoFrame();
+        Assert::AreEqual(2U, vmTrigger.Conditions().GetItemAt(0)->GetCurrentHits());
+        Assert::AreEqual(3U, vmTrigger.Conditions().GetItemAt(1)->GetCurrentHits());
+        Assert::AreEqual(6U, vmTrigger.Conditions().GetItemAt(2)->GetCurrentHits());
+        Assert::AreEqual(12U, vmTrigger.Conditions().GetItemAt(3)->GetCurrentHits());
+        Assert::AreEqual(24U, vmTrigger.Conditions().GetItemAt(4)->GetCurrentHits());
+        Assert::AreEqual(1U, vmTrigger.Conditions().GetItemAt(5)->GetCurrentHits());
+        Assert::AreEqual(48U, vmTrigger.Conditions().GetItemAt(6)->GetCurrentHits());
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(0)->GetTotalHits()); // start of hit-chain
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(1)->GetTotalHits()); // non hit modifier
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(2)->GetTotalHits()); // non hit modifier
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(3)->GetTotalHits()); // middle of hits-chain
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(4)->GetTotalHits()); // non hit modifier
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(5)->GetTotalHits()); // non hit modifier
+        Assert::AreEqual(62U, vmTrigger.Conditions().GetItemAt(6)->GetTotalHits()); // end of hit-chain (2+12+48)
+    }
+
+    TEST_METHOD(TestBuildHitChainTooltip)
+    {
+        TriggerViewModelHarness vmTrigger;
+        Parse(vmTrigger, "0=0.5._C:0=0.10._0=0.20._0=0.30._C:0=0.50._D:0=0.60._0=0.70.");
+        Assert::AreEqual({ 1U }, vmTrigger.Groups().Count());
+
+        auto* cond = vmTrigger.Groups().GetItemAt(0)->m_pConditionSet->conditions;
+        cond->current_hits = 2; cond = cond->next;  //         0=0 (5)
+        cond->current_hits = 3; cond = cond->next;  // AddHits 0=0 (10)
+        cond->current_hits = 6; cond = cond->next;  //         0=0 (20)
+        cond->current_hits = 12; cond = cond->next; //         0=0 (30)
+        cond->current_hits = 24; cond = cond->next; // AddHits 0=0 (50)
+        cond->current_hits = 1; cond = cond->next;  // SubHits 0=0 (60)
+        cond->current_hits = 48;                    //         0=0 (70)
+        vmTrigger.DoFrame();
+
+        const std::wstring sTooltip0 = L"";
+        const std::wstring sTooltip1 = L"+3 (condition 2)\n+6 (condition 3)\n9/20 total";
+        const std::wstring sTooltip2 = L"+24 (condition 5)\n-1 (condition 6)\n+48 (condition 7)\n71/70 total";
+        Assert::AreEqual(sTooltip0, vmTrigger.GetHitChainTooltip(0)); // non hit-chain
+        Assert::AreEqual(sTooltip1, vmTrigger.GetHitChainTooltip(1)); // middle of hit-chain
+        Assert::AreEqual(sTooltip1, vmTrigger.GetHitChainTooltip(2)); // end of hit-chain (3+6)
+        Assert::AreEqual(sTooltip0, vmTrigger.GetHitChainTooltip(3)); // new non hit-chain
+        Assert::AreEqual(sTooltip2, vmTrigger.GetHitChainTooltip(4)); // middle of new hit-chain
+        Assert::AreEqual(sTooltip2, vmTrigger.GetHitChainTooltip(5)); // middle of new hit-chain
+        Assert::AreEqual(sTooltip2, vmTrigger.GetHitChainTooltip(6)); // end of new hit-chain (24-1+48)
+    }
+
+    TEST_METHOD(TestBuildHitChainTooltipWithModifiers)
+    {
+        TriggerViewModelHarness vmTrigger;
+        Parse(vmTrigger, "C:0=0.5._I:0_A:0_C:0=0.50._N:0=0_O:0=0_P:0=0.70.");
+        Assert::AreEqual({ 1U }, vmTrigger.Groups().Count());
+
+        auto* cond = vmTrigger.Groups().GetItemAt(0)->m_pConditionSet->conditions;
+        cond->current_hits = 2; cond = cond->next;  // AddHits    0=0 (5)
+        cond->current_hits = 3; cond = cond->next;  // AddAddress 0
+        cond->current_hits = 6; cond = cond->next;  // AddSource  0
+        cond->current_hits = 12; cond = cond->next; // AddHits    0=0 (50)
+        cond->current_hits = 24; cond = cond->next; // AndNext    0=0
+        cond->current_hits = 1; cond = cond->next;  // OrNext     0=0
+        cond->current_hits = 48;                    // PauseIf    0=0 (70)
+        vmTrigger.DoFrame();
+
+        const std::wstring sTooltip = L"+2 (condition 1)\n+12 (condition 4)\n+48 (condition 7)\n62/70 total";
+        Assert::AreEqual(sTooltip, vmTrigger.GetHitChainTooltip(0)); // start of hit-chain
+        Assert::AreEqual(sTooltip, vmTrigger.GetHitChainTooltip(1)); // non hit modifier
+        Assert::AreEqual(sTooltip, vmTrigger.GetHitChainTooltip(2)); // non hit modifier
+        Assert::AreEqual(sTooltip, vmTrigger.GetHitChainTooltip(3)); // middle of hits-chain
+        Assert::AreEqual(sTooltip, vmTrigger.GetHitChainTooltip(4)); // non hit modifier
+        Assert::AreEqual(sTooltip, vmTrigger.GetHitChainTooltip(5)); // non hit modifier
+        Assert::AreEqual(sTooltip, vmTrigger.GetHitChainTooltip(6)); // end of hits-chain
+    }
 };
 
 } // namespace tests