Refactor everything to modules

This commit is contained in:
2025-01-01 14:57:43 +01:00
parent 59d2b999c2
commit 137ce0a3a7
12 changed files with 2119 additions and 2100 deletions

View File

@@ -12,6 +12,7 @@ local function init()
---@field config HeimdallConfig ---@field config HeimdallConfig
---@field stinkies table<string, boolean> ---@field stinkies table<string, boolean>
if not Heimdall_Data then Heimdall_Data = {} end if not Heimdall_Data then Heimdall_Data = {} end
---@class InitTable ---@class InitTable
---@field Init fun(): nil ---@field Init fun(): nil
@@ -37,6 +38,8 @@ local function init()
---@field messenger HeimdallMessengerConfig ---@field messenger HeimdallMessengerConfig
---@field deathReporter HeimdallDeathReporterConfig ---@field deathReporter HeimdallDeathReporterConfig
---@field inviter HeimdallInviterConfig ---@field inviter HeimdallInviterConfig
---@field dueler HeimdallDuelerConfig
---@field bully HeimdallBullyConfig
---@field whisperNotify table<string, string> ---@field whisperNotify table<string, string>
---@field stinkies table<string, boolean> ---@field stinkies table<string, boolean>
---@field agents table<string, string> ---@field agents table<string, string>
@@ -78,6 +81,13 @@ local function init()
---@field allAssist boolean ---@field allAssist boolean
---@field agentsAssist boolean ---@field agentsAssist boolean
---@class HeimdallDuelerConfig
---@field enabled boolean
---@field declineOther boolean
---@class HeimdallBullyConfig
---@field enabled boolean
--- Data --- --- Data ---
---@class HeimdallMessengerData ---@class HeimdallMessengerData
---@field queue table<string, Message> ---@field queue table<string, Message>
@@ -163,6 +173,13 @@ local function init()
allAssist = shared.GetOrDefault(Heimdall_Data, { "config", "inviter", "allAssist" }, false), allAssist = shared.GetOrDefault(Heimdall_Data, { "config", "inviter", "allAssist" }, false),
agentsAssist = shared.GetOrDefault(Heimdall_Data, { "config", "inviter", "agentsAssist" }, false), agentsAssist = shared.GetOrDefault(Heimdall_Data, { "config", "inviter", "agentsAssist" }, false),
}, },
dueler = {
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "dueler", "enabled" }, false),
declineOther = shared.GetOrDefault(Heimdall_Data, { "config", "dueler", "declineOther" }, false),
},
bully = {
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "bully", "enabled" }, false),
},
agents = shared.GetOrDefault(Heimdall_Data, { "config", "agents" }, {}), agents = shared.GetOrDefault(Heimdall_Data, { "config", "agents" }, {}),
} }

View File

@@ -5,11 +5,13 @@
## SavedVariables: Heimdall_Data ## SavedVariables: Heimdall_Data
#core #core
CLEUParser.lua Modules/CLEUParser.lua
DumpTable.lua Modules/DumpTable.lua
Spotter.lua Modules/Spotter.lua
Whoer.lua Modules/Whoer.lua
Messenger.lua Modules/Messenger.lua
DeathReporter.lua Modules/DeathReporter.lua
Inviter.lua Modules/Inviter.lua
Modules/Dueler.lua
Modules/Bully.lua
Heimdall.lua Heimdall.lua

0
Modules/Bully.lua Normal file
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,118 +1,118 @@
local addonname, shared = ... local addonname, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
---@cast addonname string ---@cast addonname string
---@diagnostic disable-next-line: missing-fields ---@diagnostic disable-next-line: missing-fields
shared.DeathReporter = {} shared.DeathReporter = {}
function shared.DeathReporter.Init() function shared.DeathReporter.Init()
-- if not Heimdall_Data.config.deathReporter.enabled then -- if not Heimdall_Data.config.deathReporter.enabled then
-- print("Heimdall - DeathReporter disabled") -- print("Heimdall - DeathReporter disabled")
-- return -- return
-- end -- end
---@type table<string, number> ---@type table<string, number>
local recentDeaths = {} local recentDeaths = {}
---@type table<string, number> ---@type table<string, number>
local recentDuels = {} local recentDuels = {}
---@param source string ---@param source string
---@param destination string ---@param destination string
---@param spellName string ---@param spellName string
local function RegisterDeath(source, destination, spellName) local function RegisterDeath(source, destination, spellName)
if not Heimdall_Data.config.deathReporter.enabled then return end if not Heimdall_Data.config.deathReporter.enabled then return end
if recentDeaths[destination] if recentDeaths[destination]
and GetTime() - recentDeaths[destination] < Heimdall_Data.config.deathReporter.throttle then and GetTime() - recentDeaths[destination] < Heimdall_Data.config.deathReporter.throttle then
return return
end end
if recentDuels[destination] if recentDuels[destination]
and GetTime() - recentDuels[destination] < Heimdall_Data.config.deathReporter.duelThrottle then and GetTime() - recentDuels[destination] < Heimdall_Data.config.deathReporter.duelThrottle then
print(string.format("Cancelling death reports for %s and %s because of recent duel", source, destination)) print(string.format("Cancelling death reports for %s and %s because of recent duel", source, destination))
return return
end end
if recentDuels[source] if recentDuels[source]
and GetTime() - recentDuels[source] < Heimdall_Data.config.deathReporter.duelThrottle then and GetTime() - recentDuels[source] < Heimdall_Data.config.deathReporter.duelThrottle then
print(string.format("Cancelling death reports for %s and %s because of recent duel", source, destination)) print(string.format("Cancelling death reports for %s and %s because of recent duel", source, destination))
return return
end end
recentDeaths[destination] = GetTime() recentDeaths[destination] = GetTime()
C_Timer.NewTimer(3, function() C_Timer.NewTimer(3, function()
if recentDuels[destination] if recentDuels[destination]
and GetTime() - recentDuels[destination] < Heimdall_Data.config.deathReporter.duelThrottle then and GetTime() - recentDuels[destination] < Heimdall_Data.config.deathReporter.duelThrottle then
print(string.format("Cancelling death reports for %s and %s because of recent duel", source, destination)) print(string.format("Cancelling death reports for %s and %s because of recent duel", source, destination))
return return
end end
if recentDuels[source] if recentDuels[source]
and GetTime() - recentDuels[source] < Heimdall_Data.config.deathReporter.duelThrottle then and GetTime() - recentDuels[source] < Heimdall_Data.config.deathReporter.duelThrottle then
print(string.format("Cancelling death reports for %s and %s because of recent duel", source, destination)) print(string.format("Cancelling death reports for %s and %s because of recent duel", source, destination))
return return
end end
local zone = Heimdall_Data.config.deathReporter.zoneOverride local zone = Heimdall_Data.config.deathReporter.zoneOverride
if not zone then if not zone then
zone = string.format("%s (%s)", GetZoneText(), GetSubZoneText()) zone = string.format("%s (%s)", GetZoneText(), GetSubZoneText())
end end
local text = string.format("%s killed %s with %s in %s", local text = string.format("%s killed %s with %s in %s",
tostring(source), tostring(source),
tostring(destination), tostring(destination),
tostring(spellName), tostring(spellName),
tostring(zone)) tostring(zone))
---@type Message ---@type Message
local msg = { local msg = {
channel = "CHANNEL", channel = "CHANNEL",
data = Heimdall_Data.config.deathReporter.notifyChannel, data = Heimdall_Data.config.deathReporter.notifyChannel,
message = text, message = text,
} }
table.insert(shared.messenger.queue, msg) table.insert(shared.messenger.queue, msg)
if Heimdall_Data.config.deathReporter.doWhisper then if Heimdall_Data.config.deathReporter.doWhisper then
for _, name in pairs(Heimdall_Data.config.whisperNotify) do for _, name in pairs(Heimdall_Data.config.whisperNotify) do
local msg = { local msg = {
channel = "WHISPER", channel = "WHISPER",
data = name, data = name,
message = text, message = text,
} }
table.insert(shared.messenger.queue, msg) table.insert(shared.messenger.queue, msg)
end end
end end
end) end)
end end
local cleuFrame = CreateFrame("Frame") local cleuFrame = CreateFrame("Frame")
cleuFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") cleuFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
cleuFrame:SetScript("OnEvent", function(self, event, ...) cleuFrame:SetScript("OnEvent", function(self, event, ...)
if not Heimdall_Data.config.deathReporter.enabled then return end if not Heimdall_Data.config.deathReporter.enabled then return end
local overkill, err = CLEUParser.GetOverkill(...) local overkill, err = CLEUParser.GetOverkill(...)
if not err and overkill > 0 then if not err and overkill > 0 then
local source, err = CLEUParser.GetSourceName(...) local source, err = CLEUParser.GetSourceName(...)
if err then source = "unknown" end if err then source = "unknown" end
local destination, err = CLEUParser.GetDestName(...) local destination, err = CLEUParser.GetDestName(...)
if err then destination = "unknown" end if err then destination = "unknown" end
local spellName, err = CLEUParser.GetSpellName(...) local spellName, err = CLEUParser.GetSpellName(...)
if err then spellName = "unknown" end if err then spellName = "unknown" end
local sourceGUID, err = CLEUParser.GetSourceGUID(...) local sourceGUID, err = CLEUParser.GetSourceGUID(...)
if err or not string.match(sourceGUID, "Player") then return end if err or not string.match(sourceGUID, "Player") then return end
local destinationGUID, err = CLEUParser.GetDestGUID(...) local destinationGUID, err = CLEUParser.GetDestGUID(...)
if err or not string.match(destinationGUID, "Player") then return end if err or not string.match(destinationGUID, "Player") then return end
RegisterDeath(source, destination, spellName) RegisterDeath(source, destination, spellName)
end end
end) end)
local systemMessageFrame = CreateFrame("Frame") local systemMessageFrame = CreateFrame("Frame")
systemMessageFrame:RegisterEvent("CHAT_MSG_SYSTEM") systemMessageFrame:RegisterEvent("CHAT_MSG_SYSTEM")
systemMessageFrame:SetScript("OnEvent", function(self, event, msg) systemMessageFrame:SetScript("OnEvent", function(self, event, msg)
if not Heimdall_Data.config.deathReporter.enabled then return end if not Heimdall_Data.config.deathReporter.enabled then return end
local source, destination = string.match(msg, "(.+) has defeated (.+) in a duel") local source, destination = string.match(msg, "(.+) has defeated (.+) in a duel")
if source and destination then if source and destination then
print(string.format("Detected duel between %s and %s", source, destination)) print(string.format("Detected duel between %s and %s", source, destination))
local now = GetTime() local now = GetTime()
recentDuels[source] = now recentDuels[source] = now
recentDuels[destination] = now recentDuels[destination] = now
end end
end) end)
print("Heimdall - DeathReporter loaded") print("Heimdall - DeathReporter loaded")
end end

0
Modules/Dueler.lua Normal file
View File

View File

@@ -1,29 +1,29 @@
local addonname, shared = ... local addonname, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
---@cast addonname string ---@cast addonname string
if not shared.dumpTable then if not shared.dumpTable then
---@param table table ---@param table table
---@param depth number? ---@param depth number?
shared.dumpTable = function(table, depth) shared.dumpTable = function(table, depth)
if not table then if not table then
print(tostring(table)) print(tostring(table))
return return
end end
if depth == nil then if depth == nil then
depth = 0 depth = 0
end end
if (depth > 200) then if (depth > 200) then
print("Error: Depth > 200 in dumpTable()") print("Error: Depth > 200 in dumpTable()")
return return
end end
for k, v in pairs(table) do for k, v in pairs(table) do
if (type(v) == "table") then if (type(v) == "table") then
print(string.rep(" ", depth) .. k .. ":") print(string.rep(" ", depth) .. k .. ":")
shared.dumpTable(v, depth + 1) shared.dumpTable(v, depth + 1)
else else
print(string.rep(" ", depth) .. k .. ": ", v) print(string.rep(" ", depth) .. k .. ": ", v)
end end
end end
end end
end end

View File

@@ -1,107 +1,107 @@
local addonname, shared = ... local addonname, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
---@cast addonname string ---@cast addonname string
---@diagnostic disable-next-line: missing-fields ---@diagnostic disable-next-line: missing-fields
shared.Messenger = {} shared.Messenger = {}
function shared.Messenger.Init() function shared.Messenger.Init()
-- if not Heimdall_Data.config.messenger.enabled then -- if not Heimdall_Data.config.messenger.enabled then
-- print("Heimdall - Messenger disabled") -- print("Heimdall - Messenger disabled")
-- return -- return
-- end -- end
---@class Message ---@class Message
---@field message string ---@field message string
---@field channel string ---@field channel string
---@field data string|number ---@field data string|number
---@type table<string, number> ---@type table<string, number>
local channelIdMap = {} local channelIdMap = {}
local FindOrJoinChannel = function(channelName, password) local FindOrJoinChannel = function(channelName, password)
local function GetChannelId(channelName) local function GetChannelId(channelName)
local channels = { GetChannelList() } local channels = { GetChannelList() }
for i = 1, #channels, 2 do for i = 1, #channels, 2 do
local id = channels[i] local id = channels[i]
local name = channels[i + 1] local name = channels[i + 1]
if name == channelName then if name == channelName then
return id return id
end end
end end
end end
local channelId = GetChannelId(channelName) local channelId = GetChannelId(channelName)
if not channelId then if not channelId then
print("Channel", tostring(channelName), "not found, joining") print("Channel", tostring(channelName), "not found, joining")
if password then if password then
JoinPermanentChannel(channelName, password) JoinPermanentChannel(channelName, password)
else else
JoinPermanentChannel(channelName) JoinPermanentChannel(channelName)
end end
end end
channelId = GetChannelId(channelName) channelId = GetChannelId(channelName)
channelIdMap[channelName] = channelId channelIdMap[channelName] = channelId
return channelId return channelId
end end
local ScanChannels = function() local ScanChannels = function()
local channels = { GetChannelList() } local channels = { GetChannelList() }
for i = 1, #channels, 2 do for i = 1, #channels, 2 do
local id = channels[i] local id = channels[i]
local name = channels[i + 1] local name = channels[i + 1]
channelIdMap[name] = id channelIdMap[name] = id
end end
end end
if not shared.messenger then shared.messenger = {} end if not shared.messenger then shared.messenger = {} end
if not shared.messenger.queue then shared.messenger.queue = {} end if not shared.messenger.queue then shared.messenger.queue = {} end
if not shared.messenger.ticker then if not shared.messenger.ticker then
local function DoMessage() local function DoMessage()
if not Heimdall_Data.config.messenger.enabled then return end if not Heimdall_Data.config.messenger.enabled then return end
---@type Message ---@type Message
local message = shared.messenger.queue[1] local message = shared.messenger.queue[1]
if not message then return end if not message then return end
if not message.message or message.message == "" then return end if not message.message or message.message == "" then return end
if not message.channel or message.channel == "" then return end if not message.channel or message.channel == "" then return end
-- Map channel names to ids -- Map channel names to ids
if message.channel == "CHANNEL" and message.data and string.match(message.data, "%D") then if message.channel == "CHANNEL" and message.data and string.match(message.data, "%D") then
print("Channel presented as string:", message.data) print("Channel presented as string:", message.data)
local channelId = channelIdMap[message.data] local channelId = channelIdMap[message.data]
if not channelId then if not channelId then
print("Channel not found, scanning") print("Channel not found, scanning")
ScanChannels() ScanChannels()
channelId = channelIdMap[message.data] channelId = channelIdMap[message.data]
end end
if not channelId then if not channelId then
print("Channel not joined, joining") print("Channel not joined, joining")
channelId = FindOrJoinChannel(message.data) channelId = FindOrJoinChannel(message.data)
end end
print("Channel resolved to id", channelId) print("Channel resolved to id", channelId)
message.data = channelId message.data = channelId
end end
table.remove(shared.messenger.queue, 1) table.remove(shared.messenger.queue, 1)
if not message.message or message.message == "" then return end if not message.message or message.message == "" then return end
if not message.channel or message.channel == "" then return end if not message.channel or message.channel == "" then return end
if not message.data or message.data == "" then return end if not message.data or message.data == "" then return end
SendChatMessage(message.message, message.channel, nil, message.data) SendChatMessage(message.message, message.channel, nil, message.data)
end end
local function Tick() local function Tick()
DoMessage() DoMessage()
shared.messenger.ticker = C_Timer.NewTimer(Heimdall_Data.config.messenger.interval, Tick, 1) shared.messenger.ticker = C_Timer.NewTimer(Heimdall_Data.config.messenger.interval, Tick, 1)
end end
Tick() Tick()
end end
--C_Timer.NewTicker(2, function() --C_Timer.NewTicker(2, function()
-- print("Q") -- print("Q")
-- table.insert(data.messenger.queue, { -- table.insert(data.messenger.queue, {
-- channel = "CHANNEL", -- channel = "CHANNEL",
-- data = "Foobar", -- data = "Foobar",
-- message = "TEST" -- message = "TEST"
-- }) -- })
--end) --end)
print("Heimdall - Messenger loaded") print("Heimdall - Messenger loaded")
end end

View File

@@ -1,119 +1,119 @@
local addonname, shared = ... local addonname, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
---@cast addonname string ---@cast addonname string
---@diagnostic disable-next-line: missing-fields ---@diagnostic disable-next-line: missing-fields
shared.Spotter = {} shared.Spotter = {}
function shared.Spotter.Init() function shared.Spotter.Init()
-- if not Heimdall_Data.config.spotter.enabled then -- if not Heimdall_Data.config.spotter.enabled then
-- print("Heimdall - Spotter disabled") -- print("Heimdall - Spotter disabled")
-- return -- return
-- end -- end
local function FormatHP(hp) local function FormatHP(hp)
if hp > 1e9 then if hp > 1e9 then
return string.format("%.1fB", hp / 1e9) return string.format("%.1fB", hp / 1e9)
elseif hp > 1e6 then elseif hp > 1e6 then
return string.format("%.1fM", hp / 1e6) return string.format("%.1fM", hp / 1e6)
elseif hp > 1e3 then elseif hp > 1e3 then
return string.format("%.1fK", hp / 1e3) return string.format("%.1fK", hp / 1e3)
else else
return hp return hp
end end
end end
---@type table<string, number> ---@type table<string, number>
local throttleTable = {} local throttleTable = {}
---@param unit string ---@param unit string
---@param name string ---@param name string
---@param faction string ---@param faction string
---@param hostile boolean ---@param hostile boolean
---@return boolean ---@return boolean
---@return string? error ---@return string? error
local function ShouldNotify(unit, name, faction, hostile) local function ShouldNotify(unit, name, faction, hostile)
if Heimdall_Data.config.spotter.stinky then if Heimdall_Data.config.spotter.stinky then
if Heimdall_Data.config.stinkies[name] then return true end if Heimdall_Data.config.stinkies[name] then return true end
end end
if Heimdall_Data.config.spotter.alliance then if Heimdall_Data.config.spotter.alliance then
if faction == "Alliance" then return true end if faction == "Alliance" then return true end
end end
if Heimdall_Data.config.spotter.hostile then if Heimdall_Data.config.spotter.hostile then
if hostile then return true end if hostile then return true end
end end
return Heimdall_Data.config.spotter.everyone return Heimdall_Data.config.spotter.everyone
end end
---@param unit string ---@param unit string
---@return string? ---@return string?
local function NotifySpotted(unit) local function NotifySpotted(unit)
if not unit then return string.format("Could not find unit %s", tostring(unit)) end if not unit then return string.format("Could not find unit %s", tostring(unit)) end
if not UnitIsPlayer(unit) then return nil end if not UnitIsPlayer(unit) then return nil end
local name = UnitName(unit) local name = UnitName(unit)
if not name then return string.format("Could not find name for unit %s", tostring(unit)) end if not name then return string.format("Could not find name for unit %s", tostring(unit)) end
local time = GetTime() local time = GetTime()
if throttleTable[name] and time - throttleTable[name] < Heimdall_Data.config.spotter.throttleTime then if throttleTable[name] and time - throttleTable[name] < Heimdall_Data.config.spotter.throttleTime then
return string.format("Throttled %s", tostring(name)) return string.format("Throttled %s", tostring(name))
end end
throttleTable[name] = time throttleTable[name] = time
local race = UnitRace(unit) local race = UnitRace(unit)
if not race then return string.format("Could not find race for unit %s", tostring(unit)) end if not race then return string.format("Could not find race for unit %s", tostring(unit)) end
local faction = shared.raceMap[race] local faction = shared.raceMap[race]
if not faction then return string.format("Could not find faction for race %s", tostring(race)) end if not faction then return string.format("Could not find faction for race %s", tostring(race)) end
local hostile = UnitCanAttack("player", unit) == 1 local hostile = UnitCanAttack("player", unit) == 1
local doNotify = ShouldNotify(unit, name, faction, hostile) local doNotify = ShouldNotify(unit, name, faction, hostile)
if not doNotify then return string.format("Not notifying for %s", tostring(name)) end if not doNotify then return string.format("Not notifying for %s", tostring(name)) end
local hp = UnitHealth(unit) local hp = UnitHealth(unit)
if not hp then return string.format("Could not find hp for unit %s", tostring(unit)) end if not hp then return string.format("Could not find hp for unit %s", tostring(unit)) end
local maxHp = UnitHealthMax(unit) local maxHp = UnitHealthMax(unit)
if not maxHp then return string.format("Could not find maxHp for unit %s", tostring(unit)) end if not maxHp then return string.format("Could not find maxHp for unit %s", tostring(unit)) end
local location = Heimdall_Data.config.spotter.zoneOverride local location = Heimdall_Data.config.spotter.zoneOverride
if not location then if not location then
local zone = GetZoneText() local zone = GetZoneText()
if not zone then return string.format("Could not find zone for unit %s", tostring(unit)) end if not zone then return string.format("Could not find zone for unit %s", tostring(unit)) end
local subzone = GetSubZoneText() local subzone = GetSubZoneText()
if not subzone then subzone = "" end if not subzone then subzone = "" end
location = string.format("%s (%s)", zone, subzone) location = string.format("%s (%s)", zone, subzone)
end end
local stinky = Heimdall_Data.config.stinkies[name] or false local stinky = Heimdall_Data.config.stinkies[name] or false
local text = string.format("I see (%s) %s %s of race %s (%s) with health %s/%s at %s", local text = string.format("I see (%s) %s %s of race %s (%s) with health %s/%s at %s",
hostile and "Hostile" or "Friendly", hostile and "Hostile" or "Friendly",
stinky and string.format("(%s)", "!!!!") or "", stinky and string.format("(%s)", "!!!!") or "",
name, name,
race, race,
faction, faction,
FormatHP(hp), FormatHP(hp),
FormatHP(maxHp), FormatHP(maxHp),
location) location)
---@type Message ---@type Message
local msg = { local msg = {
channel = "CHANNEL", channel = "CHANNEL",
data = Heimdall_Data.config.spotter.notifyChannel, data = Heimdall_Data.config.spotter.notifyChannel,
message = text message = text
} }
--shared.dumpTable(msg) --shared.dumpTable(msg)
table.insert(shared.messenger.queue, msg) table.insert(shared.messenger.queue, msg)
end end
local frame = CreateFrame("Frame") local frame = CreateFrame("Frame")
frame:RegisterEvent("NAME_PLATE_UNIT_ADDED") frame:RegisterEvent("NAME_PLATE_UNIT_ADDED")
frame:RegisterEvent("TARGET_UNIT_CHANGED") frame:RegisterEvent("TARGET_UNIT_CHANGED")
frame:SetScript("OnEvent", function(self, event, unit) frame:SetScript("OnEvent", function(self, event, unit)
if not Heimdall_Data.config.spotter.enabled then return end if not Heimdall_Data.config.spotter.enabled then return end
local err = NotifySpotted(unit) local err = NotifySpotted(unit)
if err then if err then
print(string.format("Error notifying %s: %s", tostring(unit), tostring(err))) print(string.format("Error notifying %s: %s", tostring(unit), tostring(err)))
end end
end) end)
print("Heimdall - Spotter loaded") print("Heimdall - Spotter loaded")
end end

View File

@@ -1,428 +1,428 @@
local addonname, shared = ... local addonname, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
---@cast addonname string ---@cast addonname string
---@diagnostic disable-next-line: missing-fields ---@diagnostic disable-next-line: missing-fields
shared.Whoer = {} shared.Whoer = {}
function shared.Whoer.Init() function shared.Whoer.Init()
-- if not Heimdall_Data.config.who.enabled then -- if not Heimdall_Data.config.who.enabled then
-- print("Heimdall - Whoer disabled") -- print("Heimdall - Whoer disabled")
-- return -- return
-- end -- end
if not Heimdall_Data.who then Heimdall_Data.who = {} end if not Heimdall_Data.who then Heimdall_Data.who = {} end
if not Heimdall_Data.who.data then Heimdall_Data.who.data = {} end if not Heimdall_Data.who.data then Heimdall_Data.who.data = {} end
---@type table<string, Player> ---@type table<string, Player>
HeimdallStinkies = {} HeimdallStinkies = {}
---@class Player ---@class Player
---@field name string ---@field name string
---@field guild string ---@field guild string
---@field race string ---@field race string
---@field class string ---@field class string
---@field zone string ---@field zone string
---@field lastSeenInternal number ---@field lastSeenInternal number
---@field lastSeen string ---@field lastSeen string
---@field firstSeen string ---@field firstSeen string
---@field seenCount number ---@field seenCount number
---@field stinky boolean? ---@field stinky boolean?
Player = { Player = {
---@param name string ---@param name string
---@param guild string ---@param guild string
---@param race string ---@param race string
---@param class string ---@param class string
---@param zone string ---@param zone string
---@return Player ---@return Player
new = function(name, guild, race, class, zone) new = function(name, guild, race, class, zone)
local self = setmetatable({}, { local self = setmetatable({}, {
__index = Player __index = Player
}) })
self.name = name self.name = name
self.guild = guild self.guild = guild
self.race = race self.race = race
self.class = class self.class = class
self.zone = zone self.zone = zone
self.lastSeenInternal = GetTime() self.lastSeenInternal = GetTime()
self.lastSeen = "never" self.lastSeen = "never"
self.firstSeen = "never" self.firstSeen = "never"
self.seenCount = 0 self.seenCount = 0
return self return self
end, end,
---@return string ---@return string
ToString = function(self) ToString = function(self)
local out = string.format("%s %s %s\nFirst: %s Last: %s Seen: %3d", local out = string.format("%s %s %s\nFirst: %s Last: %s Seen: %3d",
shared.padString(self.name, 16, true), shared.padString(self.name, 16, true),
shared.padString(self.guild, 26, false), shared.padString(self.guild, 26, false),
shared.padString(self.zone, 26, false), shared.padString(self.zone, 26, false),
shared.padString(self.firstSeen, 10, true), shared.padString(self.firstSeen, 10, true),
shared.padString(self.lastSeen, 10, true), shared.padString(self.lastSeen, 10, true),
self.seenCount) self.seenCount)
return string.format("|cFF%s%s|r", shared.classColors[self.class], out) return string.format("|cFF%s%s|r", shared.classColors[self.class], out)
end, end,
---@return string ---@return string
NotifyMessage = function(self) NotifyMessage = function(self)
local text = string.format( 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", "%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.name,
self.stinky and "(!!!!)" or "", self.stinky and "(!!!!)" or "",
self.class, self.class,
self.race, self.race,
tostring(shared.raceMap[self.race]), tostring(shared.raceMap[self.race]),
self.guild, self.guild,
self.zone, self.zone,
self.firstSeen, self.firstSeen,
self.lastSeen, self.lastSeen,
self.seenCount) self.seenCount)
return text return text
end end
} }
---@class WHOQuery ---@class WHOQuery
---@field query string ---@field query string
---@field filters WHOFilter[] ---@field filters WHOFilter[]
WHOQuery = { WHOQuery = {
---@param query string ---@param query string
---@param filters WHOFilter[] ---@param filters WHOFilter[]
---@return WHOQuery ---@return WHOQuery
new = function(query, filters) new = function(query, filters)
local self = setmetatable({}, { local self = setmetatable({}, {
__index = WHOQuery __index = WHOQuery
}) })
self.query = query self.query = query
self.filters = filters self.filters = filters
return self return self
end end
} }
---@alias WHOFilter fun(name: string, guild: string, level: number, race: string, class: string, zone: string): boolean ---@alias WHOFilter fun(name: string, guild: string, level: number, race: string, class: string, zone: string): boolean
---@type WHOFilter ---@type WHOFilter
local NotSiegeOfOrgrimmarFilter = function(name, guild, level, race, class, zone) local NotSiegeOfOrgrimmarFilter = function(name, guild, level, race, class, zone)
if not zone then if not zone then
return false return false
end end
return zone ~= "Siege of Orgrimmar" return zone ~= "Siege of Orgrimmar"
end end
---@type WHOFilter ---@type WHOFilter
local AllianceFilter = function(name, guild, level, race, class, zone) local AllianceFilter = function(name, guild, level, race, class, zone)
if not race then return false end if not race then return false end
if not shared.raceMap[race] then return false end if not shared.raceMap[race] then return false end
return shared.raceMap[race] == "Alliance" return shared.raceMap[race] == "Alliance"
end end
local whoQueryIdx = 1 local whoQueryIdx = 1
---@type WHOQuery[] ---@type WHOQuery[]
local whoQueries = { local whoQueries = {
WHOQuery.new("g-\"БеспредеЛ\"", {}), WHOQuery.new("g-\"БеспредеЛ\"", {}),
WHOQuery.new( WHOQuery.new(
"z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Human\" r-\"Dwarf\" r-\"Night Elf\"", "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Human\" r-\"Dwarf\" r-\"Night Elf\"",
{ NotSiegeOfOrgrimmarFilter, AllianceFilter }), { NotSiegeOfOrgrimmarFilter, AllianceFilter }),
WHOQuery.new( WHOQuery.new(
"z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Gnome\" r-\"Draenei\" r-\"Worgen\"", "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Gnome\" r-\"Draenei\" r-\"Worgen\"",
{ NotSiegeOfOrgrimmarFilter, AllianceFilter }), { NotSiegeOfOrgrimmarFilter, AllianceFilter }),
WHOQuery.new( WHOQuery.new(
"z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Kul Tiran\" r-\"Dark Iron Dwarf\" r-\"Void Elf\"", "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Kul Tiran\" r-\"Dark Iron Dwarf\" r-\"Void Elf\"",
{ NotSiegeOfOrgrimmarFilter, AllianceFilter }), { NotSiegeOfOrgrimmarFilter, AllianceFilter }),
WHOQuery.new( WHOQuery.new(
"z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Lightforged Draenei\" r-\"Mechagnome\"", "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Lightforged Draenei\" r-\"Mechagnome\"",
{ NotSiegeOfOrgrimmarFilter, AllianceFilter }), { NotSiegeOfOrgrimmarFilter, AllianceFilter }),
WHOQuery.new("Kekv Demonboo Dotmada Firobot Verminal Amaterasu Freexe Tomoki", {}) WHOQuery.new("Kekv Demonboo Dotmada Firobot Verminal Amaterasu Freexe Tomoki", {})
} }
local queryPending = false local queryPending = false
local ttl = #whoQueries * 2 local ttl = #whoQueries * 2
---@type WHOQuery? ---@type WHOQuery?
local lastQuery = nil local lastQuery = nil
---@param player Player ---@param player Player
---@return string? ---@return string?
local function Notify(player) local function Notify(player)
if not Heimdall_Data.config.who.enabled then return end 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 player then return string.format("Cannot notify for nil player %s", tostring(player)) end
if not Heimdall_Data.config.who.zoneNotifyFor[player.zone] then if not Heimdall_Data.config.who.zoneNotifyFor[player.zone] then
return string.format("Not notifying for zone %s", return string.format("Not notifying for zone %s",
tostring(player.zone)) tostring(player.zone))
end end
local text = player:NotifyMessage() local text = player:NotifyMessage()
---@type Message ---@type Message
local msg = { local msg = {
channel = "CHANNEL", channel = "CHANNEL",
data = Heimdall_Data.config.who.notifyChannel, data = Heimdall_Data.config.who.notifyChannel,
message = text message = text
} }
table.insert(shared.messenger.queue, msg) table.insert(shared.messenger.queue, msg)
if Heimdall_Data.config.who.doWhisper then if Heimdall_Data.config.who.doWhisper then
for _, name in pairs(Heimdall_Data.config.whisperNotify) do for _, name in pairs(Heimdall_Data.config.whisperNotify) do
---@type Message ---@type Message
local msg = { local msg = {
channel = "WHISPER", channel = "WHISPER",
data = name, data = name,
message = text message = text
} }
table.insert(shared.messenger.queue, msg) table.insert(shared.messenger.queue, msg)
end end
end end
return nil return nil
end end
---@param player Player ---@param player Player
---@param zone string ---@param zone string
---@return string? ---@return string?
local function NotifyZoneChanged(player, zone) local function NotifyZoneChanged(player, zone)
if not Heimdall_Data.config.who.enabled then return end 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 player then return string.format("Cannot notify for nil player %s", tostring(player)) end
if not Heimdall_Data.config.who.zoneNotifyFor[zone] if not Heimdall_Data.config.who.zoneNotifyFor[zone]
and not Heimdall_Data.config.who.zoneNotifyFor[player.zone] then 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)) return string.format("Not notifying for zones %s and %s", tostring(zone), tostring(player.zone))
end end
local text = string.format("%s of class %s (%s - %s) and guild %s moved to %s", local text = string.format("%s of class %s (%s - %s) and guild %s moved to %s",
player.name, player.name,
player.class, player.class,
player.race, player.race,
shared.raceMap[player.race] or "Unknown", shared.raceMap[player.race] or "Unknown",
player.guild, player.guild,
zone) zone)
---@type Message ---@type Message
local msg = { local msg = {
channel = "CHANNEL", channel = "CHANNEL",
data = Heimdall_Data.config.who.notifyChannel, data = Heimdall_Data.config.who.notifyChannel,
message = text message = text
} }
table.insert(shared.messenger.queue, msg) table.insert(shared.messenger.queue, msg)
if Heimdall_Data.config.who.doWhisper then if Heimdall_Data.config.who.doWhisper then
for _, name in pairs(Heimdall_Data.config.whisperNotify) do for _, name in pairs(Heimdall_Data.config.whisperNotify) do
---@type Message ---@type Message
local msg = { local msg = {
channel = "WHISPER", channel = "WHISPER",
data = name, data = name,
message = text message = text
} }
table.insert(shared.messenger.queue, msg) table.insert(shared.messenger.queue, msg)
end end
end end
return nil return nil
end end
---@param player Player ---@param player Player
---@return string? ---@return string?
local function NotifyGone(player) local function NotifyGone(player)
if not Heimdall_Data.config.who.enabled then return end 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 player then return string.format("Cannot notify for nil player %s", tostring(player)) end
if not Heimdall_Data.config.who.zoneNotifyFor[player.zone] then if not Heimdall_Data.config.who.zoneNotifyFor[player.zone] then
return string.format("Not notifying for zone %s", return string.format("Not notifying for zone %s",
tostring(player.zone)) tostring(player.zone))
end end
local text = string.format("%s of class %s and guild %s left %s", local text = string.format("%s of class %s and guild %s left %s",
player.name, player.name,
player.class, player.class,
player.guild, player.guild,
player.zone) player.zone)
---@type Message ---@type Message
local msg = { local msg = {
channel = "CHANNEL", channel = "CHANNEL",
data = Heimdall_Data.config.who.notifyChannel, data = Heimdall_Data.config.who.notifyChannel,
message = text message = text
} }
table.insert(shared.messenger.queue, msg) table.insert(shared.messenger.queue, msg)
if Heimdall_Data.config.who.doWhisper then if Heimdall_Data.config.who.doWhisper then
for _, name in pairs(Heimdall_Data.config.whisperNotify) do for _, name in pairs(Heimdall_Data.config.whisperNotify) do
---@type Message ---@type Message
local msg = { local msg = {
channel = "WHISPER", channel = "WHISPER",
data = name, data = name,
message = text message = text
} }
table.insert(shared.messenger.queue, msg) table.insert(shared.messenger.queue, msg)
end end
end end
return nil return nil
end end
local frame = CreateFrame("Frame") local frame = CreateFrame("Frame")
frame:RegisterEvent("WHO_LIST_UPDATE") frame:RegisterEvent("WHO_LIST_UPDATE")
frame:SetScript("OnEvent", function(self, event, ...) frame:SetScript("OnEvent", function(self, event, ...)
if not Heimdall_Data.config.who.enabled then return end if not Heimdall_Data.config.who.enabled then return end
---@type WHOQuery? ---@type WHOQuery?
local query = lastQuery local query = lastQuery
if not query then if not query then
print("No query wtf?") print("No query wtf?")
return return
end end
for i = 1, GetNumWhoResults() do for i = 1, GetNumWhoResults() do
local name, guild, level, race, class, zone = GetWhoInfo(i) local name, guild, level, race, class, zone = GetWhoInfo(i)
if Heimdall_Data.who.ignored[name] then return end if Heimdall_Data.who.ignored[name] then return end
local continue = false local continue = false
---@type WHOFilter[] ---@type WHOFilter[]
local filters = query.filters local filters = query.filters
for _, filter in pairs(filters) do for _, filter in pairs(filters) do
if not filter(name, guild, level, race, class, zone) then if not filter(name, guild, level, race, class, zone) then
-- Mega scuffed, yes... -- Mega scuffed, yes...
-- But wow does not have gotos -- But wow does not have gotos
continue = true continue = true
end end
end end
if not continue then if not continue then
local timestamp = date("%Y-%m-%dT%H:%M:%S") local timestamp = date("%Y-%m-%dT%H:%M:%S")
local player = HeimdallStinkies[name] local player = HeimdallStinkies[name]
if not player then if not player then
player = Player.new(name, guild, race, class, zone) player = Player.new(name, guild, race, class, zone)
if not Heimdall_Data.who then Heimdall_Data.who = {} end if not Heimdall_Data.who then Heimdall_Data.who = {} end
if not Heimdall_Data.who.data then Heimdall_Data.who.data = {} end if not Heimdall_Data.who.data then Heimdall_Data.who.data = {} end
local existing = Heimdall_Data.who.data[name] local existing = Heimdall_Data.who.data[name]
if existing then if existing then
player.lastSeen = existing.lastSeen or "never" player.lastSeen = existing.lastSeen or "never"
player.firstSeen = existing.firstSeen or "never" player.firstSeen = existing.firstSeen or "never"
player.seenCount = existing.seenCount or 0 player.seenCount = existing.seenCount or 0
end end
if player.firstSeen == "never" then if player.firstSeen == "never" then
player.firstSeen = timestamp player.firstSeen = timestamp
end end
local stinky = Heimdall_Data.config.stinkies[name] local stinky = Heimdall_Data.config.stinkies[name]
if stinky then if stinky then
player.stinky = true player.stinky = true
PlaySoundFile("Interface\\Sounds\\Domination.ogg", "Master") PlaySoundFile("Interface\\Sounds\\Domination.ogg", "Master")
else else
--PlaySoundFile("Interface\\Sounds\\Cloak.ogg", "Master") --PlaySoundFile("Interface\\Sounds\\Cloak.ogg", "Master")
end end
local err = Notify(player) local err = Notify(player)
if err then if err then
print(string.format("Error notifying for %s: %s", tostring(name), tostring(err))) print(string.format("Error notifying for %s: %s", tostring(name), tostring(err)))
end end
player.lastSeen = timestamp player.lastSeen = timestamp
player.seenCount = player.seenCount + 1 player.seenCount = player.seenCount + 1
HeimdallStinkies[name] = player HeimdallStinkies[name] = player
end end
player.lastSeenInternal = GetTime() player.lastSeenInternal = GetTime()
if player.zone ~= zone then if player.zone ~= zone then
local err = NotifyZoneChanged(player, zone) local err = NotifyZoneChanged(player, zone)
if err then if err then
print(string.format("Error notifying for %s: %s", tostring(name), tostring(err))) print(string.format("Error notifying for %s: %s", tostring(name), tostring(err)))
end end
end end
player.zone = zone player.zone = zone
player.lastSeen = timestamp player.lastSeen = timestamp
HeimdallStinkies[name] = player HeimdallStinkies[name] = player
if not Heimdall_Data.who then Heimdall_Data.who = {} end if not Heimdall_Data.who then Heimdall_Data.who = {} end
if not Heimdall_Data.who.data then Heimdall_Data.who.data = {} end if not Heimdall_Data.who.data then Heimdall_Data.who.data = {} end
Heimdall_Data.who.data[name] = player Heimdall_Data.who.data[name] = player
end end
end end
-- Turns out WA cannot do this ( -- Turns out WA cannot do this (
-- aura_env.UpdateMacro() -- aura_env.UpdateMacro()
_G["FriendsFrameCloseButton"]:Click() _G["FriendsFrameCloseButton"]:Click()
queryPending = false queryPending = false
end) end)
do do
local function UpdateStinkies() local function UpdateStinkies()
for name, player in pairs(HeimdallStinkies) do for name, player in pairs(HeimdallStinkies) do
if player.lastSeenInternal + Heimdall_Data.config.who.ttl < GetTime() then if player.lastSeenInternal + Heimdall_Data.config.who.ttl < GetTime() then
NotifyGone(player) NotifyGone(player)
--PlaySoundFile("Interface\\Sounds\\Uncloak.ogg", "Master") --PlaySoundFile("Interface\\Sounds\\Uncloak.ogg", "Master")
HeimdallStinkies[name] = nil HeimdallStinkies[name] = nil
end end
end end
end end
local function Tick() local function Tick()
UpdateStinkies() UpdateStinkies()
C_Timer.NewTimer(0.5, Tick, 1) C_Timer.NewTimer(0.5, Tick, 1)
end end
Tick() Tick()
end end
do do
local function DoQuery() local function DoQuery()
if not Heimdall_Data.config.who.enabled then return end if not Heimdall_Data.config.who.enabled then return end
if queryPending then if queryPending then
print("Tried running a who query while one is already pending, previous query:") print("Tried running a who query while one is already pending, previous query:")
shared.dumpTable(lastQuery) shared.dumpTable(lastQuery)
return return
end end
queryPending = true queryPending = true
local query = whoQueries[whoQueryIdx] local query = whoQueries[whoQueryIdx]
whoQueryIdx = whoQueryIdx + 1 whoQueryIdx = whoQueryIdx + 1
if whoQueryIdx > #whoQueries then if whoQueryIdx > #whoQueries then
whoQueryIdx = 1 whoQueryIdx = 1
end end
lastQuery = query lastQuery = query
--print(string.format("Running who query: %s", tostring(query.query))) --print(string.format("Running who query: %s", tostring(query.query)))
SetWhoToUI(1) SetWhoToUI(1)
SendWho(query.query) SendWho(query.query)
end end
local function Tick() local function Tick()
DoQuery() DoQuery()
C_Timer.NewTimer(1, Tick, 1) C_Timer.NewTimer(1, Tick, 1)
end end
Tick() Tick()
end end
local whoQueryWhisperFrame = CreateFrame("Frame") local whoQueryWhisperFrame = CreateFrame("Frame")
whoQueryWhisperFrame:RegisterEvent("CHAT_MSG_WHISPER") whoQueryWhisperFrame:RegisterEvent("CHAT_MSG_WHISPER")
whoQueryWhisperFrame:SetScript("OnEvent", function(self, event, msg, sender) whoQueryWhisperFrame:SetScript("OnEvent", function(self, event, msg, sender)
if not Heimdall_Data.config.who.enabled then return end if not Heimdall_Data.config.who.enabled then return end
if msg == "who" then if msg == "who" then
for _, player in pairs(HeimdallStinkies) do for _, player in pairs(HeimdallStinkies) do
local text = player:NotifyMessage() local text = player:NotifyMessage()
---@type Message ---@type Message
local msg = { local msg = {
channel = "WHISPER", channel = "WHISPER",
data = sender, data = sender,
message = text message = text
} }
table.insert(shared.messenger.queue, msg) table.insert(shared.messenger.queue, msg)
end end
end end
end) end)
local whoQueryChannelFrame = CreateFrame("Frame") local whoQueryChannelFrame = CreateFrame("Frame")
whoQueryChannelFrame:RegisterEvent("CHAT_MSG_CHANNEL") whoQueryChannelFrame:RegisterEvent("CHAT_MSG_CHANNEL")
whoQueryChannelFrame:SetScript("OnEvent", function(self, event, msg, sender, ...) whoQueryChannelFrame:SetScript("OnEvent", function(self, event, msg, sender, ...)
if not Heimdall_Data.config.who.enabled then return end if not Heimdall_Data.config.who.enabled then return end
local channelId = select(6, ...) local channelId = select(6, ...)
local channelname = "" local channelname = ""
---@type any[] ---@type any[]
local channels = { GetChannelList() } local channels = { GetChannelList() }
for i = 1, #channels, 2 do for i = 1, #channels, 2 do
---@type number ---@type number
local id = channels[i] local id = channels[i]
---@type string ---@type string
local name = channels[i + 1] local name = channels[i + 1]
if id == channelId then if id == channelId then
channelname = name channelname = name
end end
end end
if channelname ~= Heimdall_Data.config.who.notifyChannel then return end if channelname ~= Heimdall_Data.config.who.notifyChannel then return end
if msg == "who" then if msg == "who" then
for _, player in pairs(HeimdallStinkies) do for _, player in pairs(HeimdallStinkies) do
local text = player:NotifyMessage() local text = player:NotifyMessage()
---@type Message ---@type Message
local msg = { local msg = {
channel = "CHANNEL", channel = "CHANNEL",
data = channelname, data = channelname,
message = text message = text
} }
table.insert(shared.messenger.queue, msg) table.insert(shared.messenger.queue, msg)
end end
end end
end) end)
print("Heimdall - Whoer loaded") print("Heimdall - Whoer loaded")
end end

View File

@@ -1,5 +1,5 @@
rm Heimdall.zip rm Heimdall.zip
mkdir Heimdall mkdir Heimdall
cp *.lua *.toc Heimdall cp *.lua *.toc Modules/*.lua Heimdall
7z a Heimdall.zip Heimdall 7z a Heimdall.zip Heimdall
rm -rf Heimdall rm -rf Heimdall