mirror of
https://github.com/brues-code/TwitchEmotes.git
synced 2026-09-24 08:36:06 +00:00
311359dcc8
A texture skinnier than 16:1 draws nothing at all. Confirmed in-game with one crop shape across two sheets: ratJAM (32x512) renders, modCheck (32x1024) is blank. The earlier 2048 repack fixed those sheets by accident - two columns took them from 64:1 to 16:1, not by getting under the 1024 cap as its commit message claimed. Twenty-seven sheets were still at 32:1 and had never rendered: modCheck, meow, BOOBA, rooRave, Aware, Thinkge, alert and the rest. All repacked to 64x512, same frames in the same order, with their crops and metadata updated. Verified across all 112 animated sheets: none over 1024 or skinnier than 16:1, declared sizes match the files, frame counts fit their grids, and every repacked frame still matches what its sheet held at that index. add_emote.py learns the same limit, and resamples the source timeline at a constant rate instead of expanding holds into repeats: a hold repeats, a source faster than the ~30fps ticker drops frames, and the loop keeps its duration either way. blobDance is 50fps at source, so the old code stretched its 0.74s loop to 1.23s. Also drops the atoi note from BuildFrameString - ParseField uses atof and tolerates decimals, so that was never the failure it described.
142 lines
6.0 KiB
Lua
142 lines
6.0 KiB
Lua
-- Animated emotes are 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:left:right: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
|
|
|
|
-- Frames run row-major across however many columns the sheet is wide. Two
|
|
-- client limits force that: no texture side may exceed 1024, and a texture
|
|
-- skinnier than 16:1 doesn't draw at all -- a 32x1024 strip renders nothing,
|
|
-- while the same frames as 64x512 render fine. So a run longer than 16 frames
|
|
-- is packed in columns rather than as one tall strip.
|
|
local function GetFrameRect(animdata, framenum)
|
|
local cols = math.floor(animdata.imageWidth / animdata.frameWidth)
|
|
if cols < 1 then cols = 1 end
|
|
local left = (framenum % cols) * animdata.frameWidth
|
|
local top = math.floor(framenum / cols) * animdata.frameHeight
|
|
return left, left + animdata.frameWidth, top, top + animdata.frameHeight
|
|
end
|
|
|
|
-- Crop a Texture (not inline text) to its animdata's current frame.
|
|
local function CropToCurrentFrame(tex, animdata)
|
|
local left, right, top, bottom = GetFrameRect(animdata, GetCurrentFrameNum(animdata))
|
|
tex:SetTexCoord(left / animdata.imageWidth, right / animdata.imageWidth,
|
|
top / animdata.imageHeight, bottom / animdata.imageHeight)
|
|
end
|
|
|
|
-- The crop bounds are texel offsets, so emit them as integers rather than
|
|
-- letting a float from the frame maths stringify into the payload.
|
|
local function BuildFrameString(imagepath, animdata, framenum, w, h)
|
|
local left, right, top, bottom = GetFrameRect(animdata, framenum)
|
|
return ("|T%s:%d:%d:0:0:%d:%d:%d:%d:%d:%d|t"):format(imagepath, w, h,
|
|
animdata.imageWidth, animdata.imageHeight,
|
|
left, right, top, bottom)
|
|
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)
|