Files
wow-Heimdall/Modules/Whoer.lua

640 lines
19 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

local addonname, shared = ...
---@cast shared HeimdallShared
---@cast addonname string
local ModuleName = "Whoer"
---@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<string, Player>
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(shared.L.en.whoerNew,
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,
---@return string
NotifyRu = function(self)
local ruClass = shared.L.ru.classes[self.class]
if not ruClass then
print(string.format("[%s] Class %s not found in ru.classes", ModuleName, self.class))
end
local ruRace = shared.L.ru.races[self.race]
if not ruRace then
print(string.format("[%s] Race %s not found in ru.races", ModuleName, self.race))
end
local faction = shared.raceMap[self.race]
local ruFaction = shared.L.ru.factions[faction]
if not ruFaction then
print(string.format("[%s] Faction %s not found in ru.factions", ModuleName, tostring(faction)))
end
local text = string.format(shared.L.ru.whoerNew,
self.name,
self.stinky and "(!!!!)" or "",
ruClass or self.class,
ruRace or self.race,
ruFaction or faction,
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
}
---@class WHOFilter
---@field Run fun(name: string, guild: string, level: number, race: string, class: string, zone: string): boolean
---@field key string
---@type WHOFilter
local NotSiegeOfOrgrimmarFilter = {
Run = function(name, guild, level, race, class, zone)
if not zone then
return false
end
return zone ~= "Siege of Orgrimmar"
end,
key = "notsoo"
}
---@type WHOFilter
local AllianceFilter = {
Run = 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,
key = "ally"
}
---@class WhoQueryService
---@field queries WHOQuery[]
---@field filters WHOFilter[]
---@field getFilter fun(key: string): WHOFilter?
---@field WhoQueryToString fun(query: WHOQuery): string
---@field WhoQueryFromString fun(query: string): WHOQuery
---@field WhoQueriesToString fun(queries: WHOQuery[]): string
---@field WhoQueriesFromString fun(queries: string): WHOQuery[]
shared.WhoQueryService = {
queries = {},
filters = {
NotSiegeOfOrgrimmarFilter,
AllianceFilter
},
---@param key string
---@return WHOFilter?
getFilter = function(key)
for _, filter in pairs(shared.WhoQueryService.filters) do
if filter.key == key then
return filter
end
end
return nil
end,
---@param query WHOQuery
---@return string
WhoQueryToString = function(query)
local ret = ""
ret = ret .. query.query
ret = ret .. ";"
for _, filter in pairs(query.filters) do
ret = ret .. filter.key .. ";"
end
return ret
end,
---@param queries WHOQuery[]
---@return string
WhoQueriesToString = function(queries)
local ret = ""
for _, query in pairs(queries) do
ret = ret .. shared.WhoQueryService.WhoQueryToString(query) .. "\n"
end
return ret
end,
---@param query string
---@return WHOQuery
WhoQueryFromString = function(query)
local queryParts = shared.Split(query, ";")
local filters = {}
for _, filterKey in pairs(queryParts) do
local filter = shared.WhoQueryService.getFilter(filterKey)
if not filter then
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Filter %s not found", ModuleName, filterKey))
end
else
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Filter %s found", ModuleName, filterKey))
end
table.insert(filters, filter)
end
end
if Heimdall_Data.config.who.debug then
print(string.format("[%s] WHO query: %s with %d filters", ModuleName, queryParts[1], #filters))
end
shared.dumpTable(filters)
return WHOQuery.new(queryParts[1], filters)
end,
---@param queries string
---@return WHOQuery[]
WhoQueriesFromString = function(queries)
local queries = shared.Split(queries, "\n")
local ret = {}
for _, query in pairs(queries) do
table.insert(ret, shared.WhoQueryService.WhoQueryFromString(query))
end
return ret
end
}
shared.WhoQueryService.queries = shared.WhoQueryService.WhoQueriesFromString(Heimdall_Data.config.who.queries)
-----@type WHOQuery[]
--local whoQueries = {
-- WHOQuery.new("g-\"БеспредеЛ\"", {}),
-- WHOQuery.new("g-\"ЗАО бещёки\"", {}),
-- 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 Mld Alltros", {})
--}
local whoQueryIdx = 1
---@type WHOQuery?
local lastQuery = nil
---@param player Player
---@return string?
local function Notify(player)
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Processing notification for player: %s", ModuleName, player.name))
end
if not Heimdall_Data.config.who.enabled then
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Module disabled, skipping notification", ModuleName))
end
return
end
if not player then
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Error: Cannot notify for nil player", ModuleName))
end
return string.format("Cannot notify for nil player %s", tostring(player))
end
if not Heimdall_Data.config.who.zoneNotifyFor[player.zone] then
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Skipping notification - Zone '%s' not in notify list", ModuleName, player.zone))
end
return string.format("Not notifying for zone %s", tostring(player.zone))
end
local text = player:NotifyMessage()
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Queuing channel notification: '%s'", ModuleName, text))
end
---@type Message
local msg = {
channel = "C",
data = Heimdall_Data.config.who.notifyChannel,
message = text
}
--table.insert(shared.messenger.queue, msg)
table.insert(shared.networkMessenger.queue, msg)
if Heimdall_Data.config.who.doWhisper then
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Processing whisper notifications for %d recipients", ModuleName,
#Heimdall_Data.config.whisperNotify))
end
for _, name in pairs(Heimdall_Data.config.whisperNotify) do
---@type Message
local msg = {
channel = "W",
data = name,
message = text
}
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Queuing whisper to %s", ModuleName, name))
end
--table.insert(shared.messenger.queue, msg)
table.insert(shared.networkMessenger.queue, msg)
end
end
if Heimdall_Data.config.echoToRussian then
-- Russian message
local ruMsg = {
channel = "C",
data = Heimdall_Data.config.who.notifyChannel .. "ru",
message = player:NotifyRu()
}
if Heimdall_Data.config.essencex.who and Heimdall_Data.config.essencex.enabled then
if player.guild == "БеспредеЛ" then
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Sending Russian message to %s", ModuleName,
Heimdall_Data.config.essencex.masterChannel))
end
ruMsg.data = Heimdall_Data.config.essencex.masterChannel
table.insert(shared.messenger.queue, ruMsg)
end
end
--table.insert(shared.messenger.queue, ruMsg)
table.insert(shared.networkMessenger.queue, ruMsg)
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(shared.L.en.whoerMoved,
player.name,
player.class,
player.race,
shared.raceMap[player.race] or "Unknown",
player.guild,
zone)
---@type Message
local msg = {
channel = "C",
data = Heimdall_Data.config.who.notifyChannel,
message = text
}
--table.insert(shared.messenger.queue, msg)
table.insert(shared.networkMessenger.queue, msg)
text = string.format(shared.L.ru.whoerMoved,
player.name,
player.class,
player.race,
shared.raceMap[player.race] or "Unknown",
player.guild,
zone)
---@type Message
msg = {
channel = "C",
data = Heimdall_Data.config.who.notifyChannel .. "ru",
message = text
}
--table.insert(shared.messenger.queue, msg)
table.insert(shared.networkMessenger.queue, msg)
if Heimdall_Data.config.essencex.who and Heimdall_Data.config.essencex.enabled then
if player.guild == "БеспредеЛ" then
msg.data = Heimdall_Data.config.essencex.masterChannel
table.insert(shared.messenger.queue, msg)
end
end
if Heimdall_Data.config.who.doWhisper then
for _, name in pairs(Heimdall_Data.config.whisperNotify) do
---@type Message
local msg = {
channel = "W",
data = name,
message = text
}
--table.insert(shared.messenger.queue, msg)
table.insert(shared.networkMessenger.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(shared.L.en.whoerGone,
player.name,
player.class,
player.guild,
player.zone)
---@type Message
local msg = {
channel = "C",
data = Heimdall_Data.config.who.notifyChannel,
message = text
}
--table.insert(shared.messenger.queue, msg)
table.insert(shared.networkMessenger.queue, msg)
text = string.format(shared.L.ru.whoerGone,
player.name,
player.class,
player.guild,
player.zone)
---@type Message
msg = {
channel = "C",
data = Heimdall_Data.config.who.notifyChannel .. "ru",
message = text
}
--table.insert(shared.messenger.queue, msg)
table.insert(shared.networkMessenger.queue, msg)
if Heimdall_Data.config.essencex.who and Heimdall_Data.config.essencex.enabled then
if player.guild == "БеспредеЛ" then
msg.data = Heimdall_Data.config.essencex.masterChannel
table.insert(shared.messenger.queue, msg)
end
end
if Heimdall_Data.config.who.doWhisper then
for _, name in pairs(Heimdall_Data.config.whisperNotify) do
---@type Message
local msg = {
channel = "W",
data = name,
message = text
}
--table.insert(shared.messenger.queue, msg)
table.insert(shared.networkMessenger.queue, msg)
end
end
return nil
end
local frame = CreateFrame("Frame")
frame:RegisterEvent("WHO_LIST_UPDATE")
frame:SetScript("OnEvent", function(self, event, ...)
if Heimdall_Data.config.who.debug then
print(string.format("[%s] WHO list update received", ModuleName))
end
if not Heimdall_Data.config.who.enabled then
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Module disabled, ignoring WHO update", ModuleName))
end
return
end
---@type WHOQuery?
local query = lastQuery
if not query then
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Error: No active WHO query found", ModuleName))
end
return
end
local results = GetNumWhoResults()
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Processing %d WHO results for query: %s", ModuleName, results, query.query))
end
for i = 1, results do
local name, guild, level, race, class, zone = GetWhoInfo(i)
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Processing result %d/%d: %s/%s/%s", ModuleName, i, results, name, class, zone))
end
local continue = false
---@type WHOFilter[]
local filters = query.filters
for _, filter in pairs(filters) do
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Running filter %s on %s/%s/%s", ModuleName, filter.key, name, class, zone))
end
if not filter.Run(name, guild, level, race, class, zone) then
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Player %s filtered out by WHO filter %s", ModuleName, name, filter.key))
end
continue = true
break
end
end
if Heimdall_Data.config.who.ignored[name] then
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Ignoring blacklisted player: %s", ModuleName, name))
end
continue = true
end
if not continue then
local timestamp = date("%Y-%m-%dT%H:%M:%S")
local player = HeimdallStinkies[name]
if not player then
if Heimdall_Data.config.who.debug then
print(string.format("[%s] New player detected: %s (%s) in %s", ModuleName, name, class, zone))
end
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
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Found existing data for %s - Last seen: %s, Count: %d",
ModuleName, name, existing.lastSeen or "never", existing.seenCount or 0))
end
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
if Heimdall_Data.config.who.debug then
print(string.format("[%s] First time seeing player: %s at %s", ModuleName, name, timestamp))
end
end
local stinky = shared.IsStinky(name)
if stinky then
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Player %s marked as stinky!", ModuleName, name))
end
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("[%s] Error notifying for %s: %s", ModuleName, 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 = shared.WhoQueryService.queries[whoQueryIdx]
if not query then
if Heimdall_Data.config.who.debug then
print(string.format("[%s] Error: No WHO query found to run", ModuleName))
end
return
end
whoQueryIdx = whoQueryIdx + 1
if whoQueryIdx > #shared.WhoQueryService.queries 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