mirror of
https://github.com/brues-code/pfUI.git
synced 2026-11-02 17:06:57 +00:00
Chat: cut the per-line work of the AddMessage hook
- Save the chat history from the frames when leaving the world, through GetMessageInfo, instead of on every line. Lines written back from the history carry a mark, so they are saved as they are. History is no longer recorded while it is off, keeps 30 lines instead of 29, and whispers are dimmed in the whisper colour (gsub's match count was taken as red). - Skip the CLINK and URL passes on lines that cannot hold them. - Colour player links in one gsub with a callback instead of a gsub per name. - Read a chat line that several frames show only once, by its line ID. - Build the timestamp once per second. - Lay the tabs out on hover only when their bar comes back, not 10 times a second. - Drop ChatFrame_OnUpdate: it only blinks the flash of the bottom button pfUI hides. The lines fade in the engine's update.
This commit is contained in:
+150
-109
@@ -30,39 +30,6 @@ pfUI:RegisterModule("chat", function ()
|
||||
end
|
||||
end)
|
||||
|
||||
local realm = GetRealmName()
|
||||
local player = UnitName("player")
|
||||
local history
|
||||
local function SaveChatHistory(id, msg, r, g, b)
|
||||
-- create cache tables if not existing
|
||||
pfUI_cache = pfUI_cache or {}
|
||||
pfUI_cache["chathistory"] = pfUI_cache["chathistory"] or {}
|
||||
pfUI_cache["chathistory"][realm] = pfUI_cache["chathistory"][realm] or {}
|
||||
pfUI_cache["chathistory"][realm][player] = pfUI_cache["chathistory"][realm][player] or {}
|
||||
pfUI_cache["chathistory"][realm][player][id] = pfUI_cache["chathistory"][realm][player][id] or {}
|
||||
|
||||
if r and g and b then
|
||||
local color = rgbhex(r*.5+.2, g*.5+.2, b*.5+.2)
|
||||
msg = string.gsub(msg, "^", color)
|
||||
msg = string.gsub(msg, "|r", "|r" .. color)
|
||||
end
|
||||
|
||||
history = pfUI_cache["chathistory"][realm][player][id]
|
||||
table.insert(history, 1, msg)
|
||||
if history[30] then table.remove(history, 30) end
|
||||
end
|
||||
|
||||
local function GetChatHistory(id)
|
||||
-- create cache tables if not existing
|
||||
pfUI_cache = pfUI_cache or {}
|
||||
pfUI_cache["chathistory"] = pfUI_cache["chathistory"] or {}
|
||||
pfUI_cache["chathistory"][realm] = pfUI_cache["chathistory"][realm] or {}
|
||||
pfUI_cache["chathistory"][realm][player] = pfUI_cache["chathistory"][realm][player] or {}
|
||||
pfUI_cache["chathistory"][realm][player][id] = pfUI_cache["chathistory"][realm][player][id] or {}
|
||||
|
||||
return pfUI_cache["chathistory"][realm][player][id]
|
||||
end
|
||||
|
||||
-- [ Chat Panel Colors ]
|
||||
-- The panels normally inherit pfUI's global appearance theme; custom colors let chat
|
||||
-- deviate from it. CreateBackdrop is re-run first to restore the theme, so toggling
|
||||
@@ -456,15 +423,20 @@ pfUI:RegisterModule("chat", function ()
|
||||
|
||||
if pfUI.chat.hideLock then return end
|
||||
|
||||
-- the dock lays the tabs out again only when their bar comes back
|
||||
if pfUI.chat.left:IsMouseOver(10, -10, -10, 10) then
|
||||
pfUI.chat.left.panelTop:Show()
|
||||
FCF_DockUpdate()
|
||||
if not pfUI.chat.left.panelTop:IsShown() then
|
||||
pfUI.chat.left.panelTop:Show()
|
||||
FCF_DockUpdate()
|
||||
end
|
||||
elseif pfUI.chat.right:IsMouseOver(10, -10, -10, 10) then
|
||||
-- disable while dock is active
|
||||
if pfUI.chat.right:GetAlpha() == 0 then return end
|
||||
|
||||
pfUI.chat.right.panelTop:Show()
|
||||
FCF_DockUpdate()
|
||||
if not pfUI.chat.right.panelTop:IsShown() then
|
||||
pfUI.chat.right.panelTop:Show()
|
||||
FCF_DockUpdate()
|
||||
end
|
||||
else
|
||||
pfUI.chat.left.panelTop:Hide()
|
||||
pfUI.chat.right.panelTop:Hide()
|
||||
@@ -587,6 +559,9 @@ pfUI:RegisterModule("chat", function ()
|
||||
end
|
||||
|
||||
for i=1, NUM_CHAT_WINDOWS do
|
||||
-- ChatFrame_OnUpdate only blinks the flash of the bottom button hidden
|
||||
-- below; the lines fade in the engine's own update
|
||||
_G["ChatFrame" .. i]:SetScript("OnUpdate", nil)
|
||||
_G["ChatFrame" .. i .. "UpButton"]:Hide()
|
||||
_G["ChatFrame" .. i .. "UpButton"].Show = function() return end
|
||||
_G["ChatFrame" .. i .. "DownButton"]:Hide()
|
||||
@@ -732,6 +707,62 @@ pfUI:RegisterModule("chat", function ()
|
||||
return pfUI_playerDB[name].level
|
||||
end
|
||||
|
||||
-- the sender of the line AddMessage is reading, from its chat event
|
||||
local lineSender, lineSenderClass, lineSenderRace, lineSenderSex
|
||||
|
||||
-- [ FormatPlayerLink ]
|
||||
-- The gsub callback for a player link: the name in its class colour, with
|
||||
-- the level and icons the settings ask for. Returns the link unchanged when
|
||||
-- there is nothing to add, as a Lua 5.0 callback that returns nil deletes
|
||||
-- the match.
|
||||
local function FormatPlayerLink(link, name, shown)
|
||||
local real = strsplit(":", name)
|
||||
if shown ~= real then return link end
|
||||
|
||||
local color, icon = "", ""
|
||||
if C.chat.text.classcolor == "1" then
|
||||
local _, class, raceKey, sex
|
||||
if lineSenderClass and real == lineSender then
|
||||
class, raceKey, sex = lineSenderClass, lineSenderRace, lineSenderSex
|
||||
else
|
||||
_, class, _, raceKey, sex = C_PlayerCache.GetPlayerInfoByName(real)
|
||||
end
|
||||
if not class then
|
||||
class = GetUnitInfo(real)
|
||||
end
|
||||
if class and class ~= UNKNOWN then
|
||||
color = "|c" .. PFUI_CLASS_COLORS[class].colorStr
|
||||
if C.chat.text.factionicons == "1" then
|
||||
icon = GetFactionIcon(raceKey)
|
||||
end
|
||||
if C.chat.text.raceicons == "1" then
|
||||
icon = icon .. GetRaceIcon(raceKey, sex)
|
||||
end
|
||||
elseif C.chat.text.tintunknown == "1" then
|
||||
color = unknowncolorhex
|
||||
end
|
||||
end
|
||||
|
||||
local suffix = ""
|
||||
local level = C.chat.text.playerlevel == "1" and GetPlayerLevel(real)
|
||||
if level and level > 0 then
|
||||
suffix = " " .. rgbhex(GetDifficultyColor(level)) .. level .. "|r"
|
||||
elseif level then
|
||||
-- Show ?? for unknown levels (e.g. -1 from UnitLevel)
|
||||
suffix = " |cffff0000??|r"
|
||||
end
|
||||
|
||||
if color == "" and suffix == "" then return link end
|
||||
return left .. icon .. color .. "|Hplayer:" .. name .. "|h" .. color .. real .. "|h|r" .. suffix .. right
|
||||
end
|
||||
|
||||
-- the timestamp of the current second
|
||||
local stampTime, stamp
|
||||
|
||||
-- the last chat line read and what it became. A line shown in several
|
||||
-- frames is read once: nothing in the result depends on the frame.
|
||||
local lastLineID, lastText, lastResult
|
||||
|
||||
-- Passes every argument after the text on: GetMessageInfo returns them all,
|
||||
-- and a hook that drops some loses them for every frame after it.
|
||||
local function AddMessage(frame, text, ...)
|
||||
@@ -744,76 +775,40 @@ pfUI:RegisterModule("chat", function ()
|
||||
|
||||
-- payload of the CHAT_MSG event printing this line; nil for lines
|
||||
-- that don't come from one (system text, addon output, history)
|
||||
local event, _, sender, _, _, _, _, _, _, _, _, _, guid = GetCurrentChatEvent()
|
||||
local event, _, sender, _, _, _, _, _, _, _, _, lineID, guid = GetCurrentChatEvent()
|
||||
|
||||
-- Remove prat CLINKs
|
||||
text = gsub(text, "{CLINK:(%x+):([%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-):([^}]-)}", "|c%1|Hitem:%2|h[%3]|h|r") -- tbc
|
||||
text = gsub(text, "{CLINK:(%x+):([%d-]-:[%d-]-:[%d-]-:[%d-]-):([^}]-)}", "|c%1|Hitem:%2|h[%3]|h|r") -- vanilla
|
||||
-- text emotes all have line 0
|
||||
if lineID and lineID ~= 0 and lineID == lastLineID and text == lastText then
|
||||
return frame:HookAddMessage(lastResult, ...)
|
||||
end
|
||||
local input = text
|
||||
|
||||
-- Remove chatter CLINKs
|
||||
text = gsub(text, "{CLINK:item:(%x+):([%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-):([^}]-)}", "|c%1|Hitem:%2|h[%3]|h|r")
|
||||
text = gsub(text, "{CLINK:enchant:(%x+):([%d-]-):([^}]-)}", "|c%1|Henchant:%2|h[%3]|h|r")
|
||||
text = gsub(text, "{CLINK:spell:(%x+):([%d-]-):([^}]-)}", "|c%1|Hspell:%2|h[%3]|h|r")
|
||||
text = gsub(text, "{CLINK:quest:(%x+):([%d-]-):([%d-]-):([^}]-)}", "|c%1|Hquest:%2:%3|h[%4]|h|r")
|
||||
-- Remove prat and chatter CLINKs
|
||||
if text:find("{CLINK:", 1, true) then
|
||||
text = gsub(text, "{CLINK:(%x+):([%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-):([^}]-)}", "|c%1|Hitem:%2|h[%3]|h|r") -- tbc
|
||||
text = gsub(text, "{CLINK:(%x+):([%d-]-:[%d-]-:[%d-]-:[%d-]-):([^}]-)}", "|c%1|Hitem:%2|h[%3]|h|r") -- vanilla
|
||||
text = gsub(text, "{CLINK:item:(%x+):([%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-):([^}]-)}", "|c%1|Hitem:%2|h[%3]|h|r")
|
||||
text = gsub(text, "{CLINK:enchant:(%x+):([%d-]-):([^}]-)}", "|c%1|Henchant:%2|h[%3]|h|r")
|
||||
text = gsub(text, "{CLINK:spell:(%x+):([%d-]-):([^}]-)}", "|c%1|Hspell:%2|h[%3]|h|r")
|
||||
text = gsub(text, "{CLINK:quest:(%x+):([%d-]-):([%d-]-):([^}]-)}", "|c%1|Hquest:%2:%3|h[%4]|h|r")
|
||||
end
|
||||
|
||||
-- detect urls
|
||||
if C.chat.text.detecturl == "1" then
|
||||
-- detect urls; every pattern needs a dot between name characters or a "://"
|
||||
if C.chat.text.detecturl == "1" and (text:find("[%w_-]%.[%w_-]") or text:find("://", 1, true)) then
|
||||
text = pfUI.chat:HandleLink(text)
|
||||
end
|
||||
|
||||
-- display class colors and player levels if already indexed
|
||||
local classcolor = C.chat.text.classcolor == "1"
|
||||
local playerlevel = C.chat.text.playerlevel == "1"
|
||||
if classcolor or playerlevel then
|
||||
if classcolor or C.chat.text.playerlevel == "1" then
|
||||
-- the engine name-queries a chat sender before the event fires,
|
||||
-- so the sender's GUID always resolves; other names in the text
|
||||
-- fall back to the name-keyed lookups
|
||||
local _, senderClass, senderRace, senderSex
|
||||
lineSender, lineSenderClass, lineSenderRace, lineSenderSex = sender, nil, nil, nil
|
||||
if classcolor and guid and guid ~= "" then
|
||||
_, senderClass, _, senderRace, senderSex = GetPlayerInfoByGUID(guid)
|
||||
end
|
||||
|
||||
for name in gfind(text, "|Hplayer:(.-)|h") do
|
||||
local real, _ = strsplit(":", name)
|
||||
local color, icon = "", ""
|
||||
|
||||
if classcolor then
|
||||
local class, raceKey, sex
|
||||
if senderClass and real == sender then
|
||||
class, raceKey, sex = senderClass, senderRace, senderSex
|
||||
else
|
||||
_, class, _, raceKey, sex = C_PlayerCache.GetPlayerInfoByName(real)
|
||||
end
|
||||
if not class then
|
||||
class = GetUnitInfo(real)
|
||||
end
|
||||
if class and class ~= UNKNOWN then
|
||||
color = "|c" .. PFUI_CLASS_COLORS[class].colorStr
|
||||
if C.chat.text.factionicons == "1" then
|
||||
icon = GetFactionIcon(raceKey)
|
||||
end
|
||||
if C.chat.text.raceicons == "1" then
|
||||
icon = icon .. GetRaceIcon(raceKey, sex)
|
||||
end
|
||||
elseif C.chat.text.tintunknown == "1" then
|
||||
color = unknowncolorhex
|
||||
end
|
||||
end
|
||||
|
||||
local suffix = ""
|
||||
local level = playerlevel and GetPlayerLevel(real)
|
||||
if level and level > 0 then
|
||||
suffix = " " .. rgbhex(GetDifficultyColor(level)) .. level .. "|r"
|
||||
elseif level then
|
||||
-- Show ?? for unknown levels (e.g. -1 from UnitLevel)
|
||||
suffix = " |cffff0000??|r"
|
||||
end
|
||||
|
||||
if color ~= "" or suffix ~= "" then
|
||||
text = string.gsub(text, "|Hplayer:"..name.."|h%["..real.."%]|h(.-:-)",
|
||||
left..icon..color.."|Hplayer:"..name.."|h" .. color .. real .. "|h|r"..suffix..right.."%1")
|
||||
end
|
||||
_, lineSenderClass, _, lineSenderRace, lineSenderSex = GetPlayerInfoByGUID(guid)
|
||||
end
|
||||
text = text:gsub("(|Hplayer:([^|]*)|h%[([^%]]*)%]|h)", FormatPlayerLink)
|
||||
end
|
||||
|
||||
-- reduce channel name to number
|
||||
@@ -823,9 +818,7 @@ pfUI:RegisterModule("chat", function ()
|
||||
|
||||
-- detect the whisper prefix BEFORE prepending a timestamp; the timestamp
|
||||
-- pushes the whisper colour code off position 1 and the find below fails
|
||||
local isWhisper = C.chat.global.whispermod == "1" and string.find(text, wcol, 1) == 1
|
||||
|
||||
if isWhisper then
|
||||
if C.chat.global.whispermod == "1" and string.find(text, wcol, 1) == 1 then
|
||||
-- patch incoming whisper string to match the colors, before the
|
||||
-- timestamp so it keeps the form StripTimestamp looks for
|
||||
text = string.gsub(text, "|r", "|r" .. wcol)
|
||||
@@ -833,19 +826,66 @@ pfUI:RegisterModule("chat", function ()
|
||||
|
||||
-- show timestamp in chat
|
||||
if C.chat.text.time == "1" then
|
||||
text = timestampStart .. date(C.chat.text.timeformat) .. timestampEnd .. text
|
||||
end
|
||||
|
||||
-- save chat history
|
||||
if isWhisper then
|
||||
SaveChatHistory(frame:GetID(), string.gsub(text, wcol, ""), cr, cg, cb)
|
||||
else
|
||||
SaveChatHistory(frame:GetID(), text, ...)
|
||||
local now = time()
|
||||
if now ~= stampTime then
|
||||
stampTime, stamp = now, timestampStart .. date(C.chat.text.timeformat) .. timestampEnd
|
||||
end
|
||||
text = stamp .. text
|
||||
end
|
||||
|
||||
lastLineID, lastText, lastResult = lineID, input, text
|
||||
frame:HookAddMessage(text, ...)
|
||||
end
|
||||
|
||||
-- [ Chat History ]
|
||||
-- The newest lines of each chat frame, kept per character and written back
|
||||
-- at login, newest first and dimmed. They are read from the frames when
|
||||
-- leaving the world, which logout and reload both do before the saved
|
||||
-- variables are written.
|
||||
local HISTORY_LINES = 30
|
||||
|
||||
local function GetChatHistory()
|
||||
local realm, player = GetRealmName(), UnitName("player")
|
||||
pfUI_cache["chathistory"] = pfUI_cache["chathistory"] or {}
|
||||
pfUI_cache["chathistory"][realm] = pfUI_cache["chathistory"][realm] or {}
|
||||
pfUI_cache["chathistory"][realm][player] = pfUI_cache["chathistory"][realm][player] or {}
|
||||
return pfUI_cache["chathistory"][realm][player]
|
||||
end
|
||||
|
||||
-- a line dimmed in its colour, which also comes back after every |r in it
|
||||
local function DimHistoryLine(text, r, g, b)
|
||||
local color = rgbhex(r*.5+.2, g*.5+.2, b*.5+.2)
|
||||
return color .. text:gsub("|r", "|r" .. color)
|
||||
end
|
||||
|
||||
local function SaveChatHistory()
|
||||
local history = GetChatHistory()
|
||||
for i=1, NUM_CHAT_WINDOWS do
|
||||
local frame = _G["ChatFrame"..i]
|
||||
if not frame.pfCombatLog then
|
||||
local lines = {}
|
||||
local num = frame:GetNumMessages()
|
||||
for index = num, math.max(1, num - HISTORY_LINES + 1), -1 do
|
||||
local text, r, g, b, _, restored = frame:GetMessageInfo(index)
|
||||
-- lines written back from the history are dimmed already
|
||||
if not restored then
|
||||
-- an incoming whisper is in the whisper colour, not its own
|
||||
if C.chat.global.whispermod == "1" and pfUI.chat.StripTimestamp(text):sub(1, #wcol) == wcol then
|
||||
text, r, g, b = text:gsub(wcol, ""), cr, cg, cb
|
||||
end
|
||||
text = DimHistoryLine(text, r, g, b)
|
||||
end
|
||||
table.insert(lines, text)
|
||||
end
|
||||
history[i] = lines
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if C.chat.text.history == "1" then
|
||||
pfUI.events:RegisterFrameEventAndCallback("PLAYER_LEAVING_WORLD", SaveChatHistory)
|
||||
end
|
||||
|
||||
for i=1,NUM_CHAT_WINDOWS do
|
||||
if C.chat.global.maxlines ~= "128" then
|
||||
_G["ChatFrame"..i]:SetMaxLines(tonumber(C.chat.global.maxlines) or 128)
|
||||
@@ -853,13 +893,14 @@ pfUI:RegisterModule("chat", function ()
|
||||
|
||||
if not _G["ChatFrame"..i].HookAddMessage then
|
||||
if C.chat.text.history == "1" then
|
||||
-- write history to chat, newest first, above the lines already there
|
||||
for _, msg in ipairs(GetChatHistory(i)) do
|
||||
_G["ChatFrame"..i]:BackFillMessage(msg, .7,.7,.7)
|
||||
-- write history to chat, newest first, above the lines already there;
|
||||
-- after the colour come the id slot and the mark SaveChatHistory reads
|
||||
for _, msg in ipairs(GetChatHistory()[i] or {}) do
|
||||
_G["ChatFrame"..i]:BackFillMessage(msg, .7,.7,.7, nil, true)
|
||||
end
|
||||
end
|
||||
|
||||
-- add chat parse and history hooks
|
||||
-- add chat parse hooks
|
||||
_G["ChatFrame"..i].HookAddMessage = _G["ChatFrame"..i].AddMessage
|
||||
_G["ChatFrame"..i].AddMessage = AddMessage
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user