From 34a8024ce402aec597eef44a8e8c5bcad99296f0 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Thu, 12 Dec 2024 16:13:36 +0100 Subject: [PATCH] Rework addon loading to properly load on ADDON_LOADED insteaad of immediately --- DeathReporter.lua | 8 + DumpTable.lua | 3 +- Heimdall.lua | 401 +++++++++++++----------- Heimdall.toc | 4 +- Messenger.lua | 154 +++++----- Spotter.lua | 205 +++++++------ Whoer.lua | 762 +++++++++++++++++++++++----------------------- 7 files changed, 801 insertions(+), 736 deletions(-) create mode 100644 DeathReporter.lua diff --git a/DeathReporter.lua b/DeathReporter.lua new file mode 100644 index 0000000..94ea83e --- /dev/null +++ b/DeathReporter.lua @@ -0,0 +1,8 @@ + +local addonname, data = ... +---@cast data HeimdallData +---@cast addonname string + +data.DeathReporter = {} +function data.DeathReporter.Init() +end diff --git a/DumpTable.lua b/DumpTable.lua index c91c545..b1dd601 100644 --- a/DumpTable.lua +++ b/DumpTable.lua @@ -1,5 +1,6 @@ -local _, data = ... +local addonname, data = ... ---@cast data HeimdallData +---@cast addonname string if not data.dumpTable then ---@param table table diff --git a/Heimdall.lua b/Heimdall.lua index e538e6e..2980da0 100644 --- a/Heimdall.lua +++ b/Heimdall.lua @@ -1,198 +1,245 @@ -local _, data = ... +local addonname, data = ... ---@cast data HeimdallData +---@cast addonname string ----@class Heimdall_Data ----@field who { data: table } -if not Heimdall_Data then Heimdall_Data = {} end +local function init() + ---@class Heimdall_Data + ---@field who { data: table } + if not Heimdall_Data then Heimdall_Data = {} end + if not Heimdall_Data.config then Heimdall_Data.config = {} end --- We don't care about these persisting --- Actually we don't want some of them to persist --- For those we DO we use (global) Heimdall_Data + -- We don't care about these persisting + -- Actually we don't want some of them to persist + -- For those we DO we use (global) Heimdall_Data ----@class HeimdallData ----@field config HeimdallConfig ----@field raceMap table ----@field classColors table ----@field stinkies table ----@field messenger HeimdallMessengerData ----@field who HeimdallWhoData ----@field dumpTable fun(table: any, depth?: number): nil ----@field utf8len fun(input: string): number ----@field padString fun(input: string, targetLength: number, left?: boolean): string + ---@class HeimdallData + ---@field config HeimdallConfig + ---@field raceMap table + ---@field classColors table + ---@field stinkies table + ---@field messenger HeimdallMessengerData + ---@field who HeimdallWhoData + ---@field dumpTable fun(table: any, depth?: number): nil + ---@field utf8len fun(input: string): number + ---@field padString fun(input: string, targetLength: number, left?: boolean): string + ---@field GetOrDefault fun(table: table, keys: string[], default: any): any + ---@field Whoer { Init: fun() } + ---@field Messenger { Init: fun() } + ---@field Spotter { Init: fun() } + ---@field DeathReporter { Init: fun() } ---- Config --- ----@class HeimdallConfig ----@field spotter HeimdallSpotterConfig ----@field who HeimdallWhoConfig ----@field messenger HeimdallMessengerConfig ----@field whisperNotify table + --- Config --- + ---@class HeimdallConfig + ---@field spotter HeimdallSpotterConfig + ---@field who HeimdallWhoConfig + ---@field messenger HeimdallMessengerConfig + ---@field whisperNotify table ----@class HeimdallSpotterConfig ----@field enabled boolean ----@field everyone boolean ----@field hostile boolean ----@field alliance boolean ----@field stinky boolean ----@field notifyChannel string ----@field zoneOverride string? ----@field throttleTime number + ---@class HeimdallSpotterConfig + ---@field enabled boolean + ---@field everyone boolean + ---@field hostile boolean + ---@field alliance boolean + ---@field stinky boolean + ---@field notifyChannel string + ---@field zoneOverride string? + ---@field throttleTime number ----@class HeimdallWhoConfig ----@field enabled boolean ----@field ignored table ----@field notifyChannel string ----@field ttl number ----@field doWhisper boolean ----@field zoneNotifyFor table + ---@class HeimdallWhoConfig + ---@field enabled boolean + ---@field ignored table + ---@field notifyChannel string + ---@field ttl number + ---@field doWhisper boolean + ---@field zoneNotifyFor table ----@class HeimdallMessengerConfig ----@field enabled boolean + ---@class HeimdallMessengerConfig + ---@field enabled boolean ---- Data --- ----@class HeimdallMessengerData ----@field queue table ----@field ticker number? + --- Data --- + ---@class HeimdallMessengerData + ---@field queue table + ---@field ticker number? ----@class HeimdallWhoData ----@field updateTicker number? ----@field whoTicker number? ----@field ignored table + ---@class HeimdallWhoData + ---@field updateTicker number? + ---@field whoTicker number? + ---@field ignored table -data.messenger = { - queue = {} -} -data.who = { - ignored = {}, -} + data.GetOrDefault = function(table, keys, default) + local value = default + if not table then return value end + if not keys then return value end -data.config = { - spotter = { - enabled = true, - everyone = false, - hostile = true, - alliance = false, - stinky = false, - notifyChannel = "Foobar", - zoneOverride = nil, - throttleTime = 1 - }, - who = { - enabled = true, - ignored = {}, - notifyChannel = "Foobar", - ttl = 10, - doWhisper = true, - zoneNotifyFor = { - ["Orgrimmar"] = true, - ["Thunder Bluff"] = true, - ["Undercity"] = true, - ["Durotar"] = true, - ["Echo Isles"] = true, - ["Valley of Trials"] = true, - } - }, - messenger = { - enabled = true - }, - whisperNotify = { - "Extazyk", - "Smokefire", - "Smokemantra", - "Хихихантер", - "Муркот", - "Растафаркрай", - "Frosstmorn", - "Pulsjkee", - "Paskoo", - "Totleta", - "Healleta", - "Deathleta", - "Shootleta", - "Stableta" + local traverse = table + for i = 1, #keys do + local key = keys[i] + if traverse[key] then + traverse = traverse[key] + else + break + end + + if i == #keys then + value = traverse + end + end + return value + end + + data.messenger = { + queue = {} + } + data.who = { + ignored = {}, } -} -data.raceMap = { - ["Orc"] = "Horde", - ["Undead"] = "Horde", - ["Tauren"] = "Horde", - ["Troll"] = "Horde", - ["Blood Elf"] = "Horde", - ["Goblin"] = "Horde", - ["Human"] = "Alliance", - ["Dwarf"] = "Alliance", - ["Night Elf"] = "Alliance", - ["Gnome"] = "Alliance", - ["Draenei"] = "Alliance", - ["Worgen"] = "Alliance", - ["Vulpera"] = "Horde", - ["Nightborne"] = "Horde", - ["Zandalari Troll"] = "Horde", - ["Kul Tiran"] = "Alliance", - ["Dark Iron Dwarf"] = "Alliance", - ["Void Elf"] = "Alliance", - ["Lightforged Draenei"] = "Alliance", - ["Mechagnome"] = "Alliance", - ["Mag'har Orc"] = "Horde" -} + data.config = { + spotter = { + enabled = data.GetOrDefault(Heimdall_Data, { "config", "spotter", "enabled" }, true), + everyone = data.GetOrDefault(Heimdall_Data, { "config", "spotter", "everyone" }, false), + hostile = data.GetOrDefault(Heimdall_Data, { "config", "spotter", "hostile" }, true), + alliance = data.GetOrDefault(Heimdall_Data, { "config", "spotter", "alliance" }, false), + stinky = data.GetOrDefault(Heimdall_Data, { "config", "spotter", "stinky" }, false), + notifyChannel = data.GetOrDefault(Heimdall_Data, { "config", "spotter", "notifyChannel" }, "Foobar"), + zoneOverride = data.GetOrDefault(Heimdall_Data, { "config", "spotter", "zoneOverride" }, nil), + throttleTime = data.GetOrDefault(Heimdall_Data, { "config", "spotter", "throttleTime" }, 1) + }, + who = { + enabled = data.GetOrDefault(Heimdall_Data, { "config", "who", "enabled" }, true), + ignored = data.GetOrDefault(Heimdall_Data, { "config", "who", "ignored" }, {}), + notifyChannel = data.GetOrDefault(Heimdall_Data, { "config", "who", "notifyChannel" }, "Foobar"), + ttl = data.GetOrDefault(Heimdall_Data, { "config", "who", "ttl" }, 10), + doWhisper = data.GetOrDefault(Heimdall_Data, { "config", "who", "doWhisper" }, true), + zoneNotifyFor = data.GetOrDefault(Heimdall_Data, { "config", "who", "zoneNotifyFor" }, { + ["Orgrimmar"] = true, + ["Thunder Bluff"] = true, + ["Undercity"] = true, + ["Durotar"] = true, + ["Echo Isles"] = true, + ["Valley of Trials"] = true, + }), + }, + messenger = { + enabled = data.GetOrDefault(Heimdall_Data, { "config", "messenger", "enabled" }, true), + }, + whisperNotify = data.GetOrDefault(Heimdall_Data, { "config", "whisperNotify" }, { + -- "Extazyk", + -- "Smokefire", + -- "Smokemantra", + -- "Хихихантер", + -- "Муркот", + -- "Растафаркрай", + -- "Frosstmorn", + -- "Pulsjkee", + -- "Paskoo", + "Totleta", + "Healleta", + "Deathleta", + "Shootleta", + "Stableta" + }), + } + --/run Heimdall_Data = {config = {who = {enabled = false}}} + --/dump Heimdall_Data + print("138 " .. tostring(Heimdall_Data.config.who.enabled)) + print("139 " .. tostring(data.GetOrDefault(Heimdall_Data, { "config", "who", "enabled" }, true))) + print("140 " .. tostring(data.config.who.enabled)) ----@type table -data.classColors = { - ["Warrior"] = "C69B6D", - ["Paladin"] = "F48CBA", - ["Hunter"] = "AAD372", - ["Rogue"] = "FFF468", - ["Priest"] = "FFFFFF", - ["Death Knight"] = "C41E3A", - ["Shaman"] = "0070DD", - ["Mage"] = "3FC7EB", - ["Warlock"] = "8788EE", - ["Monk"] = "00FF98", - ["Druid"] = "FF7C0A", - ["Demon Hunter"] = "A330C9" -} + data.raceMap = { + ["Orc"] = "Horde", + ["Undead"] = "Horde", + ["Tauren"] = "Horde", + ["Troll"] = "Horde", + ["Blood Elf"] = "Horde", + ["Goblin"] = "Horde", + ["Human"] = "Alliance", + ["Dwarf"] = "Alliance", + ["Night Elf"] = "Alliance", + ["Gnome"] = "Alliance", + ["Draenei"] = "Alliance", + ["Worgen"] = "Alliance", + ["Vulpera"] = "Horde", + ["Nightborne"] = "Horde", + ["Zandalari Troll"] = "Horde", + ["Kul Tiran"] = "Alliance", + ["Dark Iron Dwarf"] = "Alliance", + ["Void Elf"] = "Alliance", + ["Lightforged Draenei"] = "Alliance", + ["Mechagnome"] = "Alliance", + ["Mag'har Orc"] = "Horde" + } -data.stinkies = {} + data.classColors = { + ["Warrior"] = "C69B6D", + ["Paladin"] = "F48CBA", + ["Hunter"] = "AAD372", + ["Rogue"] = "FFF468", + ["Priest"] = "FFFFFF", + ["Death Knight"] = "C41E3A", + ["Shaman"] = "0070DD", + ["Mage"] = "3FC7EB", + ["Warlock"] = "8788EE", + ["Monk"] = "00FF98", + ["Druid"] = "FF7C0A", + ["Demon Hunter"] = "A330C9" + } ----@param input string ----@return number -data.utf8len = function(input) - if not input then - return 0 - end - local len = 0 - local i = 1 - local n = #input - while i <= n do - local c = input:byte(i) - if c >= 0 and c <= 127 then - i = i + 1 - elseif c >= 194 and c <= 223 then - i = i + 2 - elseif c >= 224 and c <= 239 then - i = i + 3 - elseif c >= 240 and c <= 244 then - i = i + 4 - else - i = i + 1 + data.stinkies = {} + + ---@param input string + ---@return number + data.utf8len = function(input) + if not input then + return 0 end - len = len + 1 - end - return len -end ----@param input string ----@param targetLength number ----@param left boolean ----@return string -data.padString = function(input, targetLength, left) - left = left or false - local len = data.utf8len(input) - if len < targetLength then - if left then - input = input .. string.rep(" ", targetLength - len) - else - input = string.rep(" ", targetLength - len) .. input + local len = 0 + local i = 1 + local n = #input + while i <= n do + local c = input:byte(i) + if c >= 0 and c <= 127 then + i = i + 1 + elseif c >= 194 and c <= 223 then + i = i + 2 + elseif c >= 224 and c <= 239 then + i = i + 3 + elseif c >= 240 and c <= 244 then + i = i + 4 + else + i = i + 1 + end + len = len + 1 end + return len end - return input + ---@param input string + ---@param targetLength number + ---@param left boolean + ---@return string + data.padString = function(input, targetLength, left) + left = left or false + local len = data.utf8len(input) + if len < targetLength then + if left then + input = input .. string.rep(" ", targetLength - len) + else + input = string.rep(" ", targetLength - len) .. input + end + end + return input + end + + data.Whoer.Init() + data.Messenger.Init() + data.Spotter.Init() + data.DeathReporter.Init() end + +local loadedFrame = CreateFrame("Frame") +loadedFrame:RegisterEvent("ADDON_LOADED") +loadedFrame:SetScript("OnEvent", function(self, event, addonName) + if addonName == addonname then + init() + end +end) diff --git a/Heimdall.toc b/Heimdall.toc index 007f7ed..cbc82f4 100644 --- a/Heimdall.toc +++ b/Heimdall.toc @@ -5,9 +5,9 @@ ## SavedVariables: Heimdall_Data #core -Heimdall.lua CLEUParser.lua +DumpTable.lua Spotter.lua Whoer.lua Messenger.lua -DumpTable.lua \ No newline at end of file +Heimdall.lua \ No newline at end of file diff --git a/Messenger.lua b/Messenger.lua index 90c5803..855ede1 100644 --- a/Messenger.lua +++ b/Messenger.lua @@ -1,91 +1,95 @@ -local _, data = ... +local addonname, data = ... ---@cast data HeimdallData +---@cast addonname string -if not data.config.messenger.enabled then return end +data.Messenger = {} +function data.Messenger.Init() + if not data.config.messenger.enabled then return end ----@class Message ----@field message string ----@field channel string ----@field data string + ---@class Message + ---@field message string + ---@field channel string + ---@field data string ----@type table -local channelIdMap = {} + ---@type table + local channelIdMap = {} -local FindOrJoinChannel = function(channelName, password) - local function GetChannelId(channelName) + local FindOrJoinChannel = function(channelName, password) + local function GetChannelId(channelName) + local channels = { GetChannelList() } + for i = 1, #channels, 2 do + local id = channels[i] + local name = channels[i + 1] + if name == channelName then + return id + end + end + end + + local channelId = GetChannelId(channelName) + if not channelId then + print("Channel", tostring(channelName), "not found, joining") + if password then + JoinPermanentChannel(channelName, password) + else + JoinPermanentChannel(channelName) + end + end + channelId = GetChannelId(channelName) + channelIdMap[channelName] = channelId + return channelId + end + + local ScanChannels = function() local channels = { GetChannelList() } for i = 1, #channels, 2 do local id = channels[i] local name = channels[i + 1] - if name == channelName then - return id - end + channelIdMap[name] = id end end - local channelId = GetChannelId(channelName) - if not channelId then - print("Channel", tostring(channelName), "not found, joining") - if password then - JoinPermanentChannel(channelName, password) - else - JoinPermanentChannel(channelName) - end - end - channelId = GetChannelId(channelName) - channelIdMap[channelName] = channelId - return channelId -end + if not data.messenger then data.messenger = {} end + if not data.messenger.queue then data.messenger.queue = {} end + if not data.messenger.ticker then + data.messenger.ticker = C_Timer.NewTicker(0.2, function() + ---@type Message + local message = data.messenger.queue[1] + if not message then return end + if not message.message or message.message == "" then return end + if not message.channel or message.channel == "" then return end -local ScanChannels = function() - local channels = { GetChannelList() } - for i = 1, #channels, 2 do - local id = channels[i] - local name = channels[i + 1] - channelIdMap[name] = id - end -end - -if not data.messenger then data.messenger = {} end -if not data.messenger.queue then data.messenger.queue = {} end -if not data.messenger.ticker then - data.messenger.ticker = C_Timer.NewTicker(0.2, function() - ---@type Message - local message = data.messenger.queue[1] - if not message then return end - if not message.message or message.message == "" then return end - if not message.channel or message.channel == "" then return end - - -- Map channel names to ids - if message.channel == "CHANNEL" and message.data and string.match(message.data, "%D") then - print("Channel presented as string:", message.data) - local channelId = channelIdMap[message.data] - if not channelId then - print("Channel not found, scanning") - ScanChannels() - channelId = channelIdMap[message.data] + -- Map channel names to ids + if message.channel == "CHANNEL" and message.data and string.match(message.data, "%D") then + print("Channel presented as string:", message.data) + local channelId = channelIdMap[message.data] + if not channelId then + print("Channel not found, scanning") + ScanChannels() + channelId = channelIdMap[message.data] + end + if not channelId then + print("Channel not joined, joining") + channelId = FindOrJoinChannel(message.data) + end + print("Channel resolved to id", channelId) + message.data = channelId end - if not channelId then - print("Channel not joined, joining") - channelId = FindOrJoinChannel(message.data) - end - print("Channel resolved to id", channelId) - message.data = channelId - end - table.remove(data.messenger.queue, 1) - if not message.message or message.message == "" then return end - if not message.channel or message.channel == "" then return end - if not message.data or message.data == "" then return end - SendChatMessage(message.message, message.channel, nil, message.data) - end) + table.remove(data.messenger.queue, 1) + if not message.message or message.message == "" then return end + if not message.channel or message.channel == "" then return end + if not message.data or message.data == "" then return end + SendChatMessage(message.message, message.channel, nil, message.data) + end) + end + + --C_Timer.NewTicker(2, function() + -- print("Q") + -- table.insert(data.messenger.queue, { + -- channel = "CHANNEL", + -- data = "Foobar", + -- message = "TEST" + -- }) + --end) end - ---C_Timer.NewTicker(2, function() --- print("Q") --- table.insert(data.messenger.queue, { --- channel = "CHANNEL", --- data = "Foobar", --- message = "TEST" --- }) ---end) diff --git a/Spotter.lua b/Spotter.lua index 0167cb8..4d644be 100644 --- a/Spotter.lua +++ b/Spotter.lua @@ -1,105 +1,108 @@ -local _, data = ... +local addonname, data = ... ---@cast data HeimdallData +---@cast addonname string -if not data.config.spotter.enabled then return end - -local function FormatHP(hp) - if hp > 1e9 then - return string.format("%.1fB", hp / 1e9) - elseif hp > 1e6 then - return string.format("%.1fM", hp / 1e6) - elseif hp > 1e3 then - return string.format("%.1fK", hp / 1e3) - else - return hp +data.Spotter = {} +function data.Spotter.Init() + if not data.config.spotter.enabled then return end + local function FormatHP(hp) + if hp > 1e9 then + return string.format("%.1fB", hp / 1e9) + elseif hp > 1e6 then + return string.format("%.1fM", hp / 1e6) + elseif hp > 1e3 then + return string.format("%.1fK", hp / 1e3) + else + return hp + end end + + ---@type table + local throttleTable = {} + + ---@param unit string + ---@param name string + ---@param faction string + ---@param hostile boolean + ---@return boolean + ---@return string? error + local function ShouldNotify(unit, name, faction, hostile) + if data.config.spotter.stinky then + if data.stinkies[name] then return true end + end + if data.config.spotter.alliance then + if faction == "Alliance" then return true end + end + if data.config.spotter.hostile then + if hostile then return true end + end + return data.config.spotter.everyone + end + + ---@param unit string + ---@return string? + local function NotifySpotted(unit) + if not unit then return string.format("Could not find unit %s", tostring(unit)) end + if not UnitIsPlayer(unit) then return nil end + + local name = UnitName(unit) + if not name then return string.format("Could not find name for unit %s", tostring(unit)) end + + local time = GetTime() + if throttleTable[name] and time - throttleTable[name] < data.config.spotter.throttleTime then + return string.format("Throttled %s", tostring(name)) + end + throttleTable[name] = time + + local race = UnitRace(unit) + if not race then return string.format("Could not find race for unit %s", tostring(unit)) end + local faction = data.raceMap[race] + if not faction then return string.format("Could not find faction for race %s", tostring(race)) end + + local hostile = UnitCanAttack("player", unit) + local doNotify = ShouldNotify(unit, name, faction, hostile) + if not doNotify then return string.format("Not notifying for %s", tostring(name)) end + + local hp = UnitHealth(unit) + if not hp then return string.format("Could not find hp for unit %s", tostring(unit)) end + + local maxHp = UnitHealthMax(unit) + if not maxHp then return string.format("Could not find maxHp for unit %s", tostring(unit)) end + + local location = data.config.spotter.zoneOverride + if not location then + local zone = GetZoneText() + if not zone then return string.format("Could not find zone for unit %s", tostring(unit)) end + local subzone = GetSubZoneText() + if not subzone then subzone = "" end + location = string.format("%s (%s)", zone, subzone) + end + + local text = string.format("I see (%s) %s of race (%s) with health %s/%s at %s", + hostile and "Hostile" or "Friendly", + name, + race, + FormatHP(hp), + FormatHP(maxHp), + location) + + ---@type Message + local msg = { + channel = "CHANNEL", + data = data.config.spotter.notifyChannel, + message = text + } + data.dumpTable(msg) + table.insert(data.messenger.queue, msg) + end + + local frame = CreateFrame("Frame") + frame:RegisterEvent("NAME_PLATE_UNIT_ADDED") + frame:RegisterEvent("TARGET_UNIT_CHANGED") + frame:SetScript("OnEvent", function(self, event, unit) + local err = NotifySpotted(unit) + if err then + print(string.format("Error notifying %s: %s", tostring(unit), tostring(err))) + end + end) end - ----@type table -local throttleTable = {} - ----@param unit string ----@param name string ----@param faction string ----@param hostile boolean ----@return boolean ----@return string? error -local function ShouldNotify(unit, name, faction, hostile) - if data.config.spotter.stinky then - if data.stinkies[name] then return true end - end - if data.config.spotter.alliance then - if faction == "Alliance" then return true end - end - if data.config.spotter.hostile then - if hostile then return true end - end - return data.config.spotter.everyone -end - ----@param unit string ----@return string? -local function NotifySpotted(unit) - if not unit then return string.format("Could not find unit %s", tostring(unit)) end - if not UnitIsPlayer(unit) then return nil end - - local name = UnitName(unit) - if not name then return string.format("Could not find name for unit %s", tostring(unit)) end - - local time = GetTime() - if throttleTable[name] and time - throttleTable[name] < data.config.spotter.throttleTime then - return string.format("Throttled %s", tostring(name)) - end - throttleTable[name] = time - - local race = UnitRace(unit) - if not race then return string.format("Could not find race for unit %s", tostring(unit)) end - local faction = data.raceMap[race] - if not faction then return string.format("Could not find faction for race %s", tostring(race)) end - - local hostile = UnitCanAttack("player", unit) - local doNotify = ShouldNotify(unit, name, faction, hostile) - if not doNotify then return string.format("Not notifying for %s", tostring(name)) end - - local hp = UnitHealth(unit) - if not hp then return string.format("Could not find hp for unit %s", tostring(unit)) end - - local maxHp = UnitHealthMax(unit) - if not maxHp then return string.format("Could not find maxHp for unit %s", tostring(unit)) end - - local location = data.config.spotter.zoneOverride - if not location then - local zone = GetZoneText() - if not zone then return string.format("Could not find zone for unit %s", tostring(unit)) end - local subzone = GetSubZoneText() - if not subzone then subzone = "" end - location = string.format("%s (%s)", zone, subzone) - end - - local text = string.format("I see (%s) %s of race (%s) with health %s/%s at %s", - hostile and "Hostile" or "Friendly", - name, - race, - FormatHP(hp), - FormatHP(maxHp), - location) - - ---@type Message - local msg = { - channel = "CHANNEL", - data = data.config.spotter.notifyChannel, - message = text - } - data.dumpTable(msg) - table.insert(data.messenger.queue, msg) -end - -local frame = CreateFrame("Frame") -frame:RegisterEvent("NAME_PLATE_UNIT_ADDED") -frame:RegisterEvent("TARGET_UNIT_CHANGED") -frame:SetScript("OnEvent", function(self, event, unit) - local err = NotifySpotted(unit) - if err then - print(string.format("Error notifying %s: %s", tostring(unit), tostring(err))) - end -end) diff --git a/Whoer.lua b/Whoer.lua index 196d79e..5bef00d 100644 --- a/Whoer.lua +++ b/Whoer.lua @@ -1,402 +1,404 @@ -local _, data = ... +local addonname, data = ... ---@cast data HeimdallData +---@cast addonname string -if not data.config.who.enabled then return end -if not Heimdall_Data.who then Heimdall_Data.who = {} end -if not Heimdall_Data.who.data then Heimdall_Data.who.data = {} end -print(Heimdall_Data) -print(Heimdall_Data.who) -print(Heimdall_Data.who.data) +data.Whoer = {} +function data.Whoer.Init() + if not data.config.who.enabled then return end + if not Heimdall_Data.who then Heimdall_Data.who = {} end + if not Heimdall_Data.who.data then Heimdall_Data.who.data = {} end ----@type table -local players = {} + ---@type table + local players = {} ----@class Player ----@field name string ----@field guild string ----@field race string ----@field class string ----@field zone string ----@field lastSeenInternal number ----@field lastSeen string ----@field firstSeen string ----@field seenCount number -Player = { - ---@param name string - ---@param guild string - ---@param race string - ---@param class string - ---@param zone string - ---@return Player - new = function(name, guild, race, class, zone) - local self = setmetatable({}, { - __index = Player - }) - self.name = name - self.guild = guild - self.race = race - self.class = class - self.zone = zone - self.lastSeenInternal = GetTime() - self.lastSeen = "never" - self.firstSeen = "never" - self.seenCount = 0 - return self - end, - ---@return string - ToString = function(self) - local out = string.format("%s %s %s\nFirst: %s Last: %s Seen: %3d", - data.padString(self.name, 16, true), - data.padString(self.guild, 26, false), - data.padString(self.zone, 26, false), - data.padString(self.firstSeen, 10, true), - data.padString(self.lastSeen, 10, true), - self.seenCount) - return string.format("|cFF%s%s|r", data.classColors[self.class], out) - end, - ---@return string - NotifyMessage = function(self) - local text = string.format("%s of class %s and guild %s in %s, first seen: %s, last seen: %s, times seen: %d", - self.name, - self.class, - self.guild, - self.zone, - self.firstSeen, - self.lastSeen, - self.seenCount) - return text - end -} - ----@class WHOQuery ----@field query string ----@field filters WHOFilter[] -WHOQuery = { - ---@param query string - ---@param filters WHOFilter[] - ---@return WHOQuery - new = function(query, filters) - local self = setmetatable({}, { - __index = WHOQuery - }) - self.query = query - self.filters = filters - return self - end -} - ----@alias WHOFilter fun(name: string, guild: string, level: number, race: string, class: string, zone: string): boolean ----@type WHOFilter -local NotSiegeOfOrgrimmarFilter = function(name, guild, level, race, class, zone) - if not zone then - return false - end - return zone ~= "Siege of Orgrimmar" -end ----@type WHOFilter -local AllianceFilter = function(name, guild, level, race, class, zone) - if not race then - return false - end - if not data.raceMap[race] then - return false - end - return data.raceMap[race] == "Alliance" -end - -local whoQueryIdx = 1 ----@type table -local whoQueries = { - WHOQuery.new("g-\"БеспредеЛ\"", {}), - WHOQuery.new( - "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" z-\"Echo Isles\" r-\"Human\" r-\"Dwarf\" r-\"Night Elf\"", - { NotSiegeOfOrgrimmarFilter, AllianceFilter }), - WHOQuery.new( - "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" z-\"Echo Isles\" r-\"Gnome\" r-\"Draenei\" r-\"Worgen\"", - { NotSiegeOfOrgrimmarFilter, AllianceFilter }), - WHOQuery.new( - "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" z-\"Echo Isles\" r-\"Kul Tiran\" r-\"Dark Iron Dwarf\" r-\"Void Elf\"", - { NotSiegeOfOrgrimmarFilter, AllianceFilter }), - WHOQuery.new( - "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" z-\"Echo Isles\" r-\"Lightforged Draenei\" r-\"Mechagnome\"", - { NotSiegeOfOrgrimmarFilter, AllianceFilter }), - WHOQuery.new("Kekv Demonboo Dotmada Firobot Verminal", {}) -} -local queryPending = false -local ttl = #whoQueries * 2 ----@type WHOQuery? -local lastQuery = nil - ----@param player Player ----@return string? -local function Notify(player) - if not player then return string.format("Cannot notify for nil player %s", tostring(player)) end - if not data.config.who.zoneNotifyFor[player.zone] then - return string.format("Not notifying for zone %s", - tostring(player.zone)) - end - - local text = player:NotifyMessage() - ---@type Message - local msg = { - channel = "CHANNEL", - data = data.config.who.notifyChannel, - message = text - } - table.insert(data.messenger.queue, msg) - - if data.config.who.doWhisper then - for _, name in pairs(data.config.whisperNotify) do - ---@type Message - local msg = { - channel = "WHISPER", - data = name, - message = text - } - table.insert(data.messenger.queue, msg) + ---@class Player + ---@field name string + ---@field guild string + ---@field race string + ---@field class string + ---@field zone string + ---@field lastSeenInternal number + ---@field lastSeen string + ---@field firstSeen string + ---@field seenCount number + Player = { + ---@param name string + ---@param guild string + ---@param race string + ---@param class string + ---@param zone string + ---@return Player + new = function(name, guild, race, class, zone) + local self = setmetatable({}, { + __index = Player + }) + self.name = name + self.guild = guild + self.race = race + self.class = class + self.zone = zone + self.lastSeenInternal = GetTime() + self.lastSeen = "never" + self.firstSeen = "never" + self.seenCount = 0 + return self + end, + ---@return string + ToString = function(self) + local out = string.format("%s %s %s\nFirst: %s Last: %s Seen: %3d", + data.padString(self.name, 16, true), + data.padString(self.guild, 26, false), + data.padString(self.zone, 26, false), + data.padString(self.firstSeen, 10, true), + data.padString(self.lastSeen, 10, true), + self.seenCount) + return string.format("|cFF%s%s|r", data.classColors[self.class], out) + end, + ---@return string + NotifyMessage = function(self) + local text = string.format( + "%s of class %s and guild %s in %s, first seen: %s, last seen: %s, times seen: %d", + self.name, + self.class, + self.guild, + self.zone, + self.firstSeen, + self.lastSeen, + self.seenCount) + return text end - end - - return nil -end ----@param player Player ----@param zone string ----@return string? -local function NotifyZoneChanged(player, zone) - if not player then return string.format("Cannot notify for nil player %s", tostring(player)) end - if not data.config.who.zoneNotifyFor[zone] - and not data.config.who.zoneNotifyFor[player.zone] then - return string.format("Not notifying for zones %s and %s", tostring(zone), tostring(player.zone)) - end - local text = string.format("%s of class %s and guild %s moved to %s", - player.name, - player.class, - player.guild, - zone) - - ---@type Message - local msg = { - channel = "CHANNEL", - data = data.config.who.notifyChannel, - message = text } - table.insert(data.messenger.queue, msg) - if data.config.who.doWhisper then - for _, name in pairs(data.config.whisperNotify) do - ---@type Message - local msg = { - channel = "WHISPER", - data = name, - message = text - } - table.insert(data.messenger.queue, msg) + ---@class WHOQuery + ---@field query string + ---@field filters WHOFilter[] + WHOQuery = { + ---@param query string + ---@param filters WHOFilter[] + ---@return WHOQuery + new = function(query, filters) + local self = setmetatable({}, { + __index = WHOQuery + }) + self.query = query + self.filters = filters + return self end - end - - return nil -end ----@param player Player ----@return string? -local function NotifyGone(player) - if not player then return string.format("Cannot notify for nil player %s", tostring(player)) end - if not data.config.who.zoneNotifyFor[player.zone] then - return string.format("Not notifying for zone %s", - tostring(player.zone)) - end - - local text = string.format("%s of class %s and guild %s left %s", - player.name, - player.class, - player.guild, - player.zone) - - ---@type Message - local msg = { - channel = "CHANNEL", - data = data.config.who.notifyChannel, - message = text } - table.insert(data.messenger.queue, msg) - if data.config.who.doWhisper then - for _, name in pairs(data.config.whisperNotify) do - ---@type Message - local msg = { - channel = "WHISPER", - data = name, - message = text - } - table.insert(data.messenger.queue, msg) + ---@alias WHOFilter fun(name: string, guild: string, level: number, race: string, class: string, zone: string): boolean + ---@type WHOFilter + local NotSiegeOfOrgrimmarFilter = function(name, guild, level, race, class, zone) + if not zone then + return false end + return zone ~= "Siege of Orgrimmar" + end + ---@type WHOFilter + local AllianceFilter = function(name, guild, level, race, class, zone) + if not race then + return false + end + if not data.raceMap[race] then + return false + end + return data.raceMap[race] == "Alliance" end - return nil -end - -local frame = CreateFrame("Frame") -frame:RegisterEvent("WHO_LIST_UPDATE") -frame:SetScript("OnEvent", function(self, event, ...) + local whoQueryIdx = 1 + ---@type table + local whoQueries = { + WHOQuery.new("g-\"БеспредеЛ\"", {}), + WHOQuery.new( + "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" z-\"Echo Isles\" r-\"Human\" r-\"Dwarf\" r-\"Night Elf\"", + { NotSiegeOfOrgrimmarFilter, AllianceFilter }), + WHOQuery.new( + "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" z-\"Echo Isles\" r-\"Gnome\" r-\"Draenei\" r-\"Worgen\"", + { NotSiegeOfOrgrimmarFilter, AllianceFilter }), + WHOQuery.new( + "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" z-\"Echo Isles\" r-\"Kul Tiran\" r-\"Dark Iron Dwarf\" r-\"Void Elf\"", + { NotSiegeOfOrgrimmarFilter, AllianceFilter }), + WHOQuery.new( + "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" z-\"Echo Isles\" r-\"Lightforged Draenei\" r-\"Mechagnome\"", + { NotSiegeOfOrgrimmarFilter, AllianceFilter }), + WHOQuery.new("Kekv Demonboo Dotmada Firobot Verminal", {}) + } + local queryPending = false + local ttl = #whoQueries * 2 ---@type WHOQuery? - local query = lastQuery - if not query then - print("No query wtf?") - return + local lastQuery = nil + + ---@param player Player + ---@return string? + local function Notify(player) + if not player then return string.format("Cannot notify for nil player %s", tostring(player)) end + if not data.config.who.zoneNotifyFor[player.zone] then + return string.format("Not notifying for zone %s", + tostring(player.zone)) + end + + local text = player:NotifyMessage() + ---@type Message + local msg = { + channel = "CHANNEL", + data = data.config.who.notifyChannel, + message = text + } + table.insert(data.messenger.queue, msg) + + if data.config.who.doWhisper then + for _, name in pairs(data.config.whisperNotify) do + ---@type Message + local msg = { + channel = "WHISPER", + data = name, + message = text + } + table.insert(data.messenger.queue, msg) + end + end + + return nil + end + ---@param player Player + ---@param zone string + ---@return string? + local function NotifyZoneChanged(player, zone) + if not player then return string.format("Cannot notify for nil player %s", tostring(player)) end + if not data.config.who.zoneNotifyFor[zone] + and not data.config.who.zoneNotifyFor[player.zone] then + return string.format("Not notifying for zones %s and %s", tostring(zone), tostring(player.zone)) + end + local text = string.format("%s of class %s and guild %s moved to %s", + player.name, + player.class, + player.guild, + zone) + + ---@type Message + local msg = { + channel = "CHANNEL", + data = data.config.who.notifyChannel, + message = text + } + table.insert(data.messenger.queue, msg) + + if data.config.who.doWhisper then + for _, name in pairs(data.config.whisperNotify) do + ---@type Message + local msg = { + channel = "WHISPER", + data = name, + message = text + } + table.insert(data.messenger.queue, msg) + end + end + + return nil + end + ---@param player Player + ---@return string? + local function NotifyGone(player) + if not player then return string.format("Cannot notify for nil player %s", tostring(player)) end + if not data.config.who.zoneNotifyFor[player.zone] then + return string.format("Not notifying for zone %s", + tostring(player.zone)) + end + + local text = string.format("%s of class %s and guild %s left %s", + player.name, + player.class, + player.guild, + player.zone) + + ---@type Message + local msg = { + channel = "CHANNEL", + data = data.config.who.notifyChannel, + message = text + } + table.insert(data.messenger.queue, msg) + + if data.config.who.doWhisper then + for _, name in pairs(data.config.whisperNotify) do + ---@type Message + local msg = { + channel = "WHISPER", + data = name, + message = text + } + table.insert(data.messenger.queue, msg) + end + end + + return nil end - for i = 1, GetNumWhoResults() do - local name, guild, level, race, class, zone = GetWhoInfo(i) - if data.who.ignored[name] then return end - local continue = false - - ---@type WHOFilter[] - local filters = query.filters - for _, filter in pairs(filters) do - if not filter(name, guild, level, race, class, zone) then - -- Mega scuffed, yes... - -- But wow does not have gotos - continue = true - end - end - - if not continue then - local timestamp = date("%Y-%m-%dT%H:%M:%S") - local player = players[name] - if not player then - player = Player.new(name, guild, race, class, zone) - if not Heimdall_Data.who then Heimdall_Data.who = {} end - if not Heimdall_Data.who.data then Heimdall_Data.who.data = {} end - local existing = Heimdall_Data.who.data[name] - - if existing then - player.lastSeen = existing.lastSeen or "never" - player.firstSeen = existing.firstSeen or "never" - player.seenCount = existing.seenCount or 0 - end - if player.firstSeen == "never" then - player.firstSeen = timestamp - end - - local stinky = data.stinkies[name] - if stinky then - PlaySoundFile("Interface\\Sounds\\Domination.ogg", "Master") - else - PlaySoundFile("Interface\\Sounds\\Cloak.ogg", "Master") - end - - local err = Notify(player) - if err then - print(string.format("Error notifying for %s: %s", tostring(name), tostring(err))) - end - - player.lastSeen = timestamp - player.seenCount = player.seenCount + 1 - players[name] = player - end - - player.lastSeenInternal = GetTime() - if player.zone ~= zone then - local err = NotifyZoneChanged(player, zone) - if err then - print(string.format("Error notifying for %s: %s", tostring(name), tostring(err))) - end - end - player.zone = zone - player.lastSeen = timestamp - players[name] = player - if not Heimdall_Data.who then Heimdall_Data.who = {} end - if not Heimdall_Data.who.data then Heimdall_Data.who.data = {} end - Heimdall_Data.who.data[name] = player - end - end - -- Turns out WA cannot do this ( - -- aura_env.UpdateMacro() - _G["FriendsFrameCloseButton"]:Click() - queryPending = false - print(queryPending) -end) - -if not data.who.updateTicker then - data.who.updateTicker = C_Timer.NewTicker(0.5, function() - for name, player in pairs(players) do - if player.lastSeenInternal + data.config.who.ttl < GetTime() then - NotifyGone(player) - PlaySoundFile("Interface\\Sounds\\Uncloak.ogg", "Master") - players[name] = nil - end - end - end) -end - -if not data.who.whoTicker then - data.who.whoTicker = C_Timer.NewTicker(1, function() - if queryPending then - print("Tried running a who query while one is already pending, previous query:") - data.dumpTable(lastQuery) + local frame = CreateFrame("Frame") + frame:RegisterEvent("WHO_LIST_UPDATE") + frame:SetScript("OnEvent", function(self, event, ...) + ---@type WHOQuery? + local query = lastQuery + if not query then + print("No query wtf?") return end - queryPending = true - local query = whoQueries[whoQueryIdx] - whoQueryIdx = whoQueryIdx + 1 - if whoQueryIdx > #whoQueries then - whoQueryIdx = 1 + for i = 1, GetNumWhoResults() do + local name, guild, level, race, class, zone = GetWhoInfo(i) + if data.who.ignored[name] then return end + local continue = false + + ---@type WHOFilter[] + local filters = query.filters + for _, filter in pairs(filters) do + if not filter(name, guild, level, race, class, zone) then + -- Mega scuffed, yes... + -- But wow does not have gotos + continue = true + end + end + + if not continue then + local timestamp = date("%Y-%m-%dT%H:%M:%S") + local player = players[name] + if not player then + player = Player.new(name, guild, race, class, zone) + if not Heimdall_Data.who then Heimdall_Data.who = {} end + if not Heimdall_Data.who.data then Heimdall_Data.who.data = {} end + local existing = Heimdall_Data.who.data[name] + + if existing then + player.lastSeen = existing.lastSeen or "never" + player.firstSeen = existing.firstSeen or "never" + player.seenCount = existing.seenCount or 0 + end + if player.firstSeen == "never" then + player.firstSeen = timestamp + end + + local stinky = data.stinkies[name] + if stinky then + PlaySoundFile("Interface\\Sounds\\Domination.ogg", "Master") + else + PlaySoundFile("Interface\\Sounds\\Cloak.ogg", "Master") + end + + local err = Notify(player) + if err then + print(string.format("Error notifying for %s: %s", tostring(name), tostring(err))) + end + + player.lastSeen = timestamp + player.seenCount = player.seenCount + 1 + players[name] = player + end + + player.lastSeenInternal = GetTime() + if player.zone ~= zone then + local err = NotifyZoneChanged(player, zone) + if err then + print(string.format("Error notifying for %s: %s", tostring(name), tostring(err))) + end + end + player.zone = zone + player.lastSeen = timestamp + players[name] = player + if not Heimdall_Data.who then Heimdall_Data.who = {} end + if not Heimdall_Data.who.data then Heimdall_Data.who.data = {} end + Heimdall_Data.who.data[name] = player + end + end + -- Turns out WA cannot do this ( + -- aura_env.UpdateMacro() + _G["FriendsFrameCloseButton"]:Click() + queryPending = false + print(queryPending) + end) + + if not data.who.updateTicker then + data.who.updateTicker = C_Timer.NewTicker(0.5, function() + for name, player in pairs(players) do + if player.lastSeenInternal + data.config.who.ttl < GetTime() then + NotifyGone(player) + PlaySoundFile("Interface\\Sounds\\Uncloak.ogg", "Master") + players[name] = nil + end + end + end) + end + + if not data.who.whoTicker then + data.who.whoTicker = C_Timer.NewTicker(1, function() + if queryPending then + print("Tried running a who query while one is already pending, previous query:") + data.dumpTable(lastQuery) + return + end + queryPending = true + + local query = whoQueries[whoQueryIdx] + whoQueryIdx = whoQueryIdx + 1 + if whoQueryIdx > #whoQueries then + whoQueryIdx = 1 + end + lastQuery = query + print(string.format("Running who query: %s", tostring(query.query))) + SetWhoToUI(1) + SendWho(query.query) + end) + end + + local whoQueryWhisperFrame = CreateFrame("Frame") + whoQueryWhisperFrame:RegisterEvent("CHAT_MSG_WHISPER") + whoQueryWhisperFrame:SetScript("OnEvent", function(self, event, msg, sender) + if msg == "who" then + for _, player in pairs(players) do + local text = player:NotifyMessage() + ---@type Message + local msg = { + channel = "WHISPER", + data = sender, + message = text + } + table.insert(data.messenger.queue, msg) + end + end + end) + + local whoQueryChannelFrame = CreateFrame("Frame") + whoQueryChannelFrame:RegisterEvent("CHAT_MSG_CHANNEL") + whoQueryChannelFrame:SetScript("OnEvent", function(self, event, msg, sender, ...) + local channelId = select(6, ...) + local channelname = "" + ---@type any[] + local channels = { GetChannelList() } + for i = 1, #channels, 2 do + ---@type number + local id = channels[i] + ---@type string + local name = channels[i + 1] + if id == channelId then + channelname = name + end + end + + if channelname ~= data.config.who.notifyChannel then + return + end + + if msg == "who" then + for _, player in pairs(players) do + local text = player:NotifyMessage() + ---@type Message + local msg = { + channel = "CHANNEL", + data = sender, + message = text + } + table.insert(data.messenger.queue, msg) + end end - lastQuery = query - print(string.format("Running who query: %s", tostring(query.query))) - SetWhoToUI(1) - SendWho(query.query) end) end - -local whoQueryWhisperFrame = CreateFrame("Frame") -whoQueryWhisperFrame:RegisterEvent("CHAT_MSG_WHISPER") -whoQueryWhisperFrame:SetScript("OnEvent", function(self, event, msg, sender) - if msg == "who" then - for _, player in pairs(players) do - local text = player:NotifyMessage() - ---@type Message - local msg = { - channel = "WHISPER", - data = sender, - message = text - } - table.insert(data.messenger.queue, msg) - end - end -end) - -local whoQueryChannelFrame = CreateFrame("Frame") -whoQueryChannelFrame:RegisterEvent("CHAT_MSG_CHANNEL") -whoQueryChannelFrame:SetScript("OnEvent", function(self, event, msg, sender, ...) - local channelId = select(6, ...) - local channelname = "" - ---@type any[] - local channels = { GetChannelList() } - for i = 1, #channels, 2 do - ---@type number - local id = channels[i] - ---@type string - local name = channels[i + 1] - if id == channelId then - channelname = name - end - end - - if channelname ~= data.config.who.notifyChannel then - return - end - - if msg == "who" then - for _, player in pairs(players) do - local text = player:NotifyMessage() - ---@type Message - local msg = { - channel = "CHANNEL", - data = sender, - message = text - } - table.insert(data.messenger.queue, msg) - end - end -end)