From 3c4083b3db35c92ddfebe06895277b3e7d58061c Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Mon, 7 Sep 2026 09:52:54 +0200 Subject: [PATCH] Add optimized TokensWorth chat and loot modules Add Block NPC Spam, World Chat Hider, and Loot Quality to Extras with shared chat filtering, event-driven loot quality handling, runtime safety fixes, and updated documentation/provenance. --- README.md | 11 +++- ShaguTweaks-extras.toc | 6 ++ THIRD_PARTY_NOTICES.md | 3 + mods/chat-filter-common.lua | 68 +++++++++++++++++++ mods/chat-npc-spam.lua | 92 ++++++++++++++++++++++++++ mods/loot-quality.lua | 128 ++++++++++++++++++++++++++++++++++++ mods/world-chat-hider.lua | 59 +++++++++++++++++ 7 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 mods/chat-filter-common.lua create mode 100644 mods/chat-npc-spam.lua create mode 100644 mods/loot-quality.lua create mode 100644 mods/world-chat-hider.lua diff --git a/README.md b/README.md index 3021195..027ca3c 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,11 @@ Settings: **Esc → Advanced Options**. - Buy Em All uses ClassicAPI merchant, item count and bag-family data, including specialty bags. - Added modifier-aware Key-Down Casting with Shift/Ctrl/Alt binding support and an independent Alt Self-Cast option. - Added Metric Range for metre labels in range tooltips without changing numeric values. -- Added optimized modules adapted from TokensWorth/ShaguTweaks-mods: Mouseover Right bars, Hide Macro Text, Unit Frame Abbreviated Names, Cursor Tooltip, Hide Combat Tooltip and MiniMap Framerate & Latency. +- Added optimized modules adapted from TokensWorth/ShaguTweaks-mods: Mouseover Right bars, Hide Macro Text, Unit Frame Abbreviated Names, Cursor Tooltip, Hide Combat Tooltip, MiniMap Framerate & Latency, Block NPC Spam, World Chat Hider and Loot Quality. - MiniMap Framerate & Latency updates both counters through one ClassicAPI ticker instead of permanent per-frame handlers. +- Block NPC Spam and World Chat Hider share one on-demand chat filter dispatcher instead of stacking separate global chat wrappers. +- World Chat Hider suppresses World messages locally in instances and never leaves or rejoins the channel. +- Loot Quality is event-driven and checks all current loot slots instead of only visible loot buttons. - Hide Combat Tooltip uses ClassicAPI modifier-state events instead of permanent per-frame modifier polling. - Various stability fixes across legacy ShaguTweaks Extras modules. @@ -96,6 +99,10 @@ Other modules can also benefit indirectly from ClassicAPI through shared ShaguTw - Auction Alt-Buy - Buy Em All +### Loot + +- Loot Quality + ### Tooltip & Items - Metric Range @@ -113,6 +120,8 @@ Other modules can also benefit indirectly from ClassicAPI through shared ShaguTw ### Chat +- Block NPC Spam +- World Chat Hider - Chat Timestamps - Center Text Input Box - Enable Text Shadow diff --git a/ShaguTweaks-extras.toc b/ShaguTweaks-extras.toc index 93f065d..20cdd5f 100644 --- a/ShaguTweaks-extras.toc +++ b/ShaguTweaks-extras.toc @@ -39,6 +39,9 @@ mods\bag-search.lua mods\auction-alt-buy.lua mods\buy-em-all.lua +# loot +mods\loot-quality.lua + # tooltip & items mods\metric-range.lua mods\cursor-tooltip.lua @@ -56,6 +59,9 @@ mods\macro-icons.lua mods\macro-tweaks.lua # chat +mods\chat-filter-common.lua +mods\chat-npc-spam.lua +mods\world-chat-hider.lua mods\chat-input-center.lua mods\chat-text-shadow.lua mods\chat-timestamps.lua diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index c283563..5ea40a3 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -38,6 +38,9 @@ The following modules in this fork contain code adapted from - `mods/cursor-tooltip.lua` - `mods/hide-combat-tooltip.lua` - `mods/minimap-framerate-latency.lua` +- `mods/chat-npc-spam.lua` +- `mods/world-chat-hider.lua` +- `mods/loot-quality.lua` Source: - https://github.com/TokensWorth/ShaguTweaks-mods diff --git a/mods/chat-filter-common.lua b/mods/chat-filter-common.lua new file mode 100644 index 0000000..4b9ce63 --- /dev/null +++ b/mods/chat-filter-common.lua @@ -0,0 +1,68 @@ +-- Shared chat-filter dispatcher for Extras modules. +-- Installs one ChatFrame_OnEvent wrapper only when a filter is enabled. + +local Filter = ShaguTweaks.ExtrasChatFilter + +if not Filter then + Filter = { + handlers = {}, + order = {}, + known = {}, + installed = false, + installer = nil, + } + ShaguTweaks.ExtrasChatFilter = Filter +end + +function Filter:Install() + if self.installed then return end + + self.installed = true + self.original = ChatFrame_OnEvent + + ChatFrame_OnEvent = function(event) + for i = 1, table.getn(Filter.order) do + local current = Filter.handlers[Filter.order[i]] + if current and current(event) then + return + end + end + + if Filter.original then + return Filter.original(event) + end + end +end + +function Filter:ScheduleInstall() + if self.installed or self.installer then return end + + -- ShaguTweaks enables modules during VARIABLES_LOADED in unspecified table + -- order. Install on PLAYER_ENTERING_WORLD so this dispatcher becomes the + -- outer wrapper after the core Chat Spam Filter regardless of module order. + -- That prevents locally hidden World/NPC messages from entering its cache. + self.installer = CreateFrame("Frame") + self.installer:RegisterEvent("PLAYER_ENTERING_WORLD") + self.installer:SetScript("OnEvent", function() + Filter:Install() + this:UnregisterAllEvents() + this:Hide() + Filter.installer = nil + end) +end + +function Filter:Register(key, handler) + if type(key) ~= "string" or type(handler) ~= "function" then return end + + if not self.known[key] then + self.known[key] = true + self.order[table.getn(self.order) + 1] = key + end + + self.handlers[key] = handler + self:ScheduleInstall() +end + +function Filter:Unregister(key) + self.handlers[key] = nil +end diff --git a/mods/chat-npc-spam.lua b/mods/chat-npc-spam.lua new file mode 100644 index 0000000..ac5df78 --- /dev/null +++ b/mods/chat-npc-spam.lua @@ -0,0 +1,92 @@ +-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons). + +local T = ShaguTweaks.T +local Filter = ShaguTweaks.ExtrasChatFilter + +local module = ShaguTweaks:register({ + title = T["Block NPC Spam"], + description = T["Blocks known repetitive NPC say/yell spam messages."], + expansions = { ["vanilla"] = true, ["tbc"] = nil }, + category = T["Chat"], + enabled = nil, +}) + +local blockedEvents = { + ["CHAT_MSG_MONSTER_SAY"] = true, + ["CHAT_MSG_MONSTER_YELL"] = true, +} + +local blockedNPCs = {} +local npcNames = { + -- enUS + "Tansy Sparkpen", + "Fara Boltbreaker", + "Shellcoin Promoter", + + -- ruRU + "Зазывала ярмарки Новолуния", + "Томас Миллер", + "Уильям", + "Донна", + "Жюстина Демалье", + "Мерлис Малаган", + "Лиана Пирс", + "Сюзанна", + "Джейни Аншип", +} + +for _, name in pairs(npcNames) do + blockedNPCs[name] = true + blockedNPCs[string.lower(name)] = true +end + +local blockedPhrases = { + { "shellcoin", "invest" }, + { "shells", "trade" }, + { "shells", "money" }, +} + +local function IsBlockedNPC(sender) + if not sender then return false end + return blockedNPCs[sender] or blockedNPCs[string.lower(sender)] or false +end + +local function IsBlockedPhrase(message) + if not message then return false end + + local normalized = string.lower(string.gsub(message, "[^A-Za-z0-9]", "")) + + for _, phrase in pairs(blockedPhrases) do + local matched = true + + for _, word in pairs(phrase) do + if not string.find(normalized, word, 1, true) then + matched = false + break + end + end + + if matched then return true end + end + + return false +end + +local function FilterNPCSpam(event) + if not blockedEvents[event] then return false end + if not arg1 or not arg2 then return false end + + return IsBlockedNPC(arg2) or IsBlockedPhrase(arg1) +end + +module.enable = function(self) + if Filter then + Filter:Register("BlockNPCSpam", FilterNPCSpam) + end +end + +module.disable = function(self) + if Filter then + Filter:Unregister("BlockNPCSpam") + end +end diff --git a/mods/loot-quality.lua b/mods/loot-quality.lua new file mode 100644 index 0000000..0ff713f --- /dev/null +++ b/mods/loot-quality.lua @@ -0,0 +1,128 @@ +-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons). + +local T = ShaguTweaks.T + +local module = ShaguTweaks:register({ + title = T["Loot Quality"], + description = T["Colors the loot frame using the highest item quality in the current loot window."], + expansions = { ["vanilla"] = true, ["tbc"] = nil }, + category = T["Loot"], + enabled = nil, +}) + +local MIN_QUALITY = 2 -- uncommon / green + +local function FindLootTitle() + local regions = { LootFrame:GetRegions() } + + for i = 1, table.getn(regions) do + local region = regions[i] + -- Some Vanilla/Turtle regions expose GetText without being FontStrings. + -- Only keep a region that can actually be recolored. + if region + and type(region.GetText) == "function" + and type(region.SetTextColor) == "function" + and region:GetText() == ITEMS then + return region + end + end +end + +module.enable = function(self) + if not self.border then + self.border = LootFrame:CreateTexture(nil, "OVERLAY") + self.border:SetTexture("Interface\\LootFrame\\UI-LootPanel") + self.border:SetPoint("TOPLEFT", LootFrame, "TOPLEFT", -4, 3) + self.border:SetPoint("BOTTOMRIGHT", LootFrame, "BOTTOMRIGHT", 5, -3) + self.border:SetDrawLayer("ARTWORK") + + self.highlight = LootFrame:CreateTexture(nil, "OVERLAY") + self.highlight:SetTexture("Interface\\LootFrame\\UI-LootPanel") + self.highlight:SetAllPoints(self.border) + self.highlight:SetDrawLayer("ARTWORK") + self.highlight:SetBlendMode("ADD") + end + + self.title = self.title or FindLootTitle() + + if self.title + and not self.titleColor + and type(self.title.GetTextColor) == "function" then + local r, g, b, a = self.title:GetTextColor() + self.titleColor = { r or 1, g or .82, b or 0, a or 1 } + end + + local function RestoreTitle() + if not self.title or type(self.title.SetTextColor) ~= "function" then return end + + local color = self.titleColor + if color then + self.title:SetTextColor(color[1], color[2], color[3], color[4]) + else + self.title:SetTextColor(1, .82, 0) + end + end + + local function ResetVisuals() + self.border:Hide() + self.highlight:Hide() + RestoreTitle() + end + + local function UpdateQuality() + local highestQuality = 0 + local count = GetNumLootItems() or 0 + + for i = 1, count do + if LootSlotIsItem(i) then + local _, _, _, quality = GetLootSlotInfo(i) + quality = tonumber(quality) or 0 + if quality > highestQuality then + highestQuality = quality + end + end + end + + local color = ITEM_QUALITY_COLORS and ITEM_QUALITY_COLORS[highestQuality] + if highestQuality < MIN_QUALITY or not color then + ResetVisuals() + return + end + + self.border:SetVertexColor(color.r, color.g, color.b) + self.highlight:SetVertexColor(color.r, color.g, color.b) + self.border:Show() + self.highlight:Show() + + if self.title and type(self.title.SetTextColor) == "function" then + self.title:SetTextColor(color.r, color.g, color.b) + end + end + + self.ResetVisuals = ResetVisuals + self.events = self.events or CreateFrame("Frame") + self.events:UnregisterAllEvents() + self.events:RegisterEvent("LOOT_OPENED") + self.events:RegisterEvent("LOOT_SLOT_CLEARED") + self.events:RegisterEvent("LOOT_CLOSED") + + self.events:SetScript("OnEvent", function() + if event == "LOOT_CLOSED" then + ResetVisuals() + else + UpdateQuality() + end + end) + + ResetVisuals() +end + +module.disable = function(self) + if self.events then + self.events:UnregisterAllEvents() + end + + if self.ResetVisuals then + self.ResetVisuals() + end +end diff --git a/mods/world-chat-hider.lua b/mods/world-chat-hider.lua new file mode 100644 index 0000000..6b67eac --- /dev/null +++ b/mods/world-chat-hider.lua @@ -0,0 +1,59 @@ +-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons). + +local T = ShaguTweaks.T +local Filter = ShaguTweaks.ExtrasChatFilter + +local module = ShaguTweaks:register({ + title = T["World Chat Hider"], + description = T["Hides World channel messages while inside an instance without leaving the channel."], + expansions = { ["vanilla"] = true, ["tbc"] = nil }, + category = T["Chat"], + enabled = nil, +}) + +local function IsInsideInstance() + if type(IsInInstance) ~= "function" then return false end + + local inside = IsInInstance() + return inside == 1 or inside == true +end + +local function IsWorldChannel() + -- Vanilla 1.12 CHAT_MSG_CHANNEL exposes the base channel name in arg9. + -- Custom channel names are not localized, so comparing the actual event is + -- both cheaper and safer than querying GetChannelName("world") every time. + if arg9 and string.lower(arg9) == "world" then + return true + end + + -- Conservative fallback for clients that omit arg9: arg4 is the numbered + -- channel label (for example "5. World"). + if arg4 then + local full = string.lower(arg4) + if full == "world" then return true end + + local _, _, base = string.find(full, "^%d+%.%s*(.+)$") + if base == "world" then return true end + end + + return false +end + +local function FilterWorldChat(event) + if event ~= "CHAT_MSG_CHANNEL" then return false end + if not IsInsideInstance() then return false end + + return IsWorldChannel() +end + +module.enable = function(self) + if Filter then + Filter:Register("WorldChatHider", FilterWorldChat) + end +end + +module.disable = function(self) + if Filter then + Filter:Unregister("WorldChatHider") + end +end