diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ec2009..9d934a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,12 @@ All notable changes to MetaHunt will be documented in this file. - **zAmmo — total ammo meter**: The zAmmo parent button can now show how stocked you are on your equipped weapon's ammo, as a percentage of your quiver/pouch capacity (all matching arrows or bullets across your bags ÷ total quiver/pouch slots × 200). Two heat-coloured display styles (red when low → green when full) are available as toggles in Options → zButtons → zAmmo → Parent Button: a **centered %** on the button, and a **vertical gauge** beside it (which flips to the side opposite the children's expand direction). A third toggle, **"Counter shows total ammo"**, switches the parent's bottom counter from the equipped stack to your full bag total. +- **zAmmo — low-ammo flash**: The zAmmo parent pulses a red glow when your total ammo drops below your SmartAmmo warning threshold (default 200). Toggle in the Parent Button box (on by default). + +- **zAmmo — ammo usage tracker**: Left-click the zAmmo parent to reset a usage counter, then hover it for a report of how much ammo you've burned since the reset — total, elapsed time, average per minute, and a per-type breakdown (how many Rough, Jagged, etc.) with percentages. Counts only ammo actually spent (restocking never corrupts it) and survives `/reload`. The parent tooltip now shows this report instead of the raw item tooltip, with a separate "Show parent tooltip" toggle; the Children "Show Tooltip" option now only affects the child buttons. + +- **zPet — unhappy pet flash**: The zPet parent pulses a red glow while your pet is Unhappy, as a persistent "feed me" reminder (complements the existing flash-on-change). Toggle in the Parent Button box (on by default). + ### Changed - **zAmmo — capacity-based counter colouring**: The heat colour on the ammo stack counters now scales to your real quiver/pouch capacity instead of a fixed number, so a stack turns green as it approaches filling your quiver (falls back to the old scale when no quiver/pouch is equipped). diff --git a/api/options-zbuttons.lua b/api/options-zbuttons.lua index 3d60d93..52cabbd 100644 --- a/api/options-zbuttons.lua +++ b/api/options-zbuttons.lua @@ -313,6 +313,21 @@ local function MTH_ZB_EnsureButtonOptionDefaults(buttonName) saved["counttotal"] = false end saved["counttotal"] = saved["counttotal"] and true or false + if saved["lowflash"] == nil then + saved["lowflash"] = true + end + saved["lowflash"] = saved["lowflash"] and true or false + if saved["parenttooltip"] == nil then + saved["parenttooltip"] = true + end + saved["parenttooltip"] = saved["parenttooltip"] and true or false + end + + if buttonName == "zButtonPet" then + if saved["unhappyflash"] == nil then + saved["unhappyflash"] = true + end + saved["unhappyflash"] = saved["unhappyflash"] and true or false end if buttonName == "zButtonTrap" then @@ -478,6 +493,16 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma parentBox:Note(containerName.."AQFilterDesc", MTH_ZB_L("ZB_DESC_AQ_FILTER", "Hide Qiraji mounts outside AQ40. Show only Qiraji inside.")) end + if buttonName == "zButtonPet" then + parentBox:Checkbox(containerName.."UnhappyFlash", MTH_ZB_L("ZB_LABEL_UNHAPPY_FLASH", "Flash when pet is unhappy"), { + checked = saved["unhappyflash"], + onClick = function() + if not this then return end + ZHunterMod_Saved[buttonName]["unhappyflash"] = this:GetChecked() == 1 + end, + }) + end + if buttonName == "zButtonAmmo" then parentBox:Checkbox(containerName.."ShowTotalPercent", MTH_ZB_L("ZB_LABEL_SHOW_TOTAL_PERCENT", "Show total % (center)"), { checked = saved["showtotalperc"], @@ -512,6 +537,26 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma end end, }) + parentBox:Checkbox(containerName.."LowFlash", MTH_ZB_L("ZB_LABEL_LOW_AMMO_FLASH", "Flash when low on ammo"), { + checked = saved["lowflash"], + onClick = function() + if not this then return end + local checked = this:GetChecked() == 1 + ZHunterMod_Saved[buttonName]["lowflash"] = checked + if buttonObj and buttonObj.ammoname and not buttonObj.isspell then + zButtonAmmo_UpdateButton(buttonObj) + end + end, + }) + parentBox:Checkbox(containerName.."ParentTooltip", MTH_ZB_L("ZB_LABEL_SHOW_PARENT_TOOLTIP", "Show parent tooltip (usage report)"), { + checked = saved["parenttooltip"], + onClick = function() + if not this then return end + local checked = this:GetChecked() == 1 + ZHunterMod_Saved[buttonName]["parenttooltip"] = checked + if buttonObj then buttonObj.parentTooltip = checked end + end, + }) end local childrenBox = panel:Box("left", containerName.."ChildrenSection", MTH_ZB_L("ZB_SECTION_CHILDREN_BUTTONS", "Children Buttons")) diff --git a/modules/zhunter/ZSpellButtonTemplate.lua b/modules/zhunter/ZSpellButtonTemplate.lua index 6197c96..b6884e3 100644 --- a/modules/zhunter/ZSpellButtonTemplate.lua +++ b/modules/zhunter/ZSpellButtonTemplate.lua @@ -797,7 +797,11 @@ function ZSpellButtonParent_OnEnter(frame) ZSpellButton_SetChildrenExpanded(frame, true) end ZSpellButton_ResetFadeTimer(frame) - if frame.tooltip and frame.id then + local showParentTooltip = frame.parentTooltip + if showParentTooltip == nil then + showParentTooltip = frame.tooltip + end + if showParentTooltip and frame.id then GameTooltip:SetOwner(frame, "ANCHOR_TOPLEFT") local msg, rank @@ -807,6 +811,9 @@ function ZSpellButtonParent_OnEnter(frame) msg = msg.." ("..rank..")" end GameTooltip:SetText(msg, 1, 1, 1) + elseif frame.suppressItemTooltip then + GameTooltip:ClearLines() + GameTooltip:SetText(frame.ammoname or frame.ammolink or "Ammo", 1, 1, 1) else local showedItemTooltip = nil if frame.ammobag and frame.ammoslot and type(GameTooltip.SetBagItem) == "function" then @@ -838,6 +845,10 @@ function ZSpellButtonParent_OnEnter(frame) end end + if type(frame.extraTooltip) == "function" then + frame.extraTooltip(GameTooltip) + end + GameTooltip:AddLine("Alt+Drag To Move This Button") GameTooltip:Show() end diff --git a/modules/zhunter/zButtonAmmo.lua b/modules/zhunter/zButtonAmmo.lua index f7abe69..6a8f0bc 100644 --- a/modules/zhunter/zButtonAmmo.lua +++ b/modules/zhunter/zButtonAmmo.lua @@ -22,6 +22,8 @@ if not root["zButtonAmmo"] then root["zButtonAmmo"]["children"]["hideonclick"] = 1 root["zButtonAmmo"]["showammoname"] = 1 root["zButtonAmmo"]["showtotalperc"] = 1 + root["zButtonAmmo"]["lowflash"] = 1 + root["zButtonAmmo"]["parenttooltip"] = 1 root["zButtonAmmo"]["lastEquipped"] = nil end @@ -521,6 +523,139 @@ local function zButtonAmmo_GetCountDivisor(category) return 2000 end +-- Reset-based ammo usage tracker (reliable, retrospective — no prediction). +-- session (consumed total + per-name breakdown + resetAt) persists in saved; +-- the baseline tables are runtime only. +local zButtonAmmo_LastByName = nil +local zButtonAmmo_LastCategory = nil +local zButtonAmmo_SettleUntil = 0 + +local function zButtonAmmo_EnsureSession() + local saved = zButtonAmmo_GetSaved() + if type(saved["session"]) ~= "table" then + saved["session"] = {} + end + local session = saved["session"] + if tonumber(session["consumed"]) == nil then + session["consumed"] = 0 + end + if type(session["byName"]) ~= "table" then + session["byName"] = {} + end + if tonumber(session["resetAt"]) == nil then + session["resetAt"] = (time and time()) or 0 + end + return session +end + +function zButtonAmmo_ResetSession() + local session = zButtonAmmo_EnsureSession() + session["consumed"] = 0 + session["byName"] = {} + session["resetAt"] = (time and time()) or 0 + zButtonAmmo_LastByName = nil + zButtonAmmo_LastCategory = nil + if MTH and MTH.Print then + MTH:Print("Ammo usage counter reset.") + end +end + +local function zButtonAmmo_CurrentCountsByName(category) + local counts = {} + for name, info in pairs(zButtonAmmo_Cache) do + if info and info.type == category then + counts[name] = (tonumber(info.count) or 0) + end + end + return counts +end + +local function zButtonAmmo_SampleConsumption() + local category = zButtonAmmo_GetEquippedAmmoCategory() + if not category then + zButtonAmmo_LastByName = nil + zButtonAmmo_LastCategory = nil + return + end + local current = zButtonAmmo_CurrentCountsByName(category) + local now = GetTime and GetTime() or 0 + local last = zButtonAmmo_LastByName + local categoryChanged = (category ~= zButtonAmmo_LastCategory) + zButtonAmmo_LastCategory = category + -- Baseline-only during the post-load settle window, on weapon/category + -- swaps, and on the first sample — these are not real shots. + if now < (zButtonAmmo_SettleUntil or 0) or categoryChanged or last == nil then + zButtonAmmo_LastByName = current + return + end + local session = zButtonAmmo_EnsureSession() + for name, lastCount in pairs(last) do + local cur = current[name] or 0 + if cur < lastCount then + local delta = lastCount - cur + session["consumed"] = (tonumber(session["consumed"]) or 0) + delta + session["byName"][name] = (tonumber(session["byName"][name]) or 0) + delta + end + end + zButtonAmmo_LastByName = current +end + +local function zButtonAmmo_FormatElapsed(seconds) + if seconds < 0 then seconds = 0 end + local mins = math.floor(seconds / 60) + local hrs = math.floor(mins / 60) + if hrs > 0 then + return hrs .. "h " .. (mins - hrs * 60) .. "m" + elseif mins > 0 then + return mins .. "m" + end + return math.floor(seconds) .. "s" +end + +function zButtonAmmo_AppendSessionReport(tooltip) + if not (tooltip and tooltip.AddLine) then + return + end + local category = zButtonAmmo_GetEquippedAmmoCategory() + if category then + local total = zButtonAmmo_GetTotalAmmoCount(category) + tooltip:AddLine("In bags: " .. total .. " " .. (category == "Bullet" and "bullets" or "arrows"), 0.8, 0.8, 0.8) + end + local session = zButtonAmmo_EnsureSession() + local used = tonumber(session["consumed"]) or 0 + local elapsed = ((time and time()) or 0) - (tonumber(session["resetAt"]) or 0) + if elapsed < 0 then elapsed = 0 end + tooltip:AddLine("Used since reset: " .. used .. " (" .. zButtonAmmo_FormatElapsed(elapsed) .. ")", 1, 0.82, 0) + if used > 0 and elapsed >= 60 then + tooltip:AddLine(string.format("Avg: ~%.1f /min", used / (elapsed / 60)), 0.7, 0.7, 0.7) + end + local byName = session["byName"] + if type(byName) == "table" and used > 0 then + local rows = {} + for name, cnt in pairs(byName) do + if (tonumber(cnt) or 0) > 0 then + table.insert(rows, { name = name, count = tonumber(cnt) or 0 }) + end + end + if table.getn(rows) > 0 then + table.sort(rows, function(a, b) return a.count > b.count end) + tooltip:AddLine(" ") + tooltip:AddLine("Breakdown:", 0.9, 0.9, 0.9) + for i = 1, table.getn(rows) do + local row = rows[i] + local frac = row.count / used + local right = row.count .. " " .. math.floor(frac * 100 + 0.5) .. "%" + if type(tooltip.AddDoubleLine) == "function" then + tooltip:AddDoubleLine(row.name, right, 1, 1, 1, 0.6, 0.8, 1) + else + tooltip:AddLine(row.name .. ": " .. right, 1, 1, 1) + end + end + end + end + tooltip:AddLine("Left-click: reset counter", 0.5, 0.5, 0.5) +end + local function zButtonAmmo_UpdateTotalPercent(button) local totalText = zButtonAmmo_EnsureTotalPercentText(button) if not totalText then @@ -610,6 +745,86 @@ local function zButtonAmmo_ShouldShowTotalCount() return saved["counttotal"] and true or false end +local function zButtonAmmo_ShouldLowFlash() + local saved = zButtonAmmo_GetSaved() + return saved["lowflash"] and true or false +end + +-- Low-ammo threshold mirrors SmartAmmo's when available. +local function zButtonAmmo_GetLowThreshold() + if type(MTHSmartAmmo_GetLowAmmoThreshold) == "function" then + local t = tonumber(MTHSmartAmmo_GetLowAmmoThreshold()) + if t and t > 0 then + return t + end + end + return 200 +end + +local function zButtonAmmo_EnsureLowFlash(button) + if not button then + return nil + end + if button.ammoLowFlash then + return button.ammoLowFlash + end + local tex = button:CreateTexture(nil, "OVERLAY") + tex:SetTexture("Interface\\Buttons\\UI-ActionButton-Border") + tex:SetBlendMode("ADD") + tex:SetVertexColor(1, 0.12, 0.12) + tex:SetPoint("CENTER", button, "CENTER", 0, 0) + tex:SetWidth((button:GetWidth() or 36) * 1.6) + tex:SetHeight((button:GetHeight() or 36) * 1.6) + tex:Hide() + button.ammoLowFlash = tex + return tex +end + +local MTH_ZH_AmmoLowFlashDriver +local zButtonAmmo_LowFlashElapsed = 0 + +local function zButtonAmmo_LowFlashOnUpdate() + if not (zButtonAmmo and zButtonAmmo.ammoLowFlash and zButtonAmmo.ammoLowFlash:IsShown()) then + return + end + zButtonAmmo_LowFlashElapsed = zButtonAmmo_LowFlashElapsed + (arg1 or 0) + zButtonAmmo.ammoLowFlash:SetAlpha(0.45 + 0.4 * math.sin(zButtonAmmo_LowFlashElapsed * 4)) +end + +local function zButtonAmmo_SetLowFlashRunning(running) + if running and not MTH_ZH_AmmoLowFlashDriver then + MTH_ZH_AmmoLowFlashDriver = CreateFrame("Frame", "MTH_ZH_AmmoLowFlashDriver") + end + if not MTH_ZH_AmmoLowFlashDriver then + return + end + MTH_ZH_AmmoLowFlashDriver:SetScript("OnUpdate", running and zButtonAmmo_LowFlashOnUpdate or nil) +end + +local function zButtonAmmo_UpdateLowFlash(button) + local tex = zButtonAmmo_EnsureLowFlash(button) + if not tex then + return + end + local low = false + if zButtonAmmo_ShouldLowFlash() then + local category = zButtonAmmo_GetEquippedAmmoCategory() + if category then + local total = zButtonAmmo_GetTotalAmmoCount(category) + if total < zButtonAmmo_GetLowThreshold() then + low = true + end + end + end + if low then + tex:Show() + zButtonAmmo_SetLowFlashRunning(true) + else + tex:Hide() + zButtonAmmo_SetLowFlashRunning(false) + end +end + function zButtonAmmo_UpdateButton(button) if not button then button = this @@ -632,6 +847,10 @@ function zButtonAmmo_UpdateButton(button) if button == zButtonAmmo and button.ammoTotalGauge then button.ammoTotalGauge:Hide() end + if button == zButtonAmmo and button.ammoLowFlash then + button.ammoLowFlash:Hide() + zButtonAmmo_SetLowFlashRunning(false) + end return end @@ -683,6 +902,7 @@ function zButtonAmmo_UpdateButton(button) end zButtonAmmo_UpdateTotalPercent(button) zButtonAmmo_UpdateTotalGauge(button) + zButtonAmmo_UpdateLowFlash(button) end end @@ -843,6 +1063,12 @@ local function zButtonAmmo_EnsureSpellOrder(ammoList) if saved["showtotalperc"] == nil then saved["showtotalperc"] = 1 end + if saved["lowflash"] == nil then + saved["lowflash"] = 1 + end + if saved["parenttooltip"] == nil then + saved["parenttooltip"] = 1 + end if not saved["parent"] then saved["parent"] = {} end @@ -887,6 +1113,7 @@ local function zButtonAmmo_ApplyRuntimeSettings() end local saved = zButtonAmmo_GetSaved() zButtonAmmo.tooltip = saved["tooltip"] and true or false + zButtonAmmo.parentTooltip = saved["parenttooltip"] and true or false zButtonAmmo.hideonclick = saved["children"] and saved["children"]["hideonclick"] and true or false zButtonAmmo.expandonhover = saved["children"] and saved["children"]["expandonhover"] and true or false zButtonAmmo.fadetimer = saved["children"] and tonumber(saved["children"]["fadetimer"]) or 0 @@ -1013,6 +1240,16 @@ function zButtonAmmo_OnEvent() end zButtonAmmo.customSetButtons = zButtonAmmo_SetButtons zButtonAmmo.customUpdateButton = zButtonAmmo_UpdateButton + zButtonAmmo.extraTooltip = zButtonAmmo_AppendSessionReport + zButtonAmmo.suppressItemTooltip = true + zButtonAmmo.beforeclick = function(btn) + if btn ~= zButtonAmmo then + return false + end + zButtonAmmo_ResetSession() + return true + end + zButtonAmmo_SettleUntil = (GetTime and GetTime() or 0) + 5 zButtonAmmo_CreateButtons() MTH_ZH_AmmoAdjust = CreateFrame("Frame", "MTH_ZH_AmmoAdjust") MTH_ZH_AmmoAdjust:RegisterEvent("UNIT_INVENTORY_CHANGED") @@ -1137,6 +1374,8 @@ function zButtonAmmo_Reset() saved["children"]["hideonclick"] = 1 saved["showammoname"] = 1 saved["showtotalperc"] = 1 + saved["lowflash"] = 1 + saved["parenttooltip"] = 1 saved["lastEquipped"] = nil end @@ -1215,6 +1454,7 @@ function MTH_ZH_AmmoAdjust_OnEvent() local listChanged = (ZHunterMod_Ammo_Buttons ~= ammoList) ZHunterMod_Ammo_Buttons = ammoList zButtonAmmo_RebuildCache() + zButtonAmmo_SampleConsumption() local cacheSignature = zButtonAmmo_GetCacheSignature(ammoList) local rebuiltButtons = false if listChanged or cacheSignature ~= zButtonAmmo_LastCacheSignature then diff --git a/modules/zhunter/zButtonPet.lua b/modules/zhunter/zButtonPet.lua index 88d6a37..ce8c7a1 100644 --- a/modules/zhunter/zButtonPet.lua +++ b/modules/zhunter/zButtonPet.lua @@ -57,6 +57,68 @@ local function zButtonPet_GetSaved() return currentRoot["zButtonPet"] end +-- Persistent "feed me" nudge: pulse the zPet parent while the pet is Unhappy. +local MTH_ZH_PetHappyFlashDriver +local zButtonPet_HappyFlashElapsed = 0 + +local function zButtonPet_EnsureHappyFlash() + local button = getglobal("zButtonPet") + if not button then + return nil + end + if button.petHappyFlash then + return button.petHappyFlash + end + local tex = button:CreateTexture(nil, "OVERLAY") + tex:SetTexture("Interface\\Buttons\\UI-ActionButton-Border") + tex:SetBlendMode("ADD") + tex:SetVertexColor(1, 0.15, 0.15) + tex:SetPoint("CENTER", button, "CENTER", 0, 0) + tex:SetWidth((button:GetWidth() or 36) * 1.6) + tex:SetHeight((button:GetHeight() or 36) * 1.6) + tex:Hide() + button.petHappyFlash = tex + return tex +end + +local function zButtonPet_HappyFlashOnUpdate() + local button = getglobal("zButtonPet") + if not (button and button.petHappyFlash and button.petHappyFlash:IsShown()) then + return + end + zButtonPet_HappyFlashElapsed = zButtonPet_HappyFlashElapsed + (arg1 or 0) + button.petHappyFlash:SetAlpha(0.4 + 0.4 * math.sin(zButtonPet_HappyFlashElapsed * 3)) +end + +local function zButtonPet_SetHappyFlashRunning(running) + if running and not MTH_ZH_PetHappyFlashDriver then + MTH_ZH_PetHappyFlashDriver = CreateFrame("Frame", "MTH_ZH_PetHappyFlashDriver") + end + if not MTH_ZH_PetHappyFlashDriver then + return + end + MTH_ZH_PetHappyFlashDriver:SetScript("OnUpdate", running and zButtonPet_HappyFlashOnUpdate or nil) +end + +local function zButtonPet_UpdateHappyFlash(happiness) + local tex = zButtonPet_EnsureHappyFlash() + if not tex then + return + end + local saved = zButtonPet_GetSaved() + local enabled = saved["unhappyflash"] + if enabled == nil then + enabled = 1 + end + if enabled and happiness ~= nil and tonumber(happiness) == 1 then + tex:Show() + zButtonPet_SetHappyFlashRunning(true) + else + tex:Hide() + zButtonPet_SetHappyFlashRunning(false) + end +end + local function zButtonPet_EnsureConfig() local saved = zButtonPet_GetSaved() saved["count"] = nil @@ -131,6 +193,9 @@ local function zButtonPet_EnsureConfig() saved["visible"][i] = 1 end end + if saved["unhappyflash"] == nil then + saved["unhappyflash"] = 1 + end end local function zButtonPet_ApplyRuntimeSettings() @@ -347,6 +412,7 @@ local function zButtonPet_RefreshSavedPetState(saved, corePetInfo) end local prevHappiness = saved["pet"]["happiness"] saved["pet"]["happiness"] = happiness + zButtonPet_UpdateHappyFlash(happiness) -- Animations: flash the pet button when happiness or loyalty changes. if type(MTH_FX_Flash) == "function" and type(MTH_FX_IsEnabled) == "function" then