mirror of
https://github.com/brues-code/TwitchEmotes.git
synced 2026-09-21 23:26:57 +00:00
fdff71922e
Animate multi-frame sprite-sheet emotes (e.g. :meow:) using ClassicAPI's extended |T texcoord-crop escape, across every context. - TwitchEmotes_animation_metadata (per-sheet frame layout) added to Emotes.lua; animated emotes' base entries cropped to frame 0 so any un-animated context shows a clean first frame, not the whole sheet. - TwitchEmotesAnimator.lua: ~30fps ticker advancing the current frame in chat lines, the autocomplete popup, dropdown menus, chat bubbles, and the stats screen (featured icons + ranked lists). - Re-converted sprite sheets to uncompressed, unflipped, native dimensions (1.12 cannot decode RLE TGA); fixed Clap.tga which was a single 32x32 frame.
127 lines
5.2 KiB
Lua
127 lines
5.2 KiB
Lua
-- Animated emotes are vertical sprite sheets; TwitchEmotes_animation_metadata
|
|
-- (Emotes.lua) maps each sheet's texture path to its frame layout. ClassicAPI's
|
|
-- inline-texture renderer supports the extended
|
|
-- |Tpath:w:h:0:0:imgW:imgH:0:frameW:top:bottom|t form (sprite-sheet crop), so we
|
|
-- animate by re-SetText-ing each visible FontString ~30fps with the current
|
|
-- frame's crop. On 1.12 a ScrollingMessageFrame's visible lines are real
|
|
-- CSimpleFontStrings exposed as the ChatFrame's FontString regions, so they can
|
|
-- be rewritten in place — the same way the retail TwitchEmotesAnimator does it.
|
|
|
|
local TWITCHEMOTES_T = 0
|
|
local sinceUpdate = 0
|
|
|
|
local function GetCurrentFrameNum(animdata)
|
|
if animdata.pingpong then
|
|
local v = math.floor((TWITCHEMOTES_T * animdata.framerate) % ((animdata.nFrames * 2) - 1))
|
|
if v > animdata.nFrames then
|
|
v = animdata.nFrames - (v % animdata.nFrames)
|
|
end
|
|
return v
|
|
end
|
|
return math.floor((TWITCHEMOTES_T * animdata.framerate) % animdata.nFrames)
|
|
end
|
|
|
|
-- Crop a Texture (not inline text) to its animdata's current frame.
|
|
local function CropToCurrentFrame(tex, animdata)
|
|
local top = GetCurrentFrameNum(animdata) * animdata.frameHeight / animdata.imageHeight
|
|
tex:SetTexCoord(0, 1, top, top + animdata.frameHeight / animdata.imageHeight)
|
|
end
|
|
|
|
local function BuildFrameString(imagepath, animdata, framenum, w, h)
|
|
local top = framenum * animdata.frameHeight
|
|
local bottom = top + animdata.frameHeight
|
|
return "|T" .. imagepath .. ":" .. w .. ":" .. h .. ":0:0:" ..
|
|
animdata.imageWidth .. ":" .. animdata.imageHeight .. ":0:" ..
|
|
animdata.frameWidth .. ":" .. top .. ":" .. bottom .. "|t"
|
|
end
|
|
|
|
-- escape the pattern-magic chars that can appear in an emote escape (paths use
|
|
-- '+' and '-'); '.' is left as-is to match the retail animator.
|
|
local function escpattern(s)
|
|
return (s:gsub("%+", "%%+"):gsub("%-", "%%-"))
|
|
end
|
|
|
|
-- Rewrite every animated emote escape in the fontstring to its current frame.
|
|
local function UpdateFontString(fontstring, w, h)
|
|
local txt = fontstring:GetText()
|
|
if txt == nil then return end
|
|
for emoteStr in txt:gmatch("(|TInterface\\AddOns\\TwitchEmotes\\Emotes.-|t)") do
|
|
local imagepath = emoteStr:match("|T(Interface\\AddOns\\TwitchEmotes\\Emotes.-%.tga).-|t")
|
|
local animdata = imagepath and TwitchEmotes_animation_metadata[imagepath]
|
|
if animdata then
|
|
local framenum = GetCurrentFrameNum(animdata)
|
|
txt = txt:gsub(escpattern(emoteStr), BuildFrameString(imagepath, animdata, framenum, w, h))
|
|
fontstring:SetText(txt)
|
|
end
|
|
end
|
|
end
|
|
|
|
local anim = CreateFrame("Frame")
|
|
anim:SetScript("OnUpdate", function(self, elapsed)
|
|
TWITCHEMOTES_T = TWITCHEMOTES_T + elapsed
|
|
sinceUpdate = sinceUpdate + elapsed
|
|
if sinceUpdate < 0.033 then return end -- ~30fps
|
|
sinceUpdate = 0
|
|
|
|
for i = 1, (NUM_CHAT_WINDOWS or 10) do
|
|
local cf = _G["ChatFrame" .. i]
|
|
if cf and cf:IsShown() then
|
|
for j = 1, cf:GetNumRegions() do
|
|
local region = select(j, cf:GetRegions())
|
|
if region and region:GetObjectType() == "FontString" then
|
|
UpdateFontString(region, 28, 28)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Autocomplete suggestion icons are Textures, not inline text: crop each
|
|
-- animated one's current frame via SetTexCoord.
|
|
local ac = TwitchEmotesACPopup
|
|
if ac and ac:IsShown() and ac.btns then
|
|
for _, b in ipairs(ac.btns) do
|
|
if b:IsShown() and b.animdata then
|
|
CropToCurrentFrame(b.ico, b.animdata)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Open dropdown menus: emote rows are inline-text FontStrings.
|
|
for level = 1, (UIDROPDOWNMENU_MAXLEVELS or 3) do
|
|
local list = _G["DropDownList" .. level]
|
|
if list and list:IsShown() then
|
|
for i = 1, (UIDROPDOWNMENU_MAXBUTTONS or 40) do
|
|
local btn = _G["DropDownList" .. level .. "Button" .. i]
|
|
if btn and btn:IsShown() then
|
|
local fs = btn:GetFontString()
|
|
if fs then UpdateFontString(fs, 28, 28) end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Chat bubbles: our own overlay FontString carries the emote text.
|
|
if C_ChatBubbles then
|
|
for _, bubble in ipairs(C_ChatBubbles.GetAllChatBubbles()) do
|
|
local ov = bubble.tweOverlay
|
|
if ov and ov:IsShown() then
|
|
UpdateFontString(ov, 28, 28)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Stats screen: featured emotes are Textures; the sent/seen lists are
|
|
-- inline-text FontString regions of the frame.
|
|
local sw = TwitchEmotesStatsFrame
|
|
if sw and sw:IsShown() then
|
|
if sw.topSentTex and sw.topSentTex.animdata then CropToCurrentFrame(sw.topSentTex, sw.topSentTex.animdata) end
|
|
if sw.topSeenTex and sw.topSeenTex.animdata then CropToCurrentFrame(sw.topSeenTex, sw.topSeenTex.animdata) end
|
|
for j = 1, sw:GetNumRegions() do
|
|
local region = select(j, sw:GetRegions())
|
|
if region and region:GetObjectType() == "FontString" then
|
|
UpdateFontString(region, 16, 16)
|
|
end
|
|
end
|
|
end
|
|
end)
|