141 Commits

Author SHA1 Message Date
a01dd5dace Release 2025-01-07 01:16:53 +01:00
b6f0fa9776 Add whispernotify and stinkies configs 2025-01-07 01:16:29 +01:00
cb03f8e23c UIPARENT EXISTS 2025-01-07 01:12:26 +01:00
fbff094227 Add every other config module 2025-01-07 01:12:26 +01:00
e08cf455ee Minor details 2025-01-07 00:46:43 +01:00
de57b4c8a8 Color the boxes so the modules are easily told apart 2025-01-07 00:44:32 +01:00
9fef1da261 Add MORE configuration 2025-01-07 00:34:27 +01:00
1f6a1a49a6 Tidy up row counts 2025-01-07 00:20:27 +01:00
fddd294a03 Unfuckup the button colors 2025-01-07 00:18:19 +01:00
00ffe6973e Add helper functions and assemble big boxes 2025-01-06 23:47:01 +01:00
f73096e439 Add BigTextFrame 2025-01-06 23:42:54 +01:00
e26ab02862 Make anyone be able to joingroup and leavegroup 2025-01-06 23:42:50 +01:00
c2b9c1267b Add a little text inset 2025-01-06 22:35:23 +01:00
a400a85dc9 Add whoer configuration 2025-01-06 22:33:28 +01:00
f5a4b49935 Add other text fields for spotter config 2025-01-06 22:30:07 +01:00
2dabe7959f Refactor spotter config into separate do block 2025-01-06 22:23:41 +01:00
dcfc406481 Refactor button colorization 2025-01-06 22:20:09 +01:00
f680d36d2b Add some basic config buttons for spotter 2025-01-06 22:17:38 +01:00
d44aa04e44 Make GridFrame automatically adjust to SetWidth and SetHeight 2025-01-06 21:26:52 +01:00
4594dff4a6 Make button more buttonlike 2025-01-06 20:14:00 +01:00
be1093d51f Close frame on escape 2025-01-06 20:11:40 +01:00
546aa27bb1 Add a basic ass enable spotter button 2025-01-06 20:10:02 +01:00
dfb2f687d0 Remove stinkied agents 2025-01-06 20:01:47 +01:00
9e344aed64 Make movable 2025-01-06 19:12:52 +01:00
ba77555cab Implement static frame too 2025-01-06 19:08:25 +01:00
e8e15444c9 Restrict frame size to follow grid (otherwise what's the point) 2025-01-06 19:06:37 +01:00
cdd859ba50 Stop tracking dovahkin 2025-01-06 18:00:44 +01:00
ab85b3712a Rework grid to have infinite rows 2025-01-06 17:35:59 +01:00
fc3732fd0c Begin reworking old approach to gridframe 2025-01-06 17:16:48 +01:00
4652c60e64 Implement a grid frame of sorts 2025-01-06 17:16:36 +01:00
e425fa9209 Implement offsetting to placement functions 2025-01-06 12:00:59 +01:00
f1e2f60836 Maybe generify UI a little 2025-01-06 12:00:59 +01:00
db7edaf802 Make little more better 2025-01-06 12:00:59 +01:00
58a7ecd723 Add more config options 2025-01-06 12:00:59 +01:00
bff1a4acf9 Make most basic config 2025-01-06 12:00:59 +01:00
b287fb41c4 Add config.lua 2025-01-06 12:00:58 +01:00
5e0f81ce53 Add more decimals to spotter coordinates 2025-01-06 11:56:38 +01:00
bd8b3fa00f Release 2025-01-06 02:03:37 +01:00
899fb888c3 Implement combatalerter 2025-01-06 02:03:23 +01:00
2d3820952f Refactor messenger to use GetChannelName 2025-01-06 02:02:37 +01:00
f6b043fa39 Prevent macroer from running in combat 2025-01-06 01:55:40 +01:00
8bce840497 Fix annoying deathReporter error 2025-01-06 01:48:10 +01:00
aff3e83bab Rework macroer to use the new stinkytracker 2025-01-06 01:42:46 +01:00
85a28bc7ca Rework the tracking functionality from macroer to stinkytracker 2025-01-06 01:40:29 +01:00
452aa2e3a0 Add reactive value 2025-01-06 01:39:07 +01:00
784cee6f04 Add config options for the 2 new modules added in previous commit 2025-01-06 01:39:07 +01:00
ca30998a5a Add basic structure for combatalerter and stinkytracker 2025-01-06 01:39:07 +01:00
476adcdc2e Actually disable inviter kicker 2025-01-06 01:09:34 +01:00
2c6142e6c4 Remove oopsie log 2025-01-06 01:04:28 +01:00
cae1eef659 Release 2025-01-06 01:03:03 +01:00
2dfb60e63d Implement some sort of semi automatic kicking from group
By overlaying a button over the existing button...
Not great but the best we can do
2025-01-06 01:02:03 +01:00
8524a1116a Implement follow command 2025-01-06 00:25:03 +01:00
60ccbc72bb Add basic structure and configuration for inviter kicker 2025-01-06 00:22:29 +01:00
8d3813f3ee Implement enabling/disabling commands 2025-01-06 00:00:16 +01:00
aa46000abf Implement joingroup and leavegroup for commander 2025-01-05 23:32:48 +01:00
6059c16a6e Implement commander only mode for commands 2025-01-05 23:27:04 +01:00
2e805abef7 Refactor commands to separate structure 2025-01-05 23:23:17 +01:00
06f143915c Add commander config to the weakaura 2025-01-05 23:23:17 +01:00
be20aa77b9 Migrate the commands from whoer to commander 2025-01-05 23:01:41 +01:00
7b2c67d130 Remove replying to whispers
Nobody is using it anyway, taking up space in the code...
2025-01-05 22:52:24 +01:00
9480c42181 Add player coordinates to spotter 2025-01-05 22:51:45 +01:00
17c163c71c Add help messages in english 2025-01-05 22:35:30 +01:00
3ee90fb767 Do not report spotter for agents 2025-01-05 22:30:09 +01:00
7f9476236d Add prefixes to commands even when no stinkies are found 2025-01-05 22:12:16 +01:00
2d2cf621bd Fix up macroer to work with the arrival and movement messages 2025-01-05 19:49:06 +01:00
bb1acd5003 Refactor macroer to include "I see" 2025-01-05 19:30:37 +01:00
c00ddd410a Remove the realm "-legionx5" from player names on dueler 2025-01-05 17:35:20 +01:00
35c51143bc Make agent tracker sniff channel messages for agents 2025-01-05 17:17:58 +01:00
16cd2f82be Fuck about with whoer 2025-01-05 00:28:55 +01:00
34e027525f Fix empty location 2025-01-03 23:42:53 +01:00
331823e34f Clean up the named who 2025-01-03 14:30:45 +01:00
18daa170c5 Add prefixes to commands/messages 2025-01-03 13:30:38 +01:00
7e8978044f Have partitioner split on "," instead of space 2025-01-03 13:22:56 +01:00
2811396234 Reverse macroer sorting, most important should be LAST not FIRST! 2025-01-02 23:07:33 +01:00
212ce2c71c Fix who messages to be partitioned 2025-01-02 23:07:01 +01:00
f76ef718ed Refactor GetChannelId out of FindOrJoinChannel
No reason for it to be nested
2025-01-02 14:11:12 +01:00
bd40d43686 Fix the weird function declaration in messenger 2025-01-02 14:10:17 +01:00
2954a93b4d Fix spotter oopsie 2025-01-02 12:14:18 +01:00
93d1d55cfa Add targetenemy as first line 2025-01-02 11:32:20 +01:00
1c7951a530 Release 2025-01-02 11:31:36 +01:00
84a53ea065 Remove debug everything 2025-01-02 11:31:19 +01:00
9dcf526b4c Update config weakaura 2025-01-02 11:23:26 +01:00
032cfc5bff Release 2025-01-02 11:19:37 +01:00
5c9450d06d Implement priority sorting of classes for macroer 2025-01-02 11:19:26 +01:00
f811dd9a6c Clean up some warnings 2025-01-02 11:06:45 +01:00
d13da3141d Implement macroer logic 2025-01-02 11:03:00 +01:00
d4bab870a4 Clean up the log messages a little 2025-01-02 11:02:56 +01:00
6ef7e74402 Implement the basic structure for macroer 2025-01-02 10:23:27 +01:00
55ce001705 Spice up the who messages a little 2025-01-02 10:23:11 +01:00
77c3fc291d Add help message to whoer 2025-01-02 00:09:25 +01:00
ff849092ff Remove inviter debug prints 2025-01-01 21:45:26 +01:00
d0cb074912 Add macroer (or the skeleton of) 2025-01-01 21:43:27 +01:00
fdedde829e Fix typo in inviter AAAAAAAAAAAAAAAA 2025-01-01 21:43:09 +01:00
4364d87bf7 Fix inviter infinite loop 2025-01-01 21:08:57 +01:00
2232f1a92d Release 2025-01-01 20:57:23 +01:00
251e11a7e8 Fix deathreporter not assigning zones (because it was "") 2025-01-01 20:57:11 +01:00
d4e4290dc5 Only promote units that aren't already
There's a big issue with this but I don't know how to fix it yet...
And that is that giving people assistant triggers a group update
Which triggers give people assistant
And so on...
2025-01-01 20:56:06 +01:00
9fd57c5d7b Fix whoer 2025-01-01 20:55:31 +01:00
873ce0a30c Minor fixes 2025-01-01 17:16:22 +01:00
36297ca09d Add emoter and ehcoer 2025-01-01 16:24:11 +01:00
0ec3421a79 Implement class listing in whoer 2025-01-01 16:14:10 +01:00
21d99ae643 Update config export 2025-01-01 16:02:35 +01:00
4dc3335a86 Add throttle to inviter so it doesn't have a stroke 2025-01-01 16:01:57 +01:00
c9627779ba Refactor agentTracker to its own module 2025-01-01 16:01:40 +01:00
3f1fae8906 Remove query pending mechanism from whoer 2025-01-01 15:42:58 +01:00
2ae12fade0 Fix target spotting for spotter 2025-01-01 15:40:42 +01:00
c66c961297 Rework the channel commands a little 2025-01-01 15:36:04 +01:00
8da5773dcd Implement "howmany" to whoer 2025-01-01 15:28:43 +01:00
18106db367 Fix issue with who ignored 2025-01-01 15:22:06 +01:00
5eb6f3cbfd Reset whoer regardless of enabled (it gets stuck otherwise) 2025-01-01 15:18:35 +01:00
614b07c01a Release 2025-01-01 15:05:11 +01:00
2c84c326dd Implement dueler 2025-01-01 15:04:20 +01:00
0ceb59c778 Remove vestigial code 2025-01-01 15:00:08 +01:00
137ce0a3a7 Refactor everything to modules 2025-01-01 14:58:05 +01:00
59d2b999c2 Update config 2025-01-01 14:56:56 +01:00
d80ffbaff5 Rework whoer 2025-01-01 14:47:40 +01:00
8c45e90ce1 Remove the fucking linter warnings 2025-01-01 14:38:02 +01:00
ed7c1a4685 Rework deathreporter 2025-01-01 14:34:12 +01:00
e32966bee2 Rework spotter 2025-01-01 14:24:58 +01:00
5e779cc5f9 Add interval config to messenger 2025-01-01 14:16:31 +01:00
5e78f623f5 Rework messenger config 2025-01-01 14:13:29 +01:00
8e90a71dfc Rework inviter config 2025-01-01 14:10:33 +01:00
9ebc95885e Rework main heimdall file to simplify config 2025-01-01 14:04:48 +01:00
e1136703a5 Fuck up some other shit just for fun 2025-01-01 14:00:07 +01:00
c446d1dc85 Add config weakaura 2025-01-01 13:59:58 +01:00
de49956aef Add report overview 2024-12-27 16:33:10 +01:00
efde43fadb Escape the lt and gt 2024-12-27 16:22:24 +01:00
af2a714b76 Bolden the reports, they are the highlights here 2024-12-27 16:21:41 +01:00
bfaa73b660 Release 2024-12-27 16:18:11 +01:00
a7c818b88b Machine translate russian 2024-12-27 16:18:02 +01:00
e1fb450544 Add readme 2024-12-27 16:16:17 +01:00
acb2910b70 Fix inviter channel scanning
It really was not so easy...
2024-12-27 14:15:34 +01:00
0dd1a6fd69 Add zip 2024-12-26 21:32:10 +01:00
5f3374a073 Rework inviter to grant assist only to members of listenChannel 2024-12-26 21:29:27 +01:00
3049a0b554 Update whoer lists 2024-12-26 21:29:16 +01:00
fa7a411e34 Remove notifications
I'm done with this shit
2024-12-17 14:34:50 +01:00
6bf1c491a0 Implement inviter 2024-12-16 01:04:28 +01:00
8e1f2c147e Fix stinky config 2024-12-15 21:01:49 +01:00
c4f4d24064 Longer who ttl 2024-12-15 21:00:33 +01:00
30578bdbb0 Add race and faction to "moved to" 2024-12-15 21:00:17 +01:00
0e771a7db3 Add stinki to whoer 2024-12-13 12:07:07 +01:00
28 changed files with 5456 additions and 2216 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.zip filter=lfs diff=lfs merge=lfs -text

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"Lua.diagnostics.globals": [
"UIParent"
]
}

View File

@@ -1,39 +1,46 @@
local addonname, data = ...
---@cast data HeimdallData
local addonname, shared = ...
---@cast shared HeimdallShared
---@cast addonname string
-- TODO: Maybe make a configuration weakaura, make use of weakaura options...
-- TODO: Implement counting kills and display on whosniffer
-- Take last N seconds of combatlog into account ie. count who does damage to who
-- Maybe even make an alert when someone does too much damage to someone else...
-- But that would not be trivial as of now, I can't think of a way to do it sensibly
-- TODO: Implement auto grouping via agent, maybe find "+" or something
local function init()
---@class Heimdall_Data
---@field who { data: table<string, Player> }
---@field config HeimdallConfig
---@field stinkies table<string, boolean>
if not Heimdall_Data then Heimdall_Data = {} end
if not Heimdall_Data.config then Heimdall_Data.config = {} end
-- We don't care about these persisting
-- Actually we don't want some of them to persist
-- For those we DO we use (global) Heimdall_Data
---@class InitTable
---@field Init fun(): nil
---@class HeimdallData
---@field config HeimdallConfig
---@class HeimdallShared
---@field raceMap table<string, string>
---@field classColors table<string, string>
---@field messenger HeimdallMessengerData
---@field who HeimdallWhoData
---@field stinkyTracker HeimdallStinkyTrackerData
---@field dumpTable fun(table: any, depth?: number): nil
---@field utf8len fun(input: string): number
---@field padString fun(input: string, targetLength: number, left?: boolean): string
---@field GetOrDefault fun(table: table<any, any>, keys: string[], default: any): any
---@field Whoer { Init: fun() }
---@field Messenger { Init: fun() }
---@field Spotter { Init: fun() }
---@field DeathReporter { Init: fun() }
---@field Whoer InitTable
---@field Messenger InitTable
---@field Spotter InitTable
---@field DeathReporter InitTable
---@field Inviter InitTable
---@field Dueler InitTable
---@field Bully InitTable
---@field AgentTracker InitTable
---@field Emoter InitTable
---@field Echoer InitTable
---@field Macroer InitTable
---@field Commander InitTable
---@field StinkyTracker InitTable
---@field CombatAlerter InitTable
---@field Config InitTable
--- Config ---
---@class HeimdallConfig
@@ -41,8 +48,19 @@ local function init()
---@field who HeimdallWhoConfig
---@field messenger HeimdallMessengerConfig
---@field deathReporter HeimdallDeathReporterConfig
---@field inviter HeimdallInviterConfig
---@field dueler HeimdallDuelerConfig
---@field bully HeimdallBullyConfig
---@field agentTracker HeimdallAgentTrackerConfig
---@field emoter HeimdallEmoterConfig
---@field echoer HeimdallEchoerConfig
---@field macroer HeimdallMacroerConfig
---@field commander HeimdallCommanderConfig
---@field stinkyTracker HeimdallStinkyTrackerConfig
---@field combatAlerter HeimdallCombatAlerterConfig
---@field whisperNotify table<string, string>
---@field stinkies table<string, boolean>
---@field agents table<string, string>
---@class HeimdallSpotterConfig
---@field enabled boolean
@@ -64,6 +82,7 @@ local function init()
---@class HeimdallMessengerConfig
---@field enabled boolean
---@field interval number
---@class HeimdallDeathReporterConfig
---@field enabled boolean
@@ -73,6 +92,56 @@ local function init()
---@field zoneOverride string?
---@field duelThrottle number
---@class HeimdallInviterConfig
---@field enabled boolean
---@field listeningChannel string
---@field keyword string
---@field allAssist boolean
---@field agentsAssist boolean
---@field throttle number
---@field kickOffline boolean
---@field cleanupInterval number
---@field afkThreshold number
---@class HeimdallDuelerConfig
---@field enabled boolean
---@field declineOther boolean
---@class HeimdallBullyConfig
---@field enabled boolean
---@class HeimdallAgentTrackerConfig
---@field enabled boolean
---@field masterChannel string
---@class HeimdallEmoterConfig
---@field enabled boolean
---@field masterChannel string
---@field prefix string
---@class HeimdallEchoerConfig
---@field enabled boolean
---@field masterChannel string
---@field prefix string
---@class HeimdallMacroerConfig
---@field enabled boolean
---@field priority string[]
---@class HeimdallCommanderConfig
---@field enabled boolean
---@field masterChannel string
---@field commander string
---@field commands table<string, boolean>
---@class HeimdallStinkyTrackerConfig
---@field enabled boolean
---@field masterChannel string
---@class HeimdallCombatAlerterConfig
---@field enabled boolean
---@field masterChannel string
--- Data ---
---@class HeimdallMessengerData
---@field queue table<string, Message>
@@ -83,7 +152,10 @@ local function init()
---@field whoTicker number?
---@field ignored table<string, boolean>
data.GetOrDefault = function(table, keys, default)
---@class HeimdallStinkyTrackerData
---@field stinkies ReactiveValue
shared.GetOrDefault = function(table, keys, default)
local value = default
if not table then return value end
if not keys then return value end
@@ -104,34 +176,31 @@ local function init()
return value
end
data.messenger = {
shared.messenger = {
queue = {}
}
data.who = {
shared.who = {
ignored = {},
}
--/run Heimdall_Data.config = {who={enabled=true},deathReporter={enabled=true}}
--/run Heimdall_Data.config = {deathReporter={enabled=true}}
--/run Heimdall_Data.config = {deathReporter={enabled=false},spotter={enabled=false}}
--/run Heimdall_Data.config = {deathReporter={enabled=false},spotter={enabled=true,everyone=true}}
data.config = {
Heimdall_Data.config = {
spotter = {
enabled = data.GetOrDefault(Heimdall_Data, { "config", "spotter", "enabled" }, true),
everyone = data.GetOrDefault(Heimdall_Data, { "config", "spotter", "everyone" }, false),
hostile = data.GetOrDefault(Heimdall_Data, { "config", "spotter", "hostile" }, true),
alliance = data.GetOrDefault(Heimdall_Data, { "config", "spotter", "alliance" }, true),
stinky = data.GetOrDefault(Heimdall_Data, { "config", "spotter", "stinky" }, true),
notifyChannel = data.GetOrDefault(Heimdall_Data, { "config", "spotter", "notifyChannel" }, "Agent"),
zoneOverride = data.GetOrDefault(Heimdall_Data, { "config", "spotter", "zoneOverride" }, nil),
throttleTime = data.GetOrDefault(Heimdall_Data, { "config", "spotter", "throttleTime" }, 10)
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "spotter", "enabled" }, true),
everyone = shared.GetOrDefault(Heimdall_Data, { "config", "spotter", "everyone" }, false),
hostile = shared.GetOrDefault(Heimdall_Data, { "config", "spotter", "hostile" }, true),
alliance = shared.GetOrDefault(Heimdall_Data, { "config", "spotter", "alliance" }, true),
stinky = shared.GetOrDefault(Heimdall_Data, { "config", "spotter", "stinky" }, true),
notifyChannel = shared.GetOrDefault(Heimdall_Data, { "config", "spotter", "notifyChannel" }, "Agent"),
zoneOverride = shared.GetOrDefault(Heimdall_Data, { "config", "spotter", "zoneOverride" }, nil),
throttleTime = shared.GetOrDefault(Heimdall_Data, { "config", "spotter", "throttleTime" }, 10)
},
who = {
enabled = data.GetOrDefault(Heimdall_Data, { "config", "who", "enabled" }, false),
ignored = data.GetOrDefault(Heimdall_Data, { "config", "who", "ignored" }, {}),
notifyChannel = data.GetOrDefault(Heimdall_Data, { "config", "who", "notifyChannel" }, "Agent"),
ttl = data.GetOrDefault(Heimdall_Data, { "config", "who", "ttl" }, 10),
doWhisper = data.GetOrDefault(Heimdall_Data, { "config", "who", "doWhisper" }, true),
zoneNotifyFor = data.GetOrDefault(Heimdall_Data, { "config", "who", "zoneNotifyFor" }, {
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "who", "enabled" }, false),
ignored = shared.GetOrDefault(Heimdall_Data, { "config", "who", "ignored" }, {}),
notifyChannel = shared.GetOrDefault(Heimdall_Data, { "config", "who", "notifyChannel" }, "Agent"),
ttl = shared.GetOrDefault(Heimdall_Data, { "config", "who", "ttl" }, 20),
doWhisper = shared.GetOrDefault(Heimdall_Data, { "config", "who", "doWhisper" }, true),
zoneNotifyFor = shared.GetOrDefault(Heimdall_Data, { "config", "who", "zoneNotifyFor" }, {
["Orgrimmar"] = true,
["Thunder Bluff"] = true,
["Undercity"] = true,
@@ -141,94 +210,73 @@ local function init()
}),
},
messenger = {
enabled = data.GetOrDefault(Heimdall_Data, { "config", "messenger", "enabled" }, true),
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "messenger", "enabled" }, true),
interval = shared.GetOrDefault(Heimdall_Data, { "config", "messenger", "interval" }, 0.2),
},
deathReporter = {
enabled = data.GetOrDefault(Heimdall_Data, { "config", "deathReporter", "enabled" }, false),
throttle = data.GetOrDefault(Heimdall_Data, { "config", "deathReporter", "throttle" }, 10),
doWhisper = data.GetOrDefault(Heimdall_Data, { "config", "deathReporter", "doWhisper" }, true),
notifyChannel = data.GetOrDefault(Heimdall_Data, { "config", "deathReporter", "notifyChannel" }, "Agent"),
zoneOverride = data.GetOrDefault(Heimdall_Data, { "config", "deathReporter", "zoneOverride" }, nil),
duelThrottle = data.GetOrDefault(Heimdall_Data, { "config", "deathReporter", "duelThrottle" }, 5),
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "deathReporter", "enabled" }, false),
throttle = shared.GetOrDefault(Heimdall_Data, { "config", "deathReporter", "throttle" }, 10),
doWhisper = shared.GetOrDefault(Heimdall_Data, { "config", "deathReporter", "doWhisper" }, true),
notifyChannel = shared.GetOrDefault(Heimdall_Data, { "config", "deathReporter", "notifyChannel" }, "Agent"),
zoneOverride = shared.GetOrDefault(Heimdall_Data, { "config", "deathReporter", "zoneOverride" }, nil),
duelThrottle = shared.GetOrDefault(Heimdall_Data, { "config", "deathReporter", "duelThrottle" }, 5),
},
whisperNotify = shared.GetOrDefault(Heimdall_Data, { "config", "whisperNotify" }, {}),
stinkies = shared.GetOrDefault(Heimdall_Data, { "config", "stinkies" }, {}),
inviter = {
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "inviter", "enabled" }, false),
listeningChannel = shared.GetOrDefault(Heimdall_Data, { "config", "inviter", "listeningChannel" }, "Agent"),
keyword = shared.GetOrDefault(Heimdall_Data, { "config", "inviter", "keyword" }, "+"),
allAssist = shared.GetOrDefault(Heimdall_Data, { "config", "inviter", "allAssist" }, false),
agentsAssist = shared.GetOrDefault(Heimdall_Data, { "config", "inviter", "agentsAssist" }, false),
throttle = shared.GetOrDefault(Heimdall_Data, { "config", "inviter", "throttle" }, 1),
kickOffline = shared.GetOrDefault(Heimdall_Data, { "config", "inviter", "kickOffline" }, false),
cleanupInterval = shared.GetOrDefault(Heimdall_Data, { "config", "inviter", "cleanupInterval" }, 10),
afkThreshold = shared.GetOrDefault(Heimdall_Data, { "config", "inviter", "afkThreshold" }, 300),
},
dueler = {
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "dueler", "enabled" }, false),
declineOther = shared.GetOrDefault(Heimdall_Data, { "config", "dueler", "declineOther" }, false),
},
bully = {
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "bully", "enabled" }, false),
},
agentTracker = {
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "agentTracker", "enabled" }, false),
masterChannel = shared.GetOrDefault(Heimdall_Data, { "config", "agentTracker", "masterChannel" }, "Agent"),
},
emoter = {
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "emoter", "enabled" }, false),
masterChannel = shared.GetOrDefault(Heimdall_Data, { "config", "emoter", "masterChannel" }, "Agent"),
prefix = shared.GetOrDefault(Heimdall_Data, { "config", "emoter", "prefix" }, ""),
},
echoer = {
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "echoer", "enabled" }, false),
masterChannel = shared.GetOrDefault(Heimdall_Data, { "config", "echoer", "masterChannel" }, "Agent"),
prefix = shared.GetOrDefault(Heimdall_Data, { "config", "echoer", "prefix" }, ""),
},
macroer = {
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "macroer", "enabled" }, false),
priority = shared.GetOrDefault(Heimdall_Data, { "config", "macroer", "priority" }, {}),
},
agents = shared.GetOrDefault(Heimdall_Data, { "config", "agents" }, {}),
commander = {
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "commander", "enabled" }, false),
masterChannel = shared.GetOrDefault(Heimdall_Data, { "config", "commander", "masterChannel" }, "Agent"),
commander = shared.GetOrDefault(Heimdall_Data, { "config", "commander", "commander" }, "Heimdállr"),
commands = shared.GetOrDefault(Heimdall_Data, { "config", "commander", "commands" }, {}),
},
stinkyTracker = {
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "stinkyTracker", "enabled" }, false),
masterChannel = shared.GetOrDefault(Heimdall_Data, { "config", "stinkyTracker", "masterChannel" }, "Agent"),
},
combatAlerter = {
enabled = shared.GetOrDefault(Heimdall_Data, { "config", "combatAlerter", "enabled" }, false),
masterChannel = shared.GetOrDefault(Heimdall_Data, { "config", "combatAlerter", "masterChannel" }, "Agent"),
},
whisperNotify = data.GetOrDefault(Heimdall_Data, { "config", "whisperNotify" }, {
"Extazyk",
"Smokefire",
"Smokemantra",
"Хихихантер",
"Муркот",
"Растафаркрай",
"Frosstmorn",
"Pulsjkee",
"Paskoo",
"Totleta",
"Healleta",
"Deathleta",
"Shootleta",
"Stableta"
}),
stinkies = data.GetOrDefault(Heimdall_Data, { "config", "stinkies" }, {
"Ahhahahh",
"Aye",
"Bbd",
"Blessly",
"Bunkkeer",
"Calmer",
"Chuvirloeban",
"Clairvoyant",
"Dewdew",
"Dwxrfshaman",
"Ebanirot",
"Heger",
"Hmor",
"Joule",
"Kaøs",
"Kromsaevmode",
"Kugisara",
"Lax",
"Negron",
"Oakskin",
"Pizdosorkam",
"Pussymism",
"Rattenfenger",
"Riener",
"Rollbot",
"Samuraqt",
"Sekiiro",
"Shadowmilf",
"Sonikblaster",
"Srakonyh",
"Stuffo",
"Subaruwrxsti",
"Sukunexd",
"Tomoki",
"Unwashed",
"Voitas",
"Wataru",
"Yooshima",
"Анджелос",
"Артейда",
"Асталабиста",
"Гебефрени",
"Курлык",
"Лжедмитресса",
"Ловилуну",
"Лопапа",
"Неонанируй",
"Паладийпал",
"Психопаточка",
"Сильверлейн",
"Сосканереалк",
"Счастьевам",
"Фоська",
"Фрил",
"Ххантуля",
"Чмодвенк",
"Шпек",
}),
}
data.raceMap = {
shared.raceMap = {
["Orc"] = "Horde",
["Undead"] = "Horde",
["Tauren"] = "Horde",
@@ -252,7 +300,7 @@ local function init()
["Mag'har Orc"] = "Horde"
}
data.classColors = {
shared.classColors = {
["Warrior"] = "C69B6D",
["Paladin"] = "F48CBA",
["Hunter"] = "AAD372",
@@ -269,7 +317,7 @@ local function init()
---@param input string
---@return number
data.utf8len = function(input)
shared.utf8len = function(input)
if not input then
return 0
end
@@ -297,9 +345,9 @@ local function init()
---@param targetLength number
---@param left boolean
---@return string
data.padString = function(input, targetLength, left)
shared.padString = function(input, targetLength, left)
left = left or false
local len = data.utf8len(input)
local len = shared.utf8len(input)
if len < targetLength then
if left then
input = input .. string.rep(" ", targetLength - len)
@@ -310,10 +358,19 @@ local function init()
return input
end
data.Whoer.Init()
data.Messenger.Init()
data.Spotter.Init()
data.DeathReporter.Init()
shared.Messenger.Init()
shared.StinkyTracker.Init()
shared.AgentTracker.Init()
shared.Whoer.Init()
shared.Spotter.Init()
shared.DeathReporter.Init()
shared.Inviter.Init()
shared.Dueler.Init()
shared.Bully.Init()
shared.Macroer.Init()
shared.Commander.Init()
shared.CombatAlerter.Init()
shared.Config.Init()
print("Heimdall loaded!")
end
@@ -324,20 +381,3 @@ loadedFrame:SetScript("OnEvent", function(self, event, addonName)
init()
end
end)
local logoutFrame = CreateFrame("Frame")
logoutFrame:RegisterEvent("PLAYER_LOGOUT")
logoutFrame:SetScript("OnEvent", function(self, event)
Heimdall_Data.config.stinkies = data.config.stinkies
end)
SlashCmdList["HEIMDALL_TOGGLE_STINKY"] = function(input)
print("Toggling stinky: " .. tostring(input))
if data.config.stinkies[input] then
data.config.stinkies[input] = nil
else
data.config.stinkies[input] = true
end
print(data.config.stinkies[input])
end
SLASH_HEIMDALL_TOGGLE_STINKY1 = "/has"

View File

@@ -1,14 +1,27 @@
## Interface: 70300
## Title: Heimdall
## Version: 3.0.0
## Notes: Watches over areas and alerts when hostiles spotted
## Author: Cyka
## SavedVariables: Heimdall_Data
#core
CLEUParser.lua
DumpTable.lua
Spotter.lua
Whoer.lua
Messenger.lua
DeathReporter.lua
Modules/CLEUParser.lua
Modules/ReactiveValue.lua
Modules/DumpTable.lua
Modules/Spotter.lua
Modules/Whoer.lua
Modules/Messenger.lua
Modules/DeathReporter.lua
Modules/Inviter.lua
Modules/Dueler.lua
Modules/Bully.lua
Modules/AgentTracker.lua
Modules/Emoter.lua
Modules/Echoer.lua
Modules/Macroer.lua
Modules/Commander.lua
Modules/StinkyTracker.lua
Modules/CombatAlerter.lua
Modules/Config.lua
Heimdall.lua

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

Binary file not shown.

View File

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

39
Modules/AgentTracker.lua Normal file
View File

@@ -0,0 +1,39 @@
local addonname, shared = ...
---@cast shared HeimdallShared
---@cast addonname string
---@diagnostic disable-next-line: missing-fields
shared.AgentTracker = {}
function shared.AgentTracker.Init()
---@type table<string, boolean>
local channelRosterFrame = CreateFrame("Frame")
channelRosterFrame:RegisterEvent("CHANNEL_ROSTER_UPDATE")
channelRosterFrame:SetScript("OnEvent", function(self, event, index)
if not Heimdall_Data.config.agentTracker.enabled then return end
local name = GetChannelDisplayInfo(index)
if name ~= Heimdall_Data.config.agentTracker.masterChannel then return end
local count = select(5, GetChannelDisplayInfo(index))
for i = 1, count do
local name = GetChannelRosterInfo(index, i)
if name then
Heimdall_Data.config.agents[name] = date("%Y-%m-%dT%H:%M:%S")
end
end
--shared.dumpTable(Heimdall_Data.config.agents)
end)
local agentTrackerChannelSniffer = CreateFrame("Frame")
agentTrackerChannelSniffer:RegisterEvent("CHAT_MSG_CHANNEL")
agentTrackerChannelSniffer:SetScript("OnEvent", function(self, event, msg, sender, ...)
if not Heimdall_Data.config.agentTracker.enabled then return end
local channelId = select(6, ...)
local channelname = GetChannelName(channelId)
if not channelname then return end
if channelname ~= Heimdall_Data.config.who.notifyChannel then return end
local agentName = sender
if not agentName then return end
Heimdall_Data.config.agents[agentName] = date("%Y-%m-%dT%H:%M:%S")
end)
print("Heimdall - AgentTracker loaded")
end

9
Modules/Bully.lua Normal file
View File

@@ -0,0 +1,9 @@
local addonname, shared = ...
---@cast shared HeimdallShared
---@cast addonname string
---@diagnostic disable-next-line: missing-fields
shared.Bully = {}
function shared.Bully.Init()
print("Heimdall - Bully loaded")
end

49
Modules/CombatAlerter.lua Normal file
View File

@@ -0,0 +1,49 @@
local addonname, shared = ...
---@cast shared HeimdallShared
---@cast addonname string
---@diagnostic disable-next-line: missing-fields
shared.CombatAlerter = {}
function shared.CombatAlerter.Init()
local alerted = {}
local combatAlerterFrame = CreateFrame("Frame")
combatAlerterFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
combatAlerterFrame:SetScript("OnEvent", function(self, event, ...)
if not Heimdall_Data.config.combatAlerter.enabled then return end
local destination, err = CLEUParser.GetDestName(...)
if err then return end
if destination ~= UnitName("player") then return end
local source, err = CLEUParser.GetSourceName(...)
if err then source = "unknown" end
if shared.stinkyTracker.stinkies and shared.stinkyTracker.stinkies[source] then
if alerted[source] then return end
alerted[source] = true
local x, y = GetPlayerMapPosition("player")
---@type Message
local msg = {
channel = "CHANNEL",
data = Heimdall_Data.config.combatAlerter.masterChannel,
message = string.format("%s is attacking me in %s(%s) at %2.2f,%2.2f ",
source,
GetZoneText(), GetSubZoneText(),
x * 100, y * 100
),
}
table.insert(shared.messenger.queue, msg)
end
end)
local combatTriggerFrame = CreateFrame("Frame")
combatTriggerFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
combatTriggerFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
-- We want to only alert once per target per combat encounter
-- Even a small throttle would probably spam too much here
-- ....but maybe we can call it a 120 second throttle or something?
-- We will see
combatTriggerFrame:SetScript("OnEvent", function(self, event, ...)
alerted = {}
end)
print("Heimdall - CombatAlerter loaded")
end

223
Modules/Commander.lua Normal file
View File

@@ -0,0 +1,223 @@
local addonname, shared = ...
---@cast shared HeimdallShared
---@cast addonname string
local helpMessages = {
ru = {
"1) who - пишет вам никнеймы текущих врагов в отслеживаемых локациях по порядку и локацию.",
"2) classes = пишет какие классы. (например 1 паладин 1 прист 1 рога.",
"3) howmany - общее число врагов (например дуротар 4 . оргримар 2 ) ",
"4) + - автоматическое приглашение в группу с дуельным рогой для сброса кд и общего сбора. ",
"5) ++ - автоматическое приглашение в группу альянса. (если нужен рефрак)",
},
en = {
"1) who - reports currently tracked stinkies in orgrimmar and durotar",
"2) classes - reports stinkies grouped by class",
"3) howmany - reports stinkies grouped by zone",
"4) + - automatically invites to group with duel rogue for cd reset",
"5) ++ - automatically invites to alliance group",
}
}
---@diagnostic disable-next-line: missing-fields
shared.Commander = {}
function shared.Commander.Init()
---@param text string
---@param size number
---@return string[]
local function Partition(text, size)
local words = {}
for word in text:gmatch("[^,]+") do
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
if #currentChunk > 0 then
ret[#ret + 1] = currentChunk
end
return ret
end
---@param arr table<string, Player>
---@return string[]
local function Count(arr)
local ret = {}
for _, player in pairs(arr) do
if Heimdall_Data.config.who.zoneNotifyFor[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 = {}
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 Heimdall_Data.config.who.zoneNotifyFor[player.zone] then
ret[#ret + 1] = string.format("%s/%s (%s) %s", player.name, player.class, player.zone,
player.stinky and "(!!!!)" or "")
end
end
return ret
end
---@param arr table<string, Player>
---@return string[]
local function WhoPartitioned(arr)
local who = Who(arr)
local text = {}
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 Heimdall_Data.config.who.zoneNotifyFor[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
return text
end
---@param arr table<string, Player>
---@return string[]
local function CountClassPartitioned(arr)
local countClass = CountClass(arr)
local text = {}
for _, line in pairs(Partition(strjoin(", ", unpack(countClass)), 200)) do
text[#text + 1] = line
end
return text
end
local function CountClassPartitionedStinkies()
local res = CountClassPartitioned(HeimdallStinkies)
if #res == 0 then
return { "No stinkies found" }
end
return res
end
local function WhoPartitionedStinkies()
local res = WhoPartitioned(HeimdallStinkies)
if #res == 0 then
return { "No stinkies found" }
end
return res
end
local function CountPartitionedStinkies()
local res = CountPartitioned(HeimdallStinkies)
if #res == 0 then
return { "No stinkies found" }
end
return res
end
local function HelpRu() return helpMessages.ru end
local function HelpEn() return helpMessages.en end
local groupInviteFrame = CreateFrame("Frame")
groupInviteFrame:SetScript("OnEvent", function(self, event, ...)
AcceptGroup()
groupInviteFrame:UnregisterEvent("PARTY_INVITE_REQUEST")
C_Timer.NewTimer(0.1, function()
_G["StaticPopup1Button1"]:Click()
end, 1)
end)
local function JoinGroup()
groupInviteFrame:RegisterEvent("PARTY_INVITE_REQUEST")
C_Timer.NewTimer(10, function()
groupInviteFrame:UnregisterEvent("PARTY_INVITE_REQUEST")
end, 1)
return { "+" }
end
local function LeaveGroup()
LeaveParty()
return {}
end
---@param target string
local function FollowTarget(target)
if not target then return end
FollowUnit(target)
return {}
end
---@class Command
---@field keywordRe string
---@field commanderOnly boolean
---@field callback fun(...: any): string[]
local commands = {
{ keywordRe = "^who", commanderOnly = false, callback = WhoPartitionedStinkies },
{ keywordRe = "^howmany", commanderOnly = false, callback = CountPartitionedStinkies },
{ keywordRe = "^classes", commanderOnly = false, callback = CountClassPartitionedStinkies },
{ keywordRe = "^help", commanderOnly = false, callback = HelpRu },
{ keywordRe = "^helpen", commanderOnly = false, callback = HelpEn },
{ keywordRe = "^joingroup", commanderOnly = false, callback = JoinGroup },
{ keywordRe = "^leavegroup", commanderOnly = false, callback = LeaveGroup },
{ keywordRe = "^follow", commanderOnly = false, callback = FollowTarget },
}
local commanderChannelFrame = CreateFrame("Frame")
commanderChannelFrame:RegisterEvent("CHAT_MSG_CHANNEL")
commanderChannelFrame:SetScript("OnEvent", function(self, event, msg, sender, ...)
if not Heimdall_Data.config.commander.enabled then return end
local channelId = select(6, ...)
local _, channelname = GetChannelName(channelId)
if channelname ~= Heimdall_Data.config.commander.masterChannel then return end
sender = string.match(sender, "^[^-]+")
for _, command in ipairs(commands) do
local enabled = Heimdall_Data.config.commander.commands[command.keywordRe] == true or false
if enabled and
(not command.commanderOnly
or (command.commanderOnly
and sender == Heimdall_Data.config.commander.commander)) then
if msg:match(command.keywordRe) then
local messages = command.callback({ strsplit(" ", msg) })
for _, message in ipairs(messages) do
---@type Message
local msg = {
channel = "CHANNEL",
data = channelname,
message = message
}
table.insert(shared.messenger.queue, msg)
end
end
end
end
end)
print("Heimdall - Commander loaded")
end

1321
Modules/Config.lua Normal file

File diff suppressed because it is too large Load Diff

View File

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

25
Modules/Dueler.lua Normal file
View File

@@ -0,0 +1,25 @@
local addonname, shared = ...
---@cast shared HeimdallShared
---@cast addonname string
---@diagnostic disable-next-line: missing-fields
shared.Dueler = {}
function shared.Dueler.Init()
local frame = CreateFrame("Frame")
frame:RegisterEvent("DUEL_REQUESTED")
frame:SetScript("OnEvent", function(self, event, sender)
if not Heimdall_Data.config.dueler.enabled then return end
local allow = Heimdall_Data.config.agents[sender]
if allow then
print("Heimdall - Dueler - Accepting duel from " .. sender)
AcceptDuel()
else
if Heimdall_Data.config.dueler.autoDecline then
print("Heimdall - Dueler - Auto declining duel from " .. sender)
CancelDuel()
end
end
end)
print("Heimdall - Dueler loaded")
end

View File

@@ -1,11 +1,11 @@
local addonname, data = ...
---@cast data HeimdallData
local addonname, shared = ...
---@cast shared HeimdallShared
---@cast addonname string
if not data.dumpTable then
if not shared.dumpTable then
---@param table table
---@param depth number?
data.dumpTable = function(table, depth)
shared.dumpTable = function(table, depth)
if not table then
print(tostring(table))
return
@@ -20,7 +20,7 @@ if not data.dumpTable then
for k, v in pairs(table) do
if (type(v) == "table") then
print(string.rep(" ", depth) .. k .. ":")
data.dumpTable(v, depth + 1)
shared.dumpTable(v, depth + 1)
else
print(string.rep(" ", depth) .. k .. ": ", v)
end

36
Modules/Echoer.lua Normal file
View File

@@ -0,0 +1,36 @@
local addonname, shared = ...
---@cast shared HeimdallShared
---@cast addonname string
---@diagnostic disable-next-line: missing-fields
shared.Echoer = {}
function shared.Echoer.Init()
local frame = CreateFrame("Frame")
frame:RegisterEvent("CHAT_MSG_CHANNEL")
frame:SetScript("OnEvent", function(self, event, msg, sender, ...)
if not Heimdall_Data.config.echoer.enabled then return end
local channelId = select(6, ...)
local channelname = ""
---@type any[]
local channels = { GetChannelList() }
for i = 1, #channels, 2 do
---@type number
local id = channels[i]
---@type string
local name = channels[i + 1]
if id == channelId then
channelname = name
end
end
if channelname ~= Heimdall_Data.config.echoer.masterChannel then return end
if string.find(msg, "^" .. Heimdall_Data.config.echoer.prefix) then
local msg = string.sub(msg, string.len(Heimdall_Data.config.echoer.prefix) + 1)
SendChatMessage(msg, "SAY")
end
end)
print("Heimdall - Echoer loaded")
end

35
Modules/Emoter.lua Normal file
View File

@@ -0,0 +1,35 @@
local addonname, shared = ...
---@cast shared HeimdallShared
---@cast addonname string
---@diagnostic disable-next-line: missing-fields
shared.Emoter = {}
function shared.Emoter.Init()
local frame = CreateFrame("Frame")
frame:RegisterEvent("CHAT_MSG_CHANNEL")
frame:SetScript("OnEvent", function(self, event, msg, sender, ...)
if not Heimdall_Data.config.emoter.enabled then return end
local channelId = select(6, ...)
local channelname = ""
---@type any[]
local channels = { GetChannelList() }
for i = 1, #channels, 2 do
---@type number
local id = channels[i]
---@type string
local name = channels[i + 1]
if id == channelId then
channelname = name
end
end
if channelname ~= Heimdall_Data.config.emoter.masterChannel then return end
if string.find(msg, "^" .. Heimdall_Data.config.emoter.prefix) then
local emote = string.sub(msg, string.len(Heimdall_Data.config.emoter.prefix) + 1)
DoEmote(emote)
end
end)
print("Heimdall - Emoter loaded")
end

139
Modules/Inviter.lua Normal file
View File

@@ -0,0 +1,139 @@
local addonname, shared = ...
---@cast shared HeimdallShared
---@cast addonname string
---@diagnostic disable-next-line: missing-fields
shared.Inviter = {}
function shared.Inviter.Init()
---@type Timer
local updateTimer = nil
local function FixGroup()
if not IsInRaid() then ConvertToRaid() end
if Heimdall_Data.config.inviter.allAssist then SetEveryoneIsAssistant() end
--shared.dumpTable(Heimdall_Data.config.inviter)
if Heimdall_Data.config.inviter.agentsAssist then
--shared.dumpTable(Heimdall_Data.config.agents)
for name, _ in pairs(Heimdall_Data.config.agents) do
if UnitInParty(name)
and not UnitIsGroupLeader(name)
and not UnitIsRaidOfficer(name) then
print("Promoting " .. name .. " to assistant")
PromoteToAssistant(name, true)
end
end
end
end
local framePool = {}
---@param name string
local function OverlayKickButtonElvUI(name)
for group = 1, 8 do
for player = 1, 5 do
local button = _G[string.format("ElvUF_RaidGroup%dUnitButton%d", group, player)]
local unitName = button and button.unit and UnitName(button.unit)
if unitName == name then
local overlayButton = framePool[button.unit] or
CreateFrame("Button",
string.format("HeimdallKickButton%s", button.unit, button, "SecureActionButtonTemplate"))
framePool[button.unit] = overlayButton
overlayButton:SetSize(button.UNIT_WIDTH/2, button.UNIT_HEIGHT/2)
overlayButton:SetPoint("CENTER", button, "CENTER", 0, 0)
overlayButton:SetNormalTexture("Interface\\Buttons\\UI-GroupLoot-KickIcon")
overlayButton:SetHighlightTexture("Interface\\Buttons\\UI-GroupLoot-KickIcon")
overlayButton:SetPushedTexture("Interface\\Buttons\\UI-GroupLoot-KickIcon")
overlayButton:SetDisabledTexture("Interface\\Buttons\\UI-GroupLoot-KickIcon")
overlayButton:SetAlpha(0.5)
overlayButton:Show()
overlayButton:SetScript("OnClick", function()
UninviteUnit(name)
overlayButton:Hide()
end)
-- button:SetAttribute("type", "macro")
-- button:SetAttribute("macrotext", "/kick " .. unit)
return
end
end
end
end
---@type table<string, number>
local groupMembers = {}
local function CleanGroups()
if not Heimdall_Data.config.inviter.kickOffline then return end
print("Cleaning groups")
local now = GetTime()
for i = 1, 40 do
local unit = "raid" .. i
if UnitExists(unit) then
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
for name, time in pairs(groupMembers) do
if time < now - Heimdall_Data.config.inviter.afkThreshold then
print(string.format("Kicking %s for being offline", name))
-- Blyat this is protected...
-- UninviteUnit(name)
OverlayKickButtonElvUI(name)
end
if not UnitInParty(name) then
print(string.format("%s no longer in party", name))
groupMembers[name] = nil
end
end
end
local function Tick()
CleanGroups()
C_Timer.NewTimer(Heimdall_Data.config.inviter.cleanupInterval, Tick, 1)
end
Tick()
local inviterGroupFrame = CreateFrame("Frame")
inviterGroupFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
inviterGroupFrame:SetScript("OnEvent", function(self, event, ...)
if not Heimdall_Data.config.inviter.enabled then return end
if not UnitIsGroupLeader("player") then return 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 not Heimdall_Data.config.inviter.enabled then return end
local channelId = select(6, ...)
local channelname = ""
---@type any[]
local channels = { GetChannelList() }
for i = 1, #channels, 2 do
---@type number
local id = channels[i]
---@type string
local name = channels[i + 1]
if id == channelId then
channelname = name
end
end
if channelname ~= Heimdall_Data.config.inviter.listeningChannel then return end
if msg == Heimdall_Data.config.inviter.keyword then InviteUnit(sender) end
end)
print("Heimdall - Inviter loaded")
end

65
Modules/Macroer.lua Normal file
View File

@@ -0,0 +1,65 @@
local addonname, shared = ...
---@cast shared HeimdallShared
---@cast addonname string
---@diagnostic disable-next-line: missing-fields
shared.Macroer = {}
function shared.Macroer.Init()
---@class stinky
---@field name string
---@field class string
---@field seenAt number
---@field hostile boolean
---@type table<string, stinky>
local recentStinkies = {}
local function FindOrCreateMacro(macroName)
local idx = GetMacroIndexByName(macroName)
if idx == 0 then
CreateMacro(macroName, "INV_Misc_QuestionMark", "")
end
idx = GetMacroIndexByName(macroName)
return idx
end
---@param stinkies table<string, stinky>
local function FixMacro(stinkies)
if not Heimdall_Data.config.macroer.enabled then return end
if InCombatLockdown() then 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
table.insert(sortedStinkies, stinky)
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)
local lines = { "/targetenemy" }
for _, stinky in pairs(sortedStinkies) do
if stinky.seenAt > GetTime() - 600 then
print(string.format("Macroing %s", stinky.name))
lines[#lines + 1] = string.format("/tar %s", stinky.name)
end
end
local idx = FindOrCreateMacro("HeimdallTarget")
local body = strjoin("\n", unpack(lines))
EditMacro(idx, "HeimdallTarget", "INV_Misc_QuestionMark", body)
end
shared.stinkyTracker.stinkies:onChange(function(value)
FixMacro(value)
end)
print("Heimdall - Macroer loaded")
end

84
Modules/Messenger.lua Normal file
View File

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

667
Modules/ReactiveValue.lua Normal file
View File

@@ -0,0 +1,667 @@
local function Init()
local metadata = {
---@param self ReactiveValue
---@param other ReactiveValue
---@return ReactiveValue|nil
__add = function(self, other)
local otherType = type(other)
if otherType == "table" and other._type and other._type == self._type and other._value then
return self._value + other._value
end
if otherType == "string" and self._type == otherType then
return self._value .. other
end
if otherType == "number" and self._type == otherType then
return self._value + other
end
return nil
end,
---@param self ReactiveValue
---@param other ReactiveValue
---@return ReactiveValue|nil
__mul = function(self, other)
local otherType = type(other)
if otherType == "table" and other._type and other._type == self._type and other._value then
return self._value * other._value
end
if otherType == "number" and self._type == otherType then
return self._value * other
end
return nil
end,
---@param self ReactiveValue
---@param other ReactiveValue
---@return ReactiveValue|nil
__sub = function(self, other)
local otherType = type(other)
if otherType == "table" and other._type and other._type == self._type and other._value then
return self._value - other._value
end
if otherType == "number" and self._type == otherType then
return self._value - other
end
return nil
end,
---@param self ReactiveValue
---@param other ReactiveValue
---@return ReactiveValue|nil
__div = function(self, other)
local otherType = type(other)
if otherType == "table" and other._type and other._type == self._type and other._value then
return self._value / other._value
end
if otherType == "number" and self._type == otherType then
return self._value / other
end
return nil
end,
---@param self ReactiveValue
---@param other ReactiveValue
---@return ReactiveValue|nil
__mod = function(self, other)
local otherType = type(other)
if otherType == "table" and other._type and other._type == self._type and other._value then
return self._value % other._value
end
if otherType == "number" and self._type == otherType then
return self._value % other
end
return nil
end,
---@param self ReactiveValue
---@param other ReactiveValue
---@return ReactiveValue|nil
__pow = function(self, other)
local otherType = type(other)
if otherType == "table" and other._type and other._type == self._type and other._value then
return self._value ^ other._value
end
if otherType == "number" and self._type == otherType then
return self._value ^ other
end
return nil
end,
---@param self ReactiveValue
---@param other ReactiveValue
---@return boolean
__eq = function(self, other)
local otherType = type(other)
if otherType == "table" and other._type and other._type == self._type and other._value then
return self._value == other._value
end
return self._value == other
end,
---@param self ReactiveValue
---@param other ReactiveValue
---@return boolean
__lt = function(self, other)
local otherType = type(other)
if otherType == "table" and other._type and other._type == self._type and other._value then
return self._value < other._value
end
return self._value < other
end,
---@param self ReactiveValue
---@param other ReactiveValue
---@return boolean
__le = function(self, other)
local otherType = type(other)
if otherType == "table" and other._type and other._type == self._type and other._value then
return self._value <= other._value
end
return self._value <= other
end,
---@param self ReactiveValue
---@param other ReactiveValue
---@return boolean
__gt = function(self, other)
local otherType = type(other)
if otherType == "table" and other._type and other._type == self._type and other._value then
return self._value > other._value
end
return self._value > other
end,
---@param self ReactiveValue
---@param other ReactiveValue
---@return boolean
__ge = function(self, other)
local otherType = type(other)
if otherType == "table" and other._type and other._type == self._type and other._value then
return self._value >= other._value
end
return self._value >= other
end,
---@param self ReactiveValue
---@return number
__len = function(self)
if self._type == "table" then
return #self._value
end
if self._type == "string" then
return string.len(self._value)
end
return 0
end,
---@param self ReactiveValue
---@return string
__tostring = function(self)
return tostring(self._value)
end,
---@param self ReactiveValue
---@param key string
---@param value any
---@return nil
__newindex = function(self, key, value)
local setupComplete = rawget(self, "_setupComplete")
if setupComplete == nil or setupComplete == false then
rawset(self, key, value)
return
end
if self._type ~= "table" then
rawset(self, key, value)
return
end
self._value[key] = value
local ChangedKey = { key }
-- If the value being assigned is a ReactiveValue
-- Then listen to changes on it as well
-- And propagate those changes upwards
if self._recursive and getmetatable(value) == getmetatable(self) then
self:_setupListeners(key, value)
end
self:_notify()
self:_notifyFieldChanged(ChangedKey)
self:_notifyAnyFieldChanged(ChangedKey)
end,
---@param self ReactiveValue
---@param key string
---@return any|nil
__index = function(self, key)
local value = rawget(self, key)
if value ~= nil then
return value
end
if rawget(self, "_type") ~= "table" then
return nil
end
local innerTable = rawget(self, "_value")
if innerTable ~= nil then
return rawget(innerTable, key)
end
return nil
end
-- __index = ReactiveValue
}
--- Sadly I could not get @generic to play nice with this class
--- I think it's not ready yet, there are issues on github describing similar problems and it is marked as WIP...
--- Guess I'll have to live without it for now and specify type of a RV in #type
---## A type safe value that can be listened to for changes
---### **Always use RV:set() for setting primitive values**
--- Supports primitive values and tables<br>
--- Tables can be listened to for changes on any field or a specific field<br>
---### Example usage (value):<br>
--- ```lua
--- local test = ReactiveValue.new(1)
--- test:onChange(function(value)
--- print("test changed to " .. value)
--- end)
--- test:set(2)
--- test:set(test + 3)
--- ```
---### Example usage (table):<br>
--- ```lua
--- local test = ReactiveValue.new({1, 2, 3})
--- test:onAnyFieldChange(function(field, value)
--- print(string.format("test.%s changed to %s", table.concat(field, "."), value))
--- end)
--- test[1] = 4 -- test.1 changed to 4
--- test[4] = {1, 2, 3} -- test.4 changed to <table>
--- test[4][1] = 14 -- No log(!!) because test[4] is a table and not a ReactiveValue
--- ```
---### To trigger a callback for `test[4][1]` in the previous example do:<br>
--- ```lua
--- local test = ReactiveValue.new({1, 2, 3}, true)
--- test:onAnyFieldChange(function(field, value)
--- print(string.format("test.%s changed to %s", table.concat(field, "."), value))
--- end)
--- test[1] = 4 -- test.1 changed to 4
--- test[4] = {1, 2, 3} -- test.4 changed to <table>
--- test[4][1] = 14 -- test.4.1 changed to 14
--- ```
---### To listen to a specific field of a table do:<br>
--- ```lua
--- local test = ReactiveValue.new({1, 2, 3}, true)
--- test:onFieldChange("1", function(value)
--- print("test.1 changed to " .. value)
--- end)
--- test[1] = 4 -- test.1 changed to 4
--- test[4] = {1, 2, 3} -- Does not trigger callback
-- ```
---@class ReactiveValue
---@field _listeners table<function, boolean>
---@field _fieldListeners table<string, table<function, boolean>>
---@field _anyFieldListeners table<number, table<function, boolean>>
---@field _oneTimeListeners table<function, boolean>
---@field _value any
---@field _type string
---@field _recursive boolean?
ReactiveValue = {
---#### Get the underlying value of a ReactiveValue
---@param self ReactiveValue
---@return any
get = function(self) end,
---### Set the underlying value of a ReactiveValue triggering listener callbacks
---@param self ReactiveValue
---@param newValue any
set = function(self, newValue) end,
---## EVENT
---### Register a listener that is triggered whenever the underlying value changes
--- Returns a function that can be called to undo the callback
---@param self ReactiveValue
---@param callback fun(value: any, type: string)
---@return fun(): nil
onChange = function(self, callback) end,
---## EVENT
---### Register a listener that is triggered whenever a specific field of a table changes
--- Returns a function that can be called to undo the callback
---@param self ReactiveValue
---@param field string
---@param callback fun(field: string[], value: any, type: string)
---@return fun(): nil
onFieldChange = function(self, field, callback) end,
---## EVENT
---### Register a listener that is triggered whenever any field of a table changes
--- Returns a function that can be called to undo the callback
---@param self ReactiveValue
---@param callback fun(field: string[], value: any, type: string)
---@param depth number? How deep to listen for changes
---@return fun(): nil
onAnyFieldChange = function(self, callback, depth) end,
---## EVENT
---### Register a listener that is triggered ONCE whenever the underlying value changes
--- Returns a function that can be called to undo the callback
---@param self ReactiveValue
---@param callback fun(value: any, type: string)
---@return fun(): nil
once = function(self, callback) end,
---### Setup listeners for all fields of a table recursively
--- This is used to ensure that listeners are notified recursively
---@param self ReactiveValue
_setupAllListenersRecursively = function(self) end,
---### Setup listeners for a specific field of a table recursively
--- This is used to ensure that listeners are notified recursively
---@param self ReactiveValue
_setupListeners = function(self, key, value, recursive) end,
---### Notify listeners that the underlying value has changed
---@param self ReactiveValue
---@return nil
---#### Event contains:
--- 2. value: any - The new value of the changed field
--- 3. type: string - The type of the new value of the changed field
_notify = function(self) end,
---### Notify listeners that a specific field of the underlying value has changed
---#### Event contains:
--- 1. field: table<string> - A list of keys that lead to the changed field
--- 2. value: any - The new value of the changed field
--- 3. type: string - The type of the new value of the changed field
---@param self ReactiveValue
_notifyFieldChanged = function(self, field) end,
---### Notify listeners that any field of the underlying value has changed
---#### Event contains:
--- 1. field: table<string> - A list of keys that lead to the changed field
--- 2. value: any - The new value of the changed field
--- 3. type: string - The type of the new value of the changed field
_notifyAnyFieldChanged = function(self, field) end,
}
---### Constructor
---@param initialValue any
---@param recursive boolean?
---@return ReactiveValue
ReactiveValue.new = function(initialValue, recursive)
local self = setmetatable({}, metadata)
self._listeners = {}
self._fieldListeners = {}
self._anyFieldListeners = {}
self._oneTimeListeners = {}
self._value = initialValue
self._type = type(initialValue)
self._recursive = recursive or false
---@return any
self.get = function(self)
return self._value
end
---@param newValue any
self.set = function(self, newValue)
if self._value == newValue then
return
end
if type(newValue) ~= self._type then
error("Expected " .. self._type .. ", got " .. type(newValue))
return
end
self._value = newValue
self:_notify()
end
self.onChange = function(self, callback)
if type(callback) ~= "function" then
error("Expected function, got " .. type(callback))
return function() end
end
self._listeners[callback] = true
return function()
self._listeners[callback] = nil
end
end
self.onFieldChange = function(self, field, callback)
if type(callback) ~= "function" then
error("Expected function, got " .. type(callback))
return function() end
end
if self._fieldListeners[field] == nil then
self._fieldListeners[field] = {}
end
self._fieldListeners[field][callback] = true
return function()
self._fieldListeners[field][callback] = nil
end
end
self.onAnyFieldChange = function(self, callback, depth)
depth = depth or 99999
if type(callback) ~= "function" then
error("Expected function, got " .. type(callback))
return function() end
end
if self._anyFieldListeners[depth] == nil then
self._anyFieldListeners[depth] = {}
end
self._anyFieldListeners[depth][callback] = true
return function()
self._anyFieldListeners[depth][callback] = nil
end
end
self.once = function(self, callback)
if type(callback) ~= "function" then
error("Expected function, got " .. type(callback))
return function() end
end
self._oneTimeListeners[callback] = true
return function()
self._oneTimeListeners[callback] = nil
end
end
self._setupAllListenersRecursively = function(self)
if self._type ~= "table" then
return
end
for key, value in pairs(self._value) do
self:_setupListeners(key, value, true)
end
end
---@param key string
---@param value any
---@param recursive boolean?
self._setupListeners = function(self, key, value, recursive)
recursive = recursive or false
if self._type ~= "table" then
return
end
if getmetatable(value) ~= getmetatable(self) then
return
end
value._recursive = true
if value._type == "table" then
value:onAnyFieldChange(function(key2)
ChangedKey = { key, table.unpack(key2) }
self:_notifyFieldChanged(ChangedKey)
self:_notifyAnyFieldChanged(ChangedKey)
end)
else
value:onChange(function(newVal)
ChangedKey = { key }
self:_notifyFieldChanged(ChangedKey)
self:_notifyAnyFieldChanged(ChangedKey)
end)
end
if recursive then
value:_setupAllListenersRecursively()
end
end
if recursive then
self:_setupAllListenersRecursively()
end
self._notify = function(self)
for listener, _ in pairs(self._oneTimeListeners) do
-- task.spawn(listener, self._value, self._type)
listener(self._value, self._type)
self._oneTimeListeners[listener] = nil
end
for listener, _ in pairs(self._listeners) do
-- task.spawn(listener, self._value, self._type)
listener(self._value, self._type)
end
end
-- TODO: Maybe implement some sort of regex here or something...
-- Such as listening to *.field1 or something
-- But this (having to loop over listeners and evaluate some condition) would tank performance
-- Compared to a simple lookup
-- So I'm not going to do anything about it for now, until I figure out a better way
---@param field table<string, string> A list of keys that lead to the changed field
---@return nil
self._notifyFieldChanged = function(self, field)
local value = self._value
for _, key in ipairs(field) do
value = value[key]
end
local strfield = table.concat(field, ".")
if self._fieldListeners[strfield] == nil then
return
end
for listener, _ in pairs(self._fieldListeners[strfield]) do
-- task.spawn(listener, value, type(value))
listener(value, type(value))
end
end
---@param self ReactiveValue
---@param field table<string, string> A list of keys that lead to the changed field
---@return nil
self._notifyAnyFieldChanged = function(self, field)
local value = self._value
for _, key in ipairs(field) do
value = value[key]
end
local keyDepth = #field
for listenerDepth, listeners in pairs(self._anyFieldListeners) do
if listenerDepth >= keyDepth then
for listener, _ in pairs(listeners) do
-- The reason this also returns type(value) is so that clients don't have to compute type(value)
-- I assume some of them might want to do it so computing it once is probably better than having every client compute it for themselves
-- task.spawn(listener, field, value, type(value))
listener(field, value, type(value))
end
end
end
end
self._setupComplete = true
return self
end
-- S -- begintest
-- S local invocations = 0
-- S -- Integer example
-- S local test = ReactiveValue.new(1)
-- S test:onChange(function(value)
-- S invocations = invocations + 1
-- S print("test changed to " .. value)
-- S end)
-- S test:set(2)
-- S assert(invocations == 1)
-- S
-- S invocations = 0
-- String example
-- S test = ReactiveValue.new("test")
-- S test:onChange(function(value)
-- S invocations = invocations + 1
-- S print("test changed to " .. value)
-- S end)
-- S test:set("test2")
-- S assert(invocations == 1)
-- S
-- S -- Type safety example
-- S local res, err = pcall(test.set, test, 1)
-- S assert(res == false)
-- S assert(err:find("Expected string, got number"))
-- S
-- S -- Table example
-- S invocations = 0
-- S test = ReactiveValue.new({1, 2, 3})
-- S local clbk = test:onChange(function(value)
-- S invocations = invocations + 1
-- S print("test changed to")
-- S dumpTable(value, 0)
-- S end)
-- S test:set({1, 2, 3, 4})
-- S assert(invocations == 1)
-- S
-- S -- Callback removal example
-- S clbk()
-- S
-- S invocations = 0
-- S -- Any field change example
-- S clbk = test:onAnyFieldChange(function(field, value)
-- S invocations = invocations + 1
-- S print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
-- S end)
-- S test.Pero = 1
-- S test.Pero = nil
-- S assert(invocations == 2)
-- S clbk()
-- S
-- S invocations = 0
-- S -- Field change example
-- S test:onFieldChange("Pero", function(value)
-- S invocations = invocations + 1
-- S print("test.Pero changed to " .. value)
-- S end)
-- S test.Pero = 2
-- S assert(invocations == 1)
-- S
-- S invocations = 0
-- S -- One time listener example
-- S test:once(function(value)
-- S invocations = invocations + 1
-- S print("test changed to")
-- S dumpTable(value, 0)
-- S end)
-- S test:set({3, 2, 1})
-- S assert(invocations == 1)
-- S
-- S invocations = 0
-- S -- Table push example
-- S test = ReactiveValue.new({})
-- S test:onChange(function(value)
-- S invocations = invocations + 1
-- S print("test changed to")
-- S dumpTable(value, 0)
-- S end)
-- S test:onAnyFieldChange(function(field, value)
-- S invocations = invocations + 1
-- S print("test." .. table.concat(field, ".") .. " changed to " .. value)
-- S end)
-- S test[#test + 1] = 4
-- S assert(invocations == 2)
-- S
-- S invocations = 0
-- S test = ReactiveValue.new({
-- S name = "pero",
-- S coins = ReactiveValue.new(1)
-- S })
-- S test.coins:onChange(function(value)
-- S invocations = invocations + 1
-- S print("test.coins changed to " .. value)
-- S end)
-- S test.coins:set(2)
-- S assert(invocations == 1)
-- S
-- S invocations = 0
-- S test = ReactiveValue.new({
-- S name = "pero",
-- S coins = ReactiveValue.new(1)
-- S }, true)
-- S test:onAnyFieldChange(function(field, value)
-- S invocations = invocations + 1
-- S print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
-- S end)
-- S test.coins:set(2)
-- S test.pero2 = ReactiveValue.new({})
-- S test.pero2.coins = ReactiveValue.new(1)
-- S test.pero2.coins:set(2)
-- S assert(invocations == 4)
-- S
-- S invocations = 0
-- S test = ReactiveValue.new({
-- S name = "pero",
-- S coins = ReactiveValue.new({
-- S value = ReactiveValue.new(1)
-- S })
-- S }, true)
-- S test:onAnyFieldChange(function(field, value)
-- S invocations = invocations + 1
-- S print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
-- S end)
-- S test.coins.value:set(2)
-- S assert(invocations == 1)
-- S
-- S invocations = 0
-- S test = ReactiveValue.new({}, true)
-- S test.coins = ReactiveValue.new({})
-- S test.coins.value = ReactiveValue.new(1)
-- S test:onAnyFieldChange(function(field, value)
-- S invocations = invocations + 1
-- S print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
-- S end)
-- S test.coins.value:set(3)
-- S assert(invocations == 1)
--S
--S invocations = 0
--S test = ReactiveValue.new({}, true)
--S test:onAnyFieldChange(function(field, value)
--S invocations = invocations + 1
--S print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
--S end, 1)
--S test.test2 = ReactiveValue.new({}, true)
--S test.test2.test3 = ReactiveValue.new(1)
--S assert(invocations == 1)
--S
-- S -- endtest
end
local frame = CreateFrame("Frame")
frame:RegisterEvent("PLAYER_LOGIN")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("GUILD_ROSTER_UPDATE")
frame:SetScript("OnEvent", function(self, event, ...)
Init()
end)
Init()

View File

@@ -1,14 +1,10 @@
local addonname, data = ...
---@cast data HeimdallData
local addonname, shared = ...
---@cast shared HeimdallShared
---@cast addonname string
data.Spotter = {}
function data.Spotter.Init()
if not data.config.spotter.enabled then
print("Heimdall - Spotter disabled")
return
end
---@diagnostic disable-next-line: missing-fields
shared.Spotter = {}
function shared.Spotter.Init()
local function FormatHP(hp)
if hp > 1e9 then
return string.format("%.1fB", hp / 1e9)
@@ -31,16 +27,17 @@ function data.Spotter.Init()
---@return boolean
---@return string? error
local function ShouldNotify(unit, name, faction, hostile)
if data.config.spotter.stinky then
if data.config.stinkies[name] then return true end
if Heimdall_Data.config.agents[name] then return false end
if Heimdall_Data.config.spotter.stinky then
if Heimdall_Data.config.stinkies[name] then return true end
end
if data.config.spotter.alliance then
if Heimdall_Data.config.spotter.alliance then
if faction == "Alliance" then return true end
end
if data.config.spotter.hostile then
if Heimdall_Data.config.spotter.hostile then
if hostile then return true end
end
return data.config.spotter.everyone
return Heimdall_Data.config.spotter.everyone
end
---@param unit string
@@ -53,14 +50,14 @@ function data.Spotter.Init()
if not name then return string.format("Could not find name for unit %s", tostring(unit)) end
local time = GetTime()
if throttleTable[name] and time - throttleTable[name] < data.config.spotter.throttleTime then
if throttleTable[name] and time - throttleTable[name] < Heimdall_Data.config.spotter.throttleTime then
return string.format("Throttled %s", tostring(name))
end
throttleTable[name] = time
local race = UnitRace(unit)
if not race then return string.format("Could not find race for unit %s", tostring(unit)) end
local faction = data.raceMap[race]
local faction = shared.raceMap[race]
if not faction then return string.format("Could not find faction for race %s", tostring(race)) end
local hostile = UnitCanAttack("player", unit)
@@ -73,8 +70,11 @@ function data.Spotter.Init()
local maxHp = UnitHealthMax(unit)
if not maxHp then return string.format("Could not find maxHp for unit %s", tostring(unit)) end
local location = data.config.spotter.zoneOverride
if not location then
local class = UnitClass(unit)
if not class then return string.format("Could not find class for unit %s", tostring(unit)) end
local location = Heimdall_Data.config.spotter.zoneOverride
if not location or location == "" then
local zone = GetZoneText()
if not zone then return string.format("Could not find zone for unit %s", tostring(unit)) end
local subzone = GetSubZoneText()
@@ -82,31 +82,38 @@ function data.Spotter.Init()
location = string.format("%s (%s)", zone, subzone)
end
local stinky = data.config.stinkies[name] or false
local text = string.format("I see (%s) %s %s of race %s (%s) with health %s/%s at %s",
local x, y = GetPlayerMapPosition("player")
local stinky = Heimdall_Data.config.stinkies[name] or false
local text = string.format("I see (%s) %s/%s %s of race %s (%s) with health %s/%s at %s (%2.2f, %2.2f)",
hostile and "Hostile" or "Friendly",
stinky and string.format("(%s)", "!!!!") or "",
name,
class,
stinky and string.format("(%s)", "!!!!") or "",
race,
faction,
FormatHP(hp),
FormatHP(maxHp),
location)
location,
x * 100, y * 100)
---@type Message
local msg = {
channel = "CHANNEL",
data = data.config.spotter.notifyChannel,
data = Heimdall_Data.config.spotter.notifyChannel,
message = text
}
data.dumpTable(msg)
table.insert(data.messenger.queue, msg)
--shared.dumpTable(msg)
table.insert(shared.messenger.queue, msg)
end
local frame = CreateFrame("Frame")
frame:RegisterEvent("NAME_PLATE_UNIT_ADDED")
frame:RegisterEvent("TARGET_UNIT_CHANGED")
frame:RegisterEvent("UNIT_TARGET")
frame:SetScript("OnEvent", function(self, event, unit)
if not Heimdall_Data.config.spotter.enabled then return end
if event == "UNIT_TARGET" then
unit = "target"
end
local err = NotifySpotted(unit)
if err then
print(string.format("Error notifying %s: %s", tostring(unit), tostring(err)))

109
Modules/StinkyTracker.lua Normal file
View File

@@ -0,0 +1,109 @@
local addonname, shared = ...
---@cast shared HeimdallShared
---@cast addonname string
---@diagnostic disable-next-line: missing-fields
shared.StinkyTracker = {}
function shared.StinkyTracker.Init()
shared.stinkyTracker = {
stinkies = ReactiveValue.new({})
}
local whoRegex = "([^ -/]+)-?%w*/(%w+)"
---@param msg string
---@return table<string, stinky>
local function ParseWho(msg)
local stinkies = {}
for name, class in string.gmatch(msg, whoRegex) do
stinkies[name] = {
name = name,
class = class,
seenAt = GetTime(),
hostile = true
}
end
return stinkies
end
local seeRegex = "I see %((%w+)%) ([^ -/]+)-?%w*/(%w+)"
---@param msg string
---@return table<string, stinky>
local function ParseSee(msg)
local stinkies = {}
local aggression, name, class = string.match(msg, seeRegex)
if not name or not class then
return stinkies
end
local stinky = {
name = name,
class = class,
seenAt = GetTime(),
hostile = aggression == "Hostile"
}
stinkies[name] = stinky
return stinkies
end
local arrivedRegex = "([^ -/]+)-?%w* of class (%w+)"
local arrivedRegexAlt = "([^ -/]+)-?%w* %(!!!!%) of class (%w+)"
---@param msg string
---@return table<string, stinky>
local function ParseArrived(msg)
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
return stinkies
end
local stinky = {
name = name,
class = class,
seenAt = GetTime(),
hostile = true
}
stinkies[name] = stinky
return stinkies
end
local frame = CreateFrame("Frame")
frame:RegisterEvent("CHAT_MSG_CHANNEL")
frame:SetScript("OnEvent", function(self, event, msg, sender, ...)
if not Heimdall_Data.config.stinkyTracker.enabled then return end
local channelId = select(6, ...)
local _, channelname = GetChannelName(channelId)
if channelname ~= Heimdall_Data.config.stinkyTracker.masterChannel then return end
if string.find(msg, "^who:") then
local whoStinkies = ParseWho(msg)
for name, stinky in pairs(whoStinkies) do
if stinky.hostile then
shared.stinkyTracker.stinkies[name] = stinky
end
end
end
if string.find(msg, "^I see") then
local seeStinkies = ParseSee(msg)
for name, stinky in pairs(seeStinkies) do
if stinky.hostile then
shared.stinkyTracker.stinkies[name] = stinky
end
if not stinky.hostile then
shared.stinkyTracker.stinkies[name] = nil
end
end
end
if string.find(msg, " and guild ") then
local arrivedStinkies = ParseArrived(msg)
for name, stinky in pairs(arrivedStinkies) do
shared.stinkyTracker.stinkies[name] = stinky
end
end
for name, stinky in pairs(shared.stinkyTracker.stinkies) do
if Heimdall_Data.config.agents[name] then
shared.stinkyTracker.stinkies[name] = nil
end
end
end)
print("Heimdall - StinkyTracker loaded")
end

View File

@@ -1,14 +1,10 @@
local addonname, data = ...
---@cast data HeimdallData
local addonname, shared = ...
---@cast shared HeimdallShared
---@cast addonname string
data.Whoer = {}
function data.Whoer.Init()
if not data.config.who.enabled then
print("Heimdall - Whoer disabled")
return
end
---@diagnostic disable-next-line: missing-fields
shared.Whoer = {}
function shared.Whoer.Init()
if not Heimdall_Data.who then Heimdall_Data.who = {} end
if not Heimdall_Data.who.data then Heimdall_Data.who.data = {} end
@@ -51,22 +47,23 @@ function data.Whoer.Init()
---@return string
ToString = function(self)
local out = string.format("%s %s %s\nFirst: %s Last: %s Seen: %3d",
data.padString(self.name, 16, true),
data.padString(self.guild, 26, false),
data.padString(self.zone, 26, false),
data.padString(self.firstSeen, 10, true),
data.padString(self.lastSeen, 10, true),
shared.padString(self.name, 16, true),
shared.padString(self.guild, 26, false),
shared.padString(self.zone, 26, false),
shared.padString(self.firstSeen, 10, true),
shared.padString(self.lastSeen, 10, true),
self.seenCount)
return string.format("|cFF%s%s|r", data.classColors[self.class], out)
return string.format("|cFF%s%s|r", shared.classColors[self.class], out)
end,
---@return string
NotifyMessage = function(self)
local text = string.format(
"%s of class %s, race %s (%s) and guild %s in %s, first seen: %s, last seen: %s, times seen: %d",
"%s %s of class %s, race %s (%s) and guild %s in %s, first seen: %s, last seen: %s, times seen: %d",
self.name,
self.stinky and "(!!!!)" or "",
self.class,
self.race,
tostring(data.raceMap[self.race]),
tostring(shared.raceMap[self.race]),
self.guild,
self.zone,
self.firstSeen,
@@ -103,34 +100,30 @@ function data.Whoer.Init()
end
---@type WHOFilter
local AllianceFilter = function(name, guild, level, race, class, zone)
if not race then
return false
end
if not data.raceMap[race] then
return false
end
return data.raceMap[race] == "Alliance"
if not race then return false end
if not shared.raceMap[race] then return false end
return shared.raceMap[race] == "Alliance"
end
local whoQueryIdx = 1
---@type table<number, WHOQuery>
---@type WHOQuery[]
local whoQueries = {
WHOQuery.new("g-\"БеспредеЛ\"", {}),
--WHOQuery.new("g-\"Dovahkin\"", {}),
WHOQuery.new(
"z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" z-\"Echo Isles\" r-\"Human\" r-\"Dwarf\" r-\"Night Elf\"",
"z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Human\" r-\"Dwarf\" r-\"Night Elf\"",
{ NotSiegeOfOrgrimmarFilter, AllianceFilter }),
WHOQuery.new(
"z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" z-\"Echo Isles\" r-\"Gnome\" r-\"Draenei\" r-\"Worgen\"",
"z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Gnome\" r-\"Draenei\" r-\"Worgen\"",
{ NotSiegeOfOrgrimmarFilter, AllianceFilter }),
WHOQuery.new(
"z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" z-\"Echo Isles\" r-\"Kul Tiran\" r-\"Dark Iron Dwarf\" r-\"Void Elf\"",
"z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Kul Tiran\" r-\"Dark Iron Dwarf\" r-\"Void Elf\"",
{ NotSiegeOfOrgrimmarFilter, AllianceFilter }),
WHOQuery.new(
"z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" z-\"Echo Isles\" r-\"Lightforged Draenei\" r-\"Mechagnome\"",
"z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Lightforged Draenei\" r-\"Mechagnome\"",
{ NotSiegeOfOrgrimmarFilter, AllianceFilter }),
WHOQuery.new("Kekv Demonboo Dotmada Firobot Verminal", {})
WHOQuery.new("Kekv Firobot Tomoki", {})
}
local queryPending = false
local ttl = #whoQueries * 2
---@type WHOQuery?
local lastQuery = nil
@@ -138,8 +131,9 @@ function data.Whoer.Init()
---@param player Player
---@return string?
local function Notify(player)
if not Heimdall_Data.config.who.enabled then return end
if not player then return string.format("Cannot notify for nil player %s", tostring(player)) end
if not data.config.who.zoneNotifyFor[player.zone] then
if not Heimdall_Data.config.who.zoneNotifyFor[player.zone] then
return string.format("Not notifying for zone %s",
tostring(player.zone))
end
@@ -148,20 +142,20 @@ function data.Whoer.Init()
---@type Message
local msg = {
channel = "CHANNEL",
data = data.config.who.notifyChannel,
data = Heimdall_Data.config.who.notifyChannel,
message = text
}
table.insert(data.messenger.queue, msg)
table.insert(shared.messenger.queue, msg)
if data.config.who.doWhisper then
for _, name in pairs(data.config.whisperNotify) do
if Heimdall_Data.config.who.doWhisper then
for _, name in pairs(Heimdall_Data.config.whisperNotify) do
---@type Message
local msg = {
channel = "WHISPER",
data = name,
message = text
}
table.insert(data.messenger.queue, msg)
table.insert(shared.messenger.queue, msg)
end
end
@@ -171,34 +165,37 @@ function data.Whoer.Init()
---@param zone string
---@return string?
local function NotifyZoneChanged(player, zone)
if not Heimdall_Data.config.who.enabled then return end
if not player then return string.format("Cannot notify for nil player %s", tostring(player)) end
if not data.config.who.zoneNotifyFor[zone]
and not data.config.who.zoneNotifyFor[player.zone] then
if not Heimdall_Data.config.who.zoneNotifyFor[zone]
and not Heimdall_Data.config.who.zoneNotifyFor[player.zone] then
return string.format("Not notifying for zones %s and %s", tostring(zone), tostring(player.zone))
end
local text = string.format("%s of class %s and guild %s moved to %s",
local text = string.format("%s of class %s (%s - %s) and guild %s moved to %s",
player.name,
player.class,
player.race,
shared.raceMap[player.race] or "Unknown",
player.guild,
zone)
---@type Message
local msg = {
channel = "CHANNEL",
data = data.config.who.notifyChannel,
data = Heimdall_Data.config.who.notifyChannel,
message = text
}
table.insert(data.messenger.queue, msg)
table.insert(shared.messenger.queue, msg)
if data.config.who.doWhisper then
for _, name in pairs(data.config.whisperNotify) do
if Heimdall_Data.config.who.doWhisper then
for _, name in pairs(Heimdall_Data.config.whisperNotify) do
---@type Message
local msg = {
channel = "WHISPER",
data = name,
message = text
}
table.insert(data.messenger.queue, msg)
table.insert(shared.messenger.queue, msg)
end
end
@@ -207,8 +204,9 @@ function data.Whoer.Init()
---@param player Player
---@return string?
local function NotifyGone(player)
if not Heimdall_Data.config.who.enabled then return end
if not player then return string.format("Cannot notify for nil player %s", tostring(player)) end
if not data.config.who.zoneNotifyFor[player.zone] then
if not Heimdall_Data.config.who.zoneNotifyFor[player.zone] then
return string.format("Not notifying for zone %s",
tostring(player.zone))
end
@@ -222,20 +220,20 @@ function data.Whoer.Init()
---@type Message
local msg = {
channel = "CHANNEL",
data = data.config.who.notifyChannel,
data = Heimdall_Data.config.who.notifyChannel,
message = text
}
table.insert(data.messenger.queue, msg)
table.insert(shared.messenger.queue, msg)
if data.config.who.doWhisper then
for _, name in pairs(data.config.whisperNotify) do
if Heimdall_Data.config.who.doWhisper then
for _, name in pairs(Heimdall_Data.config.whisperNotify) do
---@type Message
local msg = {
channel = "WHISPER",
data = name,
message = text
}
table.insert(data.messenger.queue, msg)
table.insert(shared.messenger.queue, msg)
end
end
@@ -245,6 +243,7 @@ function data.Whoer.Init()
local frame = CreateFrame("Frame")
frame:RegisterEvent("WHO_LIST_UPDATE")
frame:SetScript("OnEvent", function(self, event, ...)
if not Heimdall_Data.config.who.enabled then return end
---@type WHOQuery?
local query = lastQuery
if not query then
@@ -254,8 +253,8 @@ function data.Whoer.Init()
for i = 1, GetNumWhoResults() do
local name, guild, level, race, class, zone = GetWhoInfo(i)
if data.who.ignored[name] then return end
local continue = false
--print(name, guild, level, race, class, zone)
---@type WHOFilter[]
local filters = query.filters
@@ -266,6 +265,8 @@ function data.Whoer.Init()
continue = true
end
end
if Heimdall_Data.config.who.ignored[name] then continue = true end
--print(continue)
if not continue then
local timestamp = date("%Y-%m-%dT%H:%M:%S")
@@ -285,12 +286,12 @@ function data.Whoer.Init()
player.firstSeen = timestamp
end
local stinky = data.config.stinkies[name]
local stinky = Heimdall_Data.config.stinkies[name]
if stinky then
player.stinky = true
PlaySoundFile("Interface\\Sounds\\Domination.ogg", "Master")
--PlaySoundFile("Interface\\Sounds\\Domination.ogg", "Master")
else
PlaySoundFile("Interface\\Sounds\\Cloak.ogg", "Master")
--PlaySoundFile("Interface\\Sounds\\Cloak.ogg", "Master")
end
local err = Notify(player)
@@ -321,29 +322,28 @@ function data.Whoer.Init()
-- Turns out WA cannot do this (
-- aura_env.UpdateMacro()
_G["FriendsFrameCloseButton"]:Click()
queryPending = false
end)
if not data.who.updateTicker then
data.who.updateTicker = C_Timer.NewTicker(0.5, function()
do
local function UpdateStinkies()
for name, player in pairs(HeimdallStinkies) do
if player.lastSeenInternal + data.config.who.ttl < GetTime() then
if player.lastSeenInternal + Heimdall_Data.config.who.ttl < GetTime() then
NotifyGone(player)
PlaySoundFile("Interface\\Sounds\\Uncloak.ogg", "Master")
--PlaySoundFile("Interface\\Sounds\\Uncloak.ogg", "Master")
HeimdallStinkies[name] = nil
end
end
end)
end
local function Tick()
UpdateStinkies()
C_Timer.NewTimer(0.5, Tick, 1)
end
Tick()
end
if not data.who.whoTicker then
data.who.whoTicker = C_Timer.NewTicker(1, function()
if queryPending then
print("Tried running a who query while one is already pending, previous query:")
data.dumpTable(lastQuery)
return
end
queryPending = true
do
local function DoQuery()
if not Heimdall_Data.config.who.enabled then return end
local query = whoQueries[whoQueryIdx]
whoQueryIdx = whoQueryIdx + 1
@@ -352,62 +352,16 @@ function data.Whoer.Init()
end
lastQuery = query
--print(string.format("Running who query: %s", tostring(query.query)))
---@diagnostic disable-next-line: param-type-mismatch
SetWhoToUI(1)
SendWho(query.query)
end)
end
local whoQueryWhisperFrame = CreateFrame("Frame")
whoQueryWhisperFrame:RegisterEvent("CHAT_MSG_WHISPER")
whoQueryWhisperFrame:SetScript("OnEvent", function(self, event, msg, sender)
if msg == "who" then
for _, player in pairs(HeimdallStinkies) do
local text = player:NotifyMessage()
---@type Message
local msg = {
channel = "WHISPER",
data = sender,
message = text
}
table.insert(data.messenger.queue, msg)
local function Tick()
DoQuery()
C_Timer.NewTimer(1, Tick, 1)
end
Tick()
end
end)
local whoQueryChannelFrame = CreateFrame("Frame")
whoQueryChannelFrame:RegisterEvent("CHAT_MSG_CHANNEL")
whoQueryChannelFrame:SetScript("OnEvent", function(self, event, msg, sender, ...)
local channelId = select(6, ...)
local channelname = ""
---@type any[]
local channels = { GetChannelList() }
for i = 1, #channels, 2 do
---@type number
local id = channels[i]
---@type string
local name = channels[i + 1]
if id == channelId then
channelname = name
end
end
if channelname ~= data.config.who.notifyChannel then
return
end
if msg == "who" then
for _, player in pairs(HeimdallStinkies) do
local text = player:NotifyMessage()
---@type Message
local msg = {
channel = "CHANNEL",
data = channelname,
message = text
}
table.insert(data.messenger.queue, msg)
end
end
end)
print("Heimdall - Whoer loaded")
end

324
README.md Normal file
View File

@@ -0,0 +1,324 @@
# Heimdall WoW Addon
Heimdall is a comprehensive World of Warcraft addon designed to provide advanced player tracking, notification, and group management features.
## Report overview
- Player spotted:
- "I see (Hostile) Atomickitty of race Blood Elf (Horde) with health 6.8M/6.8M at Orgrimmar (Valley of Strength)"
- "I see (\<reaction\>) \<name\> of race \<race\> (\<faction\>) with health \<health\>/\<healthMax\> at \<location\>"
- Player appeared:
- "Любящаядева of class Mage, race Human (Alliance) and guild Анонимное сообщество in Valley of Trials, first seen: 2024-12-27T15:54:38, last seen: never, times seen: 0"
- "\<name\> of class \<class\>, race \<race\> (\<faction\>) and guild \<guild\> in \<zone\>, first seen: \<firstSeen\>, last seen: \<lastSeen\>, times seen: \<timesSeen\>"
- Player changed zone:
- "Thekinglord of class Paladin (Human - Alliance) and guild Caminantes Nocturnos R moved to Durotar"
- "\<name\> of class \<class\> (\<faction\>) and guild \<guild\> moved to \<zone\>"
- Player disappeared:
- "Thekinglord of class Paladin and guild Caminantes Nocturnos R left Valley of Trials"
- "\<name\> of class \<class\> and guild \<guild\> left \<zone\>"
- Player killed:
- "Euotuie killed Wild Mature Swine with Demon's Bite in Durotar (The Dranosh'ar Blockade)"
- "\<killer\> killed \<victim\> with \<spell\> in \<zone\> (\<subzone\>)"
## Overview
Heimdall is a multi-module addon that offers various functionalities to enhance player interaction and awareness in the game. It consists of several key modules, each with a specific purpose:
### 1. Spotter Module (`Spotter.lua`)
- Tracks and reports player sightings in real-time
- Configurable notification settings:
- Detect players by faction (Alliance, Horde)
- Identify hostile players
- Mark "stinky" players (predefined list)
- Sends notifications to a specified channel when players are spotted
- Provides detailed information about spotted players:
- Name
- Race
- Faction
- Health
- Location
- **Example report:**
- "I see (Hostile) Atomickitty of race Blood Elf (Horde) with health 6.8M/6.8M at Orgrimmar (Valley of Strength)"
- "I see (\<reaction\>) \<name\> of race \<race\> (\<faction\>) with health \<health\>/\<healthMax\> at \<location\>"
- Configuration:
- `enabled` - Whether the module is enabled
- `everyone` - Whether to report to everyone in the channel
- `hostile` - Whether to report hostile players (regardless of faction, ie. when a horde becomes alliance)
- `alliance` - Whether to report alliance players
- `stinky` - Whether to report only stinky players
- `notifyChannel` - The channel to report to (by name)
- `zoneOverride` - The zone to override the zone of the player to report (defaults to current zone/subzone)
- `throttleTime` - The time to throttle the reports to (in seconds)
- Configuration example:
```
/run Heimdall_Data.config.spotter = {enabled=true,everyone=false,hostile=true,alliance=true,stinky=true,notifyChannel="Agent",zoneOverride=nil,throttleTime=10}
```
### 2. Whoer Module (`Whoer.lua`)
- Advanced player tracking and logging system
- Periodically performs WHO queries in specific zones
- Maintains a persistent database of player information:
- First seen
- Last seen
- Seen count
- Zone history
- Sends notifications when:
- New players are detected
- Players change zones
- Players disappear from tracking
- Supports whisper notifications to predefined contacts
- Plays sound alerts for "stinky" players
- **Example report:**
- Player appeared:
- "Любящаядева of class Mage, race Human (Alliance) and guild Анонимное сообщество in Valley of Trials, first seen: 2024-12-27T15:54:38, last seen: never, times seen: 0"
- "\<name\> of class \<class\>, race \<race\> (\<faction\>) and guild \<guild\> in \<zone\>, first seen: \<firstSeen\>, last seen: \<lastSeen\>, times seen: \<timesSeen\>"
- Player changed zone:
- "Thekinglord of class Paladin (Human - Alliance) and guild Caminantes Nocturnos R moved to Durotar"
- "\<name\> of class \<class\> (\<faction\>) and guild \<guild\> moved to \<zone\>"
- Player disappeared:
- "Thekinglord of class Paladin and guild Caminantes Nocturnos R left Valley of Trials"
- "\<name\> of class \<class\> and guild \<guild\> left \<zone\>"
- Configuration:
- `enabled` - Whether the module is enabled
- `notifyChannel` - The channel to report to (by name)
- `ttl` - The time to live for a player (in seconds)
- `doWhisper` - Whether to whisper to predefined contacts
- `zoneNotifyFor` - Whether to notify for players in specific zones
- Configuration example:
```
/run Heimdall_Data.config.who = {enabled=true,notifyChannel="Agent",ttl=20,doWhisper=true,zoneNotifyFor={["Orgrimmar"]=true,["Thunder Bluff"]=true,["Undercity"]=true,["Durotar"]=true,["Echo Isles"]=true,["Valley of Trials"]=true}}
```
### 3. Messenger Module (`Messenger.lua`)
- Centralized message queuing and sending system
- Manages message delivery across different chat channels
- Handles channel joining and message routing
- Provides a reliable messaging infrastructure for other modules
- Configuration:
- `enabled` - Whether the module is enabled
- Configuration example:
```
/run Heimdall_Data.config.messenger = {enabled=true}
```
### 4. Inviter Module (`Inviter.lua`)
- Automated group invitation system
- Listens to a specific channel for invitation requests
- Supports a configurable keyword for invitations
- Automatically promotes channel members to assistants in raid groups
- Configuration:
- `enabled` - Whether the module is enabled
- `keyword` - The keyword to listen for
- `updateInterval` - The interval to update the list of channel members (in seconds)
- `listeningChannel` - The channel to listen for invitations
- Configuration example:
```
/run Heimdall_Data.config.inviter = {enabled=true,keyword="+",updateInterval=10,listeningChannel="Agent"}
```
### 5. Death Reporter Module (`DeathReporter.lua`)
- Tracks and reports player deaths in combat
- Captures detailed death information:
- Killer
- Victim
- Killing spell
- Location
- Implements throttling to prevent spam
- Handles duel detection to avoid reporting duel-related deaths
- Sends notifications to a specified channel and optional whisper contacts
- **Example report:**
- "Euotuie killed Wild Mature Swine with Demon's Bite in Durotar (The Dranosh'ar Blockade)"
- "\<killer\> killed \<victim\> with \<spell\> in \<zone\> (\<subzone\>)"
- Configuration:
- `enabled` - Whether the module is enabled
- `notifyChannel` - The channel to report to (by name)
- `doWhisper` - Whether to whisper to predefined contacts
- Configuration example:
```
/run Heimdall_Data.config.deathReporter = {enabled=true,notifyChannel="Agent",doWhisper=true}
```
### 6. Core Module (`Heimdall.lua`)
- Initializes and configures all other modules
- Manages global configuration and data persistence
- Provides utility functions for:
- UTF-8 string handling
- String padding
- Data retrieval with defaults
## Stinky Players
The addon maintains a list of "stinky" players - users of interest that trigger special notifications and tracking.
## Slash Commands
- `/has [PlayerName]`: Toggle a player's "stinky" status
## Installation
1. Download the [addon](https://git.site.quack-lab.dev/dave/wow-Heimdall/media/branch/master/Heimdall.zip)
2. Extract the addon to your World of Warcraft `Interface/AddOns` directory
3. Ensure the addon is enabled in the character selection screen
---
# Аддон Heimdall для WoW
Heimdall - это комплексный аддон для World of Warcraft, предназначенный для расширенного отслеживания игроков, уведомлений и управления группами.
## Обзор отчетов
- Обнаружен игрок:
- "I see (Hostile) Atomickitty of race Blood Elf (Horde) with health 6.8M/6.8M at Orgrimmar (Valley of Strength)"
- "Я вижу (\<reaction\>) \<name\> расы \<race\> (\<faction\>) со здоровьем \<health\>/\<healthMax\> в \<location\>"
- Появился игрок:
- "Любящаядева of class Mage, race Human (Alliance) and guild Анонимное сообщество in Valley of Trials, first seen: 2024-12-27T15:54:38, last seen: never, times seen: 0"
- "\<name\> класса \<class\>, расы \<race\> (\<faction\>) и гильдии \<guild\> в \<zone\>, первый раз замечен: \<firstSeen\>, последний раз замечен: \<lastSeen\>, встречен раз: \<timesSeen\>"
- Игрок сменил зону:
- "Thekinglord of class Paladin (Human - Alliance) and guild Caminantes Nocturnos R moved to Durotar"
- "\<name\> класса \<class\> (\<faction\>) и гильдии \<guild\> перешёл в \<zone\>"
- Игрок исчез:
- "Thekinglord of class Paladin and guild Caminantes Nocturnos R left Valley of Trials"
- "\<name\> класса \<class\> и гильдии \<guild\> покинул \<zone\>"
- Игрок убит:
- "Euotuie killed Wild Mature Swine with Demon's Bite in Durotar (The Dranosh'ar Blockade)"
- "\<killer\> убил \<victim\> с помощью \<spell\> в \<zone\> (\<subzone\>)"
## Обзор
Heimdall - это многомодульный аддон, предоставляющий различные функции для улучшения взаимодействия между игроками и повышения осведомленности в игре. Он состоит из нескольких ключевых модулей:
### 1. Модуль Обнаружения (`Spotter.lua`)
- Отслеживает и сообщает об обнаружении игроков в реальном времени
- Настраиваемые параметры уведомлений:
- Обнаружение игроков по фракции (Альянс, Орда)
- Определение враждебных игроков
- Отметка "подозрительных" игроков (предопределенный список)
- Отправляет уведомления в указанный канал при обнаружении игроков
- Предоставляет подробную информацию об обнаруженных игроках:
- Имя
- Раса
- Фракция
- Здоровье
- Местоположение
- **Пример отчета:**
- "I see (Hostile) Atomickitty of race Blood Elf (Horde) with health 6.8M/6.8M at Orgrimmar (Valley of Strength)"
- "Обнаружен (\<реакция\>) \<имя\> расы \<раса\> (\<фракция\>) со здоровьем \<здоровье\>/\<макс_здоровье\> в \<локация\>"
- Конфигурация:
- `enabled` - Включен ли модуль
- `everyone` - Сообщать ли всем в канале
- `hostile` - Сообщать ли о враждебных игроках
- `alliance` - Сообщать ли об игроках Альянса
- `stinky` - Сообщать ли только о подозрительных игроках
- `notifyChannel` - Канал для отправки сообщений (по имени)
- `zoneOverride` - Зона для переопределения местоположения игрока
- `throttleTime` - Время задержки между сообщениями (в секундах)
- Пример конфигурации:
```
/run Heimdall_Data.config.spotter = {enabled=true,everyone=false,hostile=true,alliance=true,stinky=true,notifyChannel="Agent",zoneOverride=nil,throttleTime=10}
```
### 2. Модуль WHO (`Whoer.lua`)
- Продвинутая система отслеживания и логирования игроков
- Периодически выполняет WHO запросы в определенных зонах
- Поддерживает постоянную базу данных информации об игроках:
- Первое появление
- Последнее появление
- Количество появлений
- История зон
- Отправляет уведомления когда:
- Обнаружены новые игроки
- Игроки меняют зоны
- Игроки исчезают из отслеживания
- Поддерживает уведомления шепотом предопределенным контактам
- Проигрывает звуковые оповещения для "подозрительных" игроков
- **Пример отчета:**
- Появление игрока:
- "Любящаядева of class Mage, race Human (Alliance) and guild Анонимное сообщество in Valley of Trials, first seen: 2024-12-27T15:54:38, last seen: never, times seen: 0"
- "Имя класса <класс>, расы <раса> (<фракция>) из гильдии <гильдия> в <зона>, первое появление: <первое_появление>, последнее появление: <последнее_появление>, появлений: <количество>"
- Смена зоны:
- "Thekinglord of class Paladin (Human - Alliance) and guild Caminantes Nocturnos R moved to Durotar"
- "<имя> класса <класс> (<фракция>) из гильдии <гильдия> переместился в <зона>"
- Исчезновение:
- "Thekinglord of class Paladin and guild Caminantes Nocturnos R left Valley of Trials"
- "<имя> класса <класс> из гильдии <гильдия> покинул <зона>"
- Конфигурация:
- `enabled` - Включен ли модуль
- `notifyChannel` - Канал для отправки сообщений
- `ttl` - Время жизни записи об игроке (в секундах)
- `doWhisper` - Отправлять ли шепот контактам
- `zoneNotifyFor` - Уведомлять ли об игроках в определенных зонах
- Пример конфигурации:
```
/run Heimdall_Data.config.who = {enabled=true,notifyChannel="Agent",ttl=20,doWhisper=true,zoneNotifyFor={["Orgrimmar"]=true,["Thunder Bluff"]=true,["Undercity"]=true,["Durotar"]=true,["Echo Isles"]=true,["Valley of Trials"]=true}}
```
### 3. Модуль Сообщений (`Messenger.lua`)
- Централизованная система очередей и отправки сообщений
- Управляет доставкой сообщений по разным чат-каналам
- Обрабатывает присоединение к каналам и маршрутизацию сообщений
- Предоставляет надежную инфраструктуру сообщений для других модулей
- Конфигурация:
- `enabled` - Включен ли модуль
- Пример конфигурации:
```
/run Heimdall_Data.config.messenger = {enabled=true}
```
### 4. Модуль Приглашений (`Inviter.lua`)
- Автоматическая система приглашений в группу
- Прослушивает определенный канал на запросы приглашений
- Поддерживает настраиваемое ключевое слово для приглашений
- Автоматически повышает участников канала до помощников в рейдовых группах
- Конфигурация:
- `enabled` - Включен ли модуль
- `keyword` - Ключевое слово для прослушивания
- `updateInterval` - Интервал обновления списка участников канала (в секундах)
- `listeningChannel` - Канал для прослушивания приглашений
- Пример конфигурации:
```
/run Heimdall_Data.config.inviter = {enabled=true,keyword="+",updateInterval=10,listeningChannel="Agent"}
```
### 5. Модуль Отчетов о Смертях (`DeathReporter.lua`)
- Отслеживает и сообщает о смертях игроков в бою
- Сохраняет подробную информацию о смерти:
- Убийца
- Жертва
- Убивающее заклинание
- Местоположение
- Реализует задержку для предотвращения спама
- Обрабатывает определение дуэлей во избежание сообщений о смертях в дуэлях
- Отправляет уведомления в указанный канал и опционально шепотом контактам
- **Пример отчета:**
- "Euotuie killed Wild Mature Swine with Demon's Bite in Durotar (The Dranosh'ar Blockade)"
- "<убийца> убил <жертва> с помощью <заклинание> в <зона> (<подзона>)"
- Конфигурация:
- `enabled` - Включен ли модуль
- `notifyChannel` - Канал для отправки сообщений
- `doWhisper` - Отправлять ли шепот контактам
- Пример конфигурации:
```
/run Heimdall_Data.config.deathReporter = {enabled=true,notifyChannel="Agent",doWhisper=true}
```
### 6. Основной Модуль (`Heimdall.lua`)
- Инициализирует и настраивает все остальные модули
- Управляет глобальной конфигурацией и сохранением данных
- Предоставляет служебные функции для:
- Обработки UTF-8 строк
- Выравнивания строк
- Получения данных с значениями по умолчанию
## Подозрительные Игроки
Аддон поддерживает список "подозрительных" игроков - пользователей, представляющих интерес, которые вызывают специальные уведомления и отслеживание.
## Слэш-команды
- `/has [ИмяИгрока]`: Переключить статус "подозрительного" игрока
## Установка
1. Скачайте [аддон](https://git.site.quack-lab.dev/dave/wow-Heimdall/media/branch/master/Heimdall.zip)
2. Распакуйте аддон в директорию World of Warcraft `Interface/AddOns`
3. Убедитесь, что аддон включен на экране выбора персонажа

1
Weakauras/Config/export Normal file

File diff suppressed because one or more lines are too long

185
Weakauras/Config/init.lua Normal file
View File

@@ -0,0 +1,185 @@
---@param str string
---@return table<string, boolean>
local function StringToMap(str, deliminer)
if not str then return {} end
local map = {}
local parts = { strsplit(deliminer, str) }
for _, line in ipairs(parts) do
line = strtrim(line)
if line ~= "" then
map[line] = true
end
end
return map
end
---@param str string
---@return string[]
local function StringToArray(str, deliminer)
if not str then return {} end
local ret = {}
local array = { strsplit(deliminer, str) }
for i, line in ipairs(array) do
line = strtrim(line)
if line ~= "" then
ret[i] = line
end
end
return ret
end
if not Heimdall_Data then Heimdall_Data = {} end
local config = {
spotter = {
enabled = aura_env.config.spotter.enabled,
everyone = aura_env.config.spotter.everyone,
hostile = aura_env.config.spotter.hostile,
alliance = aura_env.config.spotter.alliance,
stinky = aura_env.config.spotter.stinky,
notifyChannel = aura_env.config.spotter.notifyChannel,
zoneOverride = aura_env.config.spotter.zoneOverride,
throttleTime = aura_env.config.spotter.throttleTime
},
who = {
enabled = aura_env.config.who.enabled,
ignored = StringToMap(aura_env.config.who.ignored, "\n"),
notifyChannel = aura_env.config.who.notifyChannel,
ttl = aura_env.config.who.ttl,
doWhisper = aura_env.config.who.doWhisper,
zoneNotifyFor = StringToMap(aura_env.config.who.zoneNotifyFor, "\n"),
},
messenger = {
enabled = aura_env.config.messenger.enabled,
interval = aura_env.config.messenger.interval,
},
deathReporter = {
enabled = aura_env.config.deathReporter.enabled,
throttle = aura_env.config.deathReporter.throttle,
doWhisper = aura_env.config.deathReporter.doWhisper,
notifyChannel = aura_env.config.deathReporter.notifyChannel,
zoneOverride = aura_env.config.deathReporter.zoneOverride,
duelThrottle = aura_env.config.deathReporter.duelThrottle,
},
whisperNotify = StringToArray(aura_env.config.whisperNotify, "\n"),
stinkies = StringToMap(aura_env.config.stinkies, "\n"),
inviter = {
enabled = aura_env.config.inviter.enabled,
listeningChannel = aura_env.config.inviter.listeningChannel,
keyword = aura_env.config.inviter.keyword,
allAssist = aura_env.config.inviter.allAssist,
agentsAssist = aura_env.config.inviter.agentsAssist,
throttle = aura_env.config.inviter.throttle,
kickOffline = aura_env.config.inviter.kickOffline,
cleanupInterval = aura_env.config.inviter.cleanupInterval,
afkThreshold = aura_env.config.inviter.afkThreshold,
},
dueler = {
enabled = aura_env.config.dueler.enabled,
declineOther = aura_env.config.dueler.declineOther,
},
bully = {
enabled = aura_env.config.bully.enabled,
},
agentTracker = {
enabled = aura_env.config.agentTracker.enabled,
masterChannel = aura_env.config.agentTracker.masterChannel,
},
emoter = {
enabled = aura_env.config.emoter.enabled,
masterChannel = aura_env.config.emoter.masterChannel,
prefix = aura_env.config.emoter.prefix,
},
echoer = {
enabled = aura_env.config.echoer.enabled,
masterChannel = aura_env.config.echoer.masterChannel,
prefix = aura_env.config.echoer.prefix,
},
macroer = {
enabled = aura_env.config.macroer.enabled,
priority = StringToArray(aura_env.config.macroer.priority, "\n"),
},
commander = {
enabled = aura_env.config.commander.enabled,
masterChannel = aura_env.config.commander.masterChannel,
commander = aura_env.config.commander.commander,
commands = StringToMap(aura_env.config.commander.commands, ","),
},
combatAlerter = {
enabled = aura_env.config.combatAlerter.enabled,
masterChannel = aura_env.config.combatAlerter.masterChannel,
},
stinkyTracker = {
enabled = aura_env.config.stinkyTracker.enabled,
masterChannel = aura_env.config.stinkyTracker.masterChannel,
},
}
Heimdall_Data.config.spotter.enabled = config.spotter.enabled
Heimdall_Data.config.spotter.everyone = config.spotter.everyone
Heimdall_Data.config.spotter.hostile = config.spotter.hostile
Heimdall_Data.config.spotter.alliance = config.spotter.alliance
Heimdall_Data.config.spotter.stinky = config.spotter.stinky
Heimdall_Data.config.spotter.notifyChannel = config.spotter.notifyChannel
Heimdall_Data.config.spotter.zoneOverride = config.spotter.zoneOverride
Heimdall_Data.config.spotter.throttleTime = config.spotter.throttleTime
Heimdall_Data.config.who.enabled = config.who.enabled
Heimdall_Data.config.who.ignored = config.who.ignored
Heimdall_Data.config.who.notifyChannel = config.who.notifyChannel
Heimdall_Data.config.who.ttl = config.who.ttl
Heimdall_Data.config.who.doWhisper = config.who.doWhisper
Heimdall_Data.config.who.zoneNotifyFor = config.who.zoneNotifyFor
Heimdall_Data.config.messenger.enabled = config.messenger.enabled
Heimdall_Data.config.messenger.interval = config.messenger.interval
Heimdall_Data.config.deathReporter.enabled = config.deathReporter.enabled
Heimdall_Data.config.deathReporter.throttle = config.deathReporter.throttle
Heimdall_Data.config.deathReporter.doWhisper = config.deathReporter.doWhisper
Heimdall_Data.config.deathReporter.notifyChannel = config.deathReporter.notifyChannel
Heimdall_Data.config.deathReporter.zoneOverride = config.deathReporter.zoneOverride
Heimdall_Data.config.deathReporter.duelThrottle = config.deathReporter.duelThrottle
Heimdall_Data.config.inviter.enabled = config.inviter.enabled
Heimdall_Data.config.inviter.listeningChannel = config.inviter.listeningChannel
Heimdall_Data.config.inviter.keyword = config.inviter.keyword
Heimdall_Data.config.inviter.allAssist = config.inviter.allAssist
Heimdall_Data.config.inviter.agentsAssist = config.inviter.agentsAssist
Heimdall_Data.config.inviter.throttle = config.inviter.throttle
Heimdall_Data.config.inviter.kickOffline = config.inviter.kickOffline
Heimdall_Data.config.inviter.cleanupInterval = config.inviter.cleanupInterval
Heimdall_Data.config.inviter.afkThreshold = config.inviter.afkThreshold
Heimdall_Data.config.dueler.enabled = config.dueler.enabled
Heimdall_Data.config.dueler.declineOther = config.dueler.declineOther
Heimdall_Data.config.bully.enabled = config.bully.enabled
Heimdall_Data.config.agentTracker.enabled = config.agentTracker.enabled
Heimdall_Data.config.agentTracker.masterChannel = config.agentTracker.masterChannel
Heimdall_Data.config.emoter.enabled = config.emoter.enabled
Heimdall_Data.config.emoter.masterChannel = config.emoter.masterChannel
Heimdall_Data.config.emoter.prefix = config.emoter.prefix
Heimdall_Data.config.echoer.enabled = config.echoer.enabled
Heimdall_Data.config.echoer.masterChannel = config.echoer.masterChannel
Heimdall_Data.config.echoer.prefix = config.echoer.prefix
Heimdall_Data.config.macroer.enabled = config.macroer.enabled
Heimdall_Data.config.macroer.priority = config.macroer.priority
Heimdall_Data.config.commander.enabled = config.commander.enabled
Heimdall_Data.config.commander.masterChannel = config.commander.masterChannel
Heimdall_Data.config.commander.commander = config.commander.commander
Heimdall_Data.config.commander.commands = config.commander.commands
Heimdall_Data.config.combatAlerter.enabled = config.combatAlerter.enabled
Heimdall_Data.config.combatAlerter.masterChannel = config.combatAlerter.masterChannel
Heimdall_Data.config.stinkyTracker.enabled = config.stinkyTracker.enabled
Heimdall_Data.config.stinkyTracker.masterChannel = config.stinkyTracker.masterChannel
Heimdall_Data.config.whisperNotify = config.whisperNotify
Heimdall_Data.config.stinkies = config.stinkies

5
deploy.sh Normal file
View File

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