local addonname, shared = ... ---@cast shared HeimdallShared ---@cast addonname string ---@diagnostic disable-next-line: missing-fields shared.Whoer = {} function shared.Whoer.Init() if not Heimdall_Data.who then Heimdall_Data.who = {} end if not Heimdall_Data.who.data then Heimdall_Data.who.data = {} end ---@type table HeimdallStinkies = {} ---@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 ---@field stinky boolean? 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", shared.padString(self.name, 16, true), shared.padString(self.guild, 26, false), shared.padString(self.zone, 26, false), shared.padString(self.firstSeen, 10, true), shared.padString(self.lastSeen, 10, true), self.seenCount) return string.format("|cFF%s%s|r", shared.classColors[self.class], out) end, ---@return string NotifyMessage = function(self) local text = string.format( "%s %s of class %s, race %s (%s) and guild %s in %s, first seen: %s, last seen: %s, times seen: %d", self.name, self.stinky and "(!!!!)" or "", self.class, self.race, tostring(shared.raceMap[self.race]), 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 shared.raceMap[race] then return false end return shared.raceMap[race] == "Alliance" end local whoQueryIdx = 1 ---@type WHOQuery[] local whoQueries = { WHOQuery.new("g-\"БеспредеЛ\"", {}), --WHOQuery.new("g-\"Dovahkin\"", {}), WHOQuery.new( "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Human\" r-\"Dwarf\" r-\"Night Elf\"", { NotSiegeOfOrgrimmarFilter, AllianceFilter }), WHOQuery.new( "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Gnome\" r-\"Draenei\" r-\"Worgen\"", { NotSiegeOfOrgrimmarFilter, AllianceFilter }), WHOQuery.new( "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Kul Tiran\" r-\"Dark Iron Dwarf\" r-\"Void Elf\"", { NotSiegeOfOrgrimmarFilter, AllianceFilter }), WHOQuery.new( "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Lightforged Draenei\" r-\"Mechagnome\"", { NotSiegeOfOrgrimmarFilter, AllianceFilter }), WHOQuery.new("Kekv Firobot Tomoki", {}) } local ttl = #whoQueries * 2 ---@type WHOQuery? local lastQuery = nil ---@param player Player ---@return string? local function Notify(player) if not Heimdall_Data.config.who.enabled then return end if not player then return string.format("Cannot notify for nil player %s", tostring(player)) end if not Heimdall_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 = Heimdall_Data.config.who.notifyChannel, message = text } table.insert(shared.messenger.queue, msg) if Heimdall_Data.config.who.doWhisper then for _, name in pairs(Heimdall_Data.config.whisperNotify) do ---@type Message local msg = { channel = "WHISPER", data = name, message = text } table.insert(shared.messenger.queue, msg) end end return nil end ---@param player Player ---@param zone string ---@return string? local function NotifyZoneChanged(player, zone) if not Heimdall_Data.config.who.enabled then return end if not player then return string.format("Cannot notify for nil player %s", tostring(player)) end if not Heimdall_Data.config.who.zoneNotifyFor[zone] and not Heimdall_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 (%s - %s) and guild %s moved to %s", player.name, player.class, player.race, shared.raceMap[player.race] or "Unknown", player.guild, zone) ---@type Message local msg = { channel = "CHANNEL", data = Heimdall_Data.config.who.notifyChannel, message = text } table.insert(shared.messenger.queue, msg) if Heimdall_Data.config.who.doWhisper then for _, name in pairs(Heimdall_Data.config.whisperNotify) do ---@type Message local msg = { channel = "WHISPER", data = name, message = text } table.insert(shared.messenger.queue, msg) end end return nil end ---@param player Player ---@return string? local function NotifyGone(player) if not Heimdall_Data.config.who.enabled then return end if not player then return string.format("Cannot notify for nil player %s", tostring(player)) end if not Heimdall_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 = Heimdall_Data.config.who.notifyChannel, message = text } table.insert(shared.messenger.queue, msg) if Heimdall_Data.config.who.doWhisper then for _, name in pairs(Heimdall_Data.config.whisperNotify) do ---@type Message local msg = { channel = "WHISPER", data = name, message = text } table.insert(shared.messenger.queue, msg) end end return nil end local frame = CreateFrame("Frame") frame:RegisterEvent("WHO_LIST_UPDATE") frame:SetScript("OnEvent", function(self, event, ...) if not Heimdall_Data.config.who.enabled then return end ---@type WHOQuery? local query = lastQuery if not query then print("No query wtf?") return end for i = 1, GetNumWhoResults() do local name, guild, level, race, class, zone = GetWhoInfo(i) local continue = false --print(name, guild, level, race, class, zone) ---@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 Heimdall_Data.config.who.ignored[name] then continue = true end --print(continue) if not continue then local timestamp = date("%Y-%m-%dT%H:%M:%S") local player = HeimdallStinkies[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 = Heimdall_Data.config.stinkies[name] if stinky then player.stinky = true --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 HeimdallStinkies[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 HeimdallStinkies[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() end) do local function UpdateStinkies() for name, player in pairs(HeimdallStinkies) do if player.lastSeenInternal + Heimdall_Data.config.who.ttl < GetTime() then NotifyGone(player) --PlaySoundFile("Interface\\Sounds\\Uncloak.ogg", "Master") HeimdallStinkies[name] = nil end end end local function Tick() UpdateStinkies() C_Timer.NewTimer(0.5, Tick, 1) end Tick() end do local function DoQuery() if not Heimdall_Data.config.who.enabled then return end 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))) ---@diagnostic disable-next-line: param-type-mismatch SetWhoToUI(1) SendWho(query.query) end local function Tick() DoQuery() C_Timer.NewTimer(1, Tick, 1) end Tick() end print("Heimdall - Whoer loaded") end