diff --git a/CHANGELOG.md b/CHANGELOG.md index 0902544..e6a517a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ Resurrecting on Octowow. ### Added +- **SmartAmmo — low-ammo alerts**: SmartAmmo now monitors the total matching arrows or bullets in your bags and equipped slot. Choose a warning threshold (default: 200), with chat and optional center-screen alerts for low stock and a sarcastic warning when you run out entirely. + - **New spell datastore**: Metahunt now have a datastore of all twow 1.18.1 hunter and pet spells. And you can browse them with new pages in the Book. - **Trainer cost planning**: Added shared Hunter and Pet Trainer spell cost planning for spells available now, at the next training level, and still remaining. diff --git a/api/fx-celebrate.lua b/api/fx-celebrate.lua index 12d9ce7..0147ea3 100644 --- a/api/fx-celebrate.lua +++ b/api/fx-celebrate.lua @@ -65,23 +65,32 @@ MTH_FX_ANIM_DEFS = { tooltip = "Flash a red, shaking banner in the middle of the screen when one of your pets runs away for good.", tooltipKey = "ANIM_PET_RUNAWAY_TIP" }, { key = "pet.happiness", section = "pet", default = true, - label = "Flash on happiness change", labelKey = "ANIM_PET_HAPPINESS", + label = "Flash zPet on happiness change", labelKey = "ANIM_PET_HAPPINESS", tooltip = "Briefly flash the pet happiness area green when your pet cheers up, or red when it gets unhappy.", tooltipKey = "ANIM_PET_HAPPINESS_TIP" }, { key = "pet.loyaltyup", section = "pet", default = true, - label = "Glow on loyalty gain", labelKey = "ANIM_PET_LOYALTYUP", + label = "Glow zPet on loyalty gain", labelKey = "ANIM_PET_LOYALTYUP", tooltip = "Give a short golden glow when your pet gains a loyalty level.", tooltipKey = "ANIM_PET_LOYALTYUP_TIP" }, { key = "pet.feed", section = "pet", default = true, - label = "Flash when feeding your pet", labelKey = "ANIM_PET_FEED", + label = "Flash zPet when feeding your pet", labelKey = "ANIM_PET_FEED", tooltip = "Give a short green flash when Feed-O-Matic feeds your pet.", tooltipKey = "ANIM_PET_FEED_TIP" }, { key = "combat.ammoswap", section = "combat", default = true, - label = "Flash on Smart Ammo swap", labelKey = "ANIM_COMBAT_AMMOSWAP", + label = "Flash zAmmo on Smart Ammo swap", labelKey = "ANIM_COMBAT_AMMOSWAP", tooltip = "Briefly highlight the zAmmo button when Smart Ammo swaps your ammo.", tooltipKey = "ANIM_COMBAT_AMMOSWAP_TIP" }, + { key = "combat.ammoout", section = "combat", default = true, + label = "Warn when current ammo runs out", + tooltip = "Show a red warning banner when Smart Ammo equips fallback ammunition." }, + { key = "combat.lowammo", section = "combat", default = true, + label = "Warn when total ammo is low", + tooltip = "Show a warning banner when your total arrows or bullets reach the Smart Ammo threshold." }, + { key = "combat.ammozero", section = "combat", default = true, + label = "Warn when all ammo is gone", + tooltip = "Show a sarcastic warning banner when you have no arrows or bullets left." }, { key = "combat.rangedswap", section = "combat", default = true, - label = "Flash on ranged weapon swap", labelKey = "ANIM_COMBAT_RANGEDSWAP", + label = "Flash zRanged on weapon swap", labelKey = "ANIM_COMBAT_RANGEDSWAP", tooltip = "Briefly highlight the zRanged button when you hot-swap a ranged weapon.", tooltipKey = "ANIM_COMBAT_RANGEDSWAP_TIP" }, { key = "combat.mmprocs", section = "combat", default = true, @@ -93,35 +102,35 @@ MTH_FX_ANIM_DEFS = { tooltip = "Fade the Hunter's Book window in when you open it.", tooltipKey = "ANIM_BOOK_OPEN_TIP" }, { key = "book.tabs", section = "book", default = true, - label = "Fade Hunter's Book tab changes", labelKey = "ANIM_BOOK_TABS", + label = "Fade Hunter's Book results on tab changes", labelKey = "ANIM_BOOK_TABS", tooltip = "Fade the results list back in when you switch tabs inside the Hunter's Book.", tooltipKey = "ANIM_BOOK_TABS_TIP" }, { key = "book.model", section = "book", default = true, - label = "Fade in beast models", labelKey = "ANIM_BOOK_MODEL", + label = "Fade Hunter's Book beast models", labelKey = "ANIM_BOOK_MODEL", tooltip = "Fade the beast model viewer in when it loads a new model.", tooltipKey = "ANIM_BOOK_MODEL_TIP" }, { key = "book.search", section = "book", default = true, - label = "Fade search results", labelKey = "ANIM_BOOK_SEARCH", + label = "Fade Hunter's Book search results", labelKey = "ANIM_BOOK_SEARCH", tooltip = "Fade the results list when your search filter changes.", tooltipKey = "ANIM_BOOK_SEARCH_TIP" }, { key = "ui.options", section = "ui", default = true, - label = "Slide the options window open", labelKey = "ANIM_UI_OPTIONS", + label = "Slide MetaHunt Options open", labelKey = "ANIM_UI_OPTIONS", tooltip = "Slide and fade the MetaHunt options window in when you open it.", tooltipKey = "ANIM_UI_OPTIONS_TIP" }, { key = "ui.optiontabs", section = "ui", default = true, - label = "Fade options tab changes", labelKey = "ANIM_UI_OPTIONTABS", + label = "Fade MetaHunt Options tab changes", labelKey = "ANIM_UI_OPTIONTABS", tooltip = "Fade the settings panel when you switch tabs inside the options window.", tooltipKey = "ANIM_UI_OPTIONTABS_TIP" }, { key = "ui.optionsclose", section = "ui", default = true, - label = "Fade windows closed", labelKey = "ANIM_UI_OPTIONSCLOSE", + label = "Fade MetaHunt windows closed", labelKey = "ANIM_UI_OPTIONSCLOSE", tooltip = "Fade the options and Hunter's Book windows out when you close them.", tooltipKey = "ANIM_UI_OPTIONSCLOSE_TIP" }, { key = "ui.minimap", section = "ui", default = true, - label = "Pulse the minimap button on click", labelKey = "ANIM_UI_MINIMAP", + label = "Pulse MetaHunt minimap button on click", labelKey = "ANIM_UI_MINIMAP", tooltip = "Give the minimap button a quick pulse when you click it.", tooltipKey = "ANIM_UI_MINIMAP_TIP" }, { key = "ui.minimapping", section = "ui", default = true, - label = "Ping the minimap on locate", labelKey = "ANIM_UI_MINIMAPPING", + label = "Ping minimap when locating a target", labelKey = "ANIM_UI_MINIMAPPING", tooltip = "Send a short ping ripple over the minimap when you locate a tracked NPC.", tooltipKey = "ANIM_UI_MINIMAPPING_TIP" }, { key = "ui.zbuttons", section = "ui", default = true, @@ -133,11 +142,11 @@ MTH_FX_ANIM_DEFS = { tooltip = "Fade the unified zBar buttons in when the bar becomes active.", tooltipKey = "ANIM_UI_ZBAR_TIP" }, { key = "ui.stable", section = "ui", default = true, - label = "Flash stable slots on change", labelKey = "ANIM_UI_STABLE", + label = "Flash Hunter's Book stable slots", labelKey = "ANIM_UI_STABLE", tooltip = "Flash a stable slot in the Hunter's Book when a pet is deposited or withdrawn.", tooltipKey = "ANIM_UI_STABLE_TIP" }, { key = "ui.mappins", section = "ui", default = true, - label = "Fade map markers in", labelKey = "ANIM_UI_MAPPINS", + label = "Fade World Map markers in", labelKey = "ANIM_UI_MAPPINS", tooltip = "Fade the world map markers in when you locate a beast, vendor or master.", tooltipKey = "ANIM_UI_MAPPINS_TIP" }, } @@ -758,10 +767,45 @@ function MTH_CelebrateRunaway(petName) magnitude = 11, freq = 42, }), - Sauce:FadeOut(MTH_FX_MsgFrame, 0.7, "inQuad", { delay = 1.6 }), + Sauce:FadeOut(MTH_FX_MsgFrame, 0.7, "inQuad", { delay = 5 }), }) end +function MTH_CelebrateAmmoFallback(previousAmmo, newAmmo) + if not MTH_FX_IsEnabled("combat.ammoout") or not Sauce or not MTH_FX_EnsureFrames() then return end + + MTH_FX_TitleFS:SetTextColor(1, 0.15, 0.15) + MTH_FX_TitleFS:SetText("Out of ammo!") + MTH_FX_SubFS:SetText("Equipped " .. tostring(newAmmo or "fallback ammunition")) + + Sauce:Stop(MTH_FX_MsgFrame) + MTH_FX_MsgFrame:SetAlpha(0) + MTH_FX_MsgFrame:Show() + Sauce:Sequence({ + Sauce:FadeTo(MTH_FX_MsgFrame, 1, 0.18), + Sauce:Tween(MTH_FX_MsgFrame, { + type = "mth_shake", + duration = 0.55, + easing = "linear", + magnitude = 11, + freq = 42, + }), + Sauce:FadeOut(MTH_FX_MsgFrame, 0.7, "inQuad", { delay = 5 }), + }) +end + +function MTH_CelebrateLowAmmo(ammoType, total, threshold) + if not MTH_FX_IsEnabled("combat.lowammo") or not Sauce or not MTH_FX_EnsureFrames() then return end + MTH_Celebrate("Low " .. tostring(ammoType or "ammo") .. "!", tostring(total or 0) .. " left.", "fanfare", 5) + MTH_FX_TitleFS:SetTextColor(1, 0.55, 0.10) +end + +function MTH_CelebrateAmmoZero(ammoType) + if not MTH_FX_IsEnabled("combat.ammozero") or not Sauce or not MTH_FX_EnsureFrames() then return end + MTH_Celebrate("No " .. tostring(ammoType or "ammo") .. " left!", "Excellent planning. Your ranged weapon is now decorative.", "victory", 5) + MTH_FX_TitleFS:SetTextColor(1, 0.15, 0.15) +end + -- DEBUG: mark that the whole file finished loading and record which globals it -- successfully defined. TEMP diagnostic. MTH_FX_DBG.step = "4-complete" diff --git a/api/options-zbuttons.lua b/api/options-zbuttons.lua index 6da3b6b..1847111 100644 --- a/api/options-zbuttons.lua +++ b/api/options-zbuttons.lua @@ -1333,6 +1333,32 @@ function MTH_SetupGeneralOptions() if not smartModuleEnabled and weaponSwapToggle and weaponSwapToggle.Disable then weaponSwapToggle:Disable() end smartBox:Note("MetaHuntGeneralSmartAmmoWeaponSwapHelp", "When you swap between gun and bow/crossbow, instantly equips the best matching ammo from your bags.") + local lowAmmoWarningEnabled = type(MTHSmartAmmo_GetLowAmmoWarningEnabled) == "function" and MTHSmartAmmo_GetLowAmmoWarningEnabled() and true or false + local lowAmmoWarningToggle = smartBox:Checkbox("MetaHuntGeneralSmartAmmoLowWarningToggle", "Enable low-ammo warnings", { + checked = lowAmmoWarningEnabled, + onClick = function() + if type(MTHSmartAmmo_SetLowAmmoWarningEnabled) == "function" then + MTHSmartAmmo_SetLowAmmoWarningEnabled(this:GetChecked() == 1 and 1 or nil) + end + end, + }) + if not smartModuleEnabled and lowAmmoWarningToggle and lowAmmoWarningToggle.Disable then lowAmmoWarningToggle:Disable() end + + local lowAmmoThreshold = type(MTHSmartAmmo_GetLowAmmoThreshold) == "function" and MTHSmartAmmo_GetLowAmmoThreshold() or 200 + local lowAmmoInput = smartBox:EditRow("MetaHuntGeneralSmartAmmoLowThresholdLabel", "Low-ammo threshold:", "MetaHuntGeneralSmartAmmoLowThreshold", 48, { + numeric = true, + onChange = function() + if this:HasFocus() and type(MTHSmartAmmo_SetLowAmmoThreshold) == "function" then + MTHSmartAmmo_SetLowAmmoThreshold(this:GetText()) + end + end, + }) + if lowAmmoInput then + lowAmmoInput:SetText(tostring(lowAmmoThreshold)) + if not smartModuleEnabled and lowAmmoInput.Disable then lowAmmoInput:Disable() end + end + smartBox:Note("MetaHuntGeneralSmartAmmoLowThresholdHelp", "Warns once when your total arrows or bullets reach this amount. Default: 200.") + -- ===== LEFT: Auto-Strip ===== local stripBox = panel:Box("left", "MetaHuntGeneralAutoStripBox", "Auto-Strip") stripBox:Checkbox("MetaHuntGeneralAutoStripToggle", "Enable Auto-Strip on Combat Exit", { diff --git a/modules/smartammo/engine.lua b/modules/smartammo/engine.lua index bdb506a..480ef09 100644 --- a/modules/smartammo/engine.lua +++ b/modules/smartammo/engine.lua @@ -44,6 +44,17 @@ local function MTHSmartAmmo_IsWeaponSwapEnabled() return MTHSmartAmmo_ReadFlag("weaponSwapEnabled") end +local function MTHSmartAmmo_IsLowAmmoWarningEnabled() + return MTHSmartAmmo_ReadFlag("lowAmmoWarningEnabled") +end + +local function MTHSmartAmmo_GetLowAmmoThreshold() + local settings = MTHSmartAmmo_GetSettings() + local threshold = tonumber(settings and settings.lowAmmoThreshold) or 200 + if threshold < 1 then threshold = 1 end + return math.floor(threshold) +end + local MTHSmartAmmo_InitializeHooks MTHSmartAmmo_EnsureHooks = nil @@ -114,6 +125,29 @@ function MTHSmartAmmo_GetWeaponSwapEnabled() return MTHSmartAmmo_IsWeaponSwapEnabled() and true or false end +function MTHSmartAmmo_SetLowAmmoWarningEnabled(enabled, silent) + MTHSmartAmmo_WriteFlag("lowAmmoWarningEnabled", enabled and true or false) + if not silent and DEFAULT_CHAT_FRAME then + MTH_SA_Print("Smart Ammo low-ammo warnings " .. (enabled and "Enabled." or "Disabled.")) + end +end + +function MTHSmartAmmo_GetLowAmmoWarningEnabled() + return MTHSmartAmmo_IsLowAmmoWarningEnabled() and true or false +end + +function MTHSmartAmmo_SetLowAmmoThreshold(value) + local settings = MTHSmartAmmo_GetSettings() + if not settings then return end + local threshold = tonumber(value) or 200 + if threshold < 1 then threshold = 1 end + settings.lowAmmoThreshold = math.floor(threshold) +end + +function MTHSmartAmmo_GetLowAmmoThreshold() + return MTHSmartAmmo_GetLowAmmoThreshold() +end + local MTHSmartAmmo_CastSpell_Hook local MTHSmartAmmo_UseAction_Hook local MTHSmartAmmo_CastSpellByName_Hook @@ -178,6 +212,8 @@ MTH_SA_LastKnownEquippedName = nil MTH_SA_LastFallbackNotice = { key = nil, time = 0 } MTH_SA_LastKnownWeaponAmmoType = nil MTH_SA_LastWeaponCheck = 0 +MTH_SA_LastAmmoStockCheck = 0 +MTH_SA_AmmoStockState = nil MTH_SA_StartupProtectUntil = (GetTime() or 0) + 2.0 MTH_SA_StartupPrimed = nil @@ -216,7 +252,11 @@ local function MTHSmartAmmo_ShowFallbackWarning(previousAmmo, newAmmo) DEFAULT_CHAT_FRAME:AddMessage(warning) end - if RaidWarningFrame and RaidNotice_AddMessage and ChatTypeInfo and ChatTypeInfo["RAID_WARNING"] then + local hasWarningAnimation = type(MTH_CelebrateAmmoFallback) == "function" + and type(MTH_FX_IsEnabled) == "function" and MTH_FX_IsEnabled("combat.ammoout") + if hasWarningAnimation then + MTH_CelebrateAmmoFallback(previousAmmo, newAmmo) + elseif RaidWarningFrame and RaidNotice_AddMessage and ChatTypeInfo and ChatTypeInfo["RAID_WARNING"] then RaidNotice_AddMessage(RaidWarningFrame, warning, ChatTypeInfo["RAID_WARNING"]) elseif UIErrorsFrame and UIErrorsFrame.AddMessage then UIErrorsFrame:AddMessage(warning, 1.0, 0.15, 0.15, 1.0) @@ -231,6 +271,62 @@ local function MTHSmartAmmo_ShowFallbackWarning(previousAmmo, newAmmo) end end +local function MTHSmartAmmo_ShowAmmoStockWarning(ammoType, total, threshold) + local isZero = total <= 0 + local warning + if isZero then + warning = "YOU HAVE NO " .. string.upper(ammoType) .. " LEFT!" + else + warning = "LOW " .. string.upper(ammoType) .. ": " .. tostring(total) .. " LEFT (WARNING AT " .. tostring(threshold) .. ")." + end + if MTH_SA_Print then + MTH_SA_Print(warning) + elseif DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then + DEFAULT_CHAT_FRAME:AddMessage(warning) + end + + local flag = isZero and "combat.ammozero" or "combat.lowammo" + local celebrate = isZero and MTH_CelebrateAmmoZero or MTH_CelebrateLowAmmo + if type(celebrate) == "function" and type(MTH_FX_IsEnabled) == "function" and MTH_FX_IsEnabled(flag) then + celebrate(ammoType, total, threshold) + elseif RaidWarningFrame and RaidNotice_AddMessage and ChatTypeInfo and ChatTypeInfo["RAID_WARNING"] then + RaidNotice_AddMessage(RaidWarningFrame, warning, ChatTypeInfo["RAID_WARNING"]) + elseif UIErrorsFrame and UIErrorsFrame.AddMessage then + UIErrorsFrame:AddMessage(warning, 1.0, 0.15, 0.15, 1.0) + end +end + +local function MTHSmartAmmo_CountAmmoStock(ammoType) + local rankMap = (ammoType == "Bullets" and MTH_AMMO_BULLET_RANK) or MTH_AMMO_ARROW_RANK or {} + local total = 0 + for bag = 4, 0, -1 do + for slot = GetContainerNumSlots(bag), 1, -1 do + local itemName = MTH_SA_GetContainerItemName(bag, slot) + if itemName and rankMap[itemName] then + local _, count = GetContainerItemInfo(bag, slot) + total = total + (tonumber(count) or 1) + end + end + end + return total + (tonumber(GetInventoryItemCount("player", 0)) or 0) +end + +local function MTHSmartAmmo_CheckAmmoStock() + if not MTH_SA_IsModuleEnabled() or not MTHSmartAmmo_IsLowAmmoWarningEnabled() then return end + local ammoType = MTH_SA_GetEquippedWeaponAmmoType() + if not ammoType then + MTH_SA_AmmoStockState = nil + return + end + local total = MTHSmartAmmo_CountAmmoStock(ammoType) + local threshold = MTHSmartAmmo_GetLowAmmoThreshold() + local state = total <= 0 and "zero" or (total <= threshold and "low" or "ready") + if state ~= MTH_SA_AmmoStockState and state ~= "ready" then + MTHSmartAmmo_ShowAmmoStockWarning(ammoType, total, threshold) + end + MTH_SA_AmmoStockState = state +end + local MTHSmartAmmo_FallbackCheckFrame = nil local function MTHSmartAmmo_HandleFallbackResult(previousAmmo) @@ -467,6 +563,10 @@ frame:SetScript("OnUpdate", function() MTH_SA_LastWeaponCheck = now MTHSmartAmmo_HandleWeaponSwapAutoEquip("OnUpdate-weapon-check") end + if (now - (MTH_SA_LastAmmoStockCheck or 0)) >= 1.0 then + MTH_SA_LastAmmoStockCheck = now + MTHSmartAmmo_CheckAmmoStock() + end end) function MTH_SA_CountEquippedAmmo()