38 Commits

Author SHA1 Message Date
7eee3a13a6 Add Russian localization who (kto) for our russian friends 2025-05-25 11:09:43 +02:00
263cf8e2e4 Add chatSniffer localization and configuration UI 2025-05-25 10:58:08 +02:00
ccbf0f8dc2 Add chatSniffer configuration and integrate into Heimdall initialization 2025-05-25 10:54:01 +02:00
63027c2dcf Update module initialization messages to use consistent format 2025-05-25 10:51:41 +02:00
d46c874604 Add timestamp logging to ChatSniffer events 2025-05-25 10:48:11 +02:00
fd4f707b6c Add ChatSniffer module and update saved variables 2025-05-25 10:47:58 +02:00
1168876dcc Fix FriendsFrame_OnEvent to pass additional parameters so it actually works naturally 2025-05-25 10:45:17 +02:00
bdf5afe436 Comment out debug print statements in Messenger module and fix queue loading 2025-05-25 10:41:02 +02:00
85ff907f05 Reset whoQueryIdx to 1 when no WHO query is found 2025-05-25 10:39:45 +02:00
7ae9db030b Remove redundant event registration for ADDON_LOADED in Heimdall.lua 2025-05-25 10:34:48 +02:00
edf8a12865 Refactor message queuing to use NetworkMessenger and Messenger based on configuration 2025-05-21 01:42:41 +02:00
d081eedd47 Fix debug print statement to include message count 2025-05-20 20:45:42 +02:00
8532db5a25 Rename dumpTable to dump and make it work with any values 2025-05-20 20:40:25 +02:00
26e783ee2e Update subproject commit reference in Meta 2025-05-20 20:20:59 +02:00
4bd237abef Hook friends list show to NOT show while we're waiting for who results 2025-05-20 20:19:41 +02:00
0ab14de0e2 CCP 2025-05-20 20:04:41 +02:00
a25b6a20d5 Release 3.12.0 2025-05-20 20:02:47 +02:00
e85c14ea45 Update StinkyTracker to use ReactiveValue for accessing ignored and stinkies lists 2025-05-18 16:05:12 +02:00
a564178ca2 Fix dumpTable function to ensure keys and values are converted to strings before printing 2025-05-18 16:05:03 +02:00
b4a4011b18 Refactor NetworkMessenger initialization to use ReactiveValue for queue management 2025-05-18 16:00:39 +02:00
3f3d252104 Update subproject commit reference in Meta 2025-05-18 15:54:48 +02:00
287be2a31c Move data definitions into their separate modules 2025-05-18 15:54:38 +02:00
3ef0e4c935 Refactor Heimdall messenger module to improve structure and utilize ReactiveValue for queue management 2025-05-18 15:54:11 +02:00
ce92e8e12c Refactor Commander and StinkyTracker modules for improved clarity and consistency in type annotations 2025-05-18 12:48:26 +02:00
03597d1b5e Update subproject commit reference in Meta 2025-05-18 12:45:11 +02:00
36ad9783e5 Move all config definitions to their respective modules 2025-05-18 12:43:55 +02:00
565db30125 Refactor multiple Heimdall modules to use class-based structure for improved organization and clarity 2025-05-18 12:31:26 +02:00
017cbf01f8 Refactor Heimdall modules to use class-based structure for improved organization and clarity 2025-05-18 12:28:25 +02:00
b16cf762ac Refactor Heimdall modules to improve structure and clarity, including AchievementSniffer, BonkDetector, Bully, Commander, and Config. 2025-05-18 12:27:00 +02:00
0057ac3a5c Refactor CombatAlerter, Commander, Inviter, Macroer, Sniffer modules for improved structure and clarity 2025-05-18 12:12:57 +02:00
0edf0561d8 Refactor DeathReporter, CombatAlerter, Commander, Configurator, and AgentTracker modules 2025-05-18 11:48:50 +02:00
1129d787b5 Refactor BonkDetector and Bully modules for improved structure and clarity 2025-05-18 11:43:40 +02:00
8a24496801 Add scratch.lua to .gitignore 2025-05-18 11:37:05 +02:00
6cb918c13c Refactor Heimdall module fields for improved organization and clarity 2025-05-18 11:36:40 +02:00
e3eefadb75 Refactor AgentTracker and related modules to improve agent management and logging 2025-05-18 11:16:13 +02:00
eab562b36d Enhance dumpTable function to include optional message parameter for improved logging 2025-05-18 11:15:52 +02:00
20a7c0eead Refactor StinkyTracker to improve tracking and ignore functionality 2025-05-18 10:51:33 +02:00
f70c5adfcf Add debug logging for stinky changes and simplify stinky handling 2025-05-18 10:31:36 +02:00
31 changed files with 6503 additions and 6171 deletions

2
.gitignore vendored
View File

@@ -1 +1 @@
*.zip scratch.lua

View File

@@ -1,5 +1,5 @@
globals = { "CykaPersistentData", "CreateFrame", "GetItemInfo", "aura_env" } globals = { "CykaPersistentData", "CreateFrame", "GetItemInfo", "aura_env" }
unused_args = false unused_args = false
max_line_length = 150 max_line_length = 500
exclude_files = { "Meta/" } exclude_files = { "Meta/" }
global = false global = false

View File

@@ -18,7 +18,8 @@ local function init()
---@field classColors table<string, string> ---@field classColors table<string, string>
---@field messenger HeimdallMessengerData ---@field messenger HeimdallMessengerData
---@field who HeimdallWhoData ---@field who HeimdallWhoData
---@field stinkyTracker HeimdallStinkyTrackerData ---@field stinkyTracker StinkyTrackerData
---@field agentTracker AgentTrackerData
---@field networkNodes string[] ---@field networkNodes string[]
---@field network HeimdallNetworkData ---@field network HeimdallNetworkData
---@field networkMessenger HeimdallNetworkMessengerData ---@field networkMessenger HeimdallNetworkMessengerData
@@ -26,7 +27,7 @@ local function init()
---@field _L fun(key: string, locale: string): string ---@field _L fun(key: string, locale: string): string
---@field _Locale Localization ---@field _Locale Localization
---@field VERSION string ---@field VERSION string
---@field dump fun(table: any, depth?: number): nil ---@field dump fun(table: any, msg?: string, depth?: number): nil
---@field utf8len fun(input: string): number ---@field utf8len fun(input: string): number
---@field padString fun(input: string, targetLength: number, left?: boolean): string ---@field padString fun(input: string, targetLength: number, left?: boolean): string
---@field GetOrDefault fun(table: table<any, any>, keys: string[], default: any): any ---@field GetOrDefault fun(table: table<any, any>, keys: string[], default: any): any
@@ -35,31 +36,31 @@ local function init()
---@field Memoize fun(f: function): function ---@field Memoize fun(f: function): function
---@field GetLocaleForChannel fun(channel: string): string ---@field GetLocaleForChannel fun(channel: string): string
---@field WhoQueryService WhoQueryService ---@field WhoQueryService WhoQueryService
---@field Whoer InitTable|{ShouldNotifyForZone: fun(zone: string): boolean} ---@field AchievementSniffer AchievementSniffer
---@field Messenger InitTable ---@field AgentTracker AgentTracker
---@field Spotter InitTable ---@field BonkDetector BonkDetector
---@field DeathReporter InitTable ---@field Bully Bully
---@field Inviter InitTable ---@field CombatAlerter CombatAlerter
---@field Dueler InitTable ---@field Commander Commander
---@field Bully InitTable ---@field Config Config
---@field AgentTracker InitTable ---@field Configurator Configurator
---@field Emoter InitTable ---@field DeathReporter DeathReporter
---@field Echoer InitTable ---@field Dueler Dueler
---@field Macroer InitTable ---@field Echoer Echoer
---@field Commander InitTable ---@field Emoter Emoter
---@field StinkyTracker InitTable ---@field Inviter Inviter
---@field CombatAlerter InitTable ---@field Macroer Macroer
---@field Config InitTable ---@field Messenger Messenger
---@field Sniffer InitTable ---@field MinimapTagger MinimapTagger
---@field MinimapTagger InitTable ---@field Network Network
---@field BonkDetector InitTable ---@field NetworkMessenger NetworkMessenger
---@field Noter InitTable ---@field Noter Noter
---@field Network InitTable ---@field Sniffer Sniffer
---@field NetworkMessenger InitTable ---@field Spotter Spotter
---@field StinkyCache InitTable ---@field StinkyCache StinkyCache
---@field Configurator InitTable ---@field StinkyTracker StinkyTracker
---@field AchievementSniffer InitTable ---@field Whoer Whoer
---@field ChatSniffer InitTable ---@field ChatSniffer ChatSniffer
--- Config --- --- Config ---
---@class HeimdallConfig ---@class HeimdallConfig
@@ -69,7 +70,6 @@ local function init()
---@field deathReporter HeimdallDeathReporterConfig ---@field deathReporter HeimdallDeathReporterConfig
---@field inviter HeimdallInviterConfig ---@field inviter HeimdallInviterConfig
---@field dueler HeimdallDuelerConfig ---@field dueler HeimdallDuelerConfig
---@field bully HeimdallBullyConfig
---@field agentTracker HeimdallAgentTrackerConfig ---@field agentTracker HeimdallAgentTrackerConfig
---@field emoter HeimdallEmoterConfig ---@field emoter HeimdallEmoterConfig
---@field echoer HeimdallEchoerConfig ---@field echoer HeimdallEchoerConfig
@@ -78,13 +78,13 @@ local function init()
---@field stinkyTracker HeimdallStinkyTrackerConfig ---@field stinkyTracker HeimdallStinkyTrackerConfig
---@field combatAlerter HeimdallCombatAlerterConfig ---@field combatAlerter HeimdallCombatAlerterConfig
---@field sniffer HeimdallSnifferConfig ---@field sniffer HeimdallSnifferConfig
---@field bonkDetector HeimdallBonkDetectorConfig
---@field noter HeimdallNoterConfig ---@field noter HeimdallNoterConfig
---@field network HeimdallNetworkConfig ---@field network HeimdallNetworkConfig
---@field networkMessenger HeimdallNetworkMessengerConfig ---@field networkMessenger HeimdallNetworkMessengerConfig
---@field configurator HeimdallConfiguratorConfig ---@field configurator HeimdallConfiguratorConfig
---@field stinkyCache HeimdallStinkyCacheConfig ---@field stinkyCache HeimdallStinkyCacheConfig
---@field achievementSniffer HeimdallAchievementSnifferConfig ---@field achievementSniffer HeimdallAchievementSnifferConfig
---@field chatSniffer HeimdallChatSnifferConfig
---@field whisperNotify table<string, string> ---@field whisperNotify table<string, string>
---@field addonPrefix string ---@field addonPrefix string
---@field stinkies table<string, boolean> ---@field stinkies table<string, boolean>
@@ -95,203 +95,6 @@ local function init()
---@field locale string ---@field locale string
---@field debug boolean ---@field debug boolean
---@class HeimdallSpotterConfig
---@field enabled boolean
---@field debug boolean
---@field everyone boolean
---@field hostile boolean
---@field alliance boolean
---@field stinky boolean
---@field channels string[]
---@field zoneOverride string?
---@field throttleTime number
---@class HeimdallWhoConfig
---@field enabled boolean
---@field debug boolean
---@field ignored table<string, boolean>
---@field channels string[]
---@field ttl number
---@field doWhisper boolean
---@field zoneNotifyFor table<string, boolean>
---@field queries string
---@class HeimdallMessengerConfig
---@field enabled boolean
---@field debug boolean
---@field interval number
---@class HeimdallDeathReporterConfig
---@field enabled boolean
---@field debug boolean
---@field throttle number
---@field doWhisper boolean
---@field channels string[]
---@field zoneOverride string?
---@field duelThrottle number
---@class HeimdallInviterConfig
---@field enabled boolean
---@field debug boolean
---@field channels string[]
---@field keyword string
---@field allAssist boolean
---@field agentsAssist boolean
---@field throttle number
---@field kickOffline boolean
---@field cleanupInterval number
---@field afkThreshold number
---@field listeningChannel table<string, boolean>
---@class HeimdallDuelerConfig
---@field enabled boolean
---@field debug boolean
---@field declineOther boolean
---@class HeimdallBullyConfig
---@field enabled boolean
---@field debug boolean
---@class HeimdallAgentTrackerConfig
---@field enabled boolean
---@field debug boolean
---@field channels string[]
---@class HeimdallEmoterConfig
---@field enabled boolean
---@field debug boolean
---@field channels string[]
---@field prefix string
---@class HeimdallEchoerConfig
---@field enabled boolean
---@field debug boolean
---@field channels string[]
---@field prefix string
---@class HeimdallMacroerConfig
---@field enabled boolean
---@field debug boolean
---@field priority string[]
---@class HeimdallCommanderConfig
---@field enabled boolean
---@field debug boolean
---@field channels string[]
---@field commander string
---@field commands table<string, boolean>
---@class HeimdallStinkyTrackerConfig
---@field enabled boolean
---@field debug boolean
---@field channels string[]
---@class HeimdallCombatAlerterConfig
---@field enabled boolean
---@field debug boolean
---@field channels string[]
---@class HeimdallSnifferConfig
---@field enabled boolean
---@field debug boolean
---@field channels string[]
---@field throttle number
---@field zoneOverride string?
---@field stinky boolean
---@class HeimdallMinimapTaggerConfig
---@field enabled boolean
---@field debug boolean
---@field channels string[]
---@field throttle number
---@field scale number
---@field tagTTL number
---@field tagSound boolean
---@field tagSoundFile string
---@field tagSoundThrottle number
---@field tagTextureFile string
---@field alertTTL number
---@field alertSound boolean
---@field alertSoundFile string
---@field alertSoundThrottle number
---@field alertTextureFile string
---@field combatTTL number
---@field combatSound boolean
---@field combatSoundFile string
---@field combatSoundThrottle number
---@field combatTextureFile string
---@field helpTTL number
---@field helpSound boolean
---@field helpSoundFile string
---@field helpSoundThrottle number
---@field helpTextureFile string
---@class HeimdallBonkDetectorConfig
---@field enabled boolean
---@field debug boolean
---@field channels string[]
---@field throttle number
---@class HeimdallNoterConfig
---@field enabled boolean
---@field debug boolean
---@field channels string[]
---@field lastNotes number
---@class HeimdallNetworkConfig
---@field enabled boolean
---@field debug boolean
---@field members string[]
---@field updateInterval number
---@class HeimdallNetworkMessengerConfig
---@field enabled boolean
---@field debug boolean
---@field interval number
---@class HeimdallConfiguratorConfig
---@field enabled boolean
---@field debug boolean
---@class HeimdallStinkyCacheConfig
---@field enabled boolean
---@field debug boolean
---@field commander string
---@field ttl number
---@class HeimdallAchievementSnifferConfig
---@field enabled boolean
---@field debug boolean
-----@field texture string
-----@field offsetX number
-----@field offsetY number
---@field rescan boolean
---@field scanInterval number
-----@field iconScale number
--- Data ---
---@class HeimdallMessengerData
---@field queue table<string, Message>
---@field ticker Timer?
---@class HeimdallNetworkMessengerData
---@field queue table<string, Message>
---@field ticker Timer?
---@class HeimdallWhoData
---@field updateTicker number?
---@field whoTicker number?
---@field ignored table<string, boolean>
---@class HeimdallStinkyTrackerData
---@field stinkies ReactiveValue
---@class HeimdallNetworkData
---@field ticker number?
---@class HeimdallStinkyCacheData
---@field stinkies table<string, {value: number, timestamp: number}>
shared.GetOrDefault = function(table, keys, default) shared.GetOrDefault = function(table, keys, default)
local value = default local value = default
if not table then return value end if not table then return value end
@@ -311,16 +114,13 @@ local function init()
return value return value
end end
shared.messenger = {
queue = {},
}
shared.who = {
ignored = {},
}
--/run Heimdall_Data.config.who.queries="g-\"БеспредеЛ\"|ally" --/run Heimdall_Data.config.who.queries="g-\"БеспредеЛ\"|ally"
Heimdall_Data.config = { Heimdall_Data.config = {
debug = shared.GetOrDefault(Heimdall_Data, { "config", "debug" }, false), debug = shared.GetOrDefault(Heimdall_Data, { "config", "debug" }, false),
chatSniffer = {
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "chatSniffer", "enabled" }, false),
debug = shared.GetOrDefault(Heimdall_Data, { "config", "chatSniffer", "debug" }, false),
},
spotter = { spotter = {
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "spotter", "enabled" }, true), enabled = shared.GetOrDefault(Heimdall_Data, { "config", "spotter", "enabled" }, true),
debug = shared.GetOrDefault(Heimdall_Data, { "config", "spotter", "debug" }, false), debug = shared.GetOrDefault(Heimdall_Data, { "config", "spotter", "debug" }, false),
@@ -419,6 +219,7 @@ local function init()
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "stinkyTracker", "enabled" }, false), enabled = shared.GetOrDefault(Heimdall_Data, { "config", "stinkyTracker", "enabled" }, false),
debug = shared.GetOrDefault(Heimdall_Data, { "config", "stinkyTracker", "debug" }, false), debug = shared.GetOrDefault(Heimdall_Data, { "config", "stinkyTracker", "debug" }, false),
channels = shared.GetOrDefault(Heimdall_Data, { "config", "stinkyTracker", "channels" }, { "Agent" }), channels = shared.GetOrDefault(Heimdall_Data, { "config", "stinkyTracker", "channels" }, { "Agent" }),
ignoredTimeout = shared.GetOrDefault(Heimdall_Data, { "config", "stinkyTracker", "ignoredTimeout" }, 600),
}, },
combatAlerter = { combatAlerter = {
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "combatAlerter", "enabled" }, false), enabled = shared.GetOrDefault(Heimdall_Data, { "config", "combatAlerter", "enabled" }, false),
@@ -671,7 +472,7 @@ local function init()
shared.Memoize = function(f) shared.Memoize = function(f)
local mem = {} -- memoizing table local mem = {} -- memoizing table
setmetatable(mem, { __mode = "kv" }) -- make it weak setmetatable(mem, { __mode = "kv" }) -- make it weak
return function(x) -- new version of f, with memoizing return function(x) -- new version of 'f', with memoizing
if Heimdall_Data.config.debug then print(string.format("[Heimdall] Memoize %s", tostring(x))) end if Heimdall_Data.config.debug then print(string.format("[Heimdall] Memoize %s", tostring(x))) end
local r = mem[x] local r = mem[x]
if r == nil then -- no previous result? if r == nil then -- no previous result?

BIN
Heimdall.zip (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -2,6 +2,16 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "AchievementSniffer" local ModuleName = "AchievementSniffer"
---@class HeimdallAchievementSnifferConfig
---@field enabled boolean
---@field debug boolean
-----@field texture string
-----@field offsetX number
-----@field offsetY number
---@field rescan boolean
---@field scanInterval number
-----@field iconScale number
-- local HeimdallRoot = "Interface\\AddOns\\Heimdall\\" -- local HeimdallRoot = "Interface\\AddOns\\Heimdall\\"
-- local TextureRoot = HeimdallRoot .. "Texture\\" -- local TextureRoot = HeimdallRoot .. "Texture\\"
@@ -74,220 +84,221 @@ local Achievements = {
12448, 12448,
} }
---@diagnostic disable-next-line: missing-fields ---@class AchievementSniffer
shared.AchievementSniffer = {} shared.AchievementSniffer = {
function shared.AchievementSniffer.Init() Init = function()
if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Module initialized", ModuleName))
end
local guidMap = {}
---@class AchievementData
---@field id number
---@field date string
---@field completed boolean
---@class Heimdall_Achievements
---@field players table<string, table<number, AchievementData>>
---@field alreadySeen table<string, boolean>
---@field rescan boolean
if not Heimdall_Achievements then Heimdall_Achievements = {} end
if not Heimdall_Achievements.players then Heimdall_Achievements.players = {} end
if not Heimdall_Achievements.alreadySeen then Heimdall_Achievements.alreadySeen = {} end
--local framePool = {}
--for i = 1, 40 do
-- local frame = CreateFrame("Frame", "HeimdallAchievementSnifferNameplate" .. i, UIParent)
-- local texture = frame:CreateTexture(nil, "ARTWORK")
-- texture:SetAllPoints(frame)
-- texture:SetTexture(TextureRoot .. Heimdall_Data.config.achievementSniffer.texture)
-- frame.texture = texture
-- frame:Hide()
-- table.insert(framePool, frame)
--end
---@param name string
---@return boolean
local function ShouldInspect(name)
local should = false
if not Heimdall_Achievements.players[name] then
if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Player %s does not have prior achievement data", ModuleName, name))
end
should = true
end
if Heimdall_Achievements.alreadySeen[name] then
if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Player %s has already been seen", ModuleName, name))
end
-- Save some memory
Heimdall_Achievements.players[name] = nil
should = false
end
if Heimdall_Data.config.achievementSniffer.rescan then
if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Rescan is enabled", ModuleName))
end
should = true
end
return should
end
-- It's not working well AT ALL
-- I don't know how to do it better
-- It simply just does not work...
--local function UpdateFrames()
-- for i, frame in ipairs(framePool) do
-- local unit = "nameplate" .. i
-- if not UnitExists(unit) then
-- --if Heimdall_Data.config.achievementSniffer.debug then
-- -- print(string.format("[%s] Unit %s does not exist, hiding frame", ModuleName, unit))
-- --end
-- frame:Hide()
-- else
-- --local unitFrame = _G[string.format("ElvUI_NamePlate%dHealthBar", i)]
-- local unitFrame = _G[string.format("NamePlate%d", i)]
-- if unitFrame == nil then
-- if Heimdall_Data.config.achievementSniffer.debug then
-- print(string.format("[%s] Unit frame for %s not found", ModuleName, unit))
-- end
-- frame:Hide()
-- else
-- local unitName = UnitName(unit)
-- if Heimdall_Data.config.achievementSniffer.debug then
-- print(string.format("[%s] Unit frame found for %s (%s)", ModuleName, unit, unitName))
-- end
-- frame:Show()
-- frame:SetSize(32, 32)
-- frame:SetFrameStrata("HIGH")
-- frame:SetFrameLevel(100)
-- frame:SetScale(Heimdall_Data.config.achievementSniffer.iconScale)
-- frame.texture:SetTexture(TextureRoot .. Heimdall_Data.config.achievementSniffer.texture)
-- frame:SetPoint("CENTER", unitFrame, "CENTER",
-- Heimdall_Data.config.achievementSniffer.offsetX,
-- Heimdall_Data.config.achievementSniffer.offsetY)
-- frame:SetParent(unitFrame)
-- frame:SetAlpha(1)
-- local exists = ShouldInspect(unitName)
-- if exists then
-- frame.texture:SetVertexColor(1, 0, 0, 1)
-- else
-- frame.texture:SetVertexColor(0, 1, 0, 1)
-- end
-- if Heimdall_Data.config.achievementSniffer.debug then
-- print(string.format("[%s] Frame updated for %s", ModuleName, unitName))
-- end
-- end
-- end
-- end
--end
---@param unit string
local function TryInspect(unit)
local targetPlayer = UnitIsPlayer(unit)
if not targetPlayer then return end
local targetName = UnitName(unit)
local targetGuid = UnitGUID(unit)
guidMap[targetGuid] = targetName
if not ShouldInspect(targetName) then
if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Not inspecting player: %s", ModuleName, targetName))
end
return
end
local canInspect = CheckInteractDistance(unit, 1)
if canInspect then
if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Inspecting player: %s", ModuleName, targetName))
end
SetAchievementComparisonUnit(unit)
else
if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Cannot inspect player (too far?): %s", ModuleName, targetName))
end
end
end
---@param name string
local function Scan(name)
if Heimdall_Data.config.achievementSniffer.debug then if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Scanning achievements for %s", ModuleName, name)) print(string.format("[%s] Module initialized", ModuleName))
end end
Heimdall_Achievements.players[name] = {} local guidMap = {}
for i, aid in ipairs(Achievements) do
local completed, month, day, year = GetAchievementComparisonInfo(aid)
if completed then
---@type string
local yearstr = "" .. year
if year < 100 then yearstr = "20" .. year end
local date = string.format("%04d-%02d-%02d", yearstr, month, day) ---@class AchievementData
---@field id number
---@field date string
---@field completed boolean
local data = { ---@class Heimdall_Achievements
id = aid, ---@field players table<string, table<number, AchievementData>>
date = date, ---@field alreadySeen table<string, boolean>
completed = completed, ---@field rescan boolean
} if not Heimdall_Achievements then Heimdall_Achievements = {} end
if not Heimdall_Achievements.players then Heimdall_Achievements.players = {} end
if not Heimdall_Achievements.alreadySeen then Heimdall_Achievements.alreadySeen = {} end
--local framePool = {}
--for i = 1, 40 do
-- local frame = CreateFrame("Frame", "HeimdallAchievementSnifferNameplate" .. i, UIParent)
-- local texture = frame:CreateTexture(nil, "ARTWORK")
-- texture:SetAllPoints(frame)
-- texture:SetTexture(TextureRoot .. Heimdall_Data.config.achievementSniffer.texture)
-- frame.texture = texture
-- frame:Hide()
-- table.insert(framePool, frame)
--end
---@param name string
---@return boolean
local function ShouldInspect(name)
local should = false
if not Heimdall_Achievements.players[name] then
if Heimdall_Data.config.achievementSniffer.debug then if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Achievement %d completed on %s", ModuleName, aid, date)) print(string.format("[%s] Player %s does not have prior achievement data", ModuleName, name))
end
should = true
end
if Heimdall_Achievements.alreadySeen[name] then
if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Player %s has already been seen", ModuleName, name))
end
-- Save some memory
Heimdall_Achievements.players[name] = nil
should = false
end
if Heimdall_Data.config.achievementSniffer.rescan then
if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Rescan is enabled", ModuleName))
end
should = true
end
return should
end
-- It's not working well AT ALL
-- I don't know how to do it better
-- It simply just does not work...
--local function UpdateFrames()
-- for i, frame in ipairs(framePool) do
-- local unit = "nameplate" .. i
-- if not UnitExists(unit) then
-- --if Heimdall_Data.config.achievementSniffer.debug then
-- -- print(string.format("[%s] Unit %s does not exist, hiding frame", ModuleName, unit))
-- --end
-- frame:Hide()
-- else
-- --local unitFrame = _G[string.format("ElvUI_NamePlate%dHealthBar", i)]
-- local unitFrame = _G[string.format("NamePlate%d", i)]
-- if unitFrame == nil then
-- if Heimdall_Data.config.achievementSniffer.debug then
-- print(string.format("[%s] Unit frame for %s not found", ModuleName, unit))
-- end
-- frame:Hide()
-- else
-- local unitName = UnitName(unit)
-- if Heimdall_Data.config.achievementSniffer.debug then
-- print(string.format("[%s] Unit frame found for %s (%s)", ModuleName, unit, unitName))
-- end
-- frame:Show()
-- frame:SetSize(32, 32)
-- frame:SetFrameStrata("HIGH")
-- frame:SetFrameLevel(100)
-- frame:SetScale(Heimdall_Data.config.achievementSniffer.iconScale)
-- frame.texture:SetTexture(TextureRoot .. Heimdall_Data.config.achievementSniffer.texture)
-- frame:SetPoint("CENTER", unitFrame, "CENTER",
-- Heimdall_Data.config.achievementSniffer.offsetX,
-- Heimdall_Data.config.achievementSniffer.offsetY)
-- frame:SetParent(unitFrame)
-- frame:SetAlpha(1)
-- local exists = ShouldInspect(unitName)
-- if exists then
-- frame.texture:SetVertexColor(1, 0, 0, 1)
-- else
-- frame.texture:SetVertexColor(0, 1, 0, 1)
-- end
-- if Heimdall_Data.config.achievementSniffer.debug then
-- print(string.format("[%s] Frame updated for %s", ModuleName, unitName))
-- end
-- end
-- end
-- end
--end
---@param unit string
local function TryInspect(unit)
local targetPlayer = UnitIsPlayer(unit)
if not targetPlayer then return end
local targetName = UnitName(unit)
local targetGuid = UnitGUID(unit)
guidMap[targetGuid] = targetName
if not ShouldInspect(targetName) then
if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Not inspecting player: %s", ModuleName, targetName))
end
return
end
local canInspect = CheckInteractDistance(unit, 1)
if canInspect then
if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Inspecting player: %s", ModuleName, targetName))
end
SetAchievementComparisonUnit(unit)
else
if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Cannot inspect player (too far?): %s", ModuleName, targetName))
end end
Heimdall_Achievements.players[name][aid] = data
end end
end end
--UpdateFrames()
end
local nameplateFrame = CreateFrame("Frame") ---@param name string
nameplateFrame:RegisterEvent("NAME_PLATE_UNIT_ADDED") local function Scan(name)
nameplateFrame:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
nameplateFrame:SetScript("OnEvent", function(self, event, unit)
if not Heimdall_Data.config.achievementSniffer.enabled then return end
if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Event triggered: %s for unit: %s", ModuleName, event, unit))
end
if event == "NAME_PLATE_UNIT_ADDED" then TryInspect(unit) end
--UpdateFrames()
end)
local inspectFrame = CreateFrame("Frame")
inspectFrame:RegisterEvent("INSPECT_ACHIEVEMENT_READY")
inspectFrame:SetScript("OnEvent", function(self, event, guid)
if not Heimdall_Data.config.achievementSniffer.enabled then return end
local name = guidMap[guid]
if not name then
if Heimdall_Data.config.achievementSniffer.debug then if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] No name found for guid: %s", ModuleName, guid)) print(string.format("[%s] Scanning achievements for %s", ModuleName, name))
end end
return Heimdall_Achievements.players[name] = {}
end for i, aid in ipairs(Achievements) do
if Heimdall_Data.config.achievementSniffer.debug then local completed, month, day, year = GetAchievementComparisonInfo(aid)
print(string.format("[%s] Event triggered: %s for player: %s", ModuleName, event, name)) if completed then
end ---@type string
Scan(name) local yearstr = "" .. year
end) if year < 100 then yearstr = "20" .. year end
local function Tick() local date = string.format("%04d-%02d-%02d", yearstr, month, day)
C_Timer.NewTimer(Heimdall_Data.config.achievementSniffer.scanInterval, Tick)
if not Heimdall_Data.config.achievementSniffer.enabled then return end
if Heimdall_Data.config.achievementSniffer.debug then local data = {
print(string.format("[%s] Scanning achievements for everyone on screen", ModuleName)) id = aid,
end date = date,
for i = 1, 40 do completed = completed,
local unit = "nameplate" .. i }
if UnitExists(unit) then if Heimdall_Data.config.achievementSniffer.debug then
TryInspect(unit) print(string.format("[%s] Achievement %d completed on %s", ModuleName, aid, date))
--else end
-- if Heimdall_Data.config.achievementSniffer.debug then Heimdall_Achievements.players[name][aid] = data
-- print(string.format("[%s] Unit %s does not exist, nothing to inspect", ModuleName, unit)) end
-- end
end end
--UpdateFrames()
end end
--UpdateFrames()
end
Tick()
print("[Heimdall] AchievementSniffer loaded") local nameplateFrame = CreateFrame("Frame")
end nameplateFrame:RegisterEvent("NAME_PLATE_UNIT_ADDED")
nameplateFrame:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
nameplateFrame:SetScript("OnEvent", function(self, event, unit)
if not Heimdall_Data.config.achievementSniffer.enabled then return end
if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Event triggered: %s for unit: %s", ModuleName, event, unit))
end
if event == "NAME_PLATE_UNIT_ADDED" then TryInspect(unit) end
--UpdateFrames()
end)
local inspectFrame = CreateFrame("Frame")
inspectFrame:RegisterEvent("INSPECT_ACHIEVEMENT_READY")
inspectFrame:SetScript("OnEvent", function(self, event, guid)
if not Heimdall_Data.config.achievementSniffer.enabled then return end
local name = guidMap[guid]
if not name then
if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] No name found for guid: %s", ModuleName, guid))
end
return
end
if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Event triggered: %s for player: %s", ModuleName, event, name))
end
Scan(name)
end)
local function Tick()
C_Timer.NewTimer(Heimdall_Data.config.achievementSniffer.scanInterval, Tick)
if not Heimdall_Data.config.achievementSniffer.enabled then return end
if Heimdall_Data.config.achievementSniffer.debug then
print(string.format("[%s] Scanning achievements for everyone on screen", ModuleName))
end
for i = 1, 40 do
local unit = "nameplate" .. i
if UnitExists(unit) then
TryInspect(unit)
--else
-- if Heimdall_Data.config.achievementSniffer.debug then
-- print(string.format("[%s] Unit %s does not exist, nothing to inspect", ModuleName, unit))
-- end
end
end
--UpdateFrames()
end
Tick()
print(string.format("[%s] Module initialized", ModuleName))
end,
}

View File

@@ -2,118 +2,143 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "AgentTracker" local ModuleName = "AgentTracker"
---@diagnostic disable-next-line: missing-fields ---@class AgentTrackerData
shared.AgentTracker = {} ---@field agents ReactiveValue<table<string, string>>
function shared.AgentTracker.Init()
--/run Heimdall_Data.config.agents["Cyheuraeth"]=date("%Y-%m-%dT%H:%M:%S")
---@type table<string, boolean>
local channelRosterFrame = CreateFrame("Frame")
channelRosterFrame:RegisterEvent("CHANNEL_ROSTER_UPDATE")
channelRosterFrame:SetScript("OnEvent", function(self, event, index)
if Heimdall_Data.config.agentTracker.debug then
print(string.format("[%s] Channel roster update received", ModuleName))
end
if not Heimdall_Data.config.agentTracker.enabled then
if Heimdall_Data.config.agentTracker.debug then
print(string.format("[%s] Module disabled, ignoring roster update", ModuleName))
end
return
end
local name = GetChannelDisplayInfo(index)
if Heimdall_Data.config.agentTracker.debug then
print(string.format("[%s] Processing channel update: %s (index: %d)", ModuleName, name or "nil", index))
end
if name ~= Heimdall_Data.config.agentTracker.masterChannel then
if Heimdall_Data.config.agentTracker.debug then
print(string.format("[%s] Ignoring non-master channel: %s", ModuleName, name or "nil"))
end
return
end
local count = select(5, GetChannelDisplayInfo(index))
if Heimdall_Data.config.agentTracker.debug then
print(string.format("[%s] Processing %d members in channel", ModuleName, count))
end
local newAgents = 0 ---@class HeimdallAgentTrackerConfig
for i = 1, count do ---@field enabled boolean
name = GetChannelRosterInfo(index, i) ---@field debug boolean
if name then ---@field channels string[]
local isNewAgent = not Heimdall_Data.config.agents[name]
Heimdall_Data.config.agents[name] = date("%Y-%m-%dT%H:%M:%S") ---@class AgentTracker
if isNewAgent then newAgents = newAgents + 1 end shared.AgentTracker = {
---@param name string
---@return boolean
Track = function(name)
if not name then return false end
local exists = shared.AgentTracker.IsAgent(name)
if exists then return false end
shared.agentTracker.agents[name] = date("%Y-%m-%dT%H:%M:%S")
-- Heimdall_Data.config.agents[name] = date("%Y-%m-%dT%H:%M:%S")
if Heimdall_Data.config.agentTracker.debug then
print(string.format("[%s] Tracking new agent: %s", ModuleName, name))
shared.dump(shared.agentTracker.agents)
end
return true
end,
---@param name string
---@return boolean
IsAgent = function(name)
if not name then return false end
return shared.agentTracker.agents[name] ~= nil
end,
---@param callback fun(agent: string)
OnChange = function(callback) shared.agentTracker.agents:onChange(callback) end,
---@param callback fun(agent: string)
ForEach = function(callback)
---@type table<string, string>
local agents = shared.agentTracker.agents:get()
for name, _ in pairs(agents) do
callback(name)
end
end,
---@return nil
Init = function()
shared.agentTracker = {
agents = ReactiveValue.new(Heimdall_Data.config.agents),
}
--/run Heimdall_Data.config.agents["Cyheuraeth"]=date("%Y-%m-%dT%H:%M:%S")
---@type table<string, boolean>
local channelRosterFrame = CreateFrame("Frame")
channelRosterFrame:RegisterEvent("CHANNEL_ROSTER_UPDATE")
channelRosterFrame:SetScript("OnEvent", function(self, event, index)
if Heimdall_Data.config.agentTracker.debug then
print(string.format("[%s] Channel roster update received", ModuleName))
end
if not Heimdall_Data.config.agentTracker.enabled then
if Heimdall_Data.config.agentTracker.debug then if Heimdall_Data.config.agentTracker.debug then
print( print(string.format("[%s] Module disabled, ignoring roster update", ModuleName))
string.format( end
"[%s] %s agent: %s", return
ModuleName, end
isNewAgent and "Added new" or "Updated existing", local name = GetChannelDisplayInfo(index)
name if Heimdall_Data.config.agentTracker.debug then
) print(string.format("[%s] Processing channel update: %s (index: %d)", ModuleName, name or "nil", index))
) end
if name ~= Heimdall_Data.config.agentTracker.masterChannel then
if Heimdall_Data.config.agentTracker.debug then
print(string.format("[%s] Ignoring non-master channel: %s", ModuleName, name or "nil"))
end
return
end
local count = select(5, GetChannelDisplayInfo(index))
if Heimdall_Data.config.agentTracker.debug then
print(string.format("[%s] Processing %d members in channel", ModuleName, count))
end
local newAgents = 0
for i = 1, count do
name = GetChannelRosterInfo(index, i)
shared.AgentTracker.Track(name)
end
if Heimdall_Data.config.agentTracker.debug then
print(string.format("[%s] Roster update complete - Added %d new agents", ModuleName, newAgents))
end
end)
local agentTrackerChannelSniffer = CreateFrame("Frame")
agentTrackerChannelSniffer:RegisterEvent("CHAT_MSG_CHANNEL")
agentTrackerChannelSniffer:SetScript("OnEvent", function(self, event, msg, sender, ...)
-- if Heimdall_Data.config.agentTracker.debug then
-- print(string.format("[%s] Channel message received from: %s", ModuleName, sender))
-- end
if not Heimdall_Data.config.agentTracker.enabled then
-- if Heimdall_Data.config.agentTracker.debug then
-- print(string.format("[%s] Module disabled, ignoring channel message", ModuleName))
-- end
return
end
local channelId = select(6, ...)
local _, channelname = GetChannelName(channelId)
local ok = false
for _, channel in pairs(Heimdall_Data.config.agentTracker.channels) do
if channel == channelname then
ok = true
break
end end
end end
end if not ok then
if Heimdall_Data.config.agentTracker.debug then
if Heimdall_Data.config.agentTracker.debug then print(string.format("[%s] Channel name does not match any of the channels", ModuleName))
print(string.format("[%s] Roster update complete - Added %d new agents", ModuleName, newAgents)) end
end return
end)
local agentTrackerChannelSniffer = CreateFrame("Frame")
agentTrackerChannelSniffer:RegisterEvent("CHAT_MSG_CHANNEL")
agentTrackerChannelSniffer:SetScript("OnEvent", function(self, event, msg, sender, ...)
-- if Heimdall_Data.config.agentTracker.debug then
-- print(string.format("[%s] Channel message received from: %s", ModuleName, sender))
-- end
if not Heimdall_Data.config.agentTracker.enabled then
-- if Heimdall_Data.config.agentTracker.debug then
-- print(string.format("[%s] Module disabled, ignoring channel message", ModuleName))
-- end
return
end
local channelId = select(6, ...)
local _, channelname = GetChannelName(channelId)
local ok = false
for _, channel in pairs(Heimdall_Data.config.agentTracker.channels) do
if channel == channelname then
ok = true
break
end end
end
if not ok then
if Heimdall_Data.config.agentTracker.debug then if Heimdall_Data.config.agentTracker.debug then
print(string.format("[%s] Channel name does not match any of the channels", ModuleName)) print(string.format("[%s] Processing message from master channel: %s", ModuleName, sender))
end end
return
end
if Heimdall_Data.config.agentTracker.debug then
print(string.format("[%s] Processing message from master channel: %s", ModuleName, sender))
shared.dump(Heimdall_Data.config.agentTracker)
end
sender = string.match(sender, "^[^-]+") sender = string.match(sender, "^[^-]+")
local isNewAgent = not Heimdall_Data.config.agents[sender] local new = shared.AgentTracker.Track(sender)
Heimdall_Data.config.agents[sender] = date("%Y-%m-%dT%H:%M:%S")
if Heimdall_Data.config.agentTracker.debug then if Heimdall_Data.config.agentTracker.debug then
print( print(
string.format( string.format(
"[%s] %s agent from message: %s", "[%s] %s agent from message: %s",
ModuleName, ModuleName,
isNewAgent and "Added new" or "Updated existing", new and "Added new" or "Updated existing",
sender sender
)
) )
) end
end end)
end)
if Heimdall_Data.config.agentTracker.debug then if Heimdall_Data.config.agentTracker.debug then
local count = 0 print(string.format("[%s] Module initialized", ModuleName))
for _ in pairs(Heimdall_Data.config.agents) do shared.dump(shared.agentTracker.agents:get(), "Agents")
count = count + 1
end end
print(string.format("[%s] Module initialized - Tracking %d agents", ModuleName, count)) print(string.format("[%s] Module initialized", ModuleName))
end end,
print("[Heimdall] AgentTracker loaded") }
end

View File

@@ -2,133 +2,141 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "BonkDetector" local ModuleName = "BonkDetector"
---@diagnostic disable-next-line: missing-fields ---@class HeimdallBonkDetectorConfig
shared.BonkDetector = {} ---@field enabled boolean
function shared.BonkDetector.Init() ---@field debug boolean
---@type table<string, number> ---@field channels string[]
local lastReportTime = {} ---@field throttle number
local frame = CreateFrame("Frame") ---@class BonkDetector
frame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") shared.BonkDetector = {
frame:SetScript("OnEvent", function(self, event, ...) ---@return nil
-- if Heimdall_Data.config.bonkDetector.debug then Init = function()
-- print(string.format("[%s] Combat log event received", ModuleName)) ---@type table<string, number>
-- end local lastReportTime = {}
if not Heimdall_Data.config.bonkDetector.enabled then
-- if Heimdall_Data.config.bonkDetector.debug then
-- print(string.format("[%s] Module disabled, ignoring combat event", ModuleName))
-- end
return
end
local subevent = select(2, ...) local frame = CreateFrame("Frame")
if not subevent:find("_DAMAGE") then frame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
if Heimdall_Data.config.bonkDetector.debug then frame:SetScript("OnEvent", function(self, event, ...)
print(string.format("[%s] Not a damage event, ignoring: %s", ModuleName, subevent)) -- if Heimdall_Data.config.bonkDetector.debug then
-- print(string.format("[%s] Combat log event received", ModuleName))
-- end
if not Heimdall_Data.config.bonkDetector.enabled then
-- if Heimdall_Data.config.bonkDetector.debug then
-- print(string.format("[%s] Module disabled, ignoring combat event", ModuleName))
-- end
return
end end
return
end
---@type string|nil, string, string, string, string local subevent = select(2, ...)
local err, source, sourceGUID, destination, destinationGUID if not subevent:find("_DAMAGE") then
if Heimdall_Data.config.bonkDetector.debug then
print(string.format("[%s] Not a damage event, ignoring: %s", ModuleName, subevent))
end
return
end
source, err = CLEUParser.GetSourceName(...) ---@type string|nil, string, string, string, string
if err then local err, source, sourceGUID, destination, destinationGUID
if Heimdall_Data.config.bonkDetector.debug then
print(string.format("[%s] Error getting source name: %s", ModuleName, err))
end
return
end
sourceGUID, err = CLEUParser.GetSourceGUID(...)
if err then
if Heimdall_Data.config.bonkDetector.debug then
print(string.format("[%s] Error getting source GUID: %s", ModuleName, err))
end
return
end
if not string.find(sourceGUID, "Player") then
if Heimdall_Data.config.bonkDetector.debug then
print(string.format("[%s] Source %s is not a player, nothing to do", ModuleName, source))
end
return
end
destination, err = CLEUParser.GetDestName(...) source, err = CLEUParser.GetSourceName(...)
if err then if err then
if Heimdall_Data.config.bonkDetector.debug then if Heimdall_Data.config.bonkDetector.debug then
print(string.format("[%s] Error getting destination name: %s", ModuleName, err)) print(string.format("[%s] Error getting source name: %s", ModuleName, err))
end
return
end end
return sourceGUID, err = CLEUParser.GetSourceGUID(...)
end if err then
destinationGUID, err = CLEUParser.GetDestGUID(...) if Heimdall_Data.config.bonkDetector.debug then
if err then print(string.format("[%s] Error getting source GUID: %s", ModuleName, err))
if Heimdall_Data.config.bonkDetector.debug then end
print(string.format("[%s] Error getting destination GUID: %s", ModuleName, err)) return
end end
return if not string.find(sourceGUID, "Player") then
end if Heimdall_Data.config.bonkDetector.debug then
if not string.find(destinationGUID, "Player") then print(string.format("[%s] Source %s is not a player, nothing to do", ModuleName, source))
if Heimdall_Data.config.bonkDetector.debug then end
print(string.format("[%s] Destination %s is not a player, nothing to do", ModuleName, destination)) return
end end
return
end
if source == destination then destination, err = CLEUParser.GetDestName(...)
if Heimdall_Data.config.bonkDetector.debug then if err then
print(string.format("[%s] Source and destination are the same, ignoring event", ModuleName)) if Heimdall_Data.config.bonkDetector.debug then
print(string.format("[%s] Error getting destination name: %s", ModuleName, err))
end
return
end
destinationGUID, err = CLEUParser.GetDestGUID(...)
if err then
if Heimdall_Data.config.bonkDetector.debug then
print(string.format("[%s] Error getting destination GUID: %s", ModuleName, err))
end
return
end
if not string.find(destinationGUID, "Player") then
if Heimdall_Data.config.bonkDetector.debug then
print(string.format("[%s] Destination %s is not a player, nothing to do", ModuleName, destination))
end
return
end end
return
end
local currentTime = GetTime() if source == destination then
local throttle = Heimdall_Data.config.bonkDetector.throttle if Heimdall_Data.config.bonkDetector.debug then
print(string.format("[%s] Source and destination are the same, ignoring event", ModuleName))
end
return
end
local currentTime = GetTime()
local throttle = Heimdall_Data.config.bonkDetector.throttle
if lastReportTime[source] and (currentTime - lastReportTime[source]) < throttle then
if Heimdall_Data.config.bonkDetector.debug then
local timeLeft = throttle - (currentTime - lastReportTime[source])
print(
string.format(
"[%s] Damage report throttled for %s (%.1f seconds remaining)",
ModuleName,
source,
timeLeft
)
)
end
return
end
lastReportTime[source] = currentTime
if lastReportTime[source] and (currentTime - lastReportTime[source]) < throttle then
if Heimdall_Data.config.bonkDetector.debug then if Heimdall_Data.config.bonkDetector.debug then
local timeLeft = throttle - (currentTime - lastReportTime[source])
print( print(
string.format( string.format(
"[%s] Damage report throttled for %s (%.1f seconds remaining)", "[%s] Processing damage event - Source: %s, Target: %s, Type: %s",
ModuleName, ModuleName,
source, source,
timeLeft destination,
subevent
) )
) )
end end
return
end
lastReportTime[source] = currentTime for _, channel in pairs(Heimdall_Data.config.bonkDetector.channels) do
local locale = shared.GetLocaleForChannel(channel)
if Heimdall_Data.config.bonkDetector.debug then local msg = string.format(shared._L("bonkDetected", locale), source, destination, subevent)
print( ---@type Message
string.format( local message = {
"[%s] Processing damage event - Source: %s, Target: %s, Type: %s", channel = "C",
ModuleName, data = channel,
source, message = msg,
destination, }
subevent if Heimdall_Data.config.bonkDetector.debug then
) print(string.format("[%s] Queuing bonk detector message", ModuleName))
) shared.dump(message)
end end
table.insert(shared.messenger.queue, message)
for _, channel in pairs(Heimdall_Data.config.bonkDetector.channels) do
local locale = shared.GetLocaleForChannel(channel)
local msg = string.format(shared._L("bonkDetected", locale), source, destination, subevent)
---@type Message
local message = {
channel = "C",
data = channel,
message = msg,
}
if Heimdall_Data.config.bonkDetector.debug then
print(string.format("[%s] Queuing bonk detector message", ModuleName))
shared.dump(message)
end end
table.insert(shared.messenger.queue, message) end)
end
end)
print("[Heimdall] BonkDetector loaded") print(string.format("[%s] Module initialized", ModuleName))
end end,
}

View File

@@ -2,9 +2,15 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "Bully" local ModuleName = "Bully"
---@diagnostic disable-next-line: missing-fields ---@class HeimdallBullyConfig
shared.Bully = {} ---@field enabled boolean
function shared.Bully.Init() ---@field debug boolean
if Heimdall_Data.config.bully.debug then print(string.format("[%s] Module initialized", ModuleName)) end
print("[Heimdall] Bully loaded") ---@class Bully
end shared.Bully = {
---@return nil
Init = function()
if Heimdall_Data.config.bully.debug then print(string.format("[%s] Module initialized", ModuleName)) end
print(string.format("[%s] Module initialized", ModuleName))
end,
}

View File

@@ -2,39 +2,48 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "ChatSniffer" local ModuleName = "ChatSniffer"
---@diagnostic disable-next-line: missing-fields ---@class HeimdallChatSnifferConfig
shared.ChatSniffer = {} ---@field enabled boolean
function shared.ChatSniffer.Init() ---@field debug boolean
Heimdall_Chat = Heimdall_Chat or {}
local frame = CreateFrame("Frame")
frame:RegisterEvent("CHAT_MSG_SAY")
frame:RegisterEvent("CHAT_MSG_YELL")
frame:RegisterEvent("CHAT_MSG_CHANNEL")
frame:RegisterEvent("CHAT_MSG_WHISPER")
frame:RegisterEvent("CHAT_MSG_CHANNEL_JOIN")
frame:RegisterEvent("CHAT_MSG_CHANNEL_LEAVE")
frame:RegisterEvent("CHAT_MSG_EMOTE")
frame:RegisterEvent("CHAT_MSG_PARTY")
frame:RegisterEvent("CHAT_MSG_PARTY_LEADER")
frame:RegisterEvent("CHAT_MSG_RAID")
frame:RegisterEvent("CHAT_MSG_RAID_LEADER")
frame:RegisterEvent("CHAT_MSG_RAID_WARNING")
frame:RegisterEvent("CHAT_MSG_SYSTEM")
frame:RegisterEvent("CHAT_MSG_TEXT_EMOTE")
frame:RegisterEvent("CHAT_MSG_YELL")
frame:SetScript("OnEvent", function(self, event, msg, sender, language, channel)
local timestamp = date("%Y-%m-%d %H:%M:%S")
local log = string.format(
"%s|%s|%s|%s|%s|%s",
tostring(timestamp),
tostring(event),
tostring(sender),
tostring(msg),
tostring(language),
tostring(channel)
)
Heimdall_Chat[#Heimdall_Chat + 1] = log
end)
print("[Heimdall] ChatSniffer loaded") ---@class ChatSniffer
end shared.ChatSniffer = {
Init = function()
Heimdall_Chat = Heimdall_Chat or {}
local frame = CreateFrame("Frame")
frame:RegisterEvent("CHAT_MSG_SAY")
frame:RegisterEvent("CHAT_MSG_YELL")
frame:RegisterEvent("CHAT_MSG_CHANNEL")
frame:RegisterEvent("CHAT_MSG_WHISPER")
frame:RegisterEvent("CHAT_MSG_CHANNEL_JOIN")
frame:RegisterEvent("CHAT_MSG_CHANNEL_LEAVE")
frame:RegisterEvent("CHAT_MSG_EMOTE")
frame:RegisterEvent("CHAT_MSG_PARTY")
frame:RegisterEvent("CHAT_MSG_PARTY_LEADER")
frame:RegisterEvent("CHAT_MSG_RAID")
frame:RegisterEvent("CHAT_MSG_RAID_LEADER")
frame:RegisterEvent("CHAT_MSG_RAID_WARNING")
frame:RegisterEvent("CHAT_MSG_SYSTEM")
frame:RegisterEvent("CHAT_MSG_TEXT_EMOTE")
frame:RegisterEvent("CHAT_MSG_YELL")
frame:SetScript("OnEvent", function(self, event, msg, sender, language, channel)
if not Heimdall_Data.config.chatSniffer.enabled then return end
if not Heimdall_Data.config.chatSniffer.debug then
shared.dump(string.format("[%s] got message", { event, msg, sender, language, channel }))
end
local timestamp = date("%Y-%m-%d %H:%M:%S")
local log = string.format(
"%s|%s|%s|%s|%s|%s",
tostring(timestamp),
tostring(event),
tostring(sender),
tostring(msg),
tostring(language),
tostring(channel)
)
Heimdall_Chat[#Heimdall_Chat + 1] = log
end)
print(string.format("[%s] Module initialized", ModuleName))
end,
}

View File

@@ -2,134 +2,140 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "CombatAlerter" local ModuleName = "CombatAlerter"
---@diagnostic disable-next-line: missing-fields ---@class HeimdallCombatAlerterConfig
shared.CombatAlerter = {} ---@field enabled boolean
function shared.CombatAlerter.Init() ---@field debug boolean
local alerted = {} ---@field channels string[]
local combatAlerterFrame = CreateFrame("Frame")
combatAlerterFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") ---@class CombatAlerter
combatAlerterFrame:SetScript("OnEvent", function(self, event, ...) shared.CombatAlerter = {
if Heimdall_Data.config.combatAlerter.debug then Init = function()
print(string.format("[%s] Combat log event received", ModuleName)) local alerted = {}
end local combatAlerterFrame = CreateFrame("Frame")
if not Heimdall_Data.config.combatAlerter.enabled then combatAlerterFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
combatAlerterFrame:SetScript("OnEvent", function(self, event, ...)
if Heimdall_Data.config.combatAlerter.debug then if Heimdall_Data.config.combatAlerter.debug then
print(string.format("[%s] Module disabled, ignoring combat event", ModuleName)) print(string.format("[%s] Combat log event received", ModuleName))
end end
return if not Heimdall_Data.config.combatAlerter.enabled then
end if Heimdall_Data.config.combatAlerter.debug then
print(string.format("[%s] Module disabled, ignoring combat event", ModuleName))
---@type string|nil, string, string end
local err, source, destination return
destination, err = CLEUParser.GetDestName(...)
if err then
if Heimdall_Data.config.combatAlerter.debug then
print(string.format("[%s] Error getting destination: %s", ModuleName, err))
end end
return
end
if Heimdall_Data.config.combatAlerter.debug then ---@type string|nil, string, string
print(string.format("[%s] Combat event destination: %s", ModuleName, destination)) local err, source, destination
end
if destination ~= UnitName("player") then destination, err = CLEUParser.GetDestName(...)
if Heimdall_Data.config.combatAlerter.debug then if err then
print(string.format("[%s] Ignoring event - not targeted at player", ModuleName)) if Heimdall_Data.config.combatAlerter.debug then
print(string.format("[%s] Error getting destination: %s", ModuleName, err))
end
return
end end
return
end
source, err = CLEUParser.GetSourceName(...)
if err then
if Heimdall_Data.config.combatAlerter.debug then if Heimdall_Data.config.combatAlerter.debug then
print(string.format("[%s] Error getting source, using 'unknown': %s", ModuleName, err)) print(string.format("[%s] Combat event destination: %s", ModuleName, destination))
end end
source = "unknown"
end
if Heimdall_Data.config.combatAlerter.debug then if destination ~= UnitName("player") then
print(string.format("[%s] Combat event source: %s", ModuleName, source)) if Heimdall_Data.config.combatAlerter.debug then
end print(string.format("[%s] Ignoring event - not targeted at player", ModuleName))
end
return
end
source, err = CLEUParser.GetSourceName(...)
if err then
if Heimdall_Data.config.combatAlerter.debug then
print(string.format("[%s] Error getting source, using 'unknown': %s", ModuleName, err))
end
source = "unknown"
end
if shared.stinkyTracker.stinkies and shared.stinkyTracker.stinkies[source] then
if Heimdall_Data.config.combatAlerter.debug then if Heimdall_Data.config.combatAlerter.debug then
print( print(string.format("[%s] Combat event source: %s", ModuleName, source))
string.format( end
"[%s] Source is tracked stinky: %s (Already alerted: %s)",
ModuleName, if shared.StinkyTracker.IsStinky(source) then
source, if Heimdall_Data.config.combatAlerter.debug then
tostring(alerted[source] or false) print(
string.format(
"[%s] Source is tracked stinky: %s (Already alerted: %s)",
ModuleName,
source,
tostring(alerted[source] or false)
)
) )
) end
end if alerted[source] then return end
if alerted[source] then return end
alerted[source] = true alerted[source] = true
local x, y = GetPlayerMapPosition("player") local x, y = GetPlayerMapPosition("player")
local zone, subZone = GetZoneText(), GetSubZoneText() local zone, subZone = GetZoneText(), GetSubZoneText()
if Heimdall_Data.config.combatAlerter.debug then if Heimdall_Data.config.combatAlerter.debug then
print( print(
string.format( string.format(
"[%s] Player location: %s/%s at %.2f,%.2f", "[%s] Player location: %s/%s at %.2f,%.2f",
ModuleName, ModuleName,
zone, zone,
subZone, subZone,
x * 100,
y * 100
)
)
end
SetMapToCurrentZone()
SetMapByID(GetCurrentMapAreaID())
local areaId = GetCurrentMapAreaID()
for _, channel in pairs(Heimdall_Data.config.combatAlerter.channels) do
local locale = shared.GetLocaleForChannel(channel)
local text = string.format(
shared._L("combatAlerterInCombat", locale),
source,
shared._L("zone", locale),
shared._L("subZone", locale),
tostring(areaId),
x * 100, x * 100,
y * 100 y * 100
) )
) ---@type Message
end local msg = {
channel = "C",
SetMapToCurrentZone() data = channel,
SetMapByID(GetCurrentMapAreaID()) message = text,
local areaId = GetCurrentMapAreaID() }
if Heimdall_Data.config.combatAlerter.debug then
for _, channel in pairs(Heimdall_Data.config.combatAlerter.channels) do print(string.format("[%s] Queuing alert message", ModuleName))
local locale = shared.GetLocaleForChannel(channel) shared.dump(msg)
local text = string.format( end
shared._L("combatAlerterInCombat", locale), table.insert(shared.messenger.queue, msg)
source,
shared._L("zone", locale),
shared._L("subZone", locale),
tostring(areaId),
x * 100,
y * 100
)
---@type Message
local msg = {
channel = "C",
data = channel,
message = text,
}
if Heimdall_Data.config.combatAlerter.debug then
print(string.format("[%s] Queuing alert message", ModuleName))
shared.dump(msg)
end end
table.insert(shared.messenger.queue, msg) elseif Heimdall_Data.config.combatAlerter.debug then
print(string.format("[%s] Source not in stinky list, ignoring: %s", ModuleName, source))
end end
elseif Heimdall_Data.config.combatAlerter.debug then end)
print(string.format("[%s] Source not in stinky list, ignoring: %s", ModuleName, source))
end
end)
local combatTriggerFrame = CreateFrame("Frame") local combatTriggerFrame = CreateFrame("Frame")
combatTriggerFrame:RegisterEvent("PLAYER_REGEN_DISABLED") combatTriggerFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
combatTriggerFrame:RegisterEvent("PLAYER_REGEN_ENABLED") combatTriggerFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
combatTriggerFrame:SetScript("OnEvent", function(self, event, ...) combatTriggerFrame:SetScript("OnEvent", function(self, event, ...)
if Heimdall_Data.config.combatAlerter.debug then if Heimdall_Data.config.combatAlerter.debug then
print(string.format("[%s] Combat state changed: %s", ModuleName, event)) print(string.format("[%s] Combat state changed: %s", ModuleName, event))
if event == "PLAYER_REGEN_DISABLED" then if event == "PLAYER_REGEN_DISABLED" then
print(string.format("[%s] Entered combat - Resetting alerts", ModuleName)) print(string.format("[%s] Entered combat - Resetting alerts", ModuleName))
else else
print(string.format("[%s] Left combat - Resetting alerts", ModuleName)) print(string.format("[%s] Left combat - Resetting alerts", ModuleName))
end
end end
end alerted = {}
alerted = {} end)
end)
if Heimdall_Data.config.combatAlerter.debug then print(string.format("[%s] Module initialized", ModuleName)) end if Heimdall_Data.config.combatAlerter.debug then print(string.format("[%s] Module initialized", ModuleName)) end
print("[Heimdall] CombatAlerter loaded") print(string.format("[%s] Module initialized", ModuleName))
end end,
}

View File

@@ -2,6 +2,13 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "Commander" local ModuleName = "Commander"
---@class HeimdallCommanderConfig
---@field enabled boolean
---@field debug boolean
---@field channels string[]
---@field commander string
---@field commands table<string, boolean>
local helpMessages = { local helpMessages = {
ru = { ru = {
"1) who - пишет вам никнеймы текущих врагов и локу.", "1) who - пишет вам никнеймы текущих врагов и локу.",
@@ -31,315 +38,401 @@ local helpMessages = {
}, },
} }
---@diagnostic disable-next-line: missing-fields ---@class Commander
shared.Commander = {} shared.Commander = {
function shared.Commander.Init() Init = function()
---@param text string ---@param text string
---@param size number ---@param size number
---@return string[] ---@return string[]
local function Partition(text, size) local function Partition(text, size)
local words = {} local words = {}
for word in text:gmatch("[^,]+") do for word in text:gmatch("[^,]+") do
words[#words + 1] = word words[#words + 1] = word
end
local ret = {}
local currentChunk = ""
for _, word in ipairs(words) do
if #currentChunk + #word + 1 <= size then
currentChunk = currentChunk .. (currentChunk == "" and word or " " .. word)
else
if #currentChunk > 0 then ret[#ret + 1] = currentChunk end
currentChunk = word
end end
end
if #currentChunk > 0 then ret[#ret + 1] = currentChunk end local ret = {}
local currentChunk = ""
return ret for _, word in ipairs(words) do
end if #currentChunk + #word + 1 <= size then
---@param arr table<string, Player> currentChunk = currentChunk .. (currentChunk == "" and word or " " .. word)
---@return string[] else
local function Count(arr) if #currentChunk > 0 then ret[#ret + 1] = currentChunk end
local ret = {} currentChunk = word
for _, player in pairs(arr) do end
if shared.Whoer.ShouldNotifyForZone(player.zone) then ret[player.zone] = (ret[player.zone] or 0) + 1 end
end
local text = {}
for zone, count in pairs(ret) do
text[#text + 1] = string.format("%s: %d", zone, count)
end
return text
end
---@param arr table<string, Player>
---@return string[]
local function CountPartitioned(arr)
local count = Count(arr)
local text = {}
---@diagnostic disable-next-line: param-type-mismatch something wrong with luals, it's picking up the "wrong" unpack
for _, line in pairs(Partition(strjoin(", ", unpack(count)), 200)) do
text[#text + 1] = line
end
return text
end
---@param arr table<string, Player>
---@return string[]
local function Who(arr)
local ret = {}
for _, player in pairs(arr) do
if shared.Whoer.ShouldNotifyForZone(player.zone) then
ret[#ret + 1] = string.format(
"%s/%s (%s) %s",
player.name,
player.class,
player.zone,
player.stinky and "(!!!!)" or ""
)
end end
end
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Command result: %s", ModuleName, strjoin(", ", unpack(ret))))
end
return ret
end
---@param arr table<string, Player>
---@return string[]
local function WhoPartitioned(arr)
local who = Who(arr)
local text = {}
---@diagnostic disable-next-line: param-type-mismatch something wrong with luals, it's picking up the "wrong" unpack
for _, line in pairs(Partition(strjoin(", ", unpack(who)), 200)) do
text[#text + 1] = "who: " .. line
end
return text
end
---@param arr table<string, Player>
---@return string[]
local function CountClass(arr)
local ret = {}
for _, player in pairs(arr) do
if shared.Whoer.ShouldNotifyForZone(player.zone) then ret[player.class] = (ret[player.class] or 0) + 1 end
end
local text = {}
for class, count in pairs(ret) do
text[#text + 1] = string.format("%s: %d", class, count)
end
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Message text: %s", ModuleName, strjoin(", ", unpack(text))))
end
return text
end
---@param arr table<string, Player>
---@return string[]
local function CountClassPartitioned(arr)
local countClass = CountClass(arr)
local text = {}
---@diagnostic disable-next-line: param-type-mismatch something wrong with luals, it's picking up the "wrong" unpack
for _, line in pairs(Partition(strjoin(", ", unpack(countClass)), 200)) do
text[#text + 1] = line
end
return text
end
local function CountClassPartitionedStinkies()
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Executing: CountClassPartitionedStinkies", ModuleName))
end
local res = CountClassPartitioned(HeimdallStinkies)
if #res == 0 then return { "No stinkies found" } end
return res
end
local function WhoPartitionedStinkies()
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Executing: WhoPartitionedStinkies", ModuleName))
shared.dump(HeimdallStinkies)
end
local res = WhoPartitioned(HeimdallStinkies)
if #res == 0 then return { "No stinkies found" } end
return res
end
local function CountPartitionedStinkies()
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Executing: CountPartitionedStinkies", ModuleName))
end
local res = CountPartitioned(HeimdallStinkies)
if #res == 0 then return { "No stinkies found" } end
return res
end
local function HelpRu()
if Heimdall_Data.config.commander.debug then print(string.format("[%s] Executing: HelpRu", ModuleName)) end
return helpMessages.ru
end
local function HelpEn()
if Heimdall_Data.config.commander.debug then print(string.format("[%s] Executing: HelpEn", ModuleName)) end
return helpMessages.en
end
local groupInviteFrame = CreateFrame("Frame")
groupInviteFrame:SetScript("OnEvent", function(self, event, ...)
if Heimdall_Data.config.commander.debug then print(string.format("[%s] Event received", ModuleName)) end
AcceptGroup()
groupInviteFrame:UnregisterEvent("PARTY_INVITE_REQUEST")
C_Timer.NewTimer(0.1, function()
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Click event triggered", ModuleName))
end
_G["StaticPopup1Button1"]:Click()
end, 1)
end)
local function JoinGroup()
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] JoinGroup command received", ModuleName))
end
groupInviteFrame:RegisterEvent("PARTY_INVITE_REQUEST")
C_Timer.NewTimer(10, function() groupInviteFrame:UnregisterEvent("PARTY_INVITE_REQUEST") end, 1)
return { "+" }
end
local function LeaveGroup()
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] LeaveGroup command received", ModuleName))
end
LeaveParty()
return {}
end
---@param target string
local function FollowTarget(target)
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Following target: %s", ModuleName, target))
end
if not target then return end
FollowUnit(target)
return {}
end
---@param args string[] if #currentChunk > 0 then ret[#ret + 1] = currentChunk end
local function MacroTarget(args)
if Heimdall_Data.config.commander.debug then return ret
end
---@param arr table<string, Player>
---@return string[]
local function Count(arr)
local ret = {}
for _, player in pairs(arr) do
if shared.Whoer.ShouldNotifyForZone(player.zone) then ret[player.zone] = (ret[player.zone] or 0) + 1 end
end
local text = {}
for zone, count in pairs(ret) do
text[#text + 1] = string.format("%s: %d", zone, count)
end
return text
end
---@param arr table<string, Player>
---@return string[]
local function CountPartitioned(arr)
local count = Count(arr)
local text = {}
---@diagnostic disable-next-line: param-type-mismatch something wrong with luals, it's picking up the "wrong" unpack ---@diagnostic disable-next-line: param-type-mismatch something wrong with luals, it's picking up the "wrong" unpack
print(string.format("[%s] Macroing: %s", ModuleName, strjoin(" ", unpack(args)))) for _, line in pairs(Partition(strjoin(", ", unpack(count)), 200)) do
text[#text + 1] = line
end
return text
end end
if #args < 2 or #args % 2 ~= 0 then ---@param arr table<string, Player>
if #args < 2 or #args % 2 ~= 0 then ---@return string[]
local function Who(arr)
local ret = {}
for _, player in pairs(arr) do
if shared.Whoer.ShouldNotifyForZone(player.zone) then
ret[#ret + 1] = string.format(
"%s/%s (%s) %s",
player.name,
player.class,
player.zone,
player.stinky and "(!!!!)" or ""
)
end
end
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Command result: %s", ModuleName, strjoin(", ", unpack(ret))))
end
return ret
end
-- This is really ugly, duplicating methods like this
-- But I have no better idea
-- We would have to drag reference channel all the way here
-- And then in here do some kind of deciding based on the fucking channel locale
-- That's also a nasty solution... I guess adding "kto" is better
---@param arr table<string, Player>
---@return string[]
local function WhoRu(arr)
local ret = {}
for _, player in pairs(arr) do
if shared.Whoer.ShouldNotifyForZone(player.zone) then
shared.dump(player)
ret[#ret + 1] = string.format(
"%s/%s (%s) %s",
player.name,
shared._L(player.class, "ru"),
shared._L(player.zone, "ru"),
player.stinky and "(!!!!)" or ""
)
end
end
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Command result: %s", ModuleName, strjoin(", ", unpack(ret))))
end
return ret
end
---@param arr table<string, Player>
---@return string[]
local function WhoPartitioned(arr)
local who = Who(arr)
local text = {}
---@diagnostic disable-next-line: param-type-mismatch something wrong with luals, it's picking up the "wrong" unpack
for _, line in pairs(Partition(strjoin(", ", unpack(who)), 200)) do
text[#text + 1] = "who: " .. line
end
return text
end
---@param arr table<string, Player>
---@return string[]
local function WhoPartitionedRu(arr)
local who = WhoRu(arr)
local text = {}
---@diagnostic disable-next-line: param-type-mismatch something wrong with luals, it's picking up the "wrong" unpack
for _, line in pairs(Partition(strjoin(", ", unpack(who)), 200)) do
text[#text + 1] = "кто: " .. line
end
return text
end
---@param arr table<string, Player>
---@return string[]
local function CountClass(arr)
local ret = {}
for _, player in pairs(arr) do
if shared.Whoer.ShouldNotifyForZone(player.zone) then
ret[player.class] = (ret[player.class] or 0) + 1
end
end
local text = {}
for class, count in pairs(ret) do
text[#text + 1] = string.format("%s: %d", class, count)
end
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Message text: %s", ModuleName, strjoin(", ", unpack(text))))
end
return text
end
---@param arr table<string, Player>
---@return string[]
local function CountClassPartitioned(arr)
local countClass = CountClass(arr)
local text = {}
---@diagnostic disable-next-line: param-type-mismatch something wrong with luals, it's picking up the "wrong" unpack
for _, line in pairs(Partition(strjoin(", ", unpack(countClass)), 200)) do
text[#text + 1] = line
end
return text
end
local function CountClassPartitionedStinkies()
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Executing: CountClassPartitionedStinkies", ModuleName))
end
local res = CountClassPartitioned(HeimdallStinkies)
if #res == 0 then return { "No stinkies found" } end
return res
end
local function WhoPartitionedStinkies()
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Executing: WhoPartitionedStinkies", ModuleName))
shared.dump(HeimdallStinkies)
end
local res = WhoPartitioned(HeimdallStinkies)
if #res == 0 then return { "No stinkies found" } end
return res
end
local function WhoPartitionedStinkiesRu()
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Executing: WhoPartitionedStinkies", ModuleName))
shared.dump(HeimdallStinkies)
end
local res = WhoPartitionedRu(HeimdallStinkies)
if #res == 0 then return { "No stinkies found" } end
return res
end
local function CountPartitionedStinkies()
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Executing: CountPartitionedStinkies", ModuleName))
end
local res = CountPartitioned(HeimdallStinkies)
if #res == 0 then return { "No stinkies found" } end
return res
end
local function HelpRu()
if Heimdall_Data.config.commander.debug then print(string.format("[%s] Executing: HelpRu", ModuleName)) end
return helpMessages.ru
end
local function HelpEn()
if Heimdall_Data.config.commander.debug then print(string.format("[%s] Executing: HelpEn", ModuleName)) end
return helpMessages.en
end
local groupInviteFrame = CreateFrame("Frame")
groupInviteFrame:SetScript("OnEvent", function(self, event, ...)
if Heimdall_Data.config.commander.debug then print(string.format("[%s] Event received", ModuleName)) end
AcceptGroup()
groupInviteFrame:UnregisterEvent("PARTY_INVITE_REQUEST")
C_Timer.NewTimer(0.1, function()
if Heimdall_Data.config.commander.debug then if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Invalid number of arguments for MacroTarget", ModuleName)) print(string.format("[%s] Click event triggered", ModuleName))
end
_G["StaticPopup1Button1"]:Click()
end, 1)
end)
local function JoinGroup()
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] JoinGroup command received", ModuleName))
end
groupInviteFrame:RegisterEvent("PARTY_INVITE_REQUEST")
C_Timer.NewTimer(10, function() groupInviteFrame:UnregisterEvent("PARTY_INVITE_REQUEST") end, 1)
return { "+" }
end
local function LeaveGroup()
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] LeaveGroup command received", ModuleName))
end
LeaveParty()
return {}
end
---@param target string
local function FollowTarget(target)
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Following target: %s", ModuleName, target))
end
if not target then return end
FollowUnit(target)
return {}
end
---@param args string[]
local function MacroTarget(args)
if Heimdall_Data.config.commander.debug then
---@diagnostic disable-next-line: param-type-mismatch something wrong with luals, it's picking up the "wrong" unpack
print(string.format("[%s] Macroing: %s", ModuleName, strjoin(" ", unpack(args))))
end
if #args < 2 or #args % 2 ~= 0 then
if #args < 2 or #args % 2 ~= 0 then
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Invalid number of arguments for MacroTarget", ModuleName))
end
return {}
end
end
table.remove(args, 1)
for i = 1, #args do
local stinky = strtrim(args[i])
local name = stinky:match("([^/]+)")
local class = stinky:match("/([^ $]+)")
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Adding stinky: %s/%s", ModuleName, name, tostring(class)))
end
shared.StinkyTracker.Track({
name = name,
class = class or "unknown",
seenAt = GetTime(),
hostile = true,
})
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Added stinky: %s/%s", ModuleName, name, tostring(class)))
end
end
return {}
end
---@param args string[]
local function IgnoreMacroTarget(args)
if Heimdall_Data.config.commander.debug then
---@diagnostic disable-next-line: param-type-mismatch something wrong with luals, it's picking up the "wrong" unpack
print(string.format("[%s] Macroing: %s", ModuleName, strjoin(" ", unpack(args))))
end
if #args < 1 then
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Invalid number of arguments for IgnoreMacroTarget", ModuleName))
end end
return {} return {}
end end
end table.remove(args, 1)
table.remove(args, 1)
for i = 1, #args do for i = 1, #args do
local stinky = strtrim(args[i]) local stinky = strtrim(args[i])
local name = stinky:match("([^/]+)") local name = stinky:match("([^/]+)")
local class = stinky:match("/([^ $]+)") if Heimdall_Data.config.commander.debug then
if Heimdall_Data.config.commander.debug then print(string.format("[%s] Ignoring stinky: %s", ModuleName, name))
print(string.format("[%s] Adding stinky: %s/%s", ModuleName, name, tostring(class))) end
shared.StinkyTracker.Ignore(name)
end end
shared.stinkyTracker.stinkies[name] = { return {}
name = name,
class = class or "unknown",
seenAt = GetTime(),
hostile = true,
}
end end
return {}
end
---@class Command ---@class Command
---@field keywordRe string ---@field keywordRe string
---@field commanderOnly boolean ---@field commanderOnly boolean
---@field callback fun(...: any): string[] ---@field callback fun(...: any): string[]
local commands = { local commands = {
{ keywordRe = "^who$", commanderOnly = false, callback = WhoPartitionedStinkies }, { keywordRe = "^who$", commanderOnly = false, callback = WhoPartitionedStinkies },
{ keywordRe = "^howmany$", commanderOnly = false, callback = CountPartitionedStinkies }, { keywordRe = "^кто$", commanderOnly = false, callback = WhoPartitionedStinkiesRu },
{ keywordRe = "^classes$", commanderOnly = false, callback = CountClassPartitionedStinkies }, { keywordRe = "^howmany$", commanderOnly = false, callback = CountPartitionedStinkies },
{ keywordRe = "^help$", commanderOnly = false, callback = HelpRu }, { keywordRe = "^classes$", commanderOnly = false, callback = CountClassPartitionedStinkies },
{ keywordRe = "^helpen$", commanderOnly = false, callback = HelpEn }, { keywordRe = "^help$", commanderOnly = false, callback = HelpRu },
{ keywordRe = "^joingroup$", commanderOnly = false, callback = JoinGroup }, { keywordRe = "^helpen$", commanderOnly = false, callback = HelpEn },
{ keywordRe = "^leavegroup$", commanderOnly = false, callback = LeaveGroup }, { keywordRe = "^joingroup$", commanderOnly = false, callback = JoinGroup },
{ keywordRe = "^follow$", commanderOnly = false, callback = FollowTarget }, { keywordRe = "^leavegroup$", commanderOnly = false, callback = LeaveGroup },
{ keywordRe = "^macro", commanderOnly = false, callback = MacroTarget }, { keywordRe = "^follow$", commanderOnly = false, callback = FollowTarget },
} { keywordRe = "^macro", commanderOnly = false, callback = MacroTarget },
{ keywordRe = "^ignore", commanderOnly = false, callback = IgnoreMacroTarget },
}
local commanderChannelFrame = CreateFrame("Frame") local commanderChannelFrame = CreateFrame("Frame")
commanderChannelFrame:RegisterEvent("CHAT_MSG_CHANNEL") commanderChannelFrame:RegisterEvent("CHAT_MSG_CHANNEL")
commanderChannelFrame:SetScript("OnEvent", function(self, event, msg, sender, ...) commanderChannelFrame:SetScript("OnEvent", function(self, event, msg, sender, ...)
--if Heimdall_Data.config.commander.debug then
-- print(string.format("[%s] Event received", ModuleName))
-- shared.dump(Heimdall_Data.config.commander)
--end
if not Heimdall_Data.config.commander.enabled then
--if Heimdall_Data.config.commander.debug then --if Heimdall_Data.config.commander.debug then
-- print(string.format("[%s] Module disabled, ignoring event", ModuleName)) -- print(string.format("[%s] Event received", ModuleName))
-- shared.dump(Heimdall_Data.config.commander)
--end --end
return if not Heimdall_Data.config.commander.enabled then
end --if Heimdall_Data.config.commander.debug then
local channelId = select(6, ...) -- print(string.format("[%s] Module disabled, ignoring event", ModuleName))
local _, channelname = GetChannelName(channelId) --end
local ok = false return
for _, channel in pairs(Heimdall_Data.config.commander.channels) do
if channel == channelname then
ok = true
break
end end
end local channelId = select(6, ...)
if not ok then local _, channelname = GetChannelName(channelId)
if Heimdall_Data.config.commander.debug then local ok = false
print( for _, channel in pairs(Heimdall_Data.config.commander.channels) do
string.format( if channel == channelname then
"[%s] Channel name '%s' does not match any of the channels '%s'", ok = true
ModuleName, break
channelname, end
table.concat(Heimdall_Data.config.commander.channels, ", ") end
if not ok then
if Heimdall_Data.config.commander.debug then
print(
string.format(
"[%s] Channel name '%s' does not match any of the channels '%s'",
ModuleName,
channelname,
table.concat(Heimdall_Data.config.commander.channels, ", ")
)
) )
) end
return
end end
return
end
sender = string.match(sender, "^[^-]+") sender = string.match(sender, "^[^-]+")
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Message from: %s", ModuleName, sender))
shared.dump(Heimdall_Data.config.commander)
end
for _, command in ipairs(commands) do
local enabled = Heimdall_Data.config.commander.commands[command.keywordRe] == true or false
if Heimdall_Data.config.commander.debug then if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Command match: %s = %s", ModuleName, command.keywordRe, tostring(enabled))) print(string.format("[%s] Message from: %s", ModuleName, sender))
shared.dump(Heimdall_Data.config.commander)
end end
if
enabled for _, command in ipairs(commands) do
and ( local enabled = Heimdall_Data.config.commander.commands[command.keywordRe] == true or false
not command.commanderOnly if Heimdall_Data.config.commander.debug then
or (command.commanderOnly and sender == Heimdall_Data.config.commander.commander) print(
) string.format("[%s] Command match: %s = %s", ModuleName, command.keywordRe, tostring(enabled))
then )
if msg:match(command.keywordRe) then end
---@diagnostic disable-next-line: redundant-parameter Currently luals does not support variadic functions as a @field if
local messages = command.callback({ strsplit(",", msg) }) enabled
if Heimdall_Data.config.commander.debug then and (
---@diagnostic disable-next-line: param-type-mismatch not command.commanderOnly
print(string.format("[%s] Messages to send: %s", ModuleName, strjoin(", ", unpack(messages)))) -- if Heimdall_Data.config.commander.debug then print(string.format("[%s] Ignoring command, sender %s not commander %s", ModuleName, sender, Heimdall_Data.config.commander.commander)) end
end
for _, message in ipairs(messages) do or (command.commanderOnly and sender == Heimdall_Data.config.commander.commander)
---@type Message )
local returnmsg = { then
channel = "C", if msg:match(command.keywordRe) then
data = channelname, ---@diagnostic disable-next-line: redundant-parameter Currently luals does not support variadic functions as a @field
message = message, local messages = command.callback({ strsplit(",", msg) })
}
if Heimdall_Data.config.commander.debug then if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Queuing message", ModuleName)) print(string.format("[%s] Messages to send: %s", ModuleName, #messages))
shared.dump(msg) shared.dump(messages)
end
for _, message in ipairs(messages) do
---@type Message
local returnmsg = {
channel = "C",
data = channelname,
message = message,
}
if Heimdall_Data.config.commander.debug then
print(string.format("[%s] Queuing message", ModuleName))
shared.dump(msg)
end
if Heimdall_Data.config.networkMessenger.enabled then
shared.NetworkMessenger.Enqueue(returnmsg)
elseif Heimdall_Data.config.messenger.enabled then
shared.Messenger.Enqueue(returnmsg)
end
end end
--table.insert(shared.messenger.queue, msg)
table.insert(shared.networkMessenger.queue, returnmsg)
end end
end end
end end
end end)
end)
print("[Heimdall] Commander module loaded") print(string.format("[%s] Module initialized", ModuleName))
end end,
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,11 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "Configurator" local ModuleName = "Configurator"
---@diagnostic disable-next-line: missing-fields ---@class HeimdallConfiguratorConfig
shared.Configurator = {} ---@field enabled boolean
function shared.Configurator.Init() print(string.format("[Heimdall] %s module loaded", ModuleName)) end ---@field debug boolean
---@class Configurator
shared.Configurator = {
Init = function() print(string.format("[%s] Module initialized", ModuleName)) end,
}

View File

@@ -2,93 +2,65 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "DeathReporter" local ModuleName = "DeathReporter"
---@diagnostic disable-next-line: missing-fields ---@class HeimdallDeathReporterConfig
shared.DeathReporter = {} ---@field enabled boolean
function shared.DeathReporter.Init() ---@field debug boolean
---@type table<string, number> ---@field throttle number
local recentDeaths = {} ---@field doWhisper boolean
---@type table<string, number> ---@field channels string[]
local recentDuels = {} ---@field zoneOverride string?
---@field duelThrottle number
---@param source string ---@class DeathReporter
---@param destination string shared.DeathReporter = {
---@param spellName string Init = function()
local function RegisterDeath(source, destination, spellName) ---@type table<string, number>
if Heimdall_Data.config.deathReporter.debug then local recentDeaths = {}
print( ---@type table<string, number>
string.format( local recentDuels = {}
"[%s] Processing death event - Source: %s, Target: %s, Spell: %s",
ModuleName,
source,
destination,
spellName
)
)
end
if not Heimdall_Data.config.deathReporter.enabled then ---@param source string
---@param destination string
---@param spellName string
local function RegisterDeath(source, destination, spellName)
if Heimdall_Data.config.deathReporter.debug then if Heimdall_Data.config.deathReporter.debug then
print(string.format("[%s] Module disabled, ignoring death event", ModuleName))
end
return
end
if
recentDeaths[destination]
and GetTime() - recentDeaths[destination] < Heimdall_Data.config.deathReporter.throttle
then
if Heimdall_Data.config.deathReporter.debug then
local timeLeft = Heimdall_Data.config.deathReporter.throttle - (GetTime() - recentDeaths[destination])
print( print(
string.format( string.format(
"[%s] Death report throttled for %s (%.1f seconds remaining)", "[%s] Processing death event - Source: %s, Target: %s, Spell: %s",
ModuleName, ModuleName,
source,
destination, destination,
timeLeft spellName
) )
) )
end end
return
end
if if not Heimdall_Data.config.deathReporter.enabled then
recentDuels[destination] if Heimdall_Data.config.deathReporter.debug then
and GetTime() - recentDuels[destination] < Heimdall_Data.config.deathReporter.duelThrottle print(string.format("[%s] Module disabled, ignoring death event", ModuleName))
then end
if Heimdall_Data.config.deathReporter.debug then return
print(
string.format(
"[%s] Ignoring death report - Recent duel detected for target: %s",
ModuleName,
destination
)
)
end end
return
end
if if
recentDuels[source] recentDeaths[destination]
and GetTime() - recentDuels[source] < Heimdall_Data.config.deathReporter.duelThrottle and GetTime() - recentDeaths[destination] < Heimdall_Data.config.deathReporter.throttle
then then
if Heimdall_Data.config.deathReporter.debug then if Heimdall_Data.config.deathReporter.debug then
print( local timeLeft = Heimdall_Data.config.deathReporter.throttle
string.format( - (GetTime() - recentDeaths[destination])
"[%s] Ignoring death report - Recent duel detected for source: %s", print(
ModuleName, string.format(
source "[%s] Death report throttled for %s (%.1f seconds remaining)",
ModuleName,
destination,
timeLeft
)
) )
) end
return
end end
return
end
if Heimdall_Data.config.deathReporter.debug then
print(string.format("[%s] Recording death for %s", ModuleName, destination))
end
recentDeaths[destination] = GetTime()
C_Timer.NewTimer(3, function()
if if
recentDuels[destination] recentDuels[destination]
and GetTime() - recentDuels[destination] < Heimdall_Data.config.deathReporter.duelThrottle and GetTime() - recentDuels[destination] < Heimdall_Data.config.deathReporter.duelThrottle
@@ -96,7 +68,7 @@ function shared.DeathReporter.Init()
if Heimdall_Data.config.deathReporter.debug then if Heimdall_Data.config.deathReporter.debug then
print( print(
string.format( string.format(
"[%s] Cancelling delayed death report - Recent duel detected for: %s", "[%s] Ignoring death report - Recent duel detected for target: %s",
ModuleName, ModuleName,
destination destination
) )
@@ -112,7 +84,7 @@ function shared.DeathReporter.Init()
if Heimdall_Data.config.deathReporter.debug then if Heimdall_Data.config.deathReporter.debug then
print( print(
string.format( string.format(
"[%s] Cancelling delayed death report - Recent duel detected for: %s", "[%s] Ignoring death report - Recent duel detected for source: %s",
ModuleName, ModuleName,
source source
) )
@@ -122,131 +94,175 @@ function shared.DeathReporter.Init()
end end
if Heimdall_Data.config.deathReporter.debug then if Heimdall_Data.config.deathReporter.debug then
print( print(string.format("[%s] Recording death for %s", ModuleName, destination))
string.format( end
"[%s] Sending death report - %s killed %s with %s", recentDeaths[destination] = GetTime()
ModuleName,
C_Timer.NewTimer(3, function()
if
recentDuels[destination]
and GetTime() - recentDuels[destination] < Heimdall_Data.config.deathReporter.duelThrottle
then
if Heimdall_Data.config.deathReporter.debug then
print(
string.format(
"[%s] Cancelling delayed death report - Recent duel detected for: %s",
ModuleName,
destination
)
)
end
return
end
if
recentDuels[source]
and GetTime() - recentDuels[source] < Heimdall_Data.config.deathReporter.duelThrottle
then
if Heimdall_Data.config.deathReporter.debug then
print(
string.format(
"[%s] Cancelling delayed death report - Recent duel detected for: %s",
ModuleName,
source
)
)
end
return
end
if Heimdall_Data.config.deathReporter.debug then
print(
string.format(
"[%s] Sending death report - %s killed %s with %s",
ModuleName,
source,
destination,
spellName
)
)
end
local zone, subzone = GetZoneText() or "Unknown", GetSubZoneText() or "Unknown"
if Heimdall_Data.config.spotter.zoneOverride then
zone = Heimdall_Data.config.spotter.zoneOverride or ""
subzone = ""
end
local x, y = GetPlayerMapPosition("player")
if Heimdall_Data.config.deathReporter.debug then
print(string.format("[%s] Player coordinates: %.2f, %.2f", ModuleName, x * 100, y * 100))
end
SetMapToCurrentZone()
SetMapByID(GetCurrentMapAreaID())
local zoneId = GetCurrentMapAreaID()
for _, channel in pairs(Heimdall_Data.config.deathReporter.channels) do
local locale = shared.GetLocaleForChannel(channel)
local text = string.format(
shared._L("killed", locale),
source, source,
destination, destination,
spellName shared._L(spellName, locale),
shared._L(zone, locale),
shared._L(subzone, locale),
zoneId,
x * 100,
y * 100
)
---@type Message
local msg = {
channel = "C",
data = channel,
message = text,
}
if Heimdall_Data.config.deathReporter.debug then
print(string.format("[%s] Queuing death report message", ModuleName))
shared.dump(msg)
end
table.insert(shared.messenger.queue, msg)
end
end)
end
local cleuFrame = CreateFrame("Frame")
cleuFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
cleuFrame:SetScript("OnEvent", function(self, event, ...)
-- if Heimdall_Data.config.deathReporter.debug then
-- print(string.format("[%s] Received combat log event", ModuleName))
-- end
if not Heimdall_Data.config.deathReporter.enabled then return end
local overkill, source, destination, spellName, sourceGUID, destinationGUID, err
overkill, err = CLEUParser.GetOverkill(...)
if not err and overkill > 0 then
source, err = CLEUParser.GetSourceName(...)
if err then
source = "unknown"
if Heimdall_Data.config.deathReporter.debug then
print(string.format("[%s] Error getting source name", ModuleName))
end
end
destination, err = CLEUParser.GetDestName(...)
if err then
destination = "unknown"
if Heimdall_Data.config.deathReporter.debug then
print(string.format("[%s] Error getting destination name", ModuleName))
end
end
spellName, err = CLEUParser.GetSpellName(...)
if err then
spellName = "unknown"
if Heimdall_Data.config.deathReporter.debug then
print(string.format("[%s] Error getting spell name", ModuleName))
end
end
sourceGUID, err = CLEUParser.GetSourceGUID(...)
if err or not string.match(sourceGUID, "Player") then return end
destinationGUID, err = CLEUParser.GetDestGUID(...)
if err or not string.match(destinationGUID, "Player") then return end
RegisterDeath(source, destination, spellName)
end
end)
local systemMessageFrame = CreateFrame("Frame")
systemMessageFrame:RegisterEvent("CHAT_MSG_SYSTEM")
systemMessageFrame:SetScript("OnEvent", function(self, event, msg)
if not Heimdall_Data.config.deathReporter.enabled then return end
local source, destination = string.match(msg, "([^ ]+) has defeated ([^ ]+) in a duel")
if Heimdall_Data.config.deathReporter.debug then
print(string.format("[%s] Received system message: %s", ModuleName, msg))
print(
string.format(
"[%s] Source: %s, Destination: %s",
ModuleName,
tostring(source),
tostring(destination)
) )
) )
end end
if not source or not destination then return end
local zone, subzone = GetZoneText() or "Unknown", GetSubZoneText() or "Unknown" source = string.match(source, "([^-]+)")
if Heimdall_Data.config.spotter.zoneOverride then destination = string.match(destination, "([^-]+)")
zone = Heimdall_Data.config.spotter.zoneOverride or "" if source and destination then
subzone = ""
end
local x, y = GetPlayerMapPosition("player")
if Heimdall_Data.config.deathReporter.debug then
print(string.format("[%s] Player coordinates: %.2f, %.2f", ModuleName, x * 100, y * 100))
end
SetMapToCurrentZone()
SetMapByID(GetCurrentMapAreaID())
local zoneId = GetCurrentMapAreaID()
for _, channel in pairs(Heimdall_Data.config.deathReporter.channels) do
local locale = shared.GetLocaleForChannel(channel)
local text = string.format(
shared._L("killed", locale),
source,
destination,
shared._L(spellName, locale),
shared._L(zone, locale),
shared._L(subzone, locale),
zoneId,
x * 100,
y * 100
)
---@type Message
local msg = {
channel = "C",
data = channel,
message = text,
}
if Heimdall_Data.config.deathReporter.debug then if Heimdall_Data.config.deathReporter.debug then
print(string.format("[%s] Queuing death report message", ModuleName)) print(string.format("[%s] Detected duel between %s and %s", ModuleName, source, destination))
shared.dump(msg)
end end
table.insert(shared.messenger.queue, msg) local now = GetTime()
recentDuels[source] = now
recentDuels[destination] = now
end end
end) end)
end
local cleuFrame = CreateFrame("Frame")
cleuFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
cleuFrame:SetScript("OnEvent", function(self, event, ...)
-- if Heimdall_Data.config.deathReporter.debug then
-- print(string.format("[%s] Received combat log event", ModuleName))
-- end
if not Heimdall_Data.config.deathReporter.enabled then return end
local overkill, source, destination, spellName, sourceGUID, destinationGUID, err
overkill, err = CLEUParser.GetOverkill(...)
if not err and overkill > 0 then
source, err = CLEUParser.GetSourceName(...)
if err then
source = "unknown"
if Heimdall_Data.config.deathReporter.debug then
print(string.format("[%s] Error getting source name", ModuleName))
end
end
destination, err = CLEUParser.GetDestName(...)
if err then
destination = "unknown"
if Heimdall_Data.config.deathReporter.debug then
print(string.format("[%s] Error getting destination name", ModuleName))
end
end
spellName, err = CLEUParser.GetSpellName(...)
if err then
spellName = "unknown"
if Heimdall_Data.config.deathReporter.debug then
print(string.format("[%s] Error getting spell name", ModuleName))
end
end
sourceGUID, err = CLEUParser.GetSourceGUID(...)
if err or not string.match(sourceGUID, "Player") then return end
destinationGUID, err = CLEUParser.GetDestGUID(...)
if err or not string.match(destinationGUID, "Player") then return end
RegisterDeath(source, destination, spellName)
end
end)
local systemMessageFrame = CreateFrame("Frame")
systemMessageFrame:RegisterEvent("CHAT_MSG_SYSTEM")
systemMessageFrame:SetScript("OnEvent", function(self, event, msg)
if not Heimdall_Data.config.deathReporter.enabled then return end
local source, destination = string.match(msg, "([^ ]+) has defeated ([^ ]+) in a duel")
if Heimdall_Data.config.deathReporter.debug then if Heimdall_Data.config.deathReporter.debug then
print(string.format("[%s] Received system message: %s", ModuleName, msg))
print( print(
string.format("[%s] Source: %s, Destination: %s", ModuleName, tostring(source), tostring(destination)) string.format(
"[%s] Module initialized with throttle: %.1fs, duel throttle: %.1fs",
ModuleName,
Heimdall_Data.config.deathReporter.throttle,
Heimdall_Data.config.deathReporter.duelThrottle
)
) )
end end
if not source or not destination then return end print(string.format("[%s] Module initialized", ModuleName))
source = string.match(source, "([^-]+)") end,
destination = string.match(destination, "([^-]+)") }
if source and destination then
if Heimdall_Data.config.deathReporter.debug then
print(string.format("[%s] Detected duel between %s and %s", ModuleName, source, destination))
end
local now = GetTime()
recentDuels[source] = now
recentDuels[destination] = now
end
end)
if Heimdall_Data.config.deathReporter.debug then
print(
string.format(
"[%s] Module initialized with throttle: %.1fs, duel throttle: %.1fs",
ModuleName,
Heimdall_Data.config.deathReporter.throttle,
Heimdall_Data.config.deathReporter.duelThrottle
)
)
end
print("[Heimdall] DeathReporter loaded")
end

View File

@@ -2,54 +2,62 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "Dueler" local ModuleName = "Dueler"
---@diagnostic disable-next-line: missing-fields ---@class HeimdallDuelerConfig
shared.Dueler = {} ---@field enabled boolean
function shared.Dueler.Init() ---@field debug boolean
local frame = CreateFrame("Frame") ---@field declineOther boolean
frame:RegisterEvent("DUEL_REQUESTED")
frame:SetScript("OnEvent", function(self, event, sender)
if Heimdall_Data.config.dueler.debug then
print(string.format("[%s] Duel request received from: %s", ModuleName, sender))
end
if not Heimdall_Data.config.dueler.enabled then
if Heimdall_Data.config.dueler.debug then
print(string.format("[%s] Module disabled, ignoring duel request", ModuleName))
end
return
end
if Heimdall_Data.config.dueler.debug then ---@class Dueler
print(string.format("[%s] Checking if sender '%s' is in agents list", ModuleName, sender)) shared.Dueler = {
end Init = function()
local frame = CreateFrame("Frame")
local allow = Heimdall_Data.config.agents[sender] frame:RegisterEvent("DUEL_REQUESTED")
if allow then frame:SetScript("OnEvent", function(self, event, sender)
if Heimdall_Data.config.dueler.debug then if Heimdall_Data.config.dueler.debug then
print(string.format("[%s] Accepting duel from trusted agent: %s", ModuleName, sender)) print(string.format("[%s] Duel request received from: %s", ModuleName, sender))
end end
AcceptDuel() if not Heimdall_Data.config.dueler.enabled then
else
if Heimdall_Data.config.dueler.declineOther then
if Heimdall_Data.config.dueler.debug then if Heimdall_Data.config.dueler.debug then
print(string.format("[%s] Auto-declining duel from untrusted sender: %s", ModuleName, sender)) print(string.format("[%s] Module disabled, ignoring duel request", ModuleName))
end end
CancelDuel() return
end
if Heimdall_Data.config.dueler.debug then
print(string.format("[%s] Checking if sender '%s' is in agents list", ModuleName, sender))
end
local allow = shared.AgentTracker.IsAgent(sender)
if allow then
if Heimdall_Data.config.dueler.debug then
print(string.format("[%s] Accepting duel from trusted agent: %s", ModuleName, sender))
end
AcceptDuel()
else else
if Heimdall_Data.config.dueler.debug then if Heimdall_Data.config.dueler.declineOther then
print(string.format("[%s] Leaving duel request from %s for manual response", ModuleName, sender)) if Heimdall_Data.config.dueler.debug then
print(string.format("[%s] Auto-declining duel from untrusted sender: %s", ModuleName, sender))
end
CancelDuel()
else
if Heimdall_Data.config.dueler.debug then
print(
string.format("[%s] Leaving duel request from %s for manual response", ModuleName, sender)
)
end
end end
end end
end end)
end)
if Heimdall_Data.config.dueler.debug then if Heimdall_Data.config.dueler.debug then
print( print(
string.format( string.format(
"[%s] Module initialized with auto-decline: %s", "[%s] Module initialized with auto-decline: %s",
ModuleName, ModuleName,
tostring(Heimdall_Data.config.dueler.declineOther) tostring(Heimdall_Data.config.dueler.declineOther)
)
) )
) end
end print(string.format("[%s] Module initialized", ModuleName))
print("[Heimdall] Dueler loaded") end,
end }

View File

@@ -3,8 +3,9 @@ local _, shared = ...
if not shared.dump then if not shared.dump then
---@param value any ---@param value any
---@param msg string?
---@param depth number? ---@param depth number?
shared.dump = function(value, depth) shared.dump = function(value, msg, depth)
if not value then if not value then
print(tostring(value)) print(tostring(value))
return return
@@ -13,6 +14,7 @@ if not shared.dump then
print(tostring(value)) print(tostring(value))
return return
end end
if msg then print(msg) end
if depth == nil then depth = 0 end if depth == nil then depth = 0 end
if depth > 200 then if depth > 200 then
print("Error: Depth > 200 in dump()") print("Error: Depth > 200 in dump()")
@@ -21,7 +23,7 @@ if not shared.dump then
for k, v in pairs(value) do for k, v in pairs(value) do
if type(v) == "table" then if type(v) == "table" then
print(string.rep(" ", depth) .. tostring(k) .. ":") print(string.rep(" ", depth) .. tostring(k) .. ":")
shared.dump(v, depth + 1) shared.dump(v, msg, depth + 1)
else else
print(string.rep(" ", depth) .. tostring(k) .. ": " .. tostring(v)) print(string.rep(" ", depth) .. tostring(k) .. ": " .. tostring(v))
end end

View File

@@ -2,57 +2,64 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "Echoer" local ModuleName = "Echoer"
---@diagnostic disable-next-line: missing-fields ---@class HeimdallEchoerConfig
shared.Echoer = {} ---@field enabled boolean
function shared.Echoer.Init() ---@field debug boolean
local frame = CreateFrame("Frame") ---@field channels string[]
frame:RegisterEvent("CHAT_MSG_CHANNEL") ---@field prefix string
frame:SetScript("OnEvent", function(self, event, msg, sender, ...)
--if Heimdall_Data.config.echoer.debug then
-- print(string.format("[%s] Channel message received from: %s", ModuleName, sender))
--end
if not Heimdall_Data.config.echoer.enabled then ---@class Echoer
shared.Echoer = {
Init = function()
local frame = CreateFrame("Frame")
frame:RegisterEvent("CHAT_MSG_CHANNEL")
frame:SetScript("OnEvent", function(self, event, msg, sender, ...)
--if Heimdall_Data.config.echoer.debug then --if Heimdall_Data.config.echoer.debug then
-- print(string.format("[%s] Module disabled, ignoring message", ModuleName)) -- print(string.format("[%s] Channel message received from: %s", ModuleName, sender))
--end --end
return
end
local channelId = select(6, ...) if not Heimdall_Data.config.echoer.enabled then
local _, channelname = GetChannelName(channelId) --if Heimdall_Data.config.echoer.debug then
local ok = false -- print(string.format("[%s] Module disabled, ignoring message", ModuleName))
for _, channel in pairs(Heimdall_Data.config.echoer.channels) do --end
if channel == channelname then return
ok = true
break
end end
end
if not ok then
if Heimdall_Data.config.echoer.debug then
print(string.format("[%s] Channel name does not match any of the channels", ModuleName))
end
return
end
if Heimdall_Data.config.echoer.debug then
print(string.format("[%s] Processing message from master channel: %s", ModuleName, sender))
shared.dump(Heimdall_Data.config.echoer)
end
if string.find(msg, "^" .. Heimdall_Data.config.echoer.prefix) then local channelId = select(6, ...)
if Heimdall_Data.config.echoer.debug then local _, channelname = GetChannelName(channelId)
print(string.format("[%s] Found echo command in message: %s", ModuleName, msg)) local ok = false
for _, channel in pairs(Heimdall_Data.config.echoer.channels) do
if channel == channelname then
ok = true
break
end
end end
local echomsg = string.sub(msg, string.len(Heimdall_Data.config.echoer.prefix) + 1) if not ok then
if Heimdall_Data.config.echoer.debug then if Heimdall_Data.config.echoer.debug then
print(string.format("[%s] Echoing message: %s", ModuleName, echomsg)) print(string.format("[%s] Channel name does not match any of the channels", ModuleName))
end
return
end
if Heimdall_Data.config.echoer.debug then
print(string.format("[%s] Processing message from master channel: %s", ModuleName, sender))
shared.dump(Heimdall_Data.config.echoer)
end end
SendChatMessage(echomsg, "SAY")
elseif Heimdall_Data.config.echoer.debug then
print(string.format("[%s] Message does not start with echo prefix", ModuleName))
end
end)
if Heimdall_Data.config.echoer.debug then print(string.format("[%s] Module initialized", ModuleName)) end if string.find(msg, "^" .. Heimdall_Data.config.echoer.prefix) then
print("[Heimdall] Echoer loaded") if Heimdall_Data.config.echoer.debug then
end print(string.format("[%s] Found echo command in message: %s", ModuleName, msg))
end
local echomsg = string.sub(msg, string.len(Heimdall_Data.config.echoer.prefix) + 1)
if Heimdall_Data.config.echoer.debug then
print(string.format("[%s] Echoing message: %s", ModuleName, echomsg))
end
SendChatMessage(echomsg, "SAY")
elseif Heimdall_Data.config.echoer.debug then
print(string.format("[%s] Message does not start with echo prefix", ModuleName))
end
end)
if Heimdall_Data.config.echoer.debug then print(string.format("[%s] Module initialized", ModuleName)) end
print(string.format("[%s] Module initialized", ModuleName))
end,
}

View File

@@ -2,58 +2,65 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "Emoter" local ModuleName = "Emoter"
---@diagnostic disable-next-line: missing-fields ---@class HeimdallEmoterConfig
shared.Emoter = {} ---@field enabled boolean
function shared.Emoter.Init() ---@field debug boolean
local frame = CreateFrame("Frame") ---@field channels string[]
frame:RegisterEvent("CHAT_MSG_CHANNEL") ---@field prefix string
frame:SetScript("OnEvent", function(self, event, msg, sender, ...)
--if Heimdall_Data.config.emoter.debug then
-- print(string.format("[%s] Channel message received from: %s", ModuleName, sender))
--end
if not Heimdall_Data.config.emoter.enabled then ---@class Emoter
shared.Emoter = {
Init = function()
local frame = CreateFrame("Frame")
frame:RegisterEvent("CHAT_MSG_CHANNEL")
frame:SetScript("OnEvent", function(self, event, msg, sender, ...)
--if Heimdall_Data.config.emoter.debug then --if Heimdall_Data.config.emoter.debug then
-- print(string.format("[%s] Module disabled, ignoring message", ModuleName)) -- print(string.format("[%s] Channel message received from: %s", ModuleName, sender))
--end --end
return
end
local channelId = select(6, ...) if not Heimdall_Data.config.emoter.enabled then
local _, channelname = GetChannelName(channelId) --if Heimdall_Data.config.emoter.debug then
local ok = false -- print(string.format("[%s] Module disabled, ignoring message", ModuleName))
for _, channel in pairs(Heimdall_Data.config.emoter.channels) do --end
if channel == channelname then return
ok = true
break
end end
end
if not ok then local channelId = select(6, ...)
local _, channelname = GetChannelName(channelId)
local ok = false
for _, channel in pairs(Heimdall_Data.config.emoter.channels) do
if channel == channelname then
ok = true
break
end
end
if not ok then
if Heimdall_Data.config.emoter.debug then
print(string.format("[%s] Channel name does not match any of the channels", ModuleName))
end
return
end
if Heimdall_Data.config.emoter.debug then if Heimdall_Data.config.emoter.debug then
print(string.format("[%s] Channel name does not match any of the channels", ModuleName)) print(string.format("[%s] Processing message from master channel: %s", ModuleName, sender))
shared.dump(Heimdall_Data.config.emoter)
end end
return
end
if Heimdall_Data.config.emoter.debug then if string.find(msg, "^" .. Heimdall_Data.config.emoter.prefix) then
print(string.format("[%s] Processing message from master channel: %s", ModuleName, sender)) if Heimdall_Data.config.emoter.debug then
shared.dump(Heimdall_Data.config.emoter) print(string.format("[%s] Found emote command in message: %s", ModuleName, msg))
end end
local emote = string.sub(msg, string.len(Heimdall_Data.config.emoter.prefix) + 1)
if string.find(msg, "^" .. Heimdall_Data.config.emoter.prefix) then if Heimdall_Data.config.emoter.debug then
if Heimdall_Data.config.emoter.debug then print(string.format("[%s] Performing emote: %s", ModuleName, emote))
print(string.format("[%s] Found emote command in message: %s", ModuleName, msg)) end
DoEmote(emote)
elseif Heimdall_Data.config.emoter.debug then
print(string.format("[%s] Message does not start with emote prefix", ModuleName))
end end
local emote = string.sub(msg, string.len(Heimdall_Data.config.emoter.prefix) + 1) end)
if Heimdall_Data.config.emoter.debug then
print(string.format("[%s] Performing emote: %s", ModuleName, emote))
end
DoEmote(emote)
elseif Heimdall_Data.config.emoter.debug then
print(string.format("[%s] Message does not start with emote prefix", ModuleName))
end
end)
if Heimdall_Data.config.emoter.debug then print(string.format("[%s] Module initialized", ModuleName)) end if Heimdall_Data.config.emoter.debug then print(string.format("[%s] Module initialized", ModuleName)) end
print("[Heimdall] Emoter loaded") print(string.format("[%s] Module initialized", ModuleName))
end end,
}

View File

@@ -2,262 +2,274 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "Inviter" local ModuleName = "Inviter"
---@diagnostic disable-next-line: missing-fields ---@class HeimdallInviterConfig
shared.Inviter = {} ---@field enabled boolean
function shared.Inviter.Init() ---@field debug boolean
-- Fallback for old config ---@field channels string[]
if type(Heimdall_Data.config.inviter.listeningChannel) == "string" then ---@field keyword string
Heimdall_Data.config.inviter.listeningChannel = { ---@field allAssist boolean
[Heimdall_Data.config.inviter.listeningChannel] = true, ---@field agentsAssist boolean
} ---@field throttle number
end ---@field kickOffline boolean
---@type Timer ---@field cleanupInterval number
local updateTimer = nil ---@field afkThreshold number
---@field listeningChannel table<string, boolean>
local function FixGroup() ---@class Inviter
if Heimdall_Data.config.inviter.debug then shared.Inviter = {
print(string.format("[%s] Checking and fixing group configuration", ModuleName)) Init = function()
-- Fallback for old config
if type(Heimdall_Data.config.inviter.listeningChannel) == "string" then
Heimdall_Data.config.inviter.listeningChannel = {
[Heimdall_Data.config.inviter.listeningChannel] = true,
}
end end
---@type Timer
local updateTimer = nil
if not IsInRaid() then local function FixGroup()
if Heimdall_Data.config.inviter.debug then if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Converting party to raid", ModuleName)) print(string.format("[%s] Checking and fixing group configuration", ModuleName))
end
ConvertToRaid()
end
if Heimdall_Data.config.inviter.allAssist then
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Setting all members to assistant", ModuleName))
end
SetEveryoneIsAssistant()
end
if Heimdall_Data.config.inviter.agentsAssist then
if Heimdall_Data.config.inviter.debug then
local agentCount = 0
for _ in pairs(Heimdall_Data.config.agents) do
agentCount = agentCount + 1
end
print(string.format("[%s] Processing %d agents for assistant promotion", ModuleName, agentCount))
end end
for name, _ in pairs(Heimdall_Data.config.agents) do if not IsInRaid() then
if UnitInParty(name) and not UnitIsGroupLeader(name) and not UnitIsRaidOfficer(name) then
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Promoting agent to assistant: %s", ModuleName, name))
end
PromoteToAssistant(name, true)
elseif Heimdall_Data.config.inviter.debug then
if not UnitInParty(name) then
print(string.format("[%s] Agent not in party: %s", ModuleName, name))
elseif UnitIsGroupLeader(name) then
print(string.format("[%s] Agent is already leader: %s", ModuleName, name))
elseif UnitIsRaidOfficer(name) then
print(string.format("[%s] Agent is already assistant: %s", ModuleName, name))
end
end
end
end
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Group configuration update complete", ModuleName))
end
end
---@param name string
---@return Frame?
local function FindPlayerRaidFrame(name)
for group = 1, 8 do
for player = 1, 5 do
local button = _G[string.format("ElvUF_RaidGroup%dUnitButton%d", group, player)]
if Heimdall_Data.config.inviter.debug then if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] button = %s", ModuleName, tostring(button))) print(string.format("[%s] Converting party to raid", ModuleName))
end
local unitName = button and button.unit and UnitName(button.unit)
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] unitName = %s", ModuleName, tostring(unitName)))
end
if unitName == name then
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] unitName == name", ModuleName))
end
return button
end end
ConvertToRaid()
end end
end
return nil
end
local framePool = {} if Heimdall_Data.config.inviter.allAssist then
local playerButtons = {} if Heimdall_Data.config.inviter.debug then
setmetatable(playerButtons, { __mode = "kv" }) print(string.format("[%s] Setting all members to assistant", ModuleName))
---@param players string[] end
local function OverlayKickButtons(players) SetEveryoneIsAssistant()
for _, frame in pairs(framePool) do end
frame:Hide()
end
for _, name in pairs(players) do
local frame = FindPlayerRaidFrame(name)
if frame then
playerButtons[name] = frame
-- All of these are ELVUI specific so they won't be in our meta...
---@diagnostic disable-next-line: undefined-field
local button = framePool[frame.unit]
or CreateFrame(
"Button",
---@diagnostic disable-next-line: undefined-field
string.format("HeimdallKickButton%s", frame.unit, frame, "SecureActionButtonTemplate")
)
---@diagnostic disable-next-line: undefined-field
framePool[frame.unit] = button
---@diagnostic disable-next-line: undefined-field if Heimdall_Data.config.inviter.agentsAssist then
button:SetSize(frame.UNIT_WIDTH / 2, frame.UNIT_HEIGHT / 2) if Heimdall_Data.config.inviter.debug then
button:SetPoint("CENTER", frame, "CENTER", 0, 0) print(string.format("[%s] Processing agents for assistant promotion", ModuleName))
button:SetNormalTexture("Interface\\Buttons\\UI-GroupLoot-KickIcon") end
button:SetHighlightTexture("Interface\\Buttons\\UI-GroupLoot-KickIcon")
button:SetPushedTexture("Interface\\Buttons\\UI-GroupLoot-KickIcon") shared.AgentTracker.ForEach(function(agent)
button:SetDisabledTexture("Interface\\Buttons\\UI-GroupLoot-KickIcon") if UnitInParty(agent) and not UnitIsGroupLeader(agent) and not UnitIsRaidOfficer(agent) then
button:SetAlpha(0.5) if Heimdall_Data.config.inviter.debug then
button:Show() print(string.format("[%s] Promoting agent to assistant: %s", ModuleName, agent))
button:SetScript("OnClick", function() end
UninviteUnit(name) PromoteToAssistant(agent, true)
button:Hide() elseif Heimdall_Data.config.inviter.debug then
if not UnitInParty(agent) then
print(string.format("[%s] Agent not in party: %s", ModuleName, agent))
elseif UnitIsGroupLeader(agent) then
print(string.format("[%s] Agent is already leader: %s", ModuleName, agent))
elseif UnitIsRaidOfficer(agent) then
print(string.format("[%s] Agent is already assistant: %s", ModuleName, agent))
end
end
end) end)
else end
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Frame for player %s not found", ModuleName, name)) if Heimdall_Data.config.inviter.debug then
end print(string.format("[%s] Group configuration update complete", ModuleName))
end end
end end
end
---@type table<string, number> ---@param name string
local groupMembers = {} ---@return Frame?
local function CleanGroups() local function FindPlayerRaidFrame(name)
if not Heimdall_Data.config.inviter.kickOffline then return end for group = 1, 8 do
print("Cleaning groups") for player = 1, 5 do
local now = GetTime() local button = _G[string.format("ElvUF_RaidGroup%dUnitButton%d", group, player)]
for i = 1, 40 do if Heimdall_Data.config.inviter.debug then
local unit = "raid" .. i print(string.format("[%s] button = %s", ModuleName, tostring(button)))
if UnitExists(unit) then end
local name = UnitName(unit)
if name then local unitName = button and button.unit and UnitName(button.unit)
-- When we load (into game) we want to consider everyone "online" if Heimdall_Data.config.inviter.debug then
-- In other words everyone we haven't seen before is "online" the first time we see them print(string.format("[%s] unitName = %s", ModuleName, tostring(unitName)))
-- This is to avoid kicking people who might not be offline which we don't know because we just logged in end
if not groupMembers[name] then if unitName == name then
groupMembers[name] = now if Heimdall_Data.config.inviter.debug then
else print(string.format("[%s] unitName == name", ModuleName))
local online = UnitIsConnected(unit) end
if online then groupMembers[name] = now end return button
end
end
end
return nil
end
local framePool = {}
local playerButtons = {}
setmetatable(playerButtons, { __mode = "kv" })
---@param players string[]
local function OverlayKickButtons(players)
for _, frame in pairs(framePool) do
frame:Hide()
end
for _, name in pairs(players) do
local frame = FindPlayerRaidFrame(name)
if frame then
playerButtons[name] = frame
-- All of these are ELVUI specific so they won't be in our meta...
---@diagnostic disable-next-line: undefined-field
local button = framePool[frame.unit]
or CreateFrame(
"Button",
---@diagnostic disable-next-line: undefined-field
string.format("HeimdallKickButton%s", frame.unit, frame, "SecureActionButtonTemplate")
)
---@diagnostic disable-next-line: undefined-field
framePool[frame.unit] = button
---@diagnostic disable-next-line: undefined-field
button:SetSize(frame.UNIT_WIDTH / 2, frame.UNIT_HEIGHT / 2)
button:SetPoint("CENTER", frame, "CENTER", 0, 0)
button:SetNormalTexture("Interface\\Buttons\\UI-GroupLoot-KickIcon")
button:SetHighlightTexture("Interface\\Buttons\\UI-GroupLoot-KickIcon")
button:SetPushedTexture("Interface\\Buttons\\UI-GroupLoot-KickIcon")
button:SetDisabledTexture("Interface\\Buttons\\UI-GroupLoot-KickIcon")
button:SetAlpha(0.5)
button:Show()
button:SetScript("OnClick", function()
UninviteUnit(name)
button:Hide()
end)
else
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Frame for player %s not found", ModuleName, name))
end end
end end
end end
end end
local afkPlayers = {}
for name, time in pairs(groupMembers) do ---@type table<string, number>
if not UnitInParty(name) then local groupMembers = {}
print(string.format("%s no longer in party", name)) local function CleanGroups()
groupMembers[name] = nil if not Heimdall_Data.config.inviter.kickOffline then return end
else print("Cleaning groups")
if time < now - Heimdall_Data.config.inviter.afkThreshold then local now = GetTime()
print(string.format("Kicking %s for being offline", name)) for i = 1, 40 do
afkPlayers[#afkPlayers + 1] = name local unit = "raid" .. i
-- Blyat this is protected... if UnitExists(unit) then
-- UninviteUnit(name) local name = UnitName(unit)
if name then
-- When we load (into game) we want to consider everyone "online"
-- In other words everyone we haven't seen before is "online" the first time we see them
-- This is to avoid kicking people who might not be offline which we don't know because we just logged in
if not groupMembers[name] then
groupMembers[name] = now
else
local online = UnitIsConnected(unit)
if online then groupMembers[name] = now end
end
end
end end
end end
end local afkPlayers = {}
OverlayKickButtons(afkPlayers) for name, time in pairs(groupMembers) do
end if not UnitInParty(name) then
local function Tick() print(string.format("%s no longer in party", name))
CleanGroups() groupMembers[name] = nil
C_Timer.NewTimer(Heimdall_Data.config.inviter.cleanupInterval, Tick, 1) else
end if time < now - Heimdall_Data.config.inviter.afkThreshold then
Tick() print(string.format("Kicking %s for being offline", name))
afkPlayers[#afkPlayers + 1] = name
local groupRosterUpdateFrame = CreateFrame("Frame") -- Blyat this is protected...
groupRosterUpdateFrame:RegisterEvent("GROUP_ROSTER_UPDATE") -- UninviteUnit(name)
groupRosterUpdateFrame:SetScript("OnEvent", function(self, event, ...) end
if Heimdall_Data.config.inviter.debug then end
print(string.format("[%s] Event received: %s", ModuleName, event))
end
if not Heimdall_Data.config.inviter.enabled then
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Module disabled, ignoring event", ModuleName))
end end
return OverlayKickButtons(afkPlayers)
end end
local function Tick()
CleanGroups()
C_Timer.NewTimer(Heimdall_Data.config.inviter.cleanupInterval, Tick, 1)
end
Tick()
local groupRosterUpdateFrame = CreateFrame("Frame")
groupRosterUpdateFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
groupRosterUpdateFrame:SetScript("OnEvent", function(self, event, ...)
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Event received: %s", ModuleName, event))
end
if not Heimdall_Data.config.inviter.enabled then
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Module disabled, ignoring event", ModuleName))
end
return
end
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Group roster changed - Checking configuration", ModuleName))
end
if updateTimer then updateTimer:Cancel() end
updateTimer = C_Timer.NewTimer(Heimdall_Data.config.inviter.throttle, FixGroup)
end)
local inviterChannelFrame = CreateFrame("Frame")
inviterChannelFrame:RegisterEvent("CHAT_MSG_CHANNEL")
inviterChannelFrame:SetScript("OnEvent", function(self, event, msg, sender, ...)
--if Heimdall_Data.config.inviter.debug then
-- print(string.format("[%s] Chat message received: %s", ModuleName, msg))
-- shared.dump(Heimdall_Data.config.inviter)
--end
if not Heimdall_Data.config.inviter.enabled then return end
local channelId = select(6, ...)
local _, channelname = GetChannelName(channelId)
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Channel name: %s", ModuleName, channelname))
end
local ok = false
for _, channel in pairs(Heimdall_Data.config.inviter.channels) do
if channel == channelname then
ok = true
break
end
end
if not ok then
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Channel name does not match any of the channels", ModuleName))
end
return
end
if msg == Heimdall_Data.config.inviter.keyword then
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Inviting %s", ModuleName, sender))
end
InviteUnit(sender)
else
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Message does not match keyword", ModuleName))
end
end
end)
if Heimdall_Data.config.inviter.debug then if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Group roster changed - Checking configuration", ModuleName)) print(
end string.format(
if updateTimer then updateTimer:Cancel() end "[%s] Module initialized - All assist: %s, Agents assist: %s",
updateTimer = C_Timer.NewTimer(Heimdall_Data.config.inviter.throttle, FixGroup) ModuleName,
end) tostring(Heimdall_Data.config.inviter.allAssist),
tostring(Heimdall_Data.config.inviter.agentsAssist)
local inviterChannelFrame = CreateFrame("Frame") )
inviterChannelFrame:RegisterEvent("CHAT_MSG_CHANNEL")
inviterChannelFrame:SetScript("OnEvent", function(self, event, msg, sender, ...)
--if Heimdall_Data.config.inviter.debug then
-- print(string.format("[%s] Chat message received: %s", ModuleName, msg))
-- shared.dump(Heimdall_Data.config.inviter)
--end
if not Heimdall_Data.config.inviter.enabled then return end
local channelId = select(6, ...)
local _, channelname = GetChannelName(channelId)
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Channel name: %s", ModuleName, channelname))
end
local ok = false
for _, channel in pairs(Heimdall_Data.config.inviter.channels) do
if channel == channelname then
ok = true
break
end
end
if not ok then
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Channel name does not match any of the channels", ModuleName))
end
return
end
if msg == Heimdall_Data.config.inviter.keyword then
if Heimdall_Data.config.inviter.debug then print(string.format("[%s] Inviting %s", ModuleName, sender)) end
InviteUnit(sender)
else
if Heimdall_Data.config.inviter.debug then
print(string.format("[%s] Message does not match keyword", ModuleName))
end
end
end)
if Heimdall_Data.config.inviter.debug then
print(
string.format(
"[%s] Module initialized - All assist: %s, Agents assist: %s",
ModuleName,
tostring(Heimdall_Data.config.inviter.allAssist),
tostring(Heimdall_Data.config.inviter.agentsAssist)
) )
) end
end
if Heimdall_Data.config.inviter.debug then if Heimdall_Data.config.inviter.debug then
print( print(
string.format( string.format(
"[%s] Module initialized - All assist: %s, Agents assist: %s", "[%s] Module initialized - All assist: %s, Agents assist: %s",
ModuleName, ModuleName,
tostring(Heimdall_Data.config.inviter.allAssist), tostring(Heimdall_Data.config.inviter.allAssist),
tostring(Heimdall_Data.config.inviter.agentsAssist) tostring(Heimdall_Data.config.inviter.agentsAssist)
)
) )
) end
end print(string.format("[%s] Module initialized", ModuleName))
print("[Heimdall] Inviter loaded") end,
end }

View File

@@ -2,100 +2,100 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "Macroer" local ModuleName = "Macroer"
---@diagnostic disable-next-line: missing-fields ---@class HeimdallMacroerConfig
shared.Macroer = {} ---@field enabled boolean
function shared.Macroer.Init() ---@field debug boolean
---@class stinky ---@field priority string[]
---@field name string
---@field class string
---@field seenAt number
---@field hostile boolean
local function FindOrCreateMacro(macroName) ---@class Macroer
if Heimdall_Data.config.macroer.debug then shared.Macroer = {
print(string.format("[%s] Finding or creating macro: %s", ModuleName, macroName)) Init = function()
end local function FindOrCreateMacro(macroName)
local idx = GetMacroIndexByName(macroName)
if idx == 0 then
if Heimdall_Data.config.macroer.debug then if Heimdall_Data.config.macroer.debug then
print(string.format("[%s] Creating new macro: %s", ModuleName, macroName)) print(string.format("[%s] Finding or creating macro: %s", ModuleName, macroName))
end end
CreateMacro(macroName, "INV_Misc_QuestionMark", "") local idx = GetMacroIndexByName(macroName)
end if idx == 0 then
idx = GetMacroIndexByName(macroName)
if Heimdall_Data.config.macroer.debug then print(string.format("[%s] Macro index: %d", ModuleName, idx)) end
return idx
end
---@param stinkies table<string, stinky>
local function FixMacro(stinkies)
if Heimdall_Data.config.macroer.debug then
print(string.format("[%s] Fixing macro with %d stinkies", ModuleName, #stinkies))
end
if not Heimdall_Data.config.macroer.enabled then
if Heimdall_Data.config.macroer.debug then
print(string.format("[%s] Module disabled, skipping macro update", ModuleName))
end
return
end
if InCombatLockdown() then
if Heimdall_Data.config.macroer.debug then
print(string.format("[%s] In combat, skipping macro update", ModuleName))
end
return
end
local priorityMap = {}
for priority, className in ipairs(Heimdall_Data.config.macroer.priority) do
priorityMap[className] = priority
end
local minPriority = #Heimdall_Data.config.macroer.priority + 1
local sortedStinkies = {}
for _, stinky in pairs(stinkies) do
if not Heimdall_Data.config.agents[stinky.name] then sortedStinkies[#sortedStinkies + 1] = stinky end
end
if Heimdall_Data.config.macroer.debug then
print(string.format("[%s] Processing %d non-agent stinkies", ModuleName, #sortedStinkies))
end
table.sort(sortedStinkies, function(a, b)
local aPriority = priorityMap[a.class] or minPriority
local bPriority = priorityMap[b.class] or minPriority
return aPriority > bPriority
end)
if Heimdall_Data.config.macroer.debug then
print(string.format("[%s] Sorted stinkies: %d", ModuleName, #sortedStinkies))
shared.dump(sortedStinkies)
end
local lines = { "/targetenemy" }
for _, stinky in pairs(sortedStinkies) do
if stinky.seenAt > GetTime() - 600 then
if Heimdall_Data.config.macroer.debug then if Heimdall_Data.config.macroer.debug then
print(string.format("[%s] Adding target macro for: %s", ModuleName, stinky.name)) print(string.format("[%s] Creating new macro: %s", ModuleName, macroName))
end end
lines[#lines + 1] = string.format("/tar %s", stinky.name) CreateMacro(macroName, "INV_Misc_QuestionMark", "")
end end
idx = GetMacroIndexByName(macroName)
if Heimdall_Data.config.macroer.debug then print(string.format("[%s] Macro index: %d", ModuleName, idx)) end
return idx
end end
local idx = FindOrCreateMacro("HeimdallTarget") ---@param stinkies table<string, Stinky>
---@diagnostic disable-next-line: param-type-mismatch local function FixMacro(stinkies)
local body = strjoin("\n", unpack(lines)) if Heimdall_Data.config.macroer.debug then
if Heimdall_Data.config.macroer.debug then print(string.format("[%s] Fixing macro with %d stinkies", ModuleName, #stinkies))
print(string.format("[%s] Updating macro with %d lines", ModuleName, #lines)) end
end if not Heimdall_Data.config.macroer.enabled then
EditMacro(idx, "HeimdallTarget", "INV_Misc_QuestionMark", body) if Heimdall_Data.config.macroer.debug then
end print(string.format("[%s] Module disabled, skipping macro update", ModuleName))
end
return
end
if InCombatLockdown() then
if Heimdall_Data.config.macroer.debug then
print(string.format("[%s] In combat, skipping macro update", ModuleName))
end
return
end
shared.stinkyTracker.stinkies:onChange(function(value) local priorityMap = {}
if Heimdall_Data.config.macroer.debug then for priority, className in ipairs(Heimdall_Data.config.macroer.priority) do
print(string.format("[%s] Stinkies changed, updating macro", ModuleName)) priorityMap[className] = priority
end end
FixMacro(value) local minPriority = #Heimdall_Data.config.macroer.priority + 1
end)
if Heimdall_Data.config.macroer.debug then print(string.format("[%s] Module initialized", ModuleName)) end local sortedStinkies = {}
print("[Heimdall] Macroer loaded") for _, stinky in pairs(stinkies) do
end if not shared.AgentTracker.IsAgent(stinky.name) then sortedStinkies[#sortedStinkies + 1] = stinky end
end
if Heimdall_Data.config.macroer.debug then
print(string.format("[%s] Processing %d non-agent stinkies", ModuleName, #sortedStinkies))
end
table.sort(sortedStinkies, function(a, b)
local aPriority = priorityMap[a.class] or minPriority
local bPriority = priorityMap[b.class] or minPriority
return aPriority > bPriority
end)
if Heimdall_Data.config.macroer.debug then
print(string.format("[%s] Sorted stinkies: %d", ModuleName, #sortedStinkies))
shared.dump(sortedStinkies)
end
local lines = { "/targetenemy" }
for _, stinky in pairs(sortedStinkies) do
if stinky.seenAt > GetTime() - 600 then
if Heimdall_Data.config.macroer.debug then
print(string.format("[%s] Adding target macro for: %s", ModuleName, stinky.name))
end
lines[#lines + 1] = string.format("/tar %s", stinky.name)
end
end
local idx = FindOrCreateMacro("HeimdallTarget")
---@diagnostic disable-next-line: param-type-mismatch
local body = strjoin("\n", unpack(lines))
if Heimdall_Data.config.macroer.debug then
print(string.format("[%s] Updating macro with %d lines", ModuleName, #lines))
end
EditMacro(idx, "HeimdallTarget", "INV_Misc_QuestionMark", body)
end
shared.StinkyTracker.OnChange(function(stinkies)
if Heimdall_Data.config.macroer.debug then
print(string.format("[%s] Stinkies changed, updating macro", ModuleName))
shared.dump(stinkies)
end
FixMacro(stinkies)
end)
if Heimdall_Data.config.macroer.debug then print(string.format("[%s] Module initialized", ModuleName)) end
print(string.format("[%s] Module initialized", ModuleName))
end,
}

View File

@@ -2,175 +2,175 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "Messenger" local ModuleName = "Messenger"
---@diagnostic disable-next-line: missing-fields ---@class HeimdallMessengerConfig
shared.Messenger = {} ---@field enabled boolean
function shared.Messenger.Init() ---@field debug boolean
---@class Message ---@field interval number
---@field message string
---@field channel string
---@field data string
local function FindOrJoinChannel(channelName, password) ---@class HeimdallMessengerData
local channelId = GetChannelName(channelName) ---@field queue ReactiveValue<table<string, Message>>
if channelId == 0 then ---@field ticker Timer?
---@class Message
---@field message string
---@field channel string
---@field data string
---@class Messenger
shared.Messenger = {
---@param message Message
Enqueue = function(message) table.insert(shared.messenger.queue, message) end,
Init = function()
shared.messenger = {
queue = ReactiveValue.new({}),
}
local function FindOrJoinChannel(channelName, password)
local channelId = GetChannelName(channelName)
if channelId == 0 then
if Heimdall_Data.config.messenger.debug then
print(string.format("[%s] Channel not found, joining: %s", ModuleName, channelName))
end
if password then
JoinPermanentChannel(channelName, password)
else
JoinPermanentChannel(channelName)
end
end
channelId = GetChannelName(channelName)
if Heimdall_Data.config.messenger.debug then if Heimdall_Data.config.messenger.debug then
print(string.format("[%s] Channel not found, joining: %s", ModuleName, channelName)) print(string.format("[%s] Channel found with ID: %s (%s)", ModuleName, channelId, channelName))
end
if password then
JoinPermanentChannel(channelName, password)
else
JoinPermanentChannel(channelName)
end end
return channelId
end end
channelId = GetChannelName(channelName)
if Heimdall_Data.config.messenger.debug then
print(string.format("[%s] Channel found with ID: %s (%s)", ModuleName, channelId, channelName))
end
return channelId
end
---@diagnostic disable-next-line: missing-fields if not shared.messenger.ticker then
if not shared.messenger then shared.messenger = {} end local function DoMessage()
if not shared.messenger.queue then shared.messenger.queue = {} end -- if Heimdall_Data.config.messenger.debug then
if not shared.messenger.ticker then -- print(
local function DoMessage() -- string.format(
if Heimdall_Data.config.messenger.debug then -- "[%s] Processing message queue - Size: %d",
print(string.format("[%s] Processing message queue - Size: %d", ModuleName, #shared.messenger.queue)) -- ModuleName,
end -- #shared.messenger.queue:get()
-- )
-- )
-- end
if not Heimdall_Data.config.messenger.enabled then if not Heimdall_Data.config.messenger.enabled then
if Heimdall_Data.config.messenger.debug then -- if Heimdall_Data.config.messenger.debug then
print(string.format("[%s] Module disabled, skipping message processing", ModuleName)) -- print(string.format("[%s] Module disabled, skipping message processing", ModuleName))
-- end
return
end end
return
end
---@type Message ---@type Message
local message = shared.messenger.queue[1] local message = shared.messenger.queue[1]
if not message then if not message then
if Heimdall_Data.config.messenger.debug then -- if Heimdall_Data.config.messenger.debug then
print(string.format("[%s] Message queue empty", ModuleName)) -- print(string.format("[%s] Message queue empty", ModuleName))
-- end
return
end end
return
end
if Heimdall_Data.config.messenger.debug then if Heimdall_Data.config.messenger.debug then shared.dump(message, "[%s] Processing message:") end
print(
string.format(
"[%s] Processing message - Channel: %s, Data: %s",
ModuleName,
message.channel or "nil",
message.data or "nil"
)
)
print(string.format("[%s] Message content: %s", ModuleName, message.message or "nil"))
end
if not message.message or message.message == "" then if not message.message or message.message == "" then
if Heimdall_Data.config.messenger.debug then if Heimdall_Data.config.messenger.debug then
print(string.format("[%s] Invalid message: empty content", ModuleName)) shared.dump(message, string.format("[%s] Invalid message: empty content", ModuleName))
end
return
end end
return
end
if not message.channel or message.channel == "" then if not message.channel or message.channel == "" then
if Heimdall_Data.config.messenger.debug then if Heimdall_Data.config.messenger.debug then
print(string.format("[%s] Invalid message: no channel specified", ModuleName)) shared.dump(message, string.format("[%s] Invalid message: no channel specified", ModuleName))
end
return
end end
return
end
if string.find(message.channel, "^C") then if string.find(message.channel, "^C") then
if Heimdall_Data.config.messenger.debug then if Heimdall_Data.config.messenger.debug then
print(string.format("[%s] Converting channel type from C to CHANNEL", ModuleName)) shared.dump(
end message,
message.channel = "CHANNEL" string.format("[%s] Converting channel type from C to CHANNEL", ModuleName)
elseif string.find(message.channel, "^W") then
if Heimdall_Data.config.messenger.debug then
print(string.format("[%s] Converting channel type from W to WHISPER", ModuleName))
end
message.channel = "WHISPER"
end
if message.channel == "CHANNEL" and message.data and string.match(message.data, "%D") then
if Heimdall_Data.config.messenger.debug then
print(
string.format(
"[%s] Processing channel message: '%s' to '%s'",
ModuleName,
message.message,
message.data
) )
)
end
local channelId = GetChannelName(message.data)
if channelId == 0 then
if Heimdall_Data.config.messenger.debug then
print(string.format("[%s] Channel not found, attempting to join: %s", ModuleName, message.data))
end end
channelId = FindOrJoinChannel(message.data) message.channel = "CHANNEL"
elseif string.find(message.channel, "^W") then
if Heimdall_Data.config.messenger.debug then if Heimdall_Data.config.messenger.debug then
print(string.format("[%s] Channel join result - ID: %s", ModuleName, channelId)) shared.dump(
message,
string.format("[%s] Converting channel type from W to WHISPER", ModuleName)
)
end end
message.channel = "WHISPER"
end end
message.data = tostring(channelId)
end
table.remove(shared.messenger.queue, 1) if message.channel == "CHANNEL" and message.data and string.match(message.data, "%D") then
if not message.message or message.message == "" then if Heimdall_Data.config.messenger.debug then
if Heimdall_Data.config.messenger.debug then shared.dump(message, string.format("[%s] Processing channel message:", ModuleName))
print(string.format("[%s] Skipping empty message", ModuleName)) end
local channelId = GetChannelName(message.data)
if channelId == 0 then
if Heimdall_Data.config.messenger.debug then
shared.dump(message, string.format("[%s] Channel not found, joining:", ModuleName))
end
channelId = FindOrJoinChannel(message.data)
if Heimdall_Data.config.messenger.debug then
print(string.format("[%s] Channel join result - ID: %s", ModuleName, channelId))
end
end
message.data = tostring(channelId)
end end
return
end
if not message.channel or message.channel == "" then
if Heimdall_Data.config.messenger.debug then
print(string.format("[%s] Skipping message with no channel", ModuleName))
end
return
end
if not message.data or message.data == "" then
if Heimdall_Data.config.messenger.debug then
print(string.format("[%s] Skipping message with no data", ModuleName))
end
return
end
if Heimdall_Data.config.messenger.debug then table.remove(shared.messenger.queue, 1)
print( if not message.message or message.message == "" then
string.format( if Heimdall_Data.config.messenger.debug then
"[%s] Sending message: '%s' to %s:%s", shared.dump(message, string.format("[%s] Skipping empty message", ModuleName))
ModuleName, end
message.message, return
message.channel, end
message.data if not message.channel or message.channel == "" then
) if Heimdall_Data.config.messenger.debug then
shared.dump(message, string.format("[%s] Skipping message with no channel", ModuleName))
end
return
end
if not message.data or message.data == "" then
if Heimdall_Data.config.messenger.debug then
shared.dump(message, string.format("[%s] Skipping message with no data", ModuleName))
end
return
end
if Heimdall_Data.config.messenger.debug then
shared.dump(message, string.format("[%s] Sending message:", ModuleName))
end
if string.len(message.message) > 255 then
shared.dump(message, string.format("[%s] Message too long!!!!: %s", ModuleName, message.message))
return
end
SendChatMessage(message.message, message.channel, nil, message.data)
end
local function Tick()
-- if Heimdall_Data.config.messenger.debug then
-- print(string.format("[%s] Tick - Queue size: %d", ModuleName, #shared.messenger.queue:get()))
-- end
DoMessage()
shared.messenger.ticker = C_Timer.NewTimer(Heimdall_Data.config.messenger.interval, Tick, 1)
end
Tick()
end
if Heimdall_Data.config.messenger.debug then
print(
string.format(
"[%s] Module initialized with interval: %s",
ModuleName,
Heimdall_Data.config.messenger.interval
) )
end
if string.len(message.message) > 255 then
print(string.format("[%s] Message too long!!!!: %s", ModuleName, message.message))
return
end
SendChatMessage(message.message, message.channel, nil, message.data)
end
local function Tick()
if Heimdall_Data.config.messenger.debug then
print(string.format("[%s] Tick - Queue size: %d", ModuleName, #shared.messenger.queue))
end
DoMessage()
shared.messenger.ticker = C_Timer.NewTimer(Heimdall_Data.config.messenger.interval, Tick, 1)
end
Tick()
end
if Heimdall_Data.config.messenger.debug then
print(
string.format(
"[%s] Module initialized with interval: %s",
ModuleName,
Heimdall_Data.config.messenger.interval
) )
) end
end print(string.format("[%s] Module initialized", ModuleName))
print("[Heimdall] Messenger loaded") end,
end }

File diff suppressed because it is too large Load Diff

View File

@@ -2,72 +2,84 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "Network" local ModuleName = "Network"
---@diagnostic disable-next-line: missing-fields ---@class HeimdallNetworkConfig
shared.Network = {} ---@field enabled boolean
function shared.Network.Init() ---@field debug boolean
if not shared.network then shared.network = {} end ---@field members string[]
local updatePending = false ---@field updateInterval number
local function FriendListUpdate() ---@class HeimdallNetworkData
updatePending = false ---@field ticker Timer?
if not Heimdall_Data.config.network.enabled then return end
---@type table<string, boolean> ---@class Network
local friends = {} shared.Network = {
for i = 1, GetNumFriends() do Init = function()
local name, _, _, _, connected, _, _, _ = GetFriendInfo(i) if not shared.network then shared.network = {} end
if name then local updatePending = false
friends[name] = connected
if Heimdall_Data.config.network.debug then local function FriendListUpdate()
print(string.format("[%s] Friend %s is %s", ModuleName, name, connected and "online" or "offline")) updatePending = false
if not Heimdall_Data.config.network.enabled then return end
---@type table<string, boolean>
local friends = {}
for i = 1, GetNumFriends() do
local name, _, _, _, connected, _, _, _ = GetFriendInfo(i)
if name then
friends[name] = connected
if Heimdall_Data.config.network.debug then
print(
string.format("[%s] Friend %s is %s", ModuleName, name, connected and "online" or "offline")
)
end
else
if Heimdall_Data.config.network.debug then
print(string.format("[%s] Friend %s is nil", ModuleName, i))
end
end end
else end
if Heimdall_Data.config.network.debug then
print(string.format("[%s] Friend %s is nil", ModuleName, i)) for _, member in ipairs(Heimdall_Data.config.network.members) do
if friends[member] == nil and member ~= UnitName("player") then
if Heimdall_Data.config.network.debug then
print(string.format("[%s] Adding friend %s", ModuleName, member))
end
AddFriend(member)
end end
end end
friends[UnitName("player")] = true
shared.networkNodes = {}
-- Why are we skipping this again...?
-- if false then shared.networkNodes[#shared.networkNodes + 1] = UnitName("player") end
for _, player in ipairs(Heimdall_Data.config.network.members) do
if friends[player] then
shared.networkNodes[#shared.networkNodes + 1] = player
if Heimdall_Data.config.network.debug then
print(string.format("[%s] Adding network node %s", ModuleName, player))
end
end
end
if Heimdall_Data.config.network.debug then
print(string.format("[%s] Network nodes:", ModuleName))
shared.dump(shared.networkNodes)
end
end end
for _, member in ipairs(Heimdall_Data.config.network.members) do local friendsFrame = CreateFrame("Frame")
if friends[member] == nil and member ~= UnitName("player") then friendsFrame:RegisterEvent("FRIENDLIST_UPDATE")
if Heimdall_Data.config.network.debug then friendsFrame:SetScript("OnEvent", function(self, event, ...) end)
print(string.format("[%s] Adding friend %s", ModuleName, member))
end local function NetworkTick()
AddFriend(member) if Heimdall_Data.config.network.debug then print("Network module is updating.") end
end ShowFriends()
updatePending = true
C_Timer.After(1, function()
if updatePending then FriendListUpdate() end
end)
shared.network.ticker = C_Timer.NewTimer(Heimdall_Data.config.network.updateInterval, NetworkTick, 1)
end end
friends[UnitName("player")] = true
shared.networkNodes = {} NetworkTick()
-- Why are we skipping this again...? print(string.format("[%s] Module initialized", ModuleName))
-- if false then shared.networkNodes[#shared.networkNodes + 1] = UnitName("player") end end,
for _, player in ipairs(Heimdall_Data.config.network.members) do }
if friends[player] then
shared.networkNodes[#shared.networkNodes + 1] = player
if Heimdall_Data.config.network.debug then
print(string.format("[%s] Adding network node %s", ModuleName, player))
end
end
end
if Heimdall_Data.config.network.debug then
print(string.format("[%s] Network nodes:", ModuleName))
shared.dump(shared.networkNodes)
end
end
local friendsFrame = CreateFrame("Frame")
friendsFrame:RegisterEvent("FRIENDLIST_UPDATE")
friendsFrame:SetScript("OnEvent", function(self, event, ...) end)
local function NetworkTick()
if Heimdall_Data.config.network.debug then print("Network module is updating.") end
ShowFriends()
updatePending = true
C_Timer.After(1, function()
if updatePending then FriendListUpdate() end
end)
shared.network.ticker = C_Timer.NewTimer(Heimdall_Data.config.network.updateInterval, NetworkTick, 1)
end
NetworkTick()
print("[Heimdall] Network module loaded")
end

View File

@@ -2,182 +2,203 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "NetworkMessenger" local ModuleName = "NetworkMessenger"
---@diagnostic disable-next-line: missing-fields ---@class HeimdallNetworkMessengerConfig
shared.NetworkMessenger = {} ---@field enabled boolean
function shared.NetworkMessenger.Init() ---@field debug boolean
RegisterAddonMessagePrefix(Heimdall_Data.config.addonPrefix) ---@field interval number
if not shared.networkMessenger then shared.networkMessenger = {} end ---@class HeimdallNetworkMessengerData
if not shared.networkMessenger.queue then shared.networkMessenger.queue = {} end ---@field queue table<string, Message>
if not shared.networkMessenger.ticker then ---@field ticker Timer?
local function DoMessage()
--if Heimdall_Data.config.networkMessenger.debug then ---@class NetworkMessenger
-- print(string.format("[%s] Processing network message queue", ModuleName)) shared.NetworkMessenger = {
--end ---@param message Message
if not Heimdall_Data.config.networkMessenger.enabled then Enqueue = function(message) table.insert(shared.networkMessenger.queue, message) end,
Init = function()
RegisterAddonMessagePrefix(Heimdall_Data.config.addonPrefix)
shared.networkMessenger = {
queue = ReactiveValue.new({}),
}
if not shared.networkMessenger.ticker then
local function DoMessage()
--if Heimdall_Data.config.networkMessenger.debug then --if Heimdall_Data.config.networkMessenger.debug then
-- print(string.format("[%s] Module disabled, skipping network message processing", ModuleName)) -- print(string.format("[%s] Processing network message queue", ModuleName))
--end --end
return if not Heimdall_Data.config.networkMessenger.enabled then
end --if Heimdall_Data.config.networkMessenger.debug then
---@type Message -- print(string.format("[%s] Module disabled, skipping network message processing", ModuleName))
local message = shared.networkMessenger.queue[1] --end
if not message then return
--if Heimdall_Data.config.networkMessenger.debug then
-- print(string.format("[%s] Network message queue empty", ModuleName))
--end
return
end
if not message.message or message.message == "" then
if Heimdall_Data.config.networkMessenger.debug then
print(string.format("[%s] Invalid network message: empty content", ModuleName))
end end
return ---@type Message
end local message = shared.networkMessenger.queue[1]
if not message.channel or message.channel == "" then if not message then
if Heimdall_Data.config.networkMessenger.debug then --if Heimdall_Data.config.networkMessenger.debug then
print(string.format("[%s] Invalid network message: no channel specified", ModuleName)) -- print(string.format("[%s] Network message queue empty", ModuleName))
--end
return
end
if not message.message or message.message == "" then
if Heimdall_Data.config.networkMessenger.debug then
print(string.format("[%s] Invalid network message: empty content", ModuleName))
end
return
end
if not message.channel or message.channel == "" then
if Heimdall_Data.config.networkMessenger.debug then
print(string.format("[%s] Invalid network message: no channel specified", ModuleName))
end
return
end end
return
end
table.remove(shared.networkMessenger.queue, 1) table.remove(shared.networkMessenger.queue, 1)
if not message.message or message.message == "" then if not message.message or message.message == "" then
if Heimdall_Data.config.networkMessenger.debug then if Heimdall_Data.config.networkMessenger.debug then
print(string.format("[%s] Skipping empty network message", ModuleName)) print(string.format("[%s] Skipping empty network message", ModuleName))
end
return
end end
return if not message.channel or message.channel == "" then
end if Heimdall_Data.config.networkMessenger.debug then
if not message.channel or message.channel == "" then print(string.format("[%s] Skipping network message with no channel", ModuleName))
if Heimdall_Data.config.networkMessenger.debug then end
print(string.format("[%s] Skipping network message with no channel", ModuleName)) return
end end
return if not message.data or message.data == "" then
end if Heimdall_Data.config.networkMessenger.debug then
if not message.data or message.data == "" then print(string.format("[%s] Skipping network message with no data", ModuleName))
if Heimdall_Data.config.networkMessenger.debug then end
print(string.format("[%s] Skipping network message with no data", ModuleName)) return
end end
return
end
if Heimdall_Data.config.networkMessenger.debug then if Heimdall_Data.config.networkMessenger.debug then
print( print(
string.format( string.format(
"[%s] Sending network message: '%s' to %s:%s", "[%s] Sending network message: '%s' to %s:%s",
ModuleName, ModuleName,
message.message, message.message,
message.channel, message.channel,
message.data message.data
)
) )
) end
local payload = string.format("dmessage|%s|%s|%s", message.message, message.channel, message.data)
if Heimdall_Data.config.networkMessenger.debug then
print(string.format("[%s] Payload: %s", ModuleName, payload))
end
if not shared.networkNodes or #shared.networkNodes == 0 then
if Heimdall_Data.config.networkMessenger.debug then
print(string.format("[%s] No network nodes found, wtf????", ModuleName))
end
return
end
local target = shared.networkNodes[1]
SendAddonMessage(Heimdall_Data.config.addonPrefix, payload, "WHISPER", target)
end end
local payload = string.format("dmessage|%s|%s|%s", message.message, message.channel, message.data) local function Tick()
--if Heimdall_Data.config.networkMessenger.debug then
-- local queueSize = #shared.networkMessenger.queue
-- print(string.format("[%s] Queue check - Network messages pending: %d", ModuleName, queueSize))
--end
DoMessage()
shared.networkMessenger.ticker =
C_Timer.NewTimer(Heimdall_Data.config.networkMessenger.interval, Tick, 1)
end
Tick()
end
-- If we are the leader then we delegate messages (dmessage)
-- If we get a "message" command from leader then we send the message
local nextIdx = 1
local addonMsgFrame = CreateFrame("Frame")
addonMsgFrame:RegisterEvent("CHAT_MSG_ADDON")
addonMsgFrame:SetScript("OnEvent", function(self, event, prefix, message, channel, source)
if not Heimdall_Data.config.networkMessenger.enabled then return end
if prefix ~= Heimdall_Data.config.addonPrefix then return end
source = string.match(source, "[^%-]+")
if Heimdall_Data.config.networkMessenger.debug then if Heimdall_Data.config.networkMessenger.debug then
print(string.format("[%s] Payload: %s", ModuleName, payload)) print(string.format("[%s] Received message from %s: %s", ModuleName, source, message))
end end
if not shared.networkNodes or #shared.networkNodes == 0 then if #shared.networkNodes == 0 then
if Heimdall_Data.config.networkMessenger.debug then if Heimdall_Data.config.networkMessenger.debug then
print(string.format("[%s] No network nodes found, wtf????", ModuleName)) print(string.format("[%s] No network nodes found, wtf????", ModuleName))
end end
return return
end end
local target = shared.networkNodes[1]
SendAddonMessage(Heimdall_Data.config.addonPrefix, payload, "WHISPER", target) -- There should always be at least one network node ergo should always exist a leader
end -- Because the us, the player, is also a node
local function Tick() --local networkLeader = shared.networkNodes[1]
--if Heimdall_Data.config.networkMessenger.debug then --if source ~= networkLeader then
-- local queueSize = #shared.networkMessenger.queue -- if Heimdall_Data.config.networkMessenger.debug then
-- print(string.format("[%s] Queue check - Network messages pending: %d", ModuleName, queueSize)) -- print(string.format("[%s] Message from %s is not from the network leader (%s)", ModuleName, source,
-- networkLeader))
-- end
-- return
--end --end
DoMessage()
shared.networkMessenger.ticker = C_Timer.NewTimer(Heimdall_Data.config.networkMessenger.interval, Tick, 1)
end
Tick()
end
-- If we are the leader then we delegate messages (dmessage) local parts = shared.Split(message, "|")
-- If we get a "message" command from leader then we send the message
local nextIdx = 1
local addonMsgFrame = CreateFrame("Frame")
addonMsgFrame:RegisterEvent("CHAT_MSG_ADDON")
addonMsgFrame:SetScript("OnEvent", function(self, event, prefix, message, channel, source)
if not Heimdall_Data.config.networkMessenger.enabled then return end
if prefix ~= Heimdall_Data.config.addonPrefix then return end
source = string.match(source, "[^%-]+")
if Heimdall_Data.config.networkMessenger.debug then
print(string.format("[%s] Received message from %s: %s", ModuleName, source, message))
end
if #shared.networkNodes == 0 then
if Heimdall_Data.config.networkMessenger.debug then if Heimdall_Data.config.networkMessenger.debug then
print(string.format("[%s] No network nodes found, wtf????", ModuleName)) print(string.format("[%s] Received message parts:", ModuleName))
shared.dump(parts)
end end
return local command = strtrim(parts[1])
end if command == "message" then
local content = strtrim(tostring(parts[2]))
local targetchannel = strtrim(tostring(parts[3]))
local target = strtrim(tostring(parts[4]))
if Heimdall_Data.config.networkMessenger.debug then
print(
string.format(
"[%s] Received message command: %s %s %s",
ModuleName,
content,
targetchannel,
target
)
)
end
---@type Message
local msg = {
channel = targetchannel,
message = content,
data = target,
}
table.insert(shared.messenger.queue, msg)
elseif command == "dmessage" then
if Heimdall_Data.config.networkMessenger.debug then
print(string.format("[%s] Received dmessage command", ModuleName))
end
parts[1] = "message"
local content = table.concat(parts, "|")
-- There should always be at least one network node ergo should always exist a leader if nextIdx > #shared.networkNodes then nextIdx = 1 end
-- Because the us, the player, is also a node local recipient = shared.networkNodes[nextIdx]
--local networkLeader = shared.networkNodes[1] nextIdx = nextIdx + 1
--if source ~= networkLeader then if Heimdall_Data.config.networkMessenger.debug then
-- if Heimdall_Data.config.networkMessenger.debug then print(string.format("[%s] Sending message %s to %s", ModuleName, content, recipient))
-- print(string.format("[%s] Message from %s is not from the network leader (%s)", ModuleName, source, end
-- networkLeader)) SendAddonMessage(Heimdall_Data.config.addonPrefix, content, "WHISPER", recipient)
-- end
-- return
--end
local parts = shared.Split(message, "|")
if Heimdall_Data.config.networkMessenger.debug then
print(string.format("[%s] Received message parts:", ModuleName))
shared.dump(parts)
end
local command = strtrim(parts[1])
if command == "message" then
local content = strtrim(tostring(parts[2]))
local targetchannel = strtrim(tostring(parts[3]))
local target = strtrim(tostring(parts[4]))
if Heimdall_Data.config.networkMessenger.debug then
print(
string.format("[%s] Received message command: %s %s %s", ModuleName, content, targetchannel, target)
)
end end
---@type Message end)
local msg = {
channel = targetchannel, --/run Heimdall_Data.Test()
message = content, Heimdall_Data.Test = function()
data = target, local testmsg = {
channel = "W",
message = "Hi, mom!",
data = "Secundus",
} }
table.insert(shared.messenger.queue, msg) for i = 1, 36 do
elseif command == "dmessage" then table.insert(shared.networkMessenger.queue, testmsg)
if Heimdall_Data.config.networkMessenger.debug then
print(string.format("[%s] Received dmessage command", ModuleName))
end end
parts[1] = "message"
local content = table.concat(parts, "|")
if nextIdx > #shared.networkNodes then nextIdx = 1 end
local recipient = shared.networkNodes[nextIdx]
nextIdx = nextIdx + 1
if Heimdall_Data.config.networkMessenger.debug then
print(string.format("[%s] Sending message %s to %s", ModuleName, content, recipient))
end
SendAddonMessage(Heimdall_Data.config.addonPrefix, content, "WHISPER", recipient)
end end
end)
--/run Heimdall_Data.Test() print(string.format("[%s] Module initialized", ModuleName))
Heimdall_Data.Test = function() end,
local testmsg = { }
channel = "W",
message = "Hi, mom!",
data = "Secundus",
}
for i = 1, 36 do
table.insert(shared.networkMessenger.queue, testmsg)
end
end
print("[Heimdall] NetworkMessenger module loaded")
end

View File

@@ -2,294 +2,306 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "Noter" local ModuleName = "Noter"
---@class HeimdallNoterConfig
---@field enabled boolean
---@field debug boolean
---@field channels string[]
---@field lastNotes number
---@class Note ---@class Note
---@field source string ---@field source string
---@field for string ---@field for string
---@field date string ---@field date string
---@field note string ---@field note string
---@diagnostic disable-next-line: missing-fields ---@class Noter
shared.Noter = {} shared.Noter = {
function shared.Noter.Init() Init = function()
-- ---Hopefully this will not be necessary -- ---Hopefully this will not be necessary
-- ---@param text string -- ---@param text string
-- ---@param size number -- ---@param size number
-- ---@return string[] -- ---@return string[]
-- local function Partition(text, size) -- local function Partition(text, size)
-- local words = {} -- local words = {}
-- for word in text:gmatch("[^,]+") do -- for word in text:gmatch("[^,]+") do
-- words[#words + 1] = word -- words[#words + 1] = word
-- end -- end
-- local ret = {} -- local ret = {}
-- local currentChunk = "" -- local currentChunk = ""
-- for _, word in ipairs(words) do -- for _, word in ipairs(words) do
-- if #currentChunk + #word + 1 <= size then -- if #currentChunk + #word + 1 <= size then
-- currentChunk = currentChunk .. (currentChunk == "" and word or " " .. word) -- currentChunk = currentChunk .. (currentChunk == "" and word or " " .. word)
-- else -- else
-- if #currentChunk > 0 then ret[#ret + 1] = currentChunk end -- if #currentChunk > 0 then ret[#ret + 1] = currentChunk end
-- currentChunk = word -- currentChunk = word
-- end -- end
-- end -- end
-- if #currentChunk > 0 then ret[#ret + 1] = currentChunk end -- if #currentChunk > 0 then ret[#ret + 1] = currentChunk end
-- return ret -- return ret
-- end -- end
---@param array any[] ---@param array any[]
---@return any[] ---@return any[]
local function Compact(array) local function Compact(array)
local compacted = {} local compacted = {}
for _, v in pairs(array) do for _, v in pairs(array) do
compacted[#compacted + 1] = v compacted[#compacted + 1] = v
end
return compacted
end
---@param name string
---@param args string[]
local function DeleteNotes(name, args)
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Delete note command received for: %s", ModuleName, name))
end
local range = args[4]
if range then
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Range received for delete note: %s", ModuleName, range))
end end
local indices = shared.Split(range, "..") return compacted
if Heimdall_Data.config.noter.debug then end
print(string.format("[%s] Indices for range deletion: %s", ModuleName, table.concat(indices, ", ")))
shared.dump(indices)
end
local start = tonumber(indices[1])
local finish = tonumber(indices[2])
if not start then ---@param name string
---@param args string[]
local function DeleteNotes(name, args)
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Delete note command received for: %s", ModuleName, name))
end
local range = args[4]
if range then
if Heimdall_Data.config.noter.debug then if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Invalid start range for delete note: %s", ModuleName, tostring(start))) print(string.format("[%s] Range received for delete note: %s", ModuleName, range))
end end
return local indices = shared.Split(range, "..")
end if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Indices for range deletion: %s", ModuleName, table.concat(indices, ", ")))
if not finish then finish = start end shared.dump(indices)
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Deleting note range %s to %s for: %s", ModuleName, start, finish, name))
end
-- Here, because we are deleting random notes, we lose the "iterative" index property
-- Ie it's not longer 1..100, it might be 1..47, 50, 68..100
-- Which means that we cannot use ipairs, bad!
for i = start, finish do
if not Heimdall_Data.config.notes[name] then Heimdall_Data.config.notes[name] = {} end
if not Heimdall_Data.config.notes[name][i] then
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Note at index %s does not exist", ModuleName, i))
end
else
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Deleting note %s at index %s", ModuleName, name, i))
shared.dump(Heimdall_Data.config.notes[name][i])
end
Heimdall_Data.config.notes[name][i] = nil
end end
end local start = tonumber(indices[1])
Heimdall_Data.config.notes[name] = Compact(Heimdall_Data.config.notes[name]) local finish = tonumber(indices[2])
end
end
---@param channel string if not start then
---@param index number if Heimdall_Data.config.noter.debug then
---@param note Note print(
local function PrintNote(channel, index, note) string.format("[%s] Invalid start range for delete note: %s", ModuleName, tostring(start))
if Heimdall_Data.config.noter.debug then )
print(string.format("[%s] Printing note at index %d for: %s", ModuleName, index, note.source)) end
print(string.format("[%s] [%s][%d] %s: %s", ModuleName, note.source, index, note.date, note.note)) return
end
if not finish then finish = start end
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Deleting note range %s to %s for: %s", ModuleName, start, finish, name))
end
-- Here, because we are deleting random notes, we lose the "iterative" index property
-- Ie it's not longer 1..100, it might be 1..47, 50, 68..100
-- Which means that we cannot use ipairs, bad!
for i = start, finish do
if not Heimdall_Data.config.notes[name] then Heimdall_Data.config.notes[name] = {} end
if not Heimdall_Data.config.notes[name][i] then
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Note at index %s does not exist", ModuleName, i))
end
else
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Deleting note %s at index %s", ModuleName, name, i))
shared.dump(Heimdall_Data.config.notes[name][i])
end
Heimdall_Data.config.notes[name][i] = nil
end
end
Heimdall_Data.config.notes[name] = Compact(Heimdall_Data.config.notes[name])
end
end end
---@type Message
local msg = { ---@param channel string
channel = "C", ---@param index number
data = channel, ---@param note Note
message = string.format("[%s][%d] %s: %s", note.source, index, note.date, note.note), local function PrintNote(channel, index, note)
}
--table.insert(shared.messenger.queue, msg)
table.insert(shared.networkMessenger.queue, msg)
end
---@param name string
---@param args string[]
local function PrintNotes(channel, name, args)
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Print note command received for: %s", ModuleName, name))
end
local range = args[3]
if not range then
if Heimdall_Data.config.noter.debug then if Heimdall_Data.config.noter.debug then
print( print(string.format("[%s] Printing note at index %d for: %s", ModuleName, index, note.source))
string.format( print(string.format("[%s] [%s][%d] %s: %s", ModuleName, note.source, index, note.date, note.note))
"[%s] No range specified for print note, defaulting to last %d notes", end
ModuleName, ---@type Message
Heimdall_Data.config.noter.lastNotes local msg = {
channel = "C",
data = channel,
message = string.format("[%s][%d] %s: %s", note.source, index, note.date, note.note),
}
if Heimdall_Data.config.networkMessenger.enabled then
shared.NetworkMessenger.Enqueue(msg)
elseif Heimdall_Data.config.messenger.enabled then
shared.Messenger.Enqueue(msg)
end
end
---@param name string
---@param args string[]
local function PrintNotes(channel, name, args)
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Print note command received for: %s", ModuleName, name))
end
local range = args[3]
if not range then
if Heimdall_Data.config.noter.debug then
print(
string.format(
"[%s] No range specified for print note, defaulting to last %d notes",
ModuleName,
Heimdall_Data.config.noter.lastNotes
)
) )
) end
end local notes = Heimdall_Data.config.notes[name] or {}
local notes = Heimdall_Data.config.notes[name] or {} local start = math.max(1, #notes - Heimdall_Data.config.noter.lastNotes + 1)
local start = math.max(1, #notes - Heimdall_Data.config.noter.lastNotes + 1) local finish = #notes
local finish = #notes
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Printing notes from %d to %d for: %s", ModuleName, start, finish, name))
end
for i = start, finish do
PrintNote(channel, i, notes[i])
end
return
end
if range then
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Range received for print note: %s", ModuleName, range))
end
local indices = shared.Split(range, "..")
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Indices for range printing: %s", ModuleName, table.concat(indices, ", ")))
shared.dump(indices)
end
local start = tonumber(indices[1])
local finish = tonumber(indices[2])
if not start then
if Heimdall_Data.config.noter.debug then if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Invalid start range for print note: %s", ModuleName, tostring(start))) print(string.format("[%s] Printing notes from %d to %d for: %s", ModuleName, start, finish, name))
end
for i = start, finish do
PrintNote(channel, i, notes[i])
end
return
end
if range then
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Range received for print note: %s", ModuleName, range))
end
local indices = shared.Split(range, "..")
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Indices for range printing: %s", ModuleName, table.concat(indices, ", ")))
shared.dump(indices)
end
local start = tonumber(indices[1])
local finish = tonumber(indices[2])
if not start then
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Invalid start range for print note: %s", ModuleName, tostring(start)))
end
return
end
if not finish then finish = start end
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Printing note range %s to %s for: %s", ModuleName, start, finish, name))
end
for i = start, finish do
if not Heimdall_Data.config.notes[name] then Heimdall_Data.config.notes[name] = {} end
if not Heimdall_Data.config.notes[name][i] then
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Note at index %s does not exist", ModuleName, i))
end
else
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Printing note %s at index %s", ModuleName, name, i))
shared.dump(Heimdall_Data.config.notes[name][i])
end
PrintNote(channel, i, Heimdall_Data.config.notes[name][i])
end
end
end
end
---@param name string
---@param sender string
---@param args string[]
local function AddNote(name, sender, args)
if not Heimdall_Data.config.notes[name] then Heimdall_Data.config.notes[name] = {} end
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Adding note for: %s from: %s", ModuleName, name, sender))
shared.dump(args)
end
local msgparts = {}
for i = 3, #args do
msgparts[#msgparts + 1] = args[i]
end
local msg = table.concat(msgparts, " ")
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Adding note for: %s from: %s", ModuleName, name, sender))
print(string.format("[%s] Note: %s", ModuleName, msg))
end
local note = {
source = sender,
date = date("%Y-%m-%dT%H:%M:%S"),
note = msg,
}
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Adding note", ModuleName))
shared.dump(note)
end
table.insert(Heimdall_Data.config.notes[name], note)
end
-- Here's the plan:
-- Implement a "note" command, that will do everything
-- Saying "note <name> <note>" will add a note to the list for the character
-- Saying "note <name>" will list last N notes
-- Saying "note <name> i" will list the i-th note
-- Saying "note <name> i..j" will list notes from i to j
-- Saying "note <name> delete i" will delete the i-th note
-- Saying "note <name> delete i..j" will delete notes from i to j
local noterChannelFrame = CreateFrame("Frame")
noterChannelFrame:RegisterEvent("CHAT_MSG_CHANNEL")
noterChannelFrame:SetScript("OnEvent", function(self, event, msg, sender, ...)
--if Heimdall_Data.config.noter.debug then
-- print(string.format("[%s] Event received", ModuleName))
-- shared.dump(Heimdall_Data.config.noter)
--end
if not Heimdall_Data.config.noter.enabled then
--if Heimdall_Data.config.noter.debug then
-- print(string.format("[%s] Module disabled, ignoring event", ModuleName))
--end
return
end
local channelId = select(6, ...)
local _, channelname = GetChannelName(channelId)
local ok = false
for _, channel in pairs(Heimdall_Data.config.noter.channels) do
if channelname == channel then
ok = true
break
end
end
if not ok then
--if Heimdall_Data.config.noter.debug then
-- print(string.format("[%s] Channel %s does not match the master channel %s", ModuleName, channelname, Heimdall_Data.config.noter.masterChannel))
--end
return
end
sender = string.match(sender, "^[^-]+")
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Message from: %s", ModuleName, sender))
shared.dump(Heimdall_Data.config.noter)
end
if not msg or msg == "" then
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Empty message, ignoring", ModuleName))
end end
return return
end end
if not finish then finish = start end local args = { strsplit(" ", msg) }
if Heimdall_Data.config.noter.debug then if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Printing note range %s to %s for: %s", ModuleName, start, finish, name)) print(string.format("[%s] Arguments received: %s", ModuleName, table.concat(args, ", ")))
shared.dump(args)
end end
local command = args[1]
for i = start, finish do if command == "note" then
if not Heimdall_Data.config.notes[name] then Heimdall_Data.config.notes[name] = {} end local name = strtrim(string.lower(args[2] or ""))
if not Heimdall_Data.config.notes[name][i] then if Heimdall_Data.config.noter.debug then
if Heimdall_Data.config.noter.debug then print(string.format("[%s] Note command received for: %s", ModuleName, name))
print(string.format("[%s] Note at index %s does not exist", ModuleName, i)) end
end local note = strtrim(args[3] or "")
if Heimdall_Data.config.noter.debug then print(string.format("[%s] Note: %s", ModuleName, note)) end
if note == "delete" then
DeleteNotes(name, args)
elseif string.find(note, "^[%d%.]*$") then
PrintNotes(channelname, name, args)
else else
if Heimdall_Data.config.noter.debug then AddNote(name, sender, args)
print(string.format("[%s] Printing note %s at index %s", ModuleName, name, i))
shared.dump(Heimdall_Data.config.notes[name][i])
end
PrintNote(channel, i, Heimdall_Data.config.notes[name][i])
end end
end end
end end)
end
---@param name string print(string.format("[%s] Module initialized", ModuleName))
---@param sender string end,
---@param args string[] }
local function AddNote(name, sender, args)
if not Heimdall_Data.config.notes[name] then Heimdall_Data.config.notes[name] = {} end
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Adding note for: %s from: %s", ModuleName, name, sender))
shared.dump(args)
end
local msgparts = {}
for i = 3, #args do
msgparts[#msgparts + 1] = args[i]
end
local msg = table.concat(msgparts, " ")
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Adding note for: %s from: %s", ModuleName, name, sender))
print(string.format("[%s] Note: %s", ModuleName, msg))
end
local note = {
source = sender,
date = date("%Y-%m-%dT%H:%M:%S"),
note = msg,
}
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Adding note", ModuleName))
shared.dump(note)
end
table.insert(Heimdall_Data.config.notes[name], note)
end
-- Here's the plan:
-- Implement a "note" command, that will do everything
-- Saying "note <name> <note>" will add a note to the list for the character
-- Saying "note <name>" will list last N notes
-- Saying "note <name> i" will list the i-th note
-- Saying "note <name> i..j" will list notes from i to j
-- Saying "note <name> delete i" will delete the i-th note
-- Saying "note <name> delete i..j" will delete notes from i to j
local noterChannelFrame = CreateFrame("Frame")
noterChannelFrame:RegisterEvent("CHAT_MSG_CHANNEL")
noterChannelFrame:SetScript("OnEvent", function(self, event, msg, sender, ...)
--if Heimdall_Data.config.noter.debug then
-- print(string.format("[%s] Event received", ModuleName))
-- shared.dump(Heimdall_Data.config.noter)
--end
if not Heimdall_Data.config.noter.enabled then
--if Heimdall_Data.config.noter.debug then
-- print(string.format("[%s] Module disabled, ignoring event", ModuleName))
--end
return
end
local channelId = select(6, ...)
local _, channelname = GetChannelName(channelId)
local ok = false
for _, channel in pairs(Heimdall_Data.config.noter.channels) do
if channelname == channel then
ok = true
break
end
end
if not ok then
--if Heimdall_Data.config.noter.debug then
-- print(string.format("[%s] Channel %s does not match the master channel %s", ModuleName, channelname, Heimdall_Data.config.noter.masterChannel))
--end
return
end
sender = string.match(sender, "^[^-]+")
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Message from: %s", ModuleName, sender))
shared.dump(Heimdall_Data.config.noter)
end
if not msg or msg == "" then
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Empty message, ignoring", ModuleName))
end
return
end
local args = { strsplit(" ", msg) }
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Arguments received: %s", ModuleName, table.concat(args, ", ")))
shared.dump(args)
end
local command = args[1]
if command == "note" then
local name = strtrim(string.lower(args[2] or ""))
if Heimdall_Data.config.noter.debug then
print(string.format("[%s] Note command received for: %s", ModuleName, name))
end
local note = strtrim(args[3] or "")
if Heimdall_Data.config.noter.debug then print(string.format("[%s] Note: %s", ModuleName, note)) end
if note == "delete" then
DeleteNotes(name, args)
elseif string.find(note, "^[%d%.]*$") then
PrintNotes(channelname, name, args)
else
AddNote(name, sender, args)
end
end
end)
print("[Heimdall] Commander module loaded")
end

View File

@@ -2,84 +2,91 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "Sniffer" local ModuleName = "Sniffer"
---@diagnostic disable-next-line: missing-fields ---@class HeimdallSnifferConfig
shared.Sniffer = {} ---@field enabled boolean
function shared.Sniffer.Init() ---@field debug boolean
if Heimdall_Data.config.sniffer.debug then print(string.format("[%s] Module initializing", ModuleName)) end ---@field channels string[]
local smellThrottle = {} ---@field throttle number -- throttleTime in the original code, matching config name now
local SmellStinky = function(stinky) ---@field zoneOverride string?
if Heimdall_Data.config.sniffer.debug then ---@field stinky boolean
print(string.format("%s: SmellStinky", ModuleName))
shared.dump(Heimdall_Data.config.sniffer)
end
if not Heimdall_Data.config.sniffer.enabled then return end
if Heimdall_Data.config.sniffer.stinky and not shared.IsStinky(stinky) then
if Heimdall_Data.config.sniffer.debug then
print(string.format("%s: Stinky not found in config", ModuleName))
end
return
end
local now = GetTime()
local throttle = smellThrottle[stinky] or 0
if now - throttle < Heimdall_Data.config.sniffer.throttle then
if Heimdall_Data.config.sniffer.debug then print(string.format("%s: Throttled", ModuleName)) end
return
end
smellThrottle[stinky] = now
for _, channel in pairs(Heimdall_Data.config.sniffer.channels) do ---@class Sniffer
local locale = shared.GetLocaleForChannel(channel) shared.Sniffer = {
local text = string.format(shared._L("snifferStinky", locale), stinky) Init = function()
---@type Message if Heimdall_Data.config.sniffer.debug then print(string.format("[%s] Module initializing", ModuleName)) end
local msg = { local smellThrottle = {}
channel = "C", local SmellStinky = function(stinky)
data = channel,
message = text,
}
if Heimdall_Data.config.sniffer.debug then if Heimdall_Data.config.sniffer.debug then
print(string.format("[%s] Queuing sniffer message", ModuleName)) print(string.format("%s: SmellStinky", ModuleName))
shared.dump(msg) shared.dump(Heimdall_Data.config.sniffer)
end end
table.insert(shared.messenger.queue, msg) if not Heimdall_Data.config.sniffer.enabled then return end
end if Heimdall_Data.config.sniffer.stinky and not shared.IsStinky(stinky) then
end if Heimdall_Data.config.sniffer.debug then
print(string.format("%s: Stinky not found in config", ModuleName))
end
return
end
if smellThrottle[stinky] and GetTime() - smellThrottle[stinky] < Heimdall_Data.config.sniffer.throttle then
if Heimdall_Data.config.sniffer.debug then print(string.format("%s: Throttled", ModuleName)) end
return
end
smellThrottle[stinky] = GetTime()
local cleuFrame = CreateFrame("Frame") for _, channel in pairs(Heimdall_Data.config.sniffer.channels) do
cleuFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") local locale = shared.GetLocaleForChannel(channel)
cleuFrame:SetScript("OnEvent", function(self, event, ...) local text = string.format(shared._L("snifferStinky", locale), stinky)
if Heimdall_Data.config.sniffer.debug then ---@type Message
print(string.format("[%s] Received event: %s", ModuleName, event)) local msg = {
end channel = "C",
if not Heimdall_Data.config.sniffer.enabled then data = channel,
if Heimdall_Data.config.sniffer.debug then message = text,
print(string.format("[%s] Module disabled, ignoring event", ModuleName)) }
if Heimdall_Data.config.sniffer.debug then
print(string.format("[%s] Queuing sniffer message", ModuleName))
shared.dump(msg)
end
table.insert(shared.messenger.queue, msg)
end end
return
end end
local source, destination, err
source, err = CLEUParser.GetSourceName(...) local cleuFrame = CreateFrame("Frame")
if Heimdall_Data.config.sniffer.debug then cleuFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
print(string.format("[%s] Processing source: %s", ModuleName, source)) cleuFrame:SetScript("OnEvent", function(self, event, ...)
end
if err then
if Heimdall_Data.config.sniffer.debug then if Heimdall_Data.config.sniffer.debug then
print(string.format("[%s] Error parsing source: %s", ModuleName, err)) print(string.format("[%s] Received event: %s", ModuleName, event))
end end
return if not Heimdall_Data.config.sniffer.enabled then
end if Heimdall_Data.config.sniffer.debug then
SmellStinky(source) print(string.format("[%s] Module disabled, ignoring event", ModuleName))
destination, err = CLEUParser.GetDestName(...) end
if Heimdall_Data.config.sniffer.debug then return
print(string.format("[%s] Processing destination: %s", ModuleName, destination)) end
end local source, destination, err
if err then source, err = CLEUParser.GetSourceName(...)
if Heimdall_Data.config.sniffer.debug then if Heimdall_Data.config.sniffer.debug then
print(string.format("[%s] Error parsing destination: %s", ModuleName, err)) print(string.format("[%s] Processing source: %s", ModuleName, source))
end end
return if err then
end if Heimdall_Data.config.sniffer.debug then
SmellStinky(destination) print(string.format("[%s] Error parsing source: %s", ModuleName, err))
end) end
if Heimdall_Data.config.sniffer.debug then print(string.format("[%s] Module initialized", ModuleName)) end return
print("[Heimdall] Sniffer loaded") end
end SmellStinky(source)
destination, err = CLEUParser.GetDestName(...)
if Heimdall_Data.config.sniffer.debug then
print(string.format("[%s] Processing destination: %s", ModuleName, destination))
end
if err then
if Heimdall_Data.config.sniffer.debug then
print(string.format("[%s] Error parsing destination: %s", ModuleName, err))
end
return
end
SmellStinky(destination)
end)
if Heimdall_Data.config.sniffer.debug then print(string.format("[%s] Module initialized", ModuleName)) end
print(string.format("[%s] Module initialized", ModuleName))
end,
}

View File

@@ -2,223 +2,237 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "Spotter" local ModuleName = "Spotter"
---@diagnostic disable-next-line: missing-fields ---@class HeimdallSpotterConfig
shared.Spotter = {} ---@field enabled boolean
function shared.Spotter.Init() ---@field debug boolean
local function FormatHP(hp) ---@field everyone boolean
if hp > 1e9 then ---@field hostile boolean
return string.format("%.1fB", hp / 1e9) ---@field alliance boolean
elseif hp > 1e6 then ---@field stinky boolean
return string.format("%.1fM", hp / 1e6) ---@field channels string[]
elseif hp > 1e3 then ---@field zoneOverride string?
return string.format("%.1fK", hp / 1e3) ---@field throttleTime number
else
return hp
end
end
---@type table<string, number> ---@class Spotter
local throttleTable = {} shared.Spotter = {
Init = function()
---@param unit string local function FormatHP(hp)
---@param name string if hp > 1e9 then
---@param faction string return string.format("%.1fB", hp / 1e9)
---@param hostile boolean elseif hp > 1e6 then
---@return boolean return string.format("%.1fM", hp / 1e6)
---@return string? error elseif hp > 1e3 then
local function ShouldNotify(unit, name, faction, hostile) return string.format("%.1fK", hp / 1e3)
if Heimdall_Data.config.spotter.debug then else
print(string.format("[%s] Checking notification criteria for %s (%s)", ModuleName, name, faction)) return hp
end
end end
if Heimdall_Data.config.agents[name] then ---@type table<string, number>
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 Heimdall_Data.config.spotter.debug then if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Skipping agent: %s", ModuleName, name)) print(string.format("[%s] Checking notification criteria for %s (%s)", ModuleName, name, faction))
end end
return false
end
if Heimdall_Data.config.spotter.stinky then if shared.AgentTracker.IsAgent(name) then
if shared.IsStinky(name) then
if Heimdall_Data.config.spotter.debug then if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Notifying - Found stinky: %s", ModuleName, name)) print(string.format("[%s] Skipping agent: %s", ModuleName, name))
end end
return true return false
end end
end
if Heimdall_Data.config.spotter.alliance then if Heimdall_Data.config.spotter.stinky then
if faction == "Alliance" then if shared.IsStinky(name) then
if Heimdall_Data.config.spotter.debug then if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Notifying - Found Alliance player: %s", ModuleName, name)) print(string.format("[%s] Notifying - Found stinky: %s", ModuleName, name))
end
return true
end end
return true
end end
end
if Heimdall_Data.config.spotter.hostile then if Heimdall_Data.config.spotter.alliance then
if hostile then if faction == "Alliance" then
if Heimdall_Data.config.spotter.debug then if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Notifying - Found hostile player: %s", ModuleName, name)) print(string.format("[%s] Notifying - Found Alliance player: %s", ModuleName, name))
end
return true
end end
return true
end end
end
if Heimdall_Data.config.spotter.debug then if Heimdall_Data.config.spotter.hostile then
print( if hostile then
string.format( if Heimdall_Data.config.spotter.debug then
"[%s] Using everyone setting: %s", print(string.format("[%s] Notifying - Found hostile player: %s", ModuleName, name))
ModuleName, end
tostring(Heimdall_Data.config.spotter.everyone) return true
end
end
if Heimdall_Data.config.spotter.debug then
print(
string.format(
"[%s] Using everyone setting: %s",
ModuleName,
tostring(Heimdall_Data.config.spotter.everyone)
)
) )
)
end
return Heimdall_Data.config.spotter.everyone
end
---@param unit string
---@return string?
local function NotifySpotted(unit)
if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Processing spotted unit: %s", ModuleName, unit))
end
if not unit then return string.format("Could not find unit %s", tostring(unit)) end
if not UnitIsPlayer(unit) then
if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Ignoring non-player unit: %s", ModuleName, unit))
end end
return nil return Heimdall_Data.config.spotter.everyone
end end
local name = UnitName(unit) ---@param unit string
if not name then return string.format("Could not find name for unit %s", tostring(unit)) end ---@return string?
if Heimdall_Data.config.spotter.debug then local function NotifySpotted(unit)
print(string.format("[%s] Processing player: %s", ModuleName, name))
end
local time = GetTime()
if throttleTable[name] and time - throttleTable[name] < Heimdall_Data.config.spotter.throttleTime then
if Heimdall_Data.config.spotter.debug then if Heimdall_Data.config.spotter.debug then
local remainingTime = Heimdall_Data.config.spotter.throttleTime - (time - throttleTable[name]) print(string.format("[%s] Processing spotted unit: %s", ModuleName, unit))
print(string.format("[%s] Player %s throttled for %.1f more seconds", ModuleName, name, remainingTime))
end end
return string.format("Throttled %s", tostring(name))
end
throttleTable[name] = time
local race = UnitRace(unit) if not unit then return string.format("Could not find unit %s", tostring(unit)) end
if not race then return string.format("Could not find race for unit %s", tostring(unit)) end if not UnitIsPlayer(unit) then
local faction = shared.raceMap[race] if Heimdall_Data.config.spotter.debug then
if not faction then return string.format("Could not find faction for race %s", tostring(race)) end print(string.format("[%s] Ignoring non-player unit: %s", ModuleName, unit))
if Heimdall_Data.config.spotter.debug then end
print(string.format("[%s] Player %s is %s (%s)", ModuleName, name, race, faction)) return nil
end
local hostile = UnitCanAttack("player", unit)
if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Player %s is %s", ModuleName, name, hostile and "hostile" or "friendly"))
end
local doNotify = ShouldNotify(unit, name, faction, hostile)
if not doNotify then
if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Skipping notification for %s", ModuleName, name))
end end
return string.format("Not notifying for %s", tostring(name))
end
local hp = UnitHealth(unit) local name = UnitName(unit)
if not hp then return string.format("Could not find hp for unit %s", tostring(unit)) end if not name then return string.format("Could not find name 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
if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Player %s health: %s/%s", ModuleName, name, FormatHP(hp), FormatHP(maxHp)))
end
local class = UnitClass(unit)
if not class then return string.format("Could not find class for unit %s", tostring(unit)) end
local zone, subzone = GetZoneText() or "Unknown", GetSubZoneText() or "Unknown"
if Heimdall_Data.config.spotter.zoneOverride then
zone = Heimdall_Data.config.spotter.zoneOverride or ""
subzone = ""
end
local x, y = GetPlayerMapPosition("player")
if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Player %s coordinates: %.2f, %.2f", ModuleName, name, x * 100, y * 100))
end
local pvpOn = UnitIsPVP(unit)
local stinky = shared.IsStinky(name) or false
SetMapToCurrentZone()
SetMapByID(GetCurrentMapAreaID())
local areaId = tostring(GetCurrentMapAreaID())
for _, channel in pairs(Heimdall_Data.config.spotter.channels) do
if Heimdall_Data.config.spotter.debug then if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Processing channel: %s", ModuleName, channel)) print(string.format("[%s] Processing player: %s", ModuleName, name))
end end
local locale = shared.GetLocaleForChannel(channel)
local text = string.format(
shared._L("spotterSpotted", locale),
hostile and shared._L("hostile", locale) or shared._L("friendly", locale),
name,
shared._L(class, locale),
stinky and string.format("(%s)", "!!!!") or "",
shared._L(race, locale),
shared._L(faction, locale),
pvpOn and shared._L("pvpOn", locale) or shared._L("pvpOff", locale),
string.gsub(FormatHP(hp), "M", "kk"),
string.gsub(FormatHP(maxHp), "M", "kk"),
shared._L(zone, locale),
shared._L(subzone, locale),
areaId,
x * 100,
y * 100
)
---@type Message local time = GetTime()
local msg = { if throttleTable[name] and time - throttleTable[name] < Heimdall_Data.config.spotter.throttleTime then
channel = "C", if Heimdall_Data.config.spotter.debug then
data = channel, local remainingTime = Heimdall_Data.config.spotter.throttleTime - (time - throttleTable[name])
message = text, print(
} string.format("[%s] Player %s throttled for %.1f more seconds", ModuleName, name, remainingTime)
if Heimdall_Data.config.spotter.debug then )
print(string.format("[%s] Queuing spotter message", ModuleName)) end
shared.dump(msg) return string.format("Throttled %s", tostring(name))
end end
table.insert(shared.messenger.queue, msg) throttleTable[name] = time
end
end
local frame = CreateFrame("Frame") local race = UnitRace(unit)
frame:RegisterEvent("NAME_PLATE_UNIT_ADDED") if not race then return string.format("Could not find race for unit %s", tostring(unit)) end
frame:RegisterEvent("UNIT_TARGET") local faction = shared.raceMap[race]
frame:SetScript("OnEvent", function(self, event, unit) if not faction then return string.format("Could not find faction for race %s", tostring(race)) end
if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Event received: %s for unit: %s", ModuleName, event, unit or "target"))
end
if not Heimdall_Data.config.spotter.enabled then
if Heimdall_Data.config.spotter.debug then if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Module disabled, ignoring event", ModuleName)) print(string.format("[%s] Player %s is %s (%s)", ModuleName, name, race, faction))
end end
return
end
if event == "UNIT_TARGET" then unit = "target" end local hostile = UnitCanAttack("player", unit)
local err = NotifySpotted(unit)
if err then
if Heimdall_Data.config.spotter.debug then if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Error processing unit %s: %s", ModuleName, unit, err)) print(string.format("[%s] Player %s is %s", ModuleName, name, hostile and "hostile" or "friendly"))
end
local doNotify = ShouldNotify(unit, name, faction, hostile)
if not doNotify then
if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Skipping notification for %s", ModuleName, name))
end
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
if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Player %s health: %s/%s", ModuleName, name, FormatHP(hp), FormatHP(maxHp)))
end
local class = UnitClass(unit)
if not class then return string.format("Could not find class for unit %s", tostring(unit)) end
local zone, subzone = GetZoneText() or "Unknown", GetSubZoneText() or "Unknown"
if Heimdall_Data.config.spotter.zoneOverride then
zone = Heimdall_Data.config.spotter.zoneOverride or ""
subzone = ""
end
local x, y = GetPlayerMapPosition("player")
if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Player %s coordinates: %.2f, %.2f", ModuleName, name, x * 100, y * 100))
end
local pvpOn = UnitIsPVP(unit)
local stinky = shared.IsStinky(name) or false
SetMapToCurrentZone()
SetMapByID(GetCurrentMapAreaID())
local areaId = tostring(GetCurrentMapAreaID())
for _, channel in pairs(Heimdall_Data.config.spotter.channels) do
if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Processing channel: %s", ModuleName, channel))
end
local locale = shared.GetLocaleForChannel(channel)
local text = string.format(
shared._L("spotterSpotted", locale),
hostile and shared._L("hostile", locale) or shared._L("friendly", locale),
name,
shared._L(class, locale),
stinky and string.format("(%s)", "!!!!") or "",
shared._L(race, locale),
shared._L(faction, locale),
pvpOn and shared._L("pvpOn", locale) or shared._L("pvpOff", locale),
string.gsub(FormatHP(hp), "M", "kk"),
string.gsub(FormatHP(maxHp), "M", "kk"),
shared._L(zone, locale),
shared._L(subzone, locale),
areaId,
x * 100,
y * 100
)
---@type Message
local msg = {
channel = "C",
data = channel,
message = text,
}
if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Queuing spotter message", ModuleName))
shared.dump(msg)
end
table.insert(shared.messenger.queue, msg)
end end
end end
end)
if Heimdall_Data.config.spotter.debug then print(string.format("[%s] Module initialized", ModuleName)) end local frame = CreateFrame("Frame")
print("[Heimdall] Spotter loaded") frame:RegisterEvent("NAME_PLATE_UNIT_ADDED")
end frame:RegisterEvent("UNIT_TARGET")
frame:SetScript("OnEvent", function(self, event, unit)
if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Event received: %s for unit: %s", ModuleName, event, unit or "target"))
end
if not Heimdall_Data.config.spotter.enabled then
if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Module disabled, ignoring event", ModuleName))
end
return
end
if event == "UNIT_TARGET" then unit = "target" end
local err = NotifySpotted(unit)
if err then
if Heimdall_Data.config.spotter.debug then
print(string.format("[%s] Error processing unit %s: %s", ModuleName, unit, err))
end
end
end)
if Heimdall_Data.config.spotter.debug then print(string.format("[%s] Module initialized", ModuleName)) end
print(string.format("[%s] Module initialized", ModuleName))
end,
}

View File

@@ -1,72 +1,83 @@
local addonname, shared = ... local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
---@cast addonname string
local ModuleName = "StinkyCache" local ModuleName = "StinkyCache"
---@diagnostic disable-next-line: missing-fields ---@class HeimdallStinkyCacheConfig
shared.StinkyCache = {} ---@field enabled boolean
function shared.StinkyCache.Init() ---@field debug boolean
shared.stinkyCache = { ---@field commander string
stinkies = {}, ---@field ttl number
}
---@param name string ---@class HeimdallStinkyCacheData
local function AskCommander(name) ---@field stinkies table<string, {value: number, timestamp: number}>
if Heimdall_Data.config.stinkyCache.debug then
print(
string.format(
"[%s] Asking commander %s about %s",
ModuleName,
Heimdall_Data.config.stinkyCache.commander,
name
)
)
end
local messageParts = { "isstinky", name }
local message = table.concat(messageParts, "|")
SendAddonMessage(
Heimdall_Data.config.addonPrefix,
message,
"WHISPER",
Heimdall_Data.config.stinkyCache.commander
)
return
end
local addonMessageFrame = CreateFrame("Frame") ---@class StinkyCache
addonMessageFrame:RegisterEvent("CHAT_MSG_ADDON") shared.StinkyCache = {
addonMessageFrame:SetScript("OnEvent", function(self, event, msg, sender, ...) Init = function()
if sender == Heimdall_Data.config.stinkyCache.commander then shared.stinkyCache = {
stinkies = {},
}
---@param name string
local function AskCommander(name)
if Heimdall_Data.config.stinkyCache.debug then if Heimdall_Data.config.stinkyCache.debug then
print( print(
string.format( string.format(
"[%s] Received stinky from commander %s: %s", "[%s] Asking commander %s about %s",
ModuleName, ModuleName,
Heimdall_Data.config.stinkyCache.commander, Heimdall_Data.config.stinkyCache.commander,
msg name
) )
) )
end end
local parts = { strsplit("|", msg) } local messageParts = { "isstinky", name }
local name, value = parts[1], parts[2] local message = table.concat(messageParts, "|")
shared.stinkyCache.stinkies[name] = { value = value, timestamp = time() } SendAddonMessage(
else Heimdall_Data.config.addonPrefix,
if Heimdall_Data.config.stinkyCache.debug then message,
print(string.format("[%s] Received stinky from non-commander %s: %s", ModuleName, sender, msg)) "WHISPER",
end Heimdall_Data.config.stinkyCache.commander
local parts = { strsplit("|", msg) } )
local command, name = parts[1], parts[2] return
if parts[1] == "isstinky" then local res = Heimdall_Data.config.stinkies[parts[2]] end
end end
end)
setmetatable(shared.stinkyCache.stinkies, { local addonMessageFrame = CreateFrame("Frame")
__index = function(self, key) addonMessageFrame:RegisterEvent("CHAT_MSG_ADDON")
local value = rawget(self, key) addonMessageFrame:SetScript("OnEvent", function(self, event, msg, sender, ...)
local now = GetTime() if sender == Heimdall_Data.config.stinkyCache.commander then
if value == nil or now - value.timestamp > Heimdall_Data.config.stinkyCache.ttl then AskCommander(key) end if Heimdall_Data.config.stinkyCache.debug then
return rawget(self, key) print(
end, string.format(
}) "[%s] Received stinky from commander %s: %s",
print("[Heimdall] StinkyCache module loaded") ModuleName,
end Heimdall_Data.config.stinkyCache.commander,
msg
)
)
end
local parts = { strsplit("|", msg) }
local name, value = parts[1], parts[2]
shared.stinkyCache.stinkies[name] = { value = value, timestamp = time() }
else
if Heimdall_Data.config.stinkyCache.debug then
print(string.format("[%s] Received stinky from non-commander %s: %s", ModuleName, sender, msg))
end
local parts = { strsplit("|", msg) }
local command, name = parts[1], parts[2]
if parts[1] == "isstinky" then local res = Heimdall_Data.config.stinkies[parts[2]] end
end
end)
setmetatable(shared.stinkyCache.stinkies, {
__index = function(self, key)
local value = rawget(self, key)
local now = GetTime()
if value == nil or now - value.timestamp > Heimdall_Data.config.stinkyCache.ttl then
AskCommander(key)
end
return rawget(self, key)
end,
})
print(string.format("[%s] Module initialized", ModuleName))
end,
}

View File

@@ -2,276 +2,363 @@ local _, shared = ...
---@cast shared HeimdallShared ---@cast shared HeimdallShared
local ModuleName = "StinkyTracker" local ModuleName = "StinkyTracker"
---@diagnostic disable-next-line: missing-fields ---@class Stinky
shared.StinkyTracker = {} ---@field name string
function shared.StinkyTracker.Init() ---@field class string
shared.stinkyTracker = { ---@field seenAt number
stinkies = ReactiveValue.new({}), ---@field hostile boolean
}
local whoRegex = "([^ -/]+)-?%w*/(%w+)" ---@class HeimdallStinkyTrackerConfig
---@param msg string ---@field enabled boolean
---@return table<string, stinky> ---@field debug boolean
local function ParseWho(msg) ---@field ignoredTimeout number
---@field channels string[]
---@class StinkyTrackerData
---@field stinkies ReactiveValue<table<string, Stinky>>
---@field ignored ReactiveValue<table<string, number>>
---@class StinkyTracker
shared.StinkyTracker = {
---@param stinky Stinky
---@return boolean
Track = function(stinky)
if Heimdall_Data.config.stinkyTracker.debug then if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Parsing WHO message: '%s'", ModuleName, msg)) print(string.format("[%s] Request to track stinky: %s (%s)", ModuleName, stinky.name, stinky.class))
end end
local stinkies = {} local ignored = shared.stinkyTracker.ignored[stinky.name]
for name, class in string.gmatch(msg, whoRegex) do -- TODO: Add a config option for the ignored timeout
stinkies[name] = { if ignored and ignored > GetTime() - 60 then
name = name,
class = class,
seenAt = GetTime(),
hostile = true,
}
if Heimdall_Data.config.stinkyTracker.debug then if Heimdall_Data.config.stinkyTracker.debug then
print( print(
string.format( string.format(
"[%s] Found hostile player: %s (%s) at %s", "[%s] Stinky is ignored, not tracking: %s (%s)",
ModuleName,
stinky.name,
stinky.class
)
)
shared.dump(shared.stinkyTracker.ignored:get())
shared.dump(shared.stinkyTracker.stinkies:get())
end
return false
else
-- Timed out or was never ignored
shared.stinkyTracker.stinkies[stinky.name] = nil
end
shared.stinkyTracker.stinkies[stinky.name] = stinky
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Stinky is now tracked: %s (%s)", ModuleName, stinky.name, stinky.class))
shared.dump(shared.stinkyTracker.stinkies:get())
shared.dump(shared.stinkyTracker.ignored:get())
end
return true
end,
---@param name string
---@return nil
Ignore = function(name)
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Request to ignore stinky: %s", ModuleName, name))
end
shared.stinkyTracker.ignored[name] = GetTime()
shared.stinkyTracker.stinkies[name] = nil
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Stinky is now ignored: %s", ModuleName, name))
shared.dump(shared.stinkyTracker.ignored:get())
shared.dump(shared.stinkyTracker.stinkies:get())
end
end,
---@param name string
---@return boolean
IsStinky = function(name)
if not shared.stinkyTracker.stinkies then return false end
if not shared.stinkyTracker.stinkies[name] then return false end
if shared.stinkyTracker.ignored[name] then return false end
return true
end,
---@param callback fun(stinkies: table<string, Stinky>)
---@return nil
OnChange = function(callback) shared.stinkyTracker.stinkies:onChange(callback) end,
---@param callback fun(name: string, stinky: Stinky)
---@return nil
ForEach = function(callback)
---@type table<string, Stinky>
local stinkies = shared.stinkyTracker.stinkies:get()
for name, stinky in pairs(stinkies) do
callback(name, stinky)
end
end,
Init = function()
shared.stinkyTracker = {
stinkies = ReactiveValue.new({}),
ignored = ReactiveValue.new({}),
}
local whoRegex = "([^ -/]+)-?%w*/(%w+)"
---@param msg string
---@return table<string, Stinky>
local function ParseWho(msg)
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Parsing WHO message: '%s'", ModuleName, msg))
end
local stinkies = {}
for name, class in string.gmatch(msg, whoRegex) do
stinkies[name] = {
name = name,
class = class,
seenAt = GetTime(),
hostile = true,
}
if Heimdall_Data.config.stinkyTracker.debug then
print(
string.format(
"[%s] Found hostile player: %s (%s) at %s",
ModuleName,
name,
class,
date("%H:%M:%S", time())
)
)
shared.dump(stinkies)
end
end
return stinkies
end
local seeRegex = "I see %((%w+)%) ([^ -/]+)-?%w*/(%w+)"
---@param msg string
---@return table<string, Stinky>
local function ParseSee(msg)
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Parsing SEE message: '%s'", ModuleName, msg))
end
local stinkies = {}
local aggression, name, class = string.match(msg, seeRegex)
if not name or not class then
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Error: Invalid SEE message format", ModuleName))
end
return stinkies
end
local stinky = {
name = name,
class = class,
seenAt = GetTime(),
hostile = aggression == "hostile",
}
stinkies[name] = stinky
if Heimdall_Data.config.stinkyTracker.debug then
print(
string.format(
"[%s] Found stinky in SEE: %s (%s) - %s at %s",
ModuleName, ModuleName,
name, name,
class, class,
aggression,
date("%H:%M:%S", time()) date("%H:%M:%S", time())
) )
) )
shared.dump(stinkies) shared.dump(stinkies)
end end
end
return stinkies
end
local seeRegex = "I see %((%w+)%) ([^ -/]+)-?%w*/(%w+)"
---@param msg string
---@return table<string, stinky>
local function ParseSee(msg)
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Parsing SEE message: '%s'", ModuleName, msg))
end
local stinkies = {}
local aggression, name, class = string.match(msg, seeRegex)
if not name or not class then
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Error: Invalid SEE message format", ModuleName))
end
return stinkies return stinkies
end end
local stinky = {
name = name,
class = class,
seenAt = GetTime(),
hostile = aggression == "hostile",
}
stinkies[name] = stinky
if Heimdall_Data.config.stinkyTracker.debug then
print(
string.format(
"[%s] Found stinky in SEE: %s (%s) - %s at %s",
ModuleName,
name,
class,
aggression,
date("%H:%M:%S", time())
)
)
shared.dump(stinkies)
end
return stinkies
end
local arrivedRegex = "([^ -/]+)-?%w*; c:([^;]+)" local arrivedRegex = "([^ -/]+)-?%w*; c:([^;]+)"
local arrivedRegexAlt = "([^ -/]+)-?%w*%(!!!!%); c:([^;]+)" local arrivedRegexAlt = "([^ -/]+)-?%w*%(!!!!%); c:([^;]+)"
---@param msg string ---@param msg string
---@return table<string, stinky> ---@return table<string, Stinky>
local function ParseArrived(msg) local function ParseArrived(msg)
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("%s: Parsing arrived message: %s", ModuleName, msg))
end
local stinkies = {}
local name, class = string.match(msg, arrivedRegex)
if not name or not class then
name, class = string.match(msg, arrivedRegexAlt)
end
if not name or not class then
if Heimdall_Data.config.stinkyTracker.debug then if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("%s: No valid stinky found in arrived message", ModuleName)) print(string.format("%s: Parsing arrived message: %s", ModuleName, msg))
end end
return stinkies local stinkies = {}
end local name, class = string.match(msg, arrivedRegex)
local stinky = { if not name or not class then
name = name, name, class = string.match(msg, arrivedRegexAlt)
class = class,
seenAt = GetTime(),
hostile = true,
}
stinkies[name] = stinky
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("%s: Found stinky in arrived: %s/%s", ModuleName, name, class))
shared.dump(stinkies)
end
return stinkies
end
local frame = CreateFrame("Frame")
frame:RegisterEvent("CHAT_MSG_CHANNEL")
frame:SetScript("OnEvent", function(self, event, msg, sender, ...)
--if Heimdall_Data.config.stinkyTracker.debug then
-- print(string.format("[%s] Event received: %s from %s", ModuleName, event, sender))
--end
if not Heimdall_Data.config.stinkyTracker.enabled then
--if Heimdall_Data.config.stinkyTracker.debug then
-- print(string.format("[%s] Module disabled, ignoring event", ModuleName))
--end
return
end
local channelId = select(6, ...)
local _, channelname = GetChannelName(channelId)
local ok = false
for _, channel in pairs(Heimdall_Data.config.stinkyTracker.channels) do
if channel == channelname then
ok = true
break
end end
end if not name or not class then
if not ok then
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Ignoring message from non-master channel: %s", ModuleName, channelname))
end
return
end
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Processing message from master channel: %s", ModuleName, sender))
shared.dump(Heimdall_Data.config.stinkyTracker)
end
if string.find(msg, "^who:") then
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Processing WHO message from %s", ModuleName, sender))
end
local whoStinkies = ParseWho(msg)
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Found stinkies in WHO message", ModuleName))
end
for name, stinky in pairs(whoStinkies) do
if stinky.hostile then
shared.stinkyTracker.stinkies[name] = stinky
if Heimdall_Data.config.stinkyTracker.debug then
print(
string.format("[%s] Added hostile stinky from WHO: %s (%s)", ModuleName, name, stinky.class)
)
end
end
end
end
if string.find(msg, "^I see") then
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Processing SEE message from %s", ModuleName, sender))
end
local seeStinkies = ParseSee(msg)
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Found stinkies in SEE message", ModuleName))
end
for name, stinky in pairs(seeStinkies) do
if stinky.hostile then
shared.stinkyTracker.stinkies[name] = stinky
if Heimdall_Data.config.stinkyTracker.debug then
print(
string.format("[%s] Added hostile stinky from SEE: %s (%s)", ModuleName, name, stinky.class)
)
end
end
if not stinky.hostile then
shared.stinkyTracker.stinkies[name] = nil
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Removed non-hostile stinky from SEE: %s", ModuleName, name))
end
end
end
end
if string.find(msg, "arrived to") or string.find(msg, "moved to") then
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Processing ARRIVED message from %s", ModuleName, sender))
end
local arrivedStinkies = ParseArrived(msg)
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Found stinkies in ARRIVED message", ModuleName))
end
for name, stinky in pairs(arrivedStinkies) do
shared.stinkyTracker.stinkies[name] = stinky
if Heimdall_Data.config.stinkyTracker.debug then if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Added stinky from ARRIVED: %s (%s)", ModuleName, name, stinky.class)) print(string.format("%s: No valid stinky found in arrived message", ModuleName))
end end
return stinkies
end end
end local stinky = {
-- Log total stinky count after processing
if Heimdall_Data.config.stinkyTracker.debug then
local count = 0
for _ in pairs(shared.stinkyTracker.stinkies:get()) do
count = count + 1
end
print(string.format("[%s] Current total stinkies tracked: %d", ModuleName, count))
end
for name, stinky in pairs(shared.stinkyTracker.stinkies) do
if Heimdall_Data.config.agents[name] then
shared.stinkyTracker.stinkies[name] = nil
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Removed agent from stinkies: %s", ModuleName, name))
end
end
end
end)
local targetFrame = CreateFrame("Frame")
targetFrame:RegisterEvent("UNIT_TARGET")
targetFrame:SetScript("OnEvent", function(self, event, unit)
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Event received: %s for unit: %s", ModuleName, event, unit or "target"))
end
unit = "target"
if not Heimdall_Data.config.stinkyTracker.enabled then
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Module disabled, ignoring event", ModuleName))
end
return
end
local name = UnitName(unit)
if not UnitIsPlayer(unit) then
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Target %s is not a player, nothing to do", ModuleName, name))
end
return
end
local enemy = UnitCanAttack("player", unit)
if enemy then
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Target %s is enemy - tracking as stinky", ModuleName, name))
end
shared.stinkyTracker.stinkies[name] = {
name = name, name = name,
class = UnitClass(unit), class = class,
seenAt = GetTime(), seenAt = GetTime(),
hostile = true, hostile = true,
} }
return stinkies[name] = stinky
end
if not shared.stinkyTracker.stinkies[name] then
if Heimdall_Data.config.stinkyTracker.debug then if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Target %s is friendly and not stinky, nothing to do", ModuleName, name)) print(string.format("%s: Found stinky in arrived: %s/%s", ModuleName, name, class))
shared.dump(stinkies)
end end
return return stinkies
end end
if Heimdall_Data.config.stinkyTracker.debug then local frame = CreateFrame("Frame")
print(string.format("[%s] Target %s is friendly and stinky - removing from stinkies", ModuleName, name)) frame:RegisterEvent("CHAT_MSG_CHANNEL")
end frame:SetScript("OnEvent", function(self, event, msg, sender, ...)
shared.stinkyTracker.stinkies[name] = nil --if Heimdall_Data.config.stinkyTracker.debug then
end) -- print(string.format("[%s] Event received: %s from %s", ModuleName, event, sender))
--end
if not Heimdall_Data.config.stinkyTracker.enabled then
--if Heimdall_Data.config.stinkyTracker.debug then
-- print(string.format("[%s] Module disabled, ignoring event", ModuleName))
--end
return
end
local channelId = select(6, ...)
local _, channelname = GetChannelName(channelId)
local ok = false
for _, channel in pairs(Heimdall_Data.config.stinkyTracker.channels) do
if channel == channelname then
ok = true
break
end
end
if not ok then
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Ignoring message from non-master channel: %s", ModuleName, channelname))
end
return
end
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Processing message from master channel: %s", ModuleName, sender))
shared.dump(Heimdall_Data.config.stinkyTracker)
end
if Heimdall_Data.config.stinkyTracker.debug then print(string.format("[%s] Module initialized", ModuleName)) end local stinkies = {}
print("[Heimdall] StinkyTracker loaded") if string.find(msg, "^who:") then
end if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Processing WHO message from %s", ModuleName, sender))
end
local whoStinkies = ParseWho(msg)
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Found stinkies in WHO message", ModuleName))
shared.dump(whoStinkies)
end
for name, stinky in pairs(whoStinkies) do
stinkies[name] = stinky
end
end
if string.find(msg, "^I see") then
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Processing SEE message from %s", ModuleName, sender))
end
local seeStinkies = ParseSee(msg)
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Found stinkies in SEE message", ModuleName))
shared.dump(seeStinkies)
end
for name, stinky in pairs(seeStinkies) do
stinkies[name] = stinky
end
end
if string.find(msg, "arrived to") or string.find(msg, "moved to") then
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Processing ARRIVED message from %s", ModuleName, sender))
end
local arrivedStinkies = ParseArrived(msg)
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Found stinkies in ARRIVED message", ModuleName))
shared.dump(arrivedStinkies)
end
for name, stinky in pairs(arrivedStinkies) do
stinkies[name] = stinky
end
end
for name, stinky in pairs(stinkies) do
if shared.stinkyTracker.ignored[name] then
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Ignoring stinky: %s (%s)", ModuleName, name, stinky.class))
end
shared.stinkyTracker.ignored[name] = nil
else
shared.stinkyTracker.stinkies[name] = stinky
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Added stinky: %s (%s)", ModuleName, name, stinky.class))
end
end
end
-- Log total stinky count after processing
if Heimdall_Data.config.stinkyTracker.debug then
local count = 0
for _ in pairs(shared.stinkyTracker.stinkies:get()) do
count = count + 1
end
print(string.format("[%s] Current total stinkies tracked: %d", ModuleName, count))
end
shared.StinkyTracker.ForEach(function(name, stinky)
if shared.AgentTracker.IsAgent(name) then
shared.stinkyTracker.stinkies[name] = nil
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Removed agent from stinkies: %s", ModuleName, name))
end
end
end)
end)
local targetFrame = CreateFrame("Frame")
targetFrame:RegisterEvent("UNIT_TARGET")
targetFrame:SetScript("OnEvent", function(self, event, unit)
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Event received: %s for unit: %s", ModuleName, event, unit or "target"))
end
unit = "target"
if not Heimdall_Data.config.stinkyTracker.enabled then
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Module disabled, ignoring event", ModuleName))
end
return
end
local name = UnitName(unit)
if not UnitIsPlayer(unit) then
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Target %s is not a player, nothing to do", ModuleName, name))
end
return
end
local enemy = UnitCanAttack("player", unit)
if enemy then
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Target %s is enemy - tracking as stinky", ModuleName, name))
end
shared.stinkyTracker.stinkies[name] = {
name = name,
class = UnitClass(unit),
seenAt = GetTime(),
hostile = true,
}
return
end
if not shared.stinkyTracker.stinkies[name] then
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Target %s is friendly and not stinky, nothing to do", ModuleName, name))
end
return
end
if Heimdall_Data.config.stinkyTracker.debug then
print(string.format("[%s] Target %s is friendly and stinky - removing from stinkies", ModuleName, name))
end
shared.stinkyTracker.stinkies[name] = nil
end)
if Heimdall_Data.config.stinkyTracker.debug then print(string.format("[%s] Module initialized", ModuleName)) end
print(string.format("[%s] Module initialized", ModuleName))
end,
}

File diff suppressed because it is too large Load Diff

2
_L.lua
View File

@@ -90,6 +90,7 @@ shared._Locale = {
updateInterval = "Update Interval", updateInterval = "Update Interval",
networkMessenger = "Network Messenger", networkMessenger = "Network Messenger",
queries = "Who queries", queries = "Who queries",
chatSniffer = "Chat Sniffer",
}, },
ru = { ru = {
bonkDetected = "%s ударил %s (%s)", bonkDetected = "%s ударил %s (%s)",
@@ -175,6 +176,7 @@ shared._Locale = {
updateInterval = "Интервал Обновления", updateInterval = "Интервал Обновления",
networkMessenger = "Сетевой Мессенджер", networkMessenger = "Сетевой Мессенджер",
queries = "Запросы Who", queries = "Запросы Who",
chatSniffer = "Сниффер Чата",
["Orgrimmar"] = "Оргриммар", ["Orgrimmar"] = "Оргриммар",
["Valley of Strength"] = "Долина Силы", ["Valley of Strength"] = "Долина Силы",
["Valley of Trials"] = "Долина Испытаний", ["Valley of Trials"] = "Долина Испытаний",