diff --git a/.github/workflows/fix-charge-refresh-retry.yml b/.github/workflows/fix-charge-refresh-retry.yml deleted file mode 100644 index 046b0e6..0000000 --- a/.github/workflows/fix-charge-refresh-retry.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Retry live charge refresh - -on: - push: - branches: - - refactor/consolidate-patch-layers - -permissions: - contents: write - -jobs: - retry: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: refactor/consolidate-patch-layers - fetch-depth: 0 - - - name: Apply prepared charge fix - shell: python - run: | - from pathlib import Path - import textwrap - - workflow = Path('.github/workflows/fix-charge-refresh.yml') - text = workflow.read_text(encoding='utf-8') - start_marker = ' from pathlib import Path\n' - end_marker = " Path('.github/workflows/fix-charge-refresh.yml').unlink()\n" - start = text.index(start_marker) - end = text.index(end_marker, start) + len(end_marker) - script = textwrap.dedent(text[start:end]) - exec(compile(script, str(workflow), 'exec')) - - Path('.github/workflows/fix-charge-refresh-retry.yml').unlink() - - - name: Validate Lua and TOC - run: | - sudo apt-get update -qq - sudo apt-get install -y -qq lua5.1 - set -e - find . -name '*.lua' -print0 | while IFS= read -r -d '' f; do - luac5.1 -p "$f" - done - python - <<'PY' - from pathlib import Path - missing = [] - for raw in Path('Guda.toc').read_text(encoding='utf-8').splitlines(): - line = raw.strip() - if not line or line.startswith('#'): - continue - path = Path(line.replace('\\', '/')) - if not path.exists(): - missing.append(line) - if missing: - raise SystemExit('Missing TOC files: ' + ', '.join(missing)) - print('Lua parse + TOC validation OK') - PY - - - name: Commit fix - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git commit -m "fix: refresh live item charges in bags and bank" - git push origin HEAD:refactor/consolidate-patch-layers diff --git a/.github/workflows/fix-charge-refresh.yml b/.github/workflows/fix-charge-refresh.yml deleted file mode 100644 index 5f5ba69..0000000 --- a/.github/workflows/fix-charge-refresh.yml +++ /dev/null @@ -1,152 +0,0 @@ -name: Fix live charge refresh - -on: - push: - branches: - - refactor/consolidate-patch-layers - -permissions: - contents: write - -jobs: - fix: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: refactor/consolidate-patch-layers - fetch-depth: 0 - - - name: Fix charge reads and refreshes - shell: python - run: | - from pathlib import Path - - # ------------------------------------------------------------ - # ItemDetection: read main-bank charges from the live bank slot, - # expose known-charge state, and support exact slot invalidation. - # ------------------------------------------------------------ - p = Path('Core/ItemDetectionClassicAPI.lua') - t = p.read_text(encoding='utf-8') - - old = ''' local ok = false\n if bagID and slotID and bagID ~= -1 then\n ok = pcall(tooltip.SetBagItem, tooltip, bagID, slotID)\n end\n if not ok then\n ok = ChargeSafeSetHyperlink(tooltip, itemData and itemData.link)\n end\n''' - new = ''' local ok = false\n if bagID and slotID then\n if bagID == -1 and tooltip.SetInventoryItem then\n -- Main-bank slots are inventory slots 40..67 on the 1.12 client.\n -- Reading the live slot preserves instance data such as remaining charges.\n ok = pcall(tooltip.SetInventoryItem, tooltip, "player", 39 + slotID)\n elseif bagID ~= -1 then\n ok = pcall(tooltip.SetBagItem, tooltip, bagID, slotID)\n end\n end\n if not ok then\n ok = ChargeSafeSetHyperlink(tooltip, itemData and itemData.link)\n end\n''' - if old not in t: - raise SystemExit('charge tooltip source block not found') - t = t.replace(old, new, 1) - - old = '''function ItemDetection:InvalidateCharges(bagID)\n if not bagID then\n chargesCache = {}\n chargeCapableLinks = {}\n return\n end\n\n local prefix = bagID .. ":"\n''' - new = '''function ItemDetection:IsKnownChargeItem(itemData)\n local itemLink = itemData and itemData.link or nil\n return itemLink and chargeCapableLinks[itemLink] and true or false\nend\n\nfunction ItemDetection:InvalidateCharges(bagID, slotID)\n if not bagID then\n chargesCache = {}\n chargeCapableLinks = {}\n return\n end\n\n if slotID then\n chargesCache[bagID .. ":" .. slotID] = nil\n return\n end\n\n local prefix = bagID .. ":"\n''' - if old not in t: - raise SystemExit('InvalidateCharges block not found') - t = t.replace(old, new, 1) - p.write_text(t, encoding='utf-8') - - # ------------------------------------------------------------ - # ItemButton: expose the small charge-overlay refresh separately so - # bag/bank events never need to rebuild a whole button just for xN. - # ------------------------------------------------------------ - p = Path('UI/ItemButton.lua') - t = p.read_text(encoding='utf-8') - marker = 'function Guda_ItemButton_SetItem(' - pos = t.find(marker) - if pos < 0: - raise SystemExit('Guda_ItemButton_SetItem marker not found') - - helper = '''function Guda_ItemButton_UpdateCharges(button)\n if not button then return end\n local chargesText = getglobal(button:GetName().."_Charges")\n if not chargesText then return end\n\n local charges = nil\n if button.hasItem and button.itemData and addon.Modules.ItemDetection then\n charges = addon.Modules.ItemDetection:GetCharges(button.itemData, button.bagID, button.slotID)\n end\n\n if charges and charges > 0 then\n chargesText:SetText("x" .. charges)\n chargesText:Show()\n else\n chargesText:Hide()\n end\nend\n\n''' - if 'function Guda_ItemButton_UpdateCharges(' not in t: - t = t[:pos] + helper + t[pos:] - - old = ''' -- Show/hide charges text (e.g. "x5" for Wizard Oil)\n if chargesText then\n local charges = nil\n if itemData and addon.Modules.ItemDetection then\n charges = addon.Modules.ItemDetection:GetCharges(itemData, bagID, slotID)\n end\n if charges and charges > 0 then\n chargesText:SetText("x" .. charges)\n chargesText:Show()\n else\n chargesText:Hide()\n end\n end\n''' - new = ''' -- Show/hide charges text (e.g. "x5" for Wizard Oil).\n -- Kept as a standalone refresh so charge-only BAG_UPDATE events do not\n -- need to rebuild the complete item button.\n Guda_ItemButton_UpdateCharges(self)\n''' - if old not in t: - raise SystemExit('inline charge block not found') - t = t.replace(old, new, 1) - p.write_text(t, encoding='utf-8') - - # ------------------------------------------------------------ - # BagFrame: BAG_UPDATE does not identify the changed slot. Refresh - # only overlays whose link is already proven to have real charges. - # ------------------------------------------------------------ - p = Path('UI/BagFrame.lua') - t = p.read_text(encoding='utf-8') - marker = '-- Initialize\nfunction BagFrame:Initialize()' - helper = '''-- Refresh only known charge-bearing items in one changed bag.\n-- Normal stacks remain on the cached negative path and incur no tooltip scan.\nfunction BagFrame:RefreshKnownChargeOverlays(bagID)\n local detection = addon.Modules.ItemDetection\n if not detection or not detection.IsKnownChargeItem or not Guda_ItemButton_UpdateCharges then return end\n\n detection:InvalidateCharges(bagID)\n local buttons = slotToButton[bagID]\n if not buttons then return end\n\n for _, button in pairs(buttons) do\n if button and button.hasItem and button:IsShown()\n and detection:IsKnownChargeItem(button.itemData) then\n Guda_ItemButton_UpdateCharges(button)\n end\n end\nend\n\n''' - if marker not in t: - raise SystemExit('BagFrame Initialize marker not found') - if 'function BagFrame:RefreshKnownChargeOverlays(' not in t: - t = t.replace(marker, helper + marker, 1) - - old = ''' local viewType = addon.Modules.DB:GetSetting("bagViewType") or "single"\n addon:DebugCategory("BAG_UPDATE (BagFrame): bagID=%d, viewType=%s", bagID, viewType)\n''' - new = ''' local viewType = addon.Modules.DB:GetSetting("bagViewType") or "single"\n addon:DebugCategory("BAG_UPDATE (BagFrame): bagID=%d, viewType=%s", bagID, viewType)\n\n -- A charge use can fire BAG_UPDATE without changing item link or stack count.\n -- Refresh only already-known charge overlays before the incremental diff path.\n BagFrame:RefreshKnownChargeOverlays(bagID)\n''' - if old not in t: - raise SystemExit('BagFrame BAG_UPDATE insertion point not found') - t = t.replace(old, new, 1) - p.write_text(t, encoding='utf-8') - - # ------------------------------------------------------------ - # BankFrame: exact main-bank slot events refresh one overlay; bank-bag - # BAG_UPDATE refreshes only known charge-bearing buttons in that bag. - # ------------------------------------------------------------ - p = Path('UI/BankFrame.lua') - t = p.read_text(encoding='utf-8') - marker = '-- Initialize\nfunction BankFrame:Initialize()' - helper = '''-- Refresh charge overlays without rebuilding bank item buttons.\nfunction BankFrame:RefreshKnownChargeOverlays(bagID, slotID)\n local detection = addon.Modules.ItemDetection\n if not detection or not detection.IsKnownChargeItem or not Guda_ItemButton_UpdateCharges then return end\n\n detection:InvalidateCharges(bagID, slotID)\n local buttons = bankSlotToButton[bagID]\n if not buttons then return end\n\n if slotID then\n local button = buttons[slotID] or buttons[tonumber(slotID)]\n if button and button.hasItem and button:IsShown()\n and detection:IsKnownChargeItem(button.itemData) then\n Guda_ItemButton_UpdateCharges(button)\n end\n return\n end\n\n for _, button in pairs(buttons) do\n if button and button.hasItem and button:IsShown()\n and detection:IsKnownChargeItem(button.itemData) then\n Guda_ItemButton_UpdateCharges(button)\n end\n end\nend\n\n''' - if marker not in t: - raise SystemExit('BankFrame Initialize marker not found') - if 'function BankFrame:RefreshKnownChargeOverlays(' not in t: - t = t.replace(marker, helper + marker, 1) - - old = ''' -- Try single-slot update if not sorting\n if not isSorting then\n -- Invalidate bag scanner cache for fresh slot data\n addon.Modules.BankScanner:InvalidateBag(-1)\n -- Try single-slot update\n''' - new = ''' -- Try single-slot update if not sorting\n if not isSorting then\n -- Refresh instance-only charge data for this exact main-bank slot.\n if addon.Modules.ItemDetection and addon.Modules.ItemDetection.InvalidateCharges then\n addon.Modules.ItemDetection:InvalidateCharges(-1, arg1)\n end\n -- Invalidate bag scanner cache for fresh slot data\n addon.Modules.BankScanner:InvalidateBag(-1)\n -- Try single-slot update\n''' - if old not in t: - raise SystemExit('main bank exact slot block not found') - t = t.replace(old, new, 1) - - old = ''' if arg1 >= 5 and arg1 <= 10 then\n -- Debug: count items in this bank bag via raw API\n''' - new = ''' if arg1 >= 5 and arg1 <= 10 then\n -- BAG_UPDATE does not expose the changed slot. Refresh only\n -- previously proven charge-bearing items in this bank bag.\n BankFrame:RefreshKnownChargeOverlays(arg1)\n\n -- Debug: count items in this bank bag via raw API\n''' - if old not in t: - raise SystemExit('bank bag BAG_UPDATE block not found') - t = t.replace(old, new, 1) - - old = ''' elseif event == "PLAYERBANKBAGSLOTS_CHANGED" then\n -- Bank container slot changed (bag added/removed)\n -- Clear bag scanner cache since structure changed\n -- NOTE: Don't clear ItemDetection cache - item properties don't change\n addon.Modules.BankScanner:ClearCache()\n''' - new = ''' elseif event == "PLAYERBANKBAGSLOTS_CHANGED" then\n -- Bank container slot changed (bag added/removed). Slot identities may\n -- be remapped, so discard charge slot state as well.\n if addon.Modules.ItemDetection and addon.Modules.ItemDetection.InvalidateCharges then\n addon.Modules.ItemDetection:InvalidateCharges(nil)\n end\n addon.Modules.BankScanner:ClearCache()\n''' - if old not in t: - raise SystemExit('bank bag structure block not found') - t = t.replace(old, new, 1) - p.write_text(t, encoding='utf-8') - - Path('.github/workflows/fix-charge-refresh.yml').unlink() - - - name: Validate Lua and TOC - run: | - sudo apt-get update -qq - sudo apt-get install -y -qq lua5.1 - set -e - find . -name '*.lua' -print0 | while IFS= read -r -d '' f; do - luac5.1 -p "$f" - done - python - <<'PY' - from pathlib import Path - toc = Path('Guda.toc') - missing = [] - for raw in toc.read_text(encoding='utf-8').splitlines(): - line = raw.strip() - if not line or line.startswith('##'): - continue - path = Path(line.replace('\\', '/')) - if not path.exists(): - missing.append(line) - if missing: - raise SystemExit('Missing TOC files: ' + ', '.join(missing)) - print('Lua parse + TOC validation OK') - PY - - - name: Commit fix - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git commit -m "fix: refresh live item charges in bags and bank" - git push origin HEAD:refactor/consolidate-patch-layers diff --git a/Core/ItemDetectionClassicAPI.lua b/Core/ItemDetectionClassicAPI.lua index c02ddde..7e77de1 100644 --- a/Core/ItemDetectionClassicAPI.lua +++ b/Core/ItemDetectionClassicAPI.lua @@ -386,8 +386,14 @@ local function GetExplicitTooltipCharges(itemData, bagID, slotID) tooltip:ClearLines() local ok = false - if bagID and slotID and bagID ~= -1 then - ok = pcall(tooltip.SetBagItem, tooltip, bagID, slotID) + if bagID and slotID then + if bagID == -1 and tooltip.SetInventoryItem then + -- Main-bank slots are inventory slots 40..67 on the 1.12 client. + -- Reading the live slot preserves instance data such as remaining charges. + ok = pcall(tooltip.SetInventoryItem, tooltip, "player", 39 + slotID) + elseif bagID ~= -1 then + ok = pcall(tooltip.SetBagItem, tooltip, bagID, slotID) + end end if not ok then ok = ChargeSafeSetHyperlink(tooltip, itemData and itemData.link) @@ -434,13 +440,23 @@ function ItemDetection:GetCharges(itemData, bagID, slotID) return charges end -function ItemDetection:InvalidateCharges(bagID) +function ItemDetection:IsKnownChargeItem(itemData) + local itemLink = itemData and itemData.link or nil + return itemLink and chargeCapableLinks[itemLink] and true or false +end + +function ItemDetection:InvalidateCharges(bagID, slotID) if not bagID then chargesCache = {} chargeCapableLinks = {} return end + if slotID then + chargesCache[bagID .. ":" .. slotID] = nil + return + end + local prefix = bagID .. ":" for key, cached in pairs(chargesCache) do if string.find(key, "^" .. prefix) then diff --git a/UI/BagFrame.lua b/UI/BagFrame.lua index d05edaf..4d65713 100644 --- a/UI/BagFrame.lua +++ b/UI/BagFrame.lua @@ -4213,6 +4213,24 @@ function Guda_BagFrame_ClearBagButtonHighlight() end end +-- Refresh only known charge-bearing items in one changed bag. +-- Normal stacks remain on the cached negative path and incur no tooltip scan. +function BagFrame:RefreshKnownChargeOverlays(bagID) + local detection = addon.Modules.ItemDetection + if not detection or not detection.IsKnownChargeItem or not Guda_ItemButton_UpdateCharges then return end + + detection:InvalidateCharges(bagID) + local buttons = slotToButton[bagID] + if not buttons then return end + + for _, button in pairs(buttons) do + if button and button.hasItem and button:IsShown() + and detection:IsKnownChargeItem(button.itemData) then + Guda_ItemButton_UpdateCharges(button) + end + end +end + -- Initialize function BagFrame:Initialize() -- Hook default bag functions (with slight delay to ensure UI is loaded) @@ -4321,6 +4339,10 @@ function BagFrame:Initialize() local viewType = addon.Modules.DB:GetSetting("bagViewType") or "single" addon:DebugCategory("BAG_UPDATE (BagFrame): bagID=%d, viewType=%s", bagID, viewType) + -- A charge use can fire BAG_UPDATE without changing item link or stack count. + -- Refresh only already-known charge overlays before the incremental diff path. + BagFrame:RefreshKnownChargeOverlays(bagID) + -- Check if sorting is in progress - use full redraw with throttle local isSorting = addon.Modules.SortEngine and addon.Modules.SortEngine.sortingInProgress diff --git a/UI/BankFrame.lua b/UI/BankFrame.lua index 32a342e..92b3544 100644 --- a/UI/BankFrame.lua +++ b/UI/BankFrame.lua @@ -2123,6 +2123,32 @@ function BankFrame:ShowBlizzardBank() ShowUIPanel(blizzardBankFrame) end +-- Refresh charge overlays without rebuilding bank item buttons. +function BankFrame:RefreshKnownChargeOverlays(bagID, slotID) + local detection = addon.Modules.ItemDetection + if not detection or not detection.IsKnownChargeItem or not Guda_ItemButton_UpdateCharges then return end + + detection:InvalidateCharges(bagID, slotID) + local buttons = bankSlotToButton[bagID] + if not buttons then return end + + if slotID then + local button = buttons[slotID] or buttons[tonumber(slotID)] + if button and button.hasItem and button:IsShown() + and detection:IsKnownChargeItem(button.itemData) then + Guda_ItemButton_UpdateCharges(button) + end + return + end + + for _, button in pairs(buttons) do + if button and button.hasItem and button:IsShown() + and detection:IsKnownChargeItem(button.itemData) then + Guda_ItemButton_UpdateCharges(button) + end + end +end + -- Initialize function BankFrame:Initialize() self:HideBlizzardBank() @@ -2312,6 +2338,10 @@ function BankFrame:Initialize() -- Try single-slot update if not sorting if not isSorting then + -- Refresh instance-only charge data for this exact main-bank slot. + if addon.Modules.ItemDetection and addon.Modules.ItemDetection.InvalidateCharges then + addon.Modules.ItemDetection:InvalidateCharges(-1, arg1) + end -- Invalidate bag scanner cache for fresh slot data addon.Modules.BankScanner:InvalidateBag(-1) -- Try single-slot update @@ -2415,6 +2445,10 @@ function BankFrame:Initialize() elseif event == "BAG_UPDATE" and arg1 then -- Check if this is a bank bag (5-10) if arg1 >= 5 and arg1 <= 10 then + -- BAG_UPDATE does not expose the changed slot. Refresh only + -- previously proven charge-bearing items in this bank bag. + BankFrame:RefreshKnownChargeOverlays(arg1) + -- Debug: count items in this bank bag via raw API local rawItemCount = 0 local numSlots = GetContainerNumSlots(arg1) or 0 @@ -2453,9 +2487,11 @@ function BankFrame:Initialize() return end elseif event == "PLAYERBANKBAGSLOTS_CHANGED" then - -- Bank container slot changed (bag added/removed) - -- Clear bag scanner cache since structure changed - -- NOTE: Don't clear ItemDetection cache - item properties don't change + -- Bank container slot changed (bag added/removed). Slot identities may + -- be remapped, so discard charge slot state as well. + if addon.Modules.ItemDetection and addon.Modules.ItemDetection.InvalidateCharges then + addon.Modules.ItemDetection:InvalidateCharges(nil) + end addon.Modules.BankScanner:ClearCache() end diff --git a/UI/ItemButton.lua b/UI/ItemButton.lua index 176ea34..1655ccb 100644 --- a/UI/ItemButton.lua +++ b/UI/ItemButton.lua @@ -1779,6 +1779,24 @@ end --===================================================== -- Set item data +function Guda_ItemButton_UpdateCharges(button) + if not button then return end + local chargesText = getglobal(button:GetName().."_Charges") + if not chargesText then return end + + local charges = nil + if button.hasItem and button.itemData and addon.Modules.ItemDetection then + charges = addon.Modules.ItemDetection:GetCharges(button.itemData, button.bagID, button.slotID) + end + + if charges and charges > 0 then + chargesText:SetText("x" .. charges) + chargesText:Show() + else + chargesText:Hide() + end +end + function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCharName, matchesFilter, isReadOnly) -- Proactively convert to number to avoid comparisons with strings in downstream functions bagID = tonumber(bagID) @@ -2265,19 +2283,10 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha end end - -- Show/hide charges text (e.g. "x5" for Wizard Oil) - if chargesText then - local charges = nil - if itemData and addon.Modules.ItemDetection then - charges = addon.Modules.ItemDetection:GetCharges(itemData, bagID, slotID) - end - if charges and charges > 0 then - chargesText:SetText("x" .. charges) - chargesText:Show() - else - chargesText:Hide() - end - end + -- Show/hide charges text (e.g. "x5" for Wizard Oil). + -- Kept as a standalone refresh so charge-only BAG_UPDATE events do not + -- need to rebuild the complete item button. + Guda_ItemButton_UpdateCharges(self) -- Handle tracking toggle on click -- Note: Tracking toggle is now handled in the main OnClick script above to avoid conflicts