-- MetaHunt: center-screen celebration effect (message + firework burst). -- Uses the embedded PizzaSauce animation library. Degrades to a silent no-op -- when PizzaSauce or the WoW frame API are unavailable (e.g. offline tests). -- Capture our own PizzaSauce instance now, before another addon that ships its -- own copy can overwrite the global. This is the pattern the library requires. local Sauce = PizzaSauce -- Localization helper: resolve a key through MetaHunt's loc system, falling back -- to the inline English default. Every user-facing string is keyed so the enUS -- strings can be harvested later; the inline default is the working English. local function MTH_FX_L(key, default) if MTH and type(MTH.GetLocalization) == "function" then return MTH:GetLocalization(key, default) end return default or key end -- --------------------------------------------------------------------------- -- DEBUG: load-path tracer. Global so /mth fxdebug can dump it. Records how far -- this file got at load time and any error from the one executable file-scope -- block (RegisterType). TEMP diagnostic. -- --------------------------------------------------------------------------- MTH_FX_DBG = { sauceType = type(PizzaSauce), createFrameType = type(CreateFrame), step = "1-top", } -- --------------------------------------------------------------------------- -- Animation settings (shared by the effect code and the options "Animations" tab) -- --------------------------------------------------------------------------- -- Sections shown, in order, in the options panel. `titleKey` localizes `title`. MTH_FX_ANIM_SECTIONS = { { id = "pet", title = "Pet", titleKey = "ANIM_SECTION_PET" }, { id = "combat", title = "Combat", titleKey = "ANIM_SECTION_COMBAT" }, { id = "book", title = "Book", titleKey = "ANIM_SECTION_BOOK" }, { id = "ui", title = "Interface", titleKey = "ANIM_SECTION_UI" }, } -- Every individual animation the addon can play. Each has a stable `key`, the -- section it belongs to, a checkbox label, an optional tooltip, and a default. -- labelKey/tooltipKey localize label/tooltip (resolved at render time). -- Adding a new animation = one entry here + one MTH_FX_IsEnabled("key") guard. MTH_FX_ANIM_DEFS = { { key = "pet.celebrate", section = "pet", default = true, label = "Celebrate new pet abilities", labelKey = "ANIM_PET_CELEBRATE", tooltip = "Play a firework and a banner in the middle of the screen when your pet learns a new ability or a new rank.", tooltipKey = "ANIM_PET_CELEBRATE_TIP" }, { key = "pet.runaway", section = "pet", default = true, label = "Warn when a pet runs away", labelKey = "ANIM_PET_RUNAWAY", 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", 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", 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", 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", tooltip = "Briefly highlight the zAmmo button when Smart Ammo swaps your ammo.", tooltipKey = "ANIM_COMBAT_AMMOSWAP_TIP" }, { key = "combat.rangedswap", section = "combat", default = true, label = "Flash on ranged 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, label = "Pulse MM Widget procs", labelKey = "ANIM_COMBAT_MMPROCS", tooltip = "Pulse an MM Widget cell when a Lock and Load or Experimental ammo proc lights up.", tooltipKey = "ANIM_COMBAT_MMPROCS_TIP" }, { key = "book.open", section = "book", default = true, label = "Fade in the Hunter's Book", labelKey = "ANIM_BOOK_OPEN", 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", 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", 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", 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", 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", 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", 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", 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", 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, label = "Fade zButton bars open", labelKey = "ANIM_UI_ZBUTTONS", tooltip = "Fade the child buttons in when a collapsible zButton bar (pet, tracking, aspects...) expands.", tooltipKey = "ANIM_UI_ZBUTTONS_TIP" }, { key = "ui.zbar", section = "ui", default = true, label = "Fade the zBar in", labelKey = "ANIM_UI_ZBAR", 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", 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", tooltip = "Fade the world map markers in when you locate a beast, vendor or master.", tooltipKey = "ANIM_UI_MAPPINS_TIP" }, } local function MTH_FX_Store() if MTH and type(MTH.GetModuleSavedVariables) == "function" then return MTH:GetModuleSavedVariables("animations") end return nil end local function MTH_FX_DefaultFor(key) local i = 1 while i <= table.getn(MTH_FX_ANIM_DEFS) do if MTH_FX_ANIM_DEFS[i].key == key then return MTH_FX_ANIM_DEFS[i].default and true or false end i = i + 1 end return true end function MTH_FX_AnimationsEnabled() local store = MTH_FX_Store() if not store or store.enabled == nil then return true end return store.enabled and true or false end function MTH_FX_SetAnimationsEnabled(value) local store = MTH_FX_Store() if not store then return end store.enabled = value and true or false end function MTH_FX_GetFlag(key) local store = MTH_FX_Store() if store and store.flags and store.flags[key] ~= nil then return store.flags[key] and true or false end return MTH_FX_DefaultFor(key) end function MTH_FX_SetFlag(key, value) local store = MTH_FX_Store() if not store then return end store.flags = store.flags or {} store.flags[key] = value and true or false end -- Master gate every animation call site checks: false when animations are -- globally disabled, or when this specific animation is turned off. function MTH_FX_IsEnabled(key) if not MTH_FX_AnimationsEnabled() then return false end if key == nil then return true end return MTH_FX_GetFlag(key) end -- Register a one-shot "ballistic" tween type: a spark that flies out from the -- center along a velocity vector while gravity pulls it back down. MTH_FX_DBG.step = "2-before-registertype" if Sauce and type(Sauce.RegisterType) == "function" and not Sauce.MTH_HasBallistic then local ok, err = pcall(function() Sauce.MTH_HasBallistic = true Sauce:RegisterType("mth_ballistic", { init = function(target, from) return from or { 0, 0 } end, apply = function(target, from, to, t, tween) local dur = tween.dur or 1 local elapsed = t * dur local vx = tween.vx or 0 local vy = tween.vy or 0 local gravity = tween.gravity or 0 local x = from[1] + vx * elapsed local y = from[2] + vy * elapsed - 0.5 * gravity * elapsed * elapsed local anchor = tween.anchor or UIParent target:ClearAllPoints() target:SetPoint("CENTER", anchor, "CENTER", x, y) end, }) end) MTH_FX_DBG.registerOk = ok MTH_FX_DBG.registerErr = err else MTH_FX_DBG.registerSkipped = true end MTH_FX_DBG.step = "3-after-registertype" -- Register a decaying "shake" tween type: oscillates the frame around its own -- current anchor point, with the amplitude fading to zero over the tween so it -- always lands back exactly where it started. Used for negative events. if Sauce and type(Sauce.RegisterType) == "function" and not Sauce.MTH_HasShake then local ok, err = pcall(function() Sauce.MTH_HasShake = true Sauce:RegisterType("mth_shake", { init = function(target) local p, rel, rp, x, y = target:GetPoint(1) return { p or "CENTER", rel, rp or p or "CENTER", x or 0, y or 0 } end, apply = function(target, from, to, t, tween) local mag = tween.magnitude or 8 local freq = tween.freq or 34 local decay = 1 - t local offset = math.sin(t * freq) * mag * decay target:ClearAllPoints() target:SetPoint(from[1], from[2], from[3], (from[4] or 0) + offset, from[5] or 0) end, }) end) MTH_FX_DBG.shakeOk = ok MTH_FX_DBG.shakeErr = err end -- Reusable flash: overlays a short additive colour glow on any frame/button and -- fades it out. Safe on any frame that supports CreateTexture; silent no-op -- otherwise. The overlay texture is cached on the frame for reuse. function MTH_FX_Flash(frame, r, g, b, duration) if not Sauce or not frame then return end if type(frame.CreateTexture) ~= "function" then return end local tex = frame.mthFxFlash if not tex then tex = frame:CreateTexture(nil, "OVERLAY") tex:SetTexture("Interface\\Buttons\\CheckButtonHilight") tex:SetBlendMode("ADD") tex:SetAllPoints(frame) frame.mthFxFlash = tex end tex:SetVertexColor(r or 1, g or 1, b or 1) tex:SetAlpha(0) tex:Show() local dur = duration or 0.45 Sauce:Stop(tex) Sauce:Sequence({ Sauce:FadeTo(tex, 0.85, dur * 0.35), Sauce:FadeTo(tex, 0, dur * 0.65, "inQuad", { onFinish = function() tex:Hide() end, }), }) end local MTH_FX_NUM_SPARKS = 22 local MTH_FX_Container local MTH_FX_MsgFrame local MTH_FX_TitleFS local MTH_FX_SubFS local MTH_FX_Sparks = {} -- Palette for the firework sparks (r, g, b), warm celebratory tones. local MTH_FX_Colors = { { 1.00, 0.82, 0.00 }, { 1.00, 0.45, 0.10 }, { 1.00, 0.20, 0.30 }, { 0.35, 0.70, 1.00 }, { 0.55, 1.00, 0.45 }, { 0.85, 0.50, 1.00 }, } local function MTH_FX_EnsureFrames() if type(CreateFrame) ~= "function" then return false end if MTH_FX_Container then return true end MTH_FX_Container = CreateFrame("Frame", "MTH_FX_Container", UIParent) MTH_FX_Container:SetWidth(1) MTH_FX_Container:SetHeight(1) MTH_FX_Container:SetPoint("CENTER", UIParent, "CENTER", 0, 40) MTH_FX_Container:SetFrameStrata("FULLSCREEN_DIALOG") MTH_FX_Container:Hide() MTH_FX_MsgFrame = CreateFrame("Frame", "MTH_FX_Message", UIParent) MTH_FX_MsgFrame:SetWidth(512) MTH_FX_MsgFrame:SetHeight(90) MTH_FX_MsgFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 80) MTH_FX_MsgFrame:SetFrameStrata("FULLSCREEN_DIALOG") MTH_FX_TitleFS = MTH_FX_MsgFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalHuge") MTH_FX_TitleFS:SetPoint("CENTER", MTH_FX_MsgFrame, "CENTER", 0, 14) MTH_FX_TitleFS:SetTextColor(1, 0.82, 0) MTH_FX_SubFS = MTH_FX_MsgFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge") MTH_FX_SubFS:SetPoint("TOP", MTH_FX_TitleFS, "BOTTOM", 0, -8) MTH_FX_SubFS:SetTextColor(1, 1, 1) MTH_FX_MsgFrame:Hide() local i = 1 while i <= MTH_FX_NUM_SPARKS do local spark = MTH_FX_Container:CreateTexture(nil, "OVERLAY") spark:SetTexture("Interface\\Cooldown\\star4") spark:SetWidth(22) spark:SetHeight(22) spark:SetBlendMode("ADD") spark:SetPoint("CENTER", MTH_FX_Container, "CENTER", 0, 0) spark:Hide() MTH_FX_Sparks[i] = spark i = i + 1 end return true end -- Public entry point: play a celebratory message + firework burst in the middle -- of the screen. `title` and `subtitle` are optional strings. function MTH_Celebrate(title, subtitle) if not Sauce then return end if not MTH_FX_EnsureFrames() then return end MTH_FX_TitleFS:SetTextColor(1, 0.82, 0) MTH_FX_TitleFS:SetText(title or "") MTH_FX_SubFS:SetText(subtitle or "") Sauce:Stop(MTH_FX_MsgFrame) MTH_FX_MsgFrame:SetAlpha(0) MTH_FX_MsgFrame:Show() Sauce:Sequence({ Sauce:Group({ Sauce:FadeTo(MTH_FX_MsgFrame, 1, 0.25), Sauce:Pulse(MTH_FX_MsgFrame, 1.22, 0.5), }), Sauce:FadeOut(MTH_FX_MsgFrame, 0.6, "inQuad", { delay = 2.2 }), }) MTH_FX_Container:Show() local colorCount = table.getn(MTH_FX_Colors) local colorIdx = 0 local i = 1 while i <= MTH_FX_NUM_SPARKS do local spark = MTH_FX_Sparks[i] local angle = (i / MTH_FX_NUM_SPARKS) * 2 * math.pi + (math.random() - 0.5) * 0.4 local speed = 150 + math.random() * 130 local vx = math.cos(angle) * speed local vy = math.sin(angle) * speed + 70 local dur = 0.9 + math.random() * 0.4 colorIdx = colorIdx + 1 if colorIdx > colorCount then colorIdx = 1 end local color = MTH_FX_Colors[colorIdx] spark:SetVertexColor(color[1], color[2], color[3]) spark:SetAlpha(1) spark:Show() Sauce:Stop(spark) Sauce:Group({ Sauce:Tween(spark, { type = "mth_ballistic", from = { 0, 0 }, duration = dur, easing = "linear", anchor = MTH_FX_Container, dur = dur, vx = vx, vy = vy, gravity = 300, }), Sauce:FadeTo(spark, 0, 0.45, "inQuad", { delay = dur - 0.45, onFinish = function() spark:Hide() end, }), }) i = i + 1 end end -- Convenience wrapper used when the hunter's pet learns a new ability/rank. function MTH_CelebratePetAbility(label) if not MTH_FX_IsEnabled("pet.celebrate") then return end MTH_Celebrate(MTH_FX_L("ANIM_PET_LEARNED_TITLE", "New Pet Ability!"), label) end -- Dramatic negative banner: a red, shaking message shown when a pet runs away -- for good. Gated on pet.runaway. Reuses the shared message frame. function MTH_CelebrateRunaway(petName) if not MTH_FX_IsEnabled("pet.runaway") then return end if not Sauce then return end if not MTH_FX_EnsureFrames() then return end MTH_FX_TitleFS:SetTextColor(1, 0.15, 0.15) MTH_FX_TitleFS:SetText(MTH_FX_L("ANIM_PET_RUNAWAY_TITLE", "Your pet ran away!")) MTH_FX_SubFS:SetText(petName or "") 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 = 1.6 }), }) end -- DEBUG: mark that the whole file finished loading and record which globals it -- successfully defined. TEMP diagnostic. MTH_FX_DBG.step = "4-complete" MTH_FX_DBG.hasCelebrate = type(MTH_Celebrate) MTH_FX_DBG.hasIsEnabled = type(MTH_FX_IsEnabled) MTH_FX_DBG.hasAnimEnabled = type(MTH_FX_AnimationsEnabled)