From 01931510eb79ced7d05d4076d13ccf38a8623357 Mon Sep 17 00:00:00 2001 From: Lothar Buchholz Date: Sun, 1 Nov 2026 00:57:12 +0100 Subject: [PATCH] - switching to new C_AddOns API changes - cascaded message hooking for Retail v12 engine - messages during encounters are sent unchanged, with a one-time notice - ignore numbered chats that do not exist - /r no longer sends to an empty whisper target if the last told target is unavailable - fixed replacements in instance chats - messages to unknown numbered channels are now discarded - right-click on the minimap button --- CHANGELOG.md | 4 +- Grichelde.lua | 6 ++- GricheldeChat.lua | 97 +++++++++++++++++++++++++++++++++++++----- GricheldeConstants.lua | 23 +++++----- GricheldeMinimap.lua | 2 +- GricheldeOptions.lua | 9 ++-- GricheldeUtils.lua | 6 +-- localisation/deDE.lua | 2 + localisation/enUS.lua | 2 + 9 files changed, 117 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d0fc89..b3db1bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - handling of german umlauts (and other unicode characters) - updated libraries and bumped version for all incarnations of WoW (Classic Era, Forever, Classic, Retail) -- +- adaptations to Retail v12 new Chat handling (Send API, encounter lockdown, instance chat) +- /r no longer sends to an empty whisper target if the last told target is unavailable + ## Version 1.1.6 - 2023-01-18 ### Changed - bumped version for latest WotLK Classic patch diff --git a/Grichelde.lua b/Grichelde.lua index f0f3af0..f50327e 100644 --- a/Grichelde.lua +++ b/Grichelde.lua @@ -20,6 +20,7 @@ local _G = _G -- initialize addon local Grichelde = LibStub("AceAddon-3.0"):NewAddon(AddonTable, AddonName, "AceConsole-3.0", "AceEvent-3.0", "AceHook-3.0") +local GetAddOnMetadata = (_G.C_AddOns and _G.C_AddOns.GetAddOnMetadata) or _G.GetAddOnMetadata Grichelde.version = GetAddOnMetadata(AddonName, "Version") Grichelde.build = GetAddOnMetadata(AddonName, "X-Build") or "Development" --Grichelde.era = _G.WOW_PROJECT_ID == _G.WOW_PROJECT_CLASSIC @@ -40,7 +41,7 @@ end function Grichelde:OnEnable() -- Hook in before message is sent to replace all character occurrences where replacements have been defined in the options - self:RawHook("SendChatMessage", true) + self:HookSendChatMessage() self.options, self.dialog = self:SetupOptions() self.ldb, self.icon = self:MinimapButton() @@ -56,7 +57,8 @@ function Grichelde:OnEnable() end function Grichelde:OnDisable() - self:Unhook("SendChatMessage") + self:UnhookAll() + self.originalSend = nil self:CloseOptions() self:HideMinimapButton() self:UnregisterChatCommand("grichelde") diff --git a/GricheldeChat.lua b/GricheldeChat.lua index 46bb793..ed835e9 100644 --- a/GricheldeChat.lua +++ b/GricheldeChat.lua @@ -2,8 +2,8 @@ local _G = _G or getfenv() local Grichelde = _G.Grichelde or {} -local IsAddOnLoaded, assert, nilOrEmpty, pairs, ipairs, spairs, tContains, tFilter, tInsert, tIsEmpty, tSize, find, sub, gsub, getNextCharUtf8, isUtf8MultiByte, getUtf8Sequence, isUpper, isLower, toUpper, toLower, bytes2Char, trim, length, lengthUtf8 - = Grichelde.F.IsAddOnLoaded, Grichelde.F.assert, Grichelde.F.nilOrEmpty, Grichelde.F.pairs, Grichelde.F.ipairs, Grichelde.F.spairs, Grichelde.F.tContains, Grichelde.F.tFilter, Grichelde.F.tInsert, Grichelde.F.tIsEmpty, Grichelde.F.tSize, +local assert, nilOrEmpty, pairs, ipairs, spairs, tContains, tFilter, tInsert, tIsEmpty, tSize, find, sub, gsub, getNextCharUtf8, isUtf8MultiByte, getUtf8Sequence, isUpper, isLower, toUpper, toLower, bytes2Char, trim, length, lengthUtf8 + = Grichelde.F.assert, Grichelde.F.nilOrEmpty, Grichelde.F.pairs, Grichelde.F.ipairs, Grichelde.F.spairs, Grichelde.F.tContains, Grichelde.F.tFilter, Grichelde.F.tInsert, Grichelde.F.tIsEmpty, Grichelde.F.tSize, Grichelde.F.find, Grichelde.F.sub, Grichelde.F.gsub, Grichelde.F.getNextCharUtf8, Grichelde.F.isUtf8MultiByte, Grichelde.F.getUtf8Sequence, Grichelde.F.isUpper, Grichelde.F.isLower, Grichelde.F.toUpper, Grichelde.F.toLower, Grichelde.F.bytes2Char, Grichelde.F.trim, Grichelde.F.length, Grichelde.F.lengthUtf8 --- Splits a long text in longest possible chunks of <= 255 length, split at last available space @@ -145,10 +145,10 @@ function Grichelde:SendChunkifiedChatMessage(message, ...) self:DebugPrint("SendChunkifiedChatMessage : #chunks:", tSize(chunks)) for _, chunk in ipairs(chunks) do - self.hooks["SendChatMessage"](chunk, ...); + self:SendOriginal(chunk, ...) end else - self.hooks["SendChatMessage"](message, ...); + self:SendOriginal(message, ...) end end @@ -886,8 +886,18 @@ function Grichelde:CheckAndExtractMessageTypeTarget(message) -- special reply handling if ("/r" == chatCmd) or ("/reply" == chatCmd) then -- reuse last type and target if possible - local lastTold, lastToldType = ChatEdit_GetLastToldTarget() + -- TODO: add the Retail 12.x replacement of ChatEdit_GetLastToldTarget once confirmed in-game + local lastTold, lastToldType + if (_G.ChatEdit_GetLastToldTarget ~= nil) then + lastTold, lastToldType = _G.ChatEdit_GetLastToldTarget() + end self:DebugPrint("CheckAndExtractMessageTypeTarget : lastTell, lastTellType =", lastTold, lastToldType) + + if (nilOrEmpty(lastTold)) then + self:ErrorPrint(self.L.Error_InvalidWhisperTarget) + -- dont send text at all + return nil + end return targetAndText, lastToldType or "WHISPER", lastTold elseif ("/tt" == chatCmd) then -- determine target from game world selection @@ -931,7 +941,7 @@ function Grichelde:CheckAndExtractMessageTypeTarget(message) local _, _, channelNumber = find(chatCmd, "^/(%d+)") if (channelNumber ~= nil) then local channelId = GetChannelName(channelNumber) - if (channelId ~= nil) then + if (channelId ~= nil) and (channelId > 0) then return targetAndText, "CHANNEL", channelId end end @@ -950,8 +960,50 @@ function Grichelde:CheckAndExtractMessageTypeTarget(message) end end +--- Hooks C_ChatInfo.SendChatMessage (Retail) and the global SendChatMessage (Classic, deprecated alias on Retail). +--- The original C_ChatInfo function is preferred for sending. +function Grichelde:HookSendChatMessage() + local chatInfo = _G.C_ChatInfo + if (chatInfo ~= nil) and (chatInfo.SendChatMessage ~= nil) then + self:RawHook(chatInfo, "SendChatMessage", "SendChatMessage", true) + self.originalSend = self.hooks[chatInfo]["SendChatMessage"] + end + + if (_G.SendChatMessage ~= nil) then + self:RawHook("SendChatMessage", true) + self.originalSend = self.originalSend or self.hooks["SendChatMessage"] + end +end + +--- Sends a message with the original (unhooked) send function. +function Grichelde:SendOriginal(...) + return self.originalSend(...) +end + +--- Midnight blocks chat modifications by addons during instance encounters. +--- Prints a notice once per lockdown. +-- @return boolean +function Grichelde:IsChatLockedDown() + local chatInfo = _G.C_ChatInfo + if (chatInfo == nil) or (chatInfo.InChatMessagingLockdown == nil) then + return false + end + + if (chatInfo.InChatMessagingLockdown()) then + if (not self.lockdownNoticeShown) then + self.lockdownNoticeShown = true + self:PrefixedPrint(self.L.Chat_Lockdown_Notice) + end + return true + end + + self.lockdownNoticeShown = false + return false +end + function Grichelde:CleanseMessage(message) local text = message or "" + local IsAddOnLoaded = (_G.C_AddOns and _G.C_AddOns.IsAddOnLoaded) or _G.IsAddOnLoaded if (IsAddOnLoaded("Misspelled")) then self:DebugPrint("Misspelled detected: cleansing message") text = _G.Misspelled:RemoveHighlighting(message) @@ -971,7 +1023,12 @@ function Grichelde:SendChatMessageOverride(message, ...) local msg, type, chan = self:CheckAndExtractMessageTypeTarget(cleasendText) if (msg ~= nil) then - msg = self:ReplaceText(msg) + if (not self:IsChatLockedDown()) then + msg = self:ReplaceText(msg) + else + -- do not modify message in any way during encounters + -- msg = msg .. self.L.Chat_Lockdown_Suffix + end if (type ~= nil) then self:SendChunkifiedChatMessage(msg, type, fallbackLang, chan, ...) @@ -989,19 +1046,39 @@ end -- @param language string -- @param channel string function Grichelde:SendChatMessage(message, type, ...) + if (self.isReplacing) then + -- re-entered through the other hooked send function, text is already replaced + self:DebugPrint("SendChatMessage : already replacing") + self:SendOriginal(message, type, ...) + return + end + + self.isReplacing = true + -- always reset the guard, even if replacing fails + local ok, err = pcall(self.ReplaceAndSendChatMessage, self, message, type, ...) + self.isReplacing = false + if (not ok) then + error(err, 0) + end +end + +function Grichelde:ReplaceAndSendChatMessage(message, type, ...) if (not self.db.profile.enabled) then self:DebugPrint("SendChatMessage : disabled") - self.hooks["SendChatMessage"](message, type, ...); + self:SendOriginal(message, type, ...) elseif (nilOrEmpty(message)) then self:DebugPrint("SendChatMessage : no text") - self.hooks["SendChatMessage"](message, type, ...); + self:SendOriginal(message, type, ...) + elseif (self:IsChatLockedDown()) then + self:DebugPrint("SendChatMessage : chat lockdown") + self:SendOriginal(message, type, ...) else local cleasendText = self:CleanseMessage(message) if (self:CheckReplacementAllowed(cleasendText, type)) then cleasendText = self:ReplaceText(cleasendText) self:SendChunkifiedChatMessage(cleasendText, type, ...) else - self.hooks["SendChatMessage"](message, type, ...); + self:SendOriginal(message, type, ...) end end end \ No newline at end of file diff --git a/GricheldeConstants.lua b/GricheldeConstants.lua index b7870de..99c2006 100644 --- a/GricheldeConstants.lua +++ b/GricheldeConstants.lua @@ -106,17 +106,17 @@ Grichelde.SUPPORTED_CHAT_COMMANDS = { } Grichelde.BLIZZ_TYPE_TO_OPTIONS = { - ["SAY"] = "say", - ["EMOTE"] = "emote", - ["YELL"] = "yell", - ["PARTY"] = "party", - ["GUILD"] = "guild", - ["OFFICER"] = "officer", - ["RAID"] = "raid", - ["RAID_WARNING"] = "raidWarning", - ["INSTANCE"] = "instance", - ["BATTLEGROUND"] = "battleground", - ["WHISPER"] = "whisper", + ["SAY"] = "say", + ["EMOTE"] = "emote", + ["YELL"] = "yell", + ["PARTY"] = "party", + ["GUILD"] = "guild", + ["OFFICER"] = "officer", + ["RAID"] = "raid", + ["RAID_WARNING"] = "raidWarning", + ["INSTANCE_CHAT"] = "instance", + ["BATTLEGROUND"] = "battleground", + ["WHISPER"] = "whisper", } -- do not replace these patterns @@ -486,7 +486,6 @@ end -- faster function lookups by mapping to local refs Grichelde.F = { - IsAddOnLoaded = _G.IsAddOnLoaded, assert = _G.assert, type = _G.type, print = _G.print, diff --git a/GricheldeMinimap.lua b/GricheldeMinimap.lua index 12f1fae..b91370d 100644 --- a/GricheldeMinimap.lua +++ b/GricheldeMinimap.lua @@ -48,7 +48,7 @@ function Grichelde:MinimapButton() icon = "Interface\\Icons\\Spell_Holy_Silence", --icon = ([[Interface\Addons\%s\%s]]):format(self.name, self.name), OnClick = clickHandler, - OnRightClick = function() self:ShowMappings() end, + OnRightClick = function() self:ToogleMappings() end, OnTooltipShow = updateTooltip, iconR = darkened, iconG = darkened, diff --git a/GricheldeOptions.lua b/GricheldeOptions.lua index 252df78..4e389cd 100644 --- a/GricheldeOptions.lua +++ b/GricheldeOptions.lua @@ -415,9 +415,7 @@ function Grichelde:SetupOptions() -- Adding options to blizzard frame LibStub("AceConfig-3.0"):RegisterOptionsTable(self.name, options) local dialog = LibStub("AceConfigDialog-3.0") - local _, categoryID = dialog:AddToBlizOptions(self.name, self.L.AddonName) - - self.optionsCategoryID = categoryID + dialog:AddToBlizOptions(self.name, self.L.AddonName) return options, dialog end @@ -466,8 +464,9 @@ function Grichelde:RefreshProfiles(event, _, profileName) end function Grichelde:ToggleOptions() - self:DebugPrint("ToggleOptions : options open: ", not not self.dialog.OpenFrames[self.name]) - if (self.dialog ~= nil) and (self.dialog.OpenFrames[self.name] ~= nil) then + local isOpen = (self.dialog ~= nil) and (self.dialog.OpenFrames[self.name] ~= nil) + self:DebugPrint("ToggleOptions : options open: ", isOpen) + if (isOpen) then self:CloseOptions() else self:OpenOptions() diff --git a/GricheldeUtils.lua b/GricheldeUtils.lua index c05174e..38a4c27 100644 --- a/GricheldeUtils.lua +++ b/GricheldeUtils.lua @@ -171,7 +171,7 @@ end --- Print UI options to chat frame function Grichelde:PrintOptions() self:PrefixedPrint(cPrefix(self.L.Debug_Options)) - self:LogPrint(-1, function(...) + self:LogPrint(function(...) print(cPrefix(self.L.AddonName) .. ":", self:Format(...)) end, self.options.args.replacements.args) end @@ -179,7 +179,7 @@ end --- Print current DB profile function Grichelde:PrintProfile() self:PrefixedPrint(cPrefix(self.L.Debug_Profile)) - self:LogPrint(-1, function(...) + self:LogPrint(function(...) print(cPrefix(self.L.AddonName) .. ":", self:Format(...)) end, self.db.profile) end @@ -187,7 +187,7 @@ end --- Print DB replacements to chat frame function Grichelde:PrintMappings() self:PrefixedPrint(cPrefix(self.L.Debug_Mappings)) - self:LogPrint(-1, function(...) + self:LogPrint(function(...) print(cPrefix(self.L.AddonName) .. ":", self:Format(...)) end, self.db.profile.replacements) end diff --git a/localisation/deDE.lua b/localisation/deDE.lua index 08e3e0e..660e91d 100644 --- a/localisation/deDE.lua +++ b/localisation/deDE.lua @@ -33,6 +33,8 @@ L.Error_InvalidCommand = "Ung\195\188ltiger Befehl" L.Error_InvalidChannel = "Ung\195\188ltiger Kanal" L.Error_InvalidWhisperTarget = "Ung\195\188ltiger Fl\195\188sterziel" L.Error_UnsupportedChannel = "Nicht unterst\195\188tzter Kanal" +L.Chat_Lockdown_Notice = "Der Chat ist w\195\164hrend dieses Kampfes durch das Spiel gesperrt, Grichelde hält sich zurück." +L.Chat_Lockdown_Suffix = "(ignoriert von Grichelde)" -- profiles L.Profiles_Available = "Verf\195\188gbare Profile:" diff --git a/localisation/enUS.lua b/localisation/enUS.lua index 998af5d..bcd22cc 100644 --- a/localisation/enUS.lua +++ b/localisation/enUS.lua @@ -33,6 +33,8 @@ L.Error_InvalidCommand = "Invalid command" L.Error_InvalidChannel = "Invalid channel" L.Error_InvalidWhisperTarget = "Invalid whisper target" L.Error_UnsupportedChannel = "Unsupported channel" +L.Chat_Lockdown_Notice = "Chat is locked by the game during this encounter. Grichelde will remain passive." +L.Chat_Lockdown_Suffix = "(ignored by Grichelde)" -- profiles L.Profiles_Available = "Available profiles:"