Compare commits
397 Commits
Author | SHA1 | Date | |
---|---|---|---|
69d7efe6f0 | |||
202c5d0f46 | |||
476e907205 | |||
724194abe2 | |||
d57b573683 | |||
5d8afe8db7 | |||
b14f1ff2f9 | |||
a1301abdb2 | |||
f897183920 | |||
c1885ce76a | |||
e676d53e97 | |||
f05156b257 | |||
c8f9d81b3e | |||
3a1639ab27 | |||
1da1e7bf9f | |||
304fbcbaae | |||
80f8500f6e | |||
78cbcbde9d | |||
![]() |
8db1cb179c | ||
a065e47545 | |||
5271029b84 | |||
7c4fb53baa | |||
1c2fb471a6 | |||
85f72b14e0 | |||
99261beb08 | |||
d34be4f18d | |||
3d26832e54 | |||
cd80c9fc0e | |||
672ca07782 | |||
b06313c4eb | |||
31be9b0ce8 | |||
d69a53b4c8 | |||
7aa5d50a6c | |||
d047c632ed | |||
9eb5c04a33 | |||
498432d968 | |||
e04eb57202 | |||
25fe8350a7 | |||
9a10386e65 | |||
75ab8646e8 | |||
37ef42e53d | |||
b2aa1b75f3 | |||
31bcd3481c | |||
966078c339 | |||
fdcd816235 | |||
054d8ab7ef | |||
0327359300 | |||
c61a4b0c04 | |||
950f95cef1 | |||
d40670fef6 | |||
d6e5e7941f | |||
a06e9027c6 | |||
8fb0595ef8 | |||
d3808a887e | |||
1ed379c23d | |||
0e66db1d8a | |||
bac16d22a9 | |||
8b4de82142 | |||
35930c52a4 | |||
293e71e619 | |||
7150189a0d | |||
0e951d7089 | |||
ef89c3001b | |||
b538c7b5de | |||
6c234e7fa4 | |||
407d8f2da2 | |||
9b755cd0be | |||
19f9d4bda9 | |||
de744337ad | |||
63ba6d2da1 | |||
79b77ee6c3 | |||
0e2935f844 | |||
0ad6a23daa | |||
35398ebf38 | |||
62b028cf56 | |||
ad676915bb | |||
61ebb22a85 | |||
8348b93b30 | |||
550e11b488 | |||
ed10ea496d | |||
6e5c7510d0 | |||
68d0393915 | |||
f1e07f0a3b | |||
90100f6f5b | |||
995966e952 | |||
a90eb8248f | |||
ffca28c67d | |||
20a2a95ce6 | |||
145fd02ba8 | |||
196a5a8cfa | |||
439e9b29d1 | |||
a3f1a0e96d | |||
c81e349e90 | |||
30068a5b11 | |||
036b6b23a8 | |||
128eb44003 | |||
0f72b2048e | |||
1e4045ab7a | |||
b871549087 | |||
58760831dc | |||
0a8ab00637 | |||
8396801d80 | |||
19af9894e5 | |||
7293f5a8fa | |||
9d12609147 | |||
2c7089504f | |||
8fbff23bee | |||
0c5078e3f3 | |||
d143a18838 | |||
ba889e442c | |||
4511ecbf0a | |||
fe37bebd2c | |||
d987436892 | |||
9f4e19104f | |||
c0568075e1 | |||
13415fb065 | |||
ca13d1e364 | |||
bf916dd8d5 | |||
4aa168ebcc | |||
2cea01f367 | |||
2ec0aea19c | |||
846584d6fe | |||
1b5912a1bf | |||
954dbfa425 | |||
42ec90a5df | |||
0b4350c8ae | |||
da28805882 | |||
6551e24069 | |||
28ef8cb33a | |||
d4b0dee037 | |||
799d3e1ddd | |||
e6f3bac946 | |||
05c7e71794 | |||
41b980d118 | |||
3efd99cdc8 | |||
119eb7965b | |||
f4421f0334 | |||
688f2f4b30 | |||
87300bf48a | |||
8cbad47acc | |||
241615238c | |||
319e6cdd77 | |||
d54e93ad85 | |||
efe0002e02 | |||
e58d92c399 | |||
fa18138c3b | |||
2a5d6e5157 | |||
c32549fa87 | |||
2689e39d70 | |||
25f2310c25 | |||
0bed5ecf41 | |||
ec2f146095 | |||
75a84baa42 | |||
1bc7ebc92a | |||
d620f577c1 | |||
7af1b40222 | |||
82f1539815 | |||
e38ba012a8 | |||
a0322718c1 | |||
01ca12f80e | |||
3376b4fa7c | |||
308b65e2f6 | |||
fa5b73b5fe | |||
0fd088320d | |||
a109c631cd | |||
cf61a74fa8 | |||
8fa4effb6b | |||
5ca69bbe24 | |||
8816468ba0 | |||
e9f17c585c | |||
373ca377a2 | |||
770420a5b2 | |||
22b1b6bc73 | |||
18fd4bb9d2 | |||
ca333a93e3 | |||
4c404225d2 | |||
9f86a4e0f9 | |||
6273263c4e | |||
0b6b8df1a9 | |||
fe730a19df | |||
636ef87cb1 | |||
d333901576 | |||
d104bcc1fa | |||
dbfbc2c347 | |||
dd620c14d3 | |||
744098abc7 | |||
d41554271d | |||
23bfbf9f4a | |||
f52ed8c791 | |||
604371a2e1 | |||
44cec2d2fb | |||
cbe9ef7303 | |||
002970484d | |||
f063ceb4e5 | |||
1eaefffe04 | |||
1291d21216 | |||
1c198f0133 | |||
23bf656f82 | |||
30fae67f6c | |||
2726955034 | |||
e433bc3319 | |||
71df812170 | |||
abb8540c12 | |||
12e0c23ea9 | |||
b98ecdd0a4 | |||
7314c67357 | |||
6b74e01f0a | |||
6becc08e18 | |||
f66a990103 | |||
c3b9772512 | |||
6bb1cc683c | |||
b5473e05e7 | |||
939ca47e3c | |||
c16e5f1e47 | |||
059f917acc | |||
43b08f22dd | |||
aa7e4a3d3e | |||
18f2b44941 | |||
b2925285a2 | |||
c0a6a3c082 | |||
7c7edcf959 | |||
dee5053345 | |||
58e071e77b | |||
a7e85acd67 | |||
176d184d91 | |||
32543d04a0 | |||
4b43ee86c0 | |||
25959be98f | |||
cb6680304f | |||
40646f16bc | |||
55e7ee2428 | |||
a2930577d3 | |||
e572f50de7 | |||
47b7f5d85a | |||
8ac29e4378 | |||
be81a31302 | |||
2e44a1ef31 | |||
d182cc1418 | |||
d3004019c6 | |||
fca49c6302 | |||
8b085009a9 | |||
2ba6d190f0 | |||
8c5a94a12a | |||
bf9e1f0319 | |||
903baf7f38 | |||
5b585ebba7 | |||
34bae5dc7b | |||
4f97859533 | |||
4c55e65863 | |||
016f0be480 | |||
fbf35d6d77 | |||
30ee1c717e | |||
e3286571b1 | |||
ece39790d2 | |||
eedadb0a3f | |||
a01dd5dace | |||
b6f0fa9776 | |||
cb03f8e23c | |||
fbff094227 | |||
e08cf455ee | |||
de57b4c8a8 | |||
9fef1da261 | |||
1f6a1a49a6 | |||
fddd294a03 | |||
00ffe6973e | |||
f73096e439 | |||
e26ab02862 | |||
c2b9c1267b | |||
a400a85dc9 | |||
f5a4b49935 | |||
2dabe7959f | |||
dcfc406481 | |||
f680d36d2b | |||
d44aa04e44 | |||
4594dff4a6 | |||
be1093d51f | |||
546aa27bb1 | |||
dfb2f687d0 | |||
9e344aed64 | |||
ba77555cab | |||
e8e15444c9 | |||
cdd859ba50 | |||
ab85b3712a | |||
fc3732fd0c | |||
4652c60e64 | |||
00f7cba8fc | |||
e425fa9209 | |||
f1e2f60836 | |||
db7edaf802 | |||
58a7ecd723 | |||
bff1a4acf9 | |||
b287fb41c4 | |||
5e0f81ce53 | |||
bd8b3fa00f | |||
899fb888c3 | |||
2d3820952f | |||
f6b043fa39 | |||
8bce840497 | |||
aff3e83bab | |||
85a28bc7ca | |||
452aa2e3a0 | |||
784cee6f04 | |||
ca30998a5a | |||
476adcdc2e | |||
2c6142e6c4 | |||
cae1eef659 | |||
2dfb60e63d | |||
8524a1116a | |||
60ccbc72bb | |||
8d3813f3ee | |||
aa46000abf | |||
6059c16a6e | |||
2e805abef7 | |||
06f143915c | |||
be20aa77b9 | |||
7b2c67d130 | |||
9480c42181 | |||
17c163c71c | |||
3ee90fb767 | |||
7f9476236d | |||
2d2cf621bd | |||
bb1acd5003 | |||
c00ddd410a | |||
35c51143bc | |||
16cd2f82be | |||
34e027525f | |||
331823e34f | |||
18daa170c5 | |||
7e8978044f | |||
2811396234 | |||
212ce2c71c | |||
f76ef718ed | |||
bd40d43686 | |||
2954a93b4d | |||
93d1d55cfa | |||
1c7951a530 | |||
84a53ea065 | |||
9dcf526b4c | |||
032cfc5bff | |||
5c9450d06d | |||
f811dd9a6c | |||
d13da3141d | |||
d4bab870a4 | |||
6ef7e74402 | |||
55ce001705 | |||
77c3fc291d | |||
ff849092ff | |||
d0cb074912 | |||
fdedde829e | |||
4364d87bf7 | |||
2232f1a92d | |||
251e11a7e8 | |||
d4e4290dc5 | |||
9fd57c5d7b | |||
873ce0a30c | |||
36297ca09d | |||
0ec3421a79 | |||
21d99ae643 | |||
4dc3335a86 | |||
c9627779ba | |||
3f1fae8906 | |||
2ae12fade0 | |||
c66c961297 | |||
8da5773dcd | |||
18106db367 | |||
5eb6f3cbfd | |||
614b07c01a | |||
2c84c326dd | |||
0ceb59c778 | |||
137ce0a3a7 | |||
59d2b999c2 | |||
d80ffbaff5 | |||
8c45e90ce1 | |||
ed7c1a4685 | |||
e32966bee2 | |||
5e779cc5f9 | |||
5e78f623f5 | |||
8e90a71dfc | |||
9ebc95885e | |||
e1136703a5 | |||
c446d1dc85 | |||
de49956aef | |||
efde43fadb | |||
af2a714b76 | |||
bfaa73b660 | |||
a7c818b88b | |||
e1fb450544 | |||
acb2910b70 | |||
0dd1a6fd69 | |||
5f3374a073 | |||
3049a0b554 | |||
fa7a411e34 | |||
6bf1c491a0 | |||
8e1f2c147e | |||
c4f4d24064 | |||
30578bdbb0 | |||
0e771a7db3 |
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.tga filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.ogg filter=lfs diff=lfs merge=lfs -text
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
Meta
|
|
||||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "Meta"]
|
||||||
|
path = Meta
|
||||||
|
url = https://git.site.quack-lab.dev/dave/wow_Meta
|
1
.luacheckrc
Symbolic link
1
.luacheckrc
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
C:/Users/Administrator/Seafile/Games-WoW/Ruski/Interface/AddOns/Heimdall/Meta/.luacheckrc
|
1
.luarc.json
Symbolic link
1
.luarc.json
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
C:/Users/Administrator/Seafile/Games-WoW/Ruski/Interface/AddOns/Heimdall/Meta/.luarc.json
|
@@ -1,115 +0,0 @@
|
|||||||
local addonname, data = ...
|
|
||||||
---@cast data HeimdallData
|
|
||||||
---@cast addonname string
|
|
||||||
|
|
||||||
data.DeathReporter = {}
|
|
||||||
function data.DeathReporter.Init()
|
|
||||||
if not data.config.deathReporter.enabled then
|
|
||||||
print("Heimdall - DeathReporter disabled")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
---@type table<string, number>
|
|
||||||
local recentDeaths = {}
|
|
||||||
---@type table<string, number>
|
|
||||||
local recentDuels = {}
|
|
||||||
|
|
||||||
---@param source string
|
|
||||||
---@param destination string
|
|
||||||
---@param spellName string
|
|
||||||
---@param overkill number
|
|
||||||
local function RegisterDeath(source, destination, spellName, overkill)
|
|
||||||
if recentDeaths[destination]
|
|
||||||
and GetTime() - recentDeaths[destination] < data.config.deathReporter.throttle then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if recentDuels[destination]
|
|
||||||
and GetTime() - recentDuels[destination] < 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
|
|
||||||
print(string.format("Cancelling death reports for %s and %s because of recent duel", source, destination))
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
recentDeaths[destination] = GetTime()
|
|
||||||
C_Timer.NewTimer(3, function()
|
|
||||||
if recentDuels[destination]
|
|
||||||
and GetTime() - recentDuels[destination] < 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
|
|
||||||
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
|
|
||||||
zone = string.format("%s (%s)", GetZoneText(), GetSubZoneText())
|
|
||||||
end
|
|
||||||
|
|
||||||
local text = string.format("%s killed %s with %s in %s",
|
|
||||||
tostring(source),
|
|
||||||
tostring(destination),
|
|
||||||
tostring(spellName),
|
|
||||||
tostring(zone))
|
|
||||||
|
|
||||||
---@type Message
|
|
||||||
local msg = {
|
|
||||||
channel = "CHANNEL",
|
|
||||||
data = data.config.deathReporter.notifyChannel,
|
|
||||||
message = text,
|
|
||||||
}
|
|
||||||
table.insert(data.messenger.queue, msg)
|
|
||||||
|
|
||||||
if data.config.deathReporter.doWhisper then
|
|
||||||
for _, name in pairs(data.config.whisperNotify) do
|
|
||||||
local msg = {
|
|
||||||
channel = "WHISPER",
|
|
||||||
data = name,
|
|
||||||
message = text,
|
|
||||||
}
|
|
||||||
table.insert(data.messenger.queue, msg)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
local cleuFrame = CreateFrame("Frame")
|
|
||||||
cleuFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
|
|
||||||
cleuFrame:SetScript("OnEvent", function(self, event, ...)
|
|
||||||
local overkill, err = CLEUParser.GetOverkill(...)
|
|
||||||
if not err and overkill > 0 then
|
|
||||||
local source, err = CLEUParser.GetSourceName(...)
|
|
||||||
if err then source = "unknown" end
|
|
||||||
local destination, err = CLEUParser.GetDestName(...)
|
|
||||||
if err then destination = "unknown" end
|
|
||||||
local spellName, err = CLEUParser.GetSpellName(...)
|
|
||||||
if err then spellName = "unknown" end
|
|
||||||
local sourceGUID, err = CLEUParser.GetSourceGUID(...)
|
|
||||||
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)
|
|
||||||
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 source and destination then
|
|
||||||
print(string.format("Detected duel between %s and %s", source, destination))
|
|
||||||
local now = GetTime()
|
|
||||||
recentDuels[source] = now
|
|
||||||
recentDuels[destination] = now
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
print("Heimdall - DeathReporter loaded")
|
|
||||||
end
|
|
1090
Heimdall.lua
1090
Heimdall.lua
File diff suppressed because it is too large
Load Diff
50
Heimdall.toc
50
Heimdall.toc
@@ -1,14 +1,36 @@
|
|||||||
## Interface: 70300
|
## Interface: 70300
|
||||||
## Title: Heimdall
|
## Title: Heimdall
|
||||||
## Notes: Watches over areas and alerts when hostiles spotted
|
## Version: 3.11.0
|
||||||
## Author: Cyka
|
## Notes: Watches over areas and alerts when hostiles spotted
|
||||||
## SavedVariables: Heimdall_Data
|
## Author: Cyka
|
||||||
|
## SavedVariables: Heimdall_Data, Heimdall_Achievements
|
||||||
#core
|
|
||||||
CLEUParser.lua
|
_L.lua
|
||||||
DumpTable.lua
|
Modules/CLEUParser.lua
|
||||||
Spotter.lua
|
Modules/ReactiveValue.lua
|
||||||
Whoer.lua
|
Modules/DumpTable.lua
|
||||||
Messenger.lua
|
Modules/Spotter.lua
|
||||||
DeathReporter.lua
|
Modules/Whoer.lua
|
||||||
Heimdall.lua
|
Modules/Messenger.lua
|
||||||
|
Modules/Network.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/MinimapTagger.lua
|
||||||
|
Modules/Config.lua
|
||||||
|
Modules/BonkDetector.lua
|
||||||
|
Modules/Sniffer.lua
|
||||||
|
Modules/Noter.lua
|
||||||
|
Modules/NetworkMessenger.lua
|
||||||
|
Modules/StinkyCache.lua
|
||||||
|
Modules/Configurator.lua
|
||||||
|
Modules/AchievementSniffer.lua
|
||||||
|
Heimdall.lua
|
||||||
|
BIN
Heimdall.zip
(Stored with Git LFS)
Normal file
BIN
Heimdall.zip
(Stored with Git LFS)
Normal file
Binary file not shown.
100
Messenger.lua
100
Messenger.lua
@@ -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
|
|
1
Meta
Submodule
1
Meta
Submodule
Submodule Meta added at 6ab88bd8dc
293
Modules/AchievementSniffer.lua
Normal file
293
Modules/AchievementSniffer.lua
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "AchievementSniffer"
|
||||||
|
|
||||||
|
-- local HeimdallRoot = "Interface\\AddOns\\Heimdall\\"
|
||||||
|
-- local TextureRoot = HeimdallRoot .. "Texture\\"
|
||||||
|
|
||||||
|
local Achievements = {
|
||||||
|
15,
|
||||||
|
958,
|
||||||
|
1266,
|
||||||
|
2078,
|
||||||
|
2141,
|
||||||
|
2200,
|
||||||
|
4958,
|
||||||
|
5456,
|
||||||
|
5749,
|
||||||
|
6460,
|
||||||
|
6753,
|
||||||
|
7382,
|
||||||
|
7383,
|
||||||
|
7384,
|
||||||
|
8929,
|
||||||
|
8982,
|
||||||
|
9017,
|
||||||
|
9038,
|
||||||
|
9493,
|
||||||
|
10059,
|
||||||
|
10079,
|
||||||
|
10278,
|
||||||
|
10657,
|
||||||
|
10672,
|
||||||
|
10684,
|
||||||
|
10688,
|
||||||
|
10689,
|
||||||
|
10692,
|
||||||
|
10693,
|
||||||
|
10698,
|
||||||
|
10790,
|
||||||
|
10875,
|
||||||
|
11124,
|
||||||
|
11126,
|
||||||
|
11127,
|
||||||
|
11128,
|
||||||
|
11153,
|
||||||
|
11157,
|
||||||
|
11164,
|
||||||
|
11188,
|
||||||
|
11189,
|
||||||
|
11190,
|
||||||
|
11446,
|
||||||
|
11473,
|
||||||
|
11610,
|
||||||
|
11657,
|
||||||
|
11658,
|
||||||
|
11659,
|
||||||
|
11660,
|
||||||
|
11674,
|
||||||
|
11992,
|
||||||
|
11993,
|
||||||
|
11994,
|
||||||
|
11995,
|
||||||
|
11996,
|
||||||
|
11997,
|
||||||
|
11998,
|
||||||
|
11999,
|
||||||
|
12000,
|
||||||
|
12001,
|
||||||
|
12020,
|
||||||
|
12026,
|
||||||
|
12074,
|
||||||
|
12445,
|
||||||
|
12447,
|
||||||
|
12448,
|
||||||
|
}
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
|
shared.AchievementSniffer = {}
|
||||||
|
function shared.AchievementSniffer.Init()
|
||||||
|
if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
print(string.format("[%s] Module initialized", ModuleName))
|
||||||
|
end
|
||||||
|
local guidMap = {}
|
||||||
|
|
||||||
|
---@class AchievementData
|
||||||
|
---@field id number
|
||||||
|
---@field date string
|
||||||
|
---@field completed boolean
|
||||||
|
|
||||||
|
---@class Heimdall_Achievements
|
||||||
|
---@field players table<string, table<number, AchievementData>>
|
||||||
|
---@field alreadySeen table<string, boolean>
|
||||||
|
---@field rescan boolean
|
||||||
|
if not Heimdall_Achievements then Heimdall_Achievements = {} end
|
||||||
|
if not Heimdall_Achievements.players then Heimdall_Achievements.players = {} end
|
||||||
|
if not Heimdall_Achievements.alreadySeen then Heimdall_Achievements.alreadySeen = {} end
|
||||||
|
|
||||||
|
--local framePool = {}
|
||||||
|
--for i = 1, 40 do
|
||||||
|
-- local frame = CreateFrame("Frame", "HeimdallAchievementSnifferNameplate" .. i, UIParent)
|
||||||
|
-- local texture = frame:CreateTexture(nil, "ARTWORK")
|
||||||
|
-- texture:SetAllPoints(frame)
|
||||||
|
-- texture:SetTexture(TextureRoot .. Heimdall_Data.config.achievementSniffer.texture)
|
||||||
|
-- frame.texture = texture
|
||||||
|
-- frame:Hide()
|
||||||
|
-- table.insert(framePool, frame)
|
||||||
|
--end
|
||||||
|
|
||||||
|
---@param name string
|
||||||
|
---@return boolean
|
||||||
|
local function ShouldInspect(name)
|
||||||
|
local should = false
|
||||||
|
if not Heimdall_Achievements.players[name] then
|
||||||
|
if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
print(string.format("[%s] Player %s does not have prior achievement data", ModuleName, name))
|
||||||
|
end
|
||||||
|
should = true
|
||||||
|
end
|
||||||
|
if Heimdall_Achievements.alreadySeen[name] then
|
||||||
|
if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
print(string.format("[%s] Player %s has already been seen", ModuleName, name))
|
||||||
|
end
|
||||||
|
-- Save some memory
|
||||||
|
Heimdall_Achievements.players[name] = nil
|
||||||
|
should = false
|
||||||
|
end
|
||||||
|
if Heimdall_Data.config.achievementSniffer.rescan then
|
||||||
|
if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
print(string.format("[%s] Rescan is enabled", ModuleName))
|
||||||
|
end
|
||||||
|
should = true
|
||||||
|
end
|
||||||
|
return should
|
||||||
|
end
|
||||||
|
|
||||||
|
-- It's not working well AT ALL
|
||||||
|
-- I don't know how to do it better
|
||||||
|
-- It simply just does not work...
|
||||||
|
--local function UpdateFrames()
|
||||||
|
-- for i, frame in ipairs(framePool) do
|
||||||
|
-- local unit = "nameplate" .. i
|
||||||
|
-- if not UnitExists(unit) then
|
||||||
|
-- --if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
-- -- print(string.format("[%s] Unit %s does not exist, hiding frame", ModuleName, unit))
|
||||||
|
-- --end
|
||||||
|
-- frame:Hide()
|
||||||
|
-- else
|
||||||
|
-- --local unitFrame = _G[string.format("ElvUI_NamePlate%dHealthBar", i)]
|
||||||
|
-- local unitFrame = _G[string.format("NamePlate%d", i)]
|
||||||
|
-- if unitFrame == nil then
|
||||||
|
-- if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
-- print(string.format("[%s] Unit frame for %s not found", ModuleName, unit))
|
||||||
|
-- end
|
||||||
|
-- frame:Hide()
|
||||||
|
-- else
|
||||||
|
-- local unitName = UnitName(unit)
|
||||||
|
-- if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
-- print(string.format("[%s] Unit frame found for %s (%s)", ModuleName, unit, unitName))
|
||||||
|
-- end
|
||||||
|
-- frame:Show()
|
||||||
|
-- frame:SetSize(32, 32)
|
||||||
|
-- frame:SetFrameStrata("HIGH")
|
||||||
|
-- frame:SetFrameLevel(100)
|
||||||
|
-- frame:SetScale(Heimdall_Data.config.achievementSniffer.iconScale)
|
||||||
|
-- frame.texture:SetTexture(TextureRoot .. Heimdall_Data.config.achievementSniffer.texture)
|
||||||
|
-- frame:SetPoint("CENTER", unitFrame, "CENTER",
|
||||||
|
-- Heimdall_Data.config.achievementSniffer.offsetX,
|
||||||
|
-- Heimdall_Data.config.achievementSniffer.offsetY)
|
||||||
|
-- frame:SetParent(unitFrame)
|
||||||
|
-- frame:SetAlpha(1)
|
||||||
|
-- local exists = ShouldInspect(unitName)
|
||||||
|
-- if exists then
|
||||||
|
-- frame.texture:SetVertexColor(1, 0, 0, 1)
|
||||||
|
-- else
|
||||||
|
-- frame.texture:SetVertexColor(0, 1, 0, 1)
|
||||||
|
-- end
|
||||||
|
-- if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
-- print(string.format("[%s] Frame updated for %s", ModuleName, unitName))
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
--end
|
||||||
|
|
||||||
|
---@param unit string
|
||||||
|
local function TryInspect(unit)
|
||||||
|
local targetPlayer = UnitIsPlayer(unit)
|
||||||
|
if not targetPlayer then return end
|
||||||
|
local targetName = UnitName(unit)
|
||||||
|
local targetGuid = UnitGUID(unit)
|
||||||
|
guidMap[targetGuid] = targetName
|
||||||
|
|
||||||
|
if not ShouldInspect(targetName) then
|
||||||
|
if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
print(string.format("[%s] Not inspecting player: %s", ModuleName, targetName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local canInspect = CheckInteractDistance(unit, 1)
|
||||||
|
if canInspect then
|
||||||
|
if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
print(string.format("[%s] Inspecting player: %s", ModuleName, targetName))
|
||||||
|
end
|
||||||
|
SetAchievementComparisonUnit(unit)
|
||||||
|
else
|
||||||
|
if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
print(string.format("[%s] Cannot inspect player (too far?): %s", ModuleName, targetName))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param name string
|
||||||
|
local function Scan(name)
|
||||||
|
if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
print(string.format("[%s] Scanning achievements for %s", ModuleName, name))
|
||||||
|
end
|
||||||
|
Heimdall_Achievements.players[name] = {}
|
||||||
|
for i, aid in ipairs(Achievements) do
|
||||||
|
local completed, month, day, year = GetAchievementComparisonInfo(aid)
|
||||||
|
if completed then
|
||||||
|
---@type string
|
||||||
|
local yearstr = "" .. year
|
||||||
|
if year < 100 then yearstr = "20" .. year end
|
||||||
|
|
||||||
|
local date = string.format("%04d-%02d-%02d", yearstr, month, day)
|
||||||
|
|
||||||
|
local data = {
|
||||||
|
id = aid,
|
||||||
|
date = date,
|
||||||
|
completed = completed,
|
||||||
|
}
|
||||||
|
if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
print(string.format("[%s] Achievement %d completed on %s", ModuleName, aid, date))
|
||||||
|
end
|
||||||
|
Heimdall_Achievements.players[name][aid] = data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--UpdateFrames()
|
||||||
|
end
|
||||||
|
|
||||||
|
local nameplateFrame = CreateFrame("Frame")
|
||||||
|
nameplateFrame:RegisterEvent("NAME_PLATE_UNIT_ADDED")
|
||||||
|
nameplateFrame:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
|
||||||
|
nameplateFrame:SetScript("OnEvent", function(self, event, unit)
|
||||||
|
if not Heimdall_Data.config.achievementSniffer.enabled then return end
|
||||||
|
if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
print(string.format("[%s] Event triggered: %s for unit: %s", ModuleName, event, unit))
|
||||||
|
end
|
||||||
|
if event == "NAME_PLATE_UNIT_ADDED" then TryInspect(unit) end
|
||||||
|
--UpdateFrames()
|
||||||
|
end)
|
||||||
|
|
||||||
|
local inspectFrame = CreateFrame("Frame")
|
||||||
|
inspectFrame:RegisterEvent("INSPECT_ACHIEVEMENT_READY")
|
||||||
|
inspectFrame:SetScript("OnEvent", function(self, event, guid)
|
||||||
|
if not Heimdall_Data.config.achievementSniffer.enabled then return end
|
||||||
|
local name = guidMap[guid]
|
||||||
|
if not name then
|
||||||
|
if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
print(string.format("[%s] No name found for guid: %s", ModuleName, guid))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
print(string.format("[%s] Event triggered: %s for player: %s", ModuleName, event, name))
|
||||||
|
end
|
||||||
|
Scan(name)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function Tick()
|
||||||
|
C_Timer.NewTimer(Heimdall_Data.config.achievementSniffer.scanInterval, Tick)
|
||||||
|
if not Heimdall_Data.config.achievementSniffer.enabled then return end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
print(string.format("[%s] Scanning achievements for everyone on screen", ModuleName))
|
||||||
|
end
|
||||||
|
for i = 1, 40 do
|
||||||
|
local unit = "nameplate" .. i
|
||||||
|
if UnitExists(unit) then
|
||||||
|
TryInspect(unit)
|
||||||
|
--else
|
||||||
|
-- if Heimdall_Data.config.achievementSniffer.debug then
|
||||||
|
-- print(string.format("[%s] Unit %s does not exist, nothing to inspect", ModuleName, unit))
|
||||||
|
-- end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--UpdateFrames()
|
||||||
|
end
|
||||||
|
Tick()
|
||||||
|
|
||||||
|
print("[Heimdall] AchievementSniffer loaded")
|
||||||
|
end
|
119
Modules/AgentTracker.lua
Normal file
119
Modules/AgentTracker.lua
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "AgentTracker"
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
|
shared.AgentTracker = {}
|
||||||
|
function shared.AgentTracker.Init()
|
||||||
|
--/run Heimdall_Data.config.agents["Cyheuraeth"]=date("%Y-%m-%dT%H:%M:%S")
|
||||||
|
---@type table<string, boolean>
|
||||||
|
local channelRosterFrame = CreateFrame("Frame")
|
||||||
|
channelRosterFrame:RegisterEvent("CHANNEL_ROSTER_UPDATE")
|
||||||
|
channelRosterFrame:SetScript("OnEvent", function(self, event, index)
|
||||||
|
if Heimdall_Data.config.agentTracker.debug then
|
||||||
|
print(string.format("[%s] Channel roster update received", ModuleName))
|
||||||
|
end
|
||||||
|
if not Heimdall_Data.config.agentTracker.enabled then
|
||||||
|
if Heimdall_Data.config.agentTracker.debug then
|
||||||
|
print(string.format("[%s] Module disabled, ignoring roster update", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local name = GetChannelDisplayInfo(index)
|
||||||
|
if Heimdall_Data.config.agentTracker.debug then
|
||||||
|
print(string.format("[%s] Processing channel update: %s (index: %d)", ModuleName, name or "nil", index))
|
||||||
|
end
|
||||||
|
if name ~= Heimdall_Data.config.agentTracker.masterChannel then
|
||||||
|
if Heimdall_Data.config.agentTracker.debug then
|
||||||
|
print(string.format("[%s] Ignoring non-master channel: %s", ModuleName, name or "nil"))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local count = select(5, GetChannelDisplayInfo(index))
|
||||||
|
if Heimdall_Data.config.agentTracker.debug then
|
||||||
|
print(string.format("[%s] Processing %d members in channel", ModuleName, count))
|
||||||
|
end
|
||||||
|
|
||||||
|
local newAgents = 0
|
||||||
|
for i = 1, count do
|
||||||
|
name = GetChannelRosterInfo(index, i)
|
||||||
|
if name then
|
||||||
|
local isNewAgent = not Heimdall_Data.config.agents[name]
|
||||||
|
Heimdall_Data.config.agents[name] = date("%Y-%m-%dT%H:%M:%S")
|
||||||
|
if isNewAgent then newAgents = newAgents + 1 end
|
||||||
|
if Heimdall_Data.config.agentTracker.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] %s agent: %s",
|
||||||
|
ModuleName,
|
||||||
|
isNewAgent and "Added new" or "Updated existing",
|
||||||
|
name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.agentTracker.debug then
|
||||||
|
print(string.format("[%s] Roster update complete - Added %d new agents", ModuleName, newAgents))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local agentTrackerChannelSniffer = CreateFrame("Frame")
|
||||||
|
agentTrackerChannelSniffer:RegisterEvent("CHAT_MSG_CHANNEL")
|
||||||
|
agentTrackerChannelSniffer:SetScript("OnEvent", function(self, event, msg, sender, ...)
|
||||||
|
-- if Heimdall_Data.config.agentTracker.debug then
|
||||||
|
-- print(string.format("[%s] Channel message received from: %s", ModuleName, sender))
|
||||||
|
-- end
|
||||||
|
if not Heimdall_Data.config.agentTracker.enabled then
|
||||||
|
-- if Heimdall_Data.config.agentTracker.debug then
|
||||||
|
-- print(string.format("[%s] Module disabled, ignoring channel message", ModuleName))
|
||||||
|
-- end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local channelId = select(6, ...)
|
||||||
|
local _, channelname = GetChannelName(channelId)
|
||||||
|
local ok = false
|
||||||
|
for _, channel in pairs(Heimdall_Data.config.agentTracker.channels) do
|
||||||
|
if channel == channelname then
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not ok then
|
||||||
|
if Heimdall_Data.config.agentTracker.debug then
|
||||||
|
print(string.format("[%s] Channel name does not match any of the channels", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if Heimdall_Data.config.agentTracker.debug then
|
||||||
|
print(string.format("[%s] Processing message from master channel: %s", ModuleName, sender))
|
||||||
|
shared.dumpTable(Heimdall_Data.config.agentTracker)
|
||||||
|
end
|
||||||
|
|
||||||
|
sender = string.match(sender, "^[^-]+")
|
||||||
|
local isNewAgent = not Heimdall_Data.config.agents[sender]
|
||||||
|
Heimdall_Data.config.agents[sender] = date("%Y-%m-%dT%H:%M:%S")
|
||||||
|
|
||||||
|
if Heimdall_Data.config.agentTracker.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] %s agent from message: %s",
|
||||||
|
ModuleName,
|
||||||
|
isNewAgent and "Added new" or "Updated existing",
|
||||||
|
sender
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
if Heimdall_Data.config.agentTracker.debug then
|
||||||
|
local count = 0
|
||||||
|
for _ in pairs(Heimdall_Data.config.agents) do
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
print(string.format("[%s] Module initialized - Tracking %d agents", ModuleName, count))
|
||||||
|
end
|
||||||
|
print("[Heimdall] AgentTracker loaded")
|
||||||
|
end
|
134
Modules/BonkDetector.lua
Normal file
134
Modules/BonkDetector.lua
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "BonkDetector"
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
|
shared.BonkDetector = {}
|
||||||
|
function shared.BonkDetector.Init()
|
||||||
|
---@type table<string, number>
|
||||||
|
local lastReportTime = {}
|
||||||
|
|
||||||
|
local frame = CreateFrame("Frame")
|
||||||
|
frame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
|
||||||
|
frame:SetScript("OnEvent", function(self, event, ...)
|
||||||
|
-- if Heimdall_Data.config.bonkDetector.debug then
|
||||||
|
-- print(string.format("[%s] Combat log event received", ModuleName))
|
||||||
|
-- end
|
||||||
|
if not Heimdall_Data.config.bonkDetector.enabled then
|
||||||
|
-- if Heimdall_Data.config.bonkDetector.debug then
|
||||||
|
-- print(string.format("[%s] Module disabled, ignoring combat event", ModuleName))
|
||||||
|
-- end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local subevent = select(2, ...)
|
||||||
|
if not subevent:find("_DAMAGE") then
|
||||||
|
if Heimdall_Data.config.bonkDetector.debug then
|
||||||
|
print(string.format("[%s] Not a damage event, ignoring: %s", ModuleName, subevent))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type string|nil, string, string, string, string
|
||||||
|
local err, source, sourceGUID, destination, destinationGUID
|
||||||
|
|
||||||
|
source, err = CLEUParser.GetSourceName(...)
|
||||||
|
if err then
|
||||||
|
if Heimdall_Data.config.bonkDetector.debug then
|
||||||
|
print(string.format("[%s] Error getting source name: %s", ModuleName, err))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
sourceGUID, err = CLEUParser.GetSourceGUID(...)
|
||||||
|
if err then
|
||||||
|
if Heimdall_Data.config.bonkDetector.debug then
|
||||||
|
print(string.format("[%s] Error getting source GUID: %s", ModuleName, err))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not string.find(sourceGUID, "Player") then
|
||||||
|
if Heimdall_Data.config.bonkDetector.debug then
|
||||||
|
print(string.format("[%s] Source %s is not a player, nothing to do", ModuleName, source))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
destination, err = CLEUParser.GetDestName(...)
|
||||||
|
if err then
|
||||||
|
if Heimdall_Data.config.bonkDetector.debug then
|
||||||
|
print(string.format("[%s] Error getting destination name: %s", ModuleName, err))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
destinationGUID, err = CLEUParser.GetDestGUID(...)
|
||||||
|
if err then
|
||||||
|
if Heimdall_Data.config.bonkDetector.debug then
|
||||||
|
print(string.format("[%s] Error getting destination GUID: %s", ModuleName, err))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not string.find(destinationGUID, "Player") then
|
||||||
|
if Heimdall_Data.config.bonkDetector.debug then
|
||||||
|
print(string.format("[%s] Destination %s is not a player, nothing to do", ModuleName, destination))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if source == destination then
|
||||||
|
if Heimdall_Data.config.bonkDetector.debug then
|
||||||
|
print(string.format("[%s] Source and destination are the same, ignoring event", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local currentTime = GetTime()
|
||||||
|
local throttle = Heimdall_Data.config.bonkDetector.throttle
|
||||||
|
|
||||||
|
if lastReportTime[source] and (currentTime - lastReportTime[source]) < throttle then
|
||||||
|
if Heimdall_Data.config.bonkDetector.debug then
|
||||||
|
local timeLeft = throttle - (currentTime - lastReportTime[source])
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Damage report throttled for %s (%.1f seconds remaining)",
|
||||||
|
ModuleName,
|
||||||
|
source,
|
||||||
|
timeLeft
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
lastReportTime[source] = currentTime
|
||||||
|
|
||||||
|
if Heimdall_Data.config.bonkDetector.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Processing damage event - Source: %s, Target: %s, Type: %s",
|
||||||
|
ModuleName,
|
||||||
|
source,
|
||||||
|
destination,
|
||||||
|
subevent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, channel in pairs(Heimdall_Data.config.bonkDetector.channels) do
|
||||||
|
local locale = shared.GetLocaleForChannel(channel)
|
||||||
|
local msg = string.format(shared._L("bonkDetected", locale), source, destination, subevent)
|
||||||
|
---@type Message
|
||||||
|
local message = {
|
||||||
|
channel = "C",
|
||||||
|
data = channel,
|
||||||
|
message = msg,
|
||||||
|
}
|
||||||
|
if Heimdall_Data.config.bonkDetector.debug then
|
||||||
|
print(string.format("[%s] Queuing bonk detector message", ModuleName))
|
||||||
|
shared.dumpTable(message)
|
||||||
|
end
|
||||||
|
table.insert(shared.messenger.queue, message)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
print("[Heimdall] BonkDetector loaded")
|
||||||
|
end
|
10
Modules/Bully.lua
Normal file
10
Modules/Bully.lua
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "Bully"
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
|
shared.Bully = {}
|
||||||
|
function shared.Bully.Init()
|
||||||
|
if Heimdall_Data.config.bully.debug then print(string.format("[%s] Module initialized", ModuleName)) end
|
||||||
|
print("[Heimdall] Bully loaded")
|
||||||
|
end
|
File diff suppressed because it is too large
Load Diff
135
Modules/CombatAlerter.lua
Normal file
135
Modules/CombatAlerter.lua
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "CombatAlerter"
|
||||||
|
|
||||||
|
---@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 Heimdall_Data.config.combatAlerter.debug then
|
||||||
|
print(string.format("[%s] Combat log event received", ModuleName))
|
||||||
|
end
|
||||||
|
if not Heimdall_Data.config.combatAlerter.enabled then
|
||||||
|
if Heimdall_Data.config.combatAlerter.debug then
|
||||||
|
print(string.format("[%s] Module disabled, ignoring combat event", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type string|nil, string, string
|
||||||
|
local err, source, destination
|
||||||
|
|
||||||
|
destination, err = CLEUParser.GetDestName(...)
|
||||||
|
if err then
|
||||||
|
if Heimdall_Data.config.combatAlerter.debug then
|
||||||
|
print(string.format("[%s] Error getting destination: %s", ModuleName, err))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.combatAlerter.debug then
|
||||||
|
print(string.format("[%s] Combat event destination: %s", ModuleName, destination))
|
||||||
|
end
|
||||||
|
|
||||||
|
if destination ~= UnitName("player") then
|
||||||
|
if Heimdall_Data.config.combatAlerter.debug then
|
||||||
|
print(string.format("[%s] Ignoring event - not targeted at player", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
source, err = CLEUParser.GetSourceName(...)
|
||||||
|
if err then
|
||||||
|
if Heimdall_Data.config.combatAlerter.debug then
|
||||||
|
print(string.format("[%s] Error getting source, using 'unknown': %s", ModuleName, err))
|
||||||
|
end
|
||||||
|
source = "unknown"
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.combatAlerter.debug then
|
||||||
|
print(string.format("[%s] Combat event source: %s", ModuleName, source))
|
||||||
|
end
|
||||||
|
|
||||||
|
if shared.stinkyTracker.stinkies and shared.stinkyTracker.stinkies[source] then
|
||||||
|
if Heimdall_Data.config.combatAlerter.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Source is tracked stinky: %s (Already alerted: %s)",
|
||||||
|
ModuleName,
|
||||||
|
source,
|
||||||
|
tostring(alerted[source] or false)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
if alerted[source] then return end
|
||||||
|
|
||||||
|
alerted[source] = true
|
||||||
|
local x, y = GetPlayerMapPosition("player")
|
||||||
|
local zone, subZone = GetZoneText(), GetSubZoneText()
|
||||||
|
|
||||||
|
if Heimdall_Data.config.combatAlerter.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Player location: %s/%s at %.2f,%.2f",
|
||||||
|
ModuleName,
|
||||||
|
zone,
|
||||||
|
subZone,
|
||||||
|
x * 100,
|
||||||
|
y * 100
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
SetMapToCurrentZone()
|
||||||
|
SetMapByID(GetCurrentMapAreaID())
|
||||||
|
local areaId = GetCurrentMapAreaID()
|
||||||
|
|
||||||
|
for _, channel in pairs(Heimdall_Data.config.combatAlerter.channels) do
|
||||||
|
local locale = shared.GetLocaleForChannel(channel)
|
||||||
|
local text = string.format(
|
||||||
|
shared._L("combatAlerterInCombat", locale),
|
||||||
|
source,
|
||||||
|
shared._L("zone", locale),
|
||||||
|
shared._L("subZone", locale),
|
||||||
|
tostring(areaId),
|
||||||
|
x * 100,
|
||||||
|
y * 100
|
||||||
|
)
|
||||||
|
---@type Message
|
||||||
|
local msg = {
|
||||||
|
channel = "C",
|
||||||
|
data = channel,
|
||||||
|
message = text,
|
||||||
|
}
|
||||||
|
if Heimdall_Data.config.combatAlerter.debug then
|
||||||
|
print(string.format("[%s] Queuing alert message", ModuleName))
|
||||||
|
shared.dumpTable(msg)
|
||||||
|
end
|
||||||
|
table.insert(shared.messenger.queue, msg)
|
||||||
|
end
|
||||||
|
elseif Heimdall_Data.config.combatAlerter.debug then
|
||||||
|
print(string.format("[%s] Source not in stinky list, ignoring: %s", ModuleName, source))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local combatTriggerFrame = CreateFrame("Frame")
|
||||||
|
combatTriggerFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
|
||||||
|
combatTriggerFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
|
||||||
|
combatTriggerFrame:SetScript("OnEvent", function(self, event, ...)
|
||||||
|
if Heimdall_Data.config.combatAlerter.debug then
|
||||||
|
print(string.format("[%s] Combat state changed: %s", ModuleName, event))
|
||||||
|
if event == "PLAYER_REGEN_DISABLED" then
|
||||||
|
print(string.format("[%s] Entered combat - Resetting alerts", ModuleName))
|
||||||
|
else
|
||||||
|
print(string.format("[%s] Left combat - Resetting alerts", ModuleName))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
alerted = {}
|
||||||
|
end)
|
||||||
|
|
||||||
|
if Heimdall_Data.config.combatAlerter.debug then print(string.format("[%s] Module initialized", ModuleName)) end
|
||||||
|
print("[Heimdall] CombatAlerter loaded")
|
||||||
|
end
|
345
Modules/Commander.lua
Normal file
345
Modules/Commander.lua
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "Commander"
|
||||||
|
|
||||||
|
local helpMessages = {
|
||||||
|
ru = {
|
||||||
|
"1) who - пишет вам никнеймы текущих врагов и локу.",
|
||||||
|
"2) classes - покажет классы врагов и число.",
|
||||||
|
"3) howmany - общее число врагов (дурик 4 . огри 2 ) ",
|
||||||
|
"4) + - атоинвайт в сбор пати и сброса кд.",
|
||||||
|
"5) ++ -автоинвайт в пати аликов (если нужен рефрак)",
|
||||||
|
"6 ) note Никнейм текст - добавление заметки.",
|
||||||
|
"7) note Никнейм - посмотреть последние заметки.",
|
||||||
|
"8) note Никнейм 5 - посмотреть конкретную заметку.",
|
||||||
|
"9) note Никнейм 1..5 - посмотреть заметки от 1 до 5",
|
||||||
|
"10) note Никнейм delete 1 - удалить заметку номер 1",
|
||||||
|
"11) note Никнейм delete 1..5 - удалить заметки 1 до 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",
|
||||||
|
"6) note <name> <note> - adds a note for the specified character.",
|
||||||
|
"7) note <name> - lists the last N notes for the character.",
|
||||||
|
"8) note <name> i - lists the i-th note for the character.",
|
||||||
|
"9) note <name> i..j - lists notes from i to j for the character.",
|
||||||
|
"10) note <name> delete i - deletes the i-th note for the character.",
|
||||||
|
"11) note <name> delete i..j - deletes notes from i to j for the character.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
---@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 shared.Whoer.ShouldNotifyForZone(player.zone) then ret[player.zone] = (ret[player.zone] or 0) + 1 end
|
||||||
|
end
|
||||||
|
local text = {}
|
||||||
|
for zone, count in pairs(ret) do
|
||||||
|
text[#text + 1] = string.format("%s: %d", zone, count)
|
||||||
|
end
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
---@param arr table<string, Player>
|
||||||
|
---@return string[]
|
||||||
|
local function CountPartitioned(arr)
|
||||||
|
local count = Count(arr)
|
||||||
|
local text = {}
|
||||||
|
---@diagnostic disable-next-line: param-type-mismatch something wrong with luals, it's picking up the "wrong" unpack
|
||||||
|
for _, line in pairs(Partition(strjoin(", ", unpack(count)), 200)) do
|
||||||
|
text[#text + 1] = line
|
||||||
|
end
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
---@param arr table<string, Player>
|
||||||
|
---@return string[]
|
||||||
|
local function Who(arr)
|
||||||
|
local ret = {}
|
||||||
|
for _, player in pairs(arr) do
|
||||||
|
if shared.Whoer.ShouldNotifyForZone(player.zone) then
|
||||||
|
ret[#ret + 1] = string.format(
|
||||||
|
"%s/%s (%s) %s",
|
||||||
|
player.name,
|
||||||
|
player.class,
|
||||||
|
player.zone,
|
||||||
|
player.stinky and "(!!!!)" or ""
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if Heimdall_Data.config.commander.debug then
|
||||||
|
print(string.format("[%s] Command result: %s", ModuleName, strjoin(", ", unpack(ret))))
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
---@param arr table<string, Player>
|
||||||
|
---@return string[]
|
||||||
|
local function WhoPartitioned(arr)
|
||||||
|
local who = Who(arr)
|
||||||
|
local text = {}
|
||||||
|
---@diagnostic disable-next-line: param-type-mismatch something wrong with luals, it's picking up the "wrong" unpack
|
||||||
|
for _, line in pairs(Partition(strjoin(", ", unpack(who)), 200)) do
|
||||||
|
text[#text + 1] = "who: " .. line
|
||||||
|
end
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
---@param arr table<string, Player>
|
||||||
|
---@return string[]
|
||||||
|
local function CountClass(arr)
|
||||||
|
local ret = {}
|
||||||
|
for _, player in pairs(arr) do
|
||||||
|
if shared.Whoer.ShouldNotifyForZone(player.zone) then ret[player.class] = (ret[player.class] or 0) + 1 end
|
||||||
|
end
|
||||||
|
local text = {}
|
||||||
|
for class, count in pairs(ret) do
|
||||||
|
text[#text + 1] = string.format("%s: %d", class, count)
|
||||||
|
end
|
||||||
|
if Heimdall_Data.config.commander.debug then
|
||||||
|
print(string.format("[%s] Message text: %s", ModuleName, strjoin(", ", unpack(text))))
|
||||||
|
end
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
---@param arr table<string, Player>
|
||||||
|
---@return string[]
|
||||||
|
local function CountClassPartitioned(arr)
|
||||||
|
local countClass = CountClass(arr)
|
||||||
|
local text = {}
|
||||||
|
---@diagnostic disable-next-line: param-type-mismatch something wrong with luals, it's picking up the "wrong" unpack
|
||||||
|
for _, line in pairs(Partition(strjoin(", ", unpack(countClass)), 200)) do
|
||||||
|
text[#text + 1] = line
|
||||||
|
end
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
local function CountClassPartitionedStinkies()
|
||||||
|
if Heimdall_Data.config.commander.debug then
|
||||||
|
print(string.format("[%s] Executing: CountClassPartitionedStinkies", ModuleName))
|
||||||
|
end
|
||||||
|
local res = CountClassPartitioned(HeimdallStinkies)
|
||||||
|
if #res == 0 then return { "No stinkies found" } end
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
local function WhoPartitionedStinkies()
|
||||||
|
if Heimdall_Data.config.commander.debug then
|
||||||
|
print(string.format("[%s] Executing: WhoPartitionedStinkies", ModuleName))
|
||||||
|
shared.dumpTable(HeimdallStinkies)
|
||||||
|
end
|
||||||
|
local res = WhoPartitioned(HeimdallStinkies)
|
||||||
|
if #res == 0 then return { "No stinkies found" } end
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
local function CountPartitionedStinkies()
|
||||||
|
if Heimdall_Data.config.commander.debug then
|
||||||
|
print(string.format("[%s] Executing: CountPartitionedStinkies", ModuleName))
|
||||||
|
end
|
||||||
|
local res = CountPartitioned(HeimdallStinkies)
|
||||||
|
if #res == 0 then return { "No stinkies found" } end
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
local function HelpRu()
|
||||||
|
if Heimdall_Data.config.commander.debug then print(string.format("[%s] Executing: HelpRu", ModuleName)) end
|
||||||
|
return helpMessages.ru
|
||||||
|
end
|
||||||
|
local function HelpEn()
|
||||||
|
if Heimdall_Data.config.commander.debug then print(string.format("[%s] Executing: HelpEn", ModuleName)) end
|
||||||
|
return helpMessages.en
|
||||||
|
end
|
||||||
|
local groupInviteFrame = CreateFrame("Frame")
|
||||||
|
groupInviteFrame:SetScript("OnEvent", function(self, event, ...)
|
||||||
|
if Heimdall_Data.config.commander.debug then print(string.format("[%s] Event received", ModuleName)) end
|
||||||
|
AcceptGroup()
|
||||||
|
groupInviteFrame:UnregisterEvent("PARTY_INVITE_REQUEST")
|
||||||
|
C_Timer.NewTimer(0.1, function()
|
||||||
|
if Heimdall_Data.config.commander.debug then
|
||||||
|
print(string.format("[%s] Click event triggered", ModuleName))
|
||||||
|
end
|
||||||
|
_G["StaticPopup1Button1"]:Click()
|
||||||
|
end, 1)
|
||||||
|
end)
|
||||||
|
local function JoinGroup()
|
||||||
|
if Heimdall_Data.config.commander.debug then
|
||||||
|
print(string.format("[%s] JoinGroup command received", ModuleName))
|
||||||
|
end
|
||||||
|
groupInviteFrame:RegisterEvent("PARTY_INVITE_REQUEST")
|
||||||
|
C_Timer.NewTimer(10, function() groupInviteFrame:UnregisterEvent("PARTY_INVITE_REQUEST") end, 1)
|
||||||
|
return { "+" }
|
||||||
|
end
|
||||||
|
local function LeaveGroup()
|
||||||
|
if Heimdall_Data.config.commander.debug then
|
||||||
|
print(string.format("[%s] LeaveGroup command received", ModuleName))
|
||||||
|
end
|
||||||
|
LeaveParty()
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
---@param target string
|
||||||
|
local function FollowTarget(target)
|
||||||
|
if Heimdall_Data.config.commander.debug then
|
||||||
|
print(string.format("[%s] Following target: %s", ModuleName, target))
|
||||||
|
end
|
||||||
|
if not target then return end
|
||||||
|
FollowUnit(target)
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param args string[]
|
||||||
|
local function MacroTarget(args)
|
||||||
|
if Heimdall_Data.config.commander.debug then
|
||||||
|
---@diagnostic disable-next-line: param-type-mismatch something wrong with luals, it's picking up the "wrong" unpack
|
||||||
|
print(string.format("[%s] Macroing: %s", ModuleName, strjoin(" ", unpack(args))))
|
||||||
|
end
|
||||||
|
if #args < 2 or #args % 2 ~= 0 then
|
||||||
|
if #args < 2 or #args % 2 ~= 0 then
|
||||||
|
if Heimdall_Data.config.commander.debug then
|
||||||
|
print(string.format("[%s] Invalid number of arguments for MacroTarget", ModuleName))
|
||||||
|
end
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.remove(args, 1)
|
||||||
|
|
||||||
|
for i = 1, #args do
|
||||||
|
local stinky = strtrim(args[i])
|
||||||
|
local name = stinky:match("([^/]+)")
|
||||||
|
local class = stinky:match("/([^ $]+)")
|
||||||
|
if Heimdall_Data.config.commander.debug then
|
||||||
|
print(string.format("[%s] Adding stinky: %s/%s", ModuleName, name, tostring(class)))
|
||||||
|
end
|
||||||
|
shared.stinkyTracker.stinkies[name] = {
|
||||||
|
name = name,
|
||||||
|
class = class or "unknown",
|
||||||
|
seenAt = GetTime(),
|
||||||
|
hostile = true,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
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 },
|
||||||
|
{ keywordRe = "^macro", commanderOnly = false, callback = MacroTarget },
|
||||||
|
}
|
||||||
|
|
||||||
|
local commanderChannelFrame = CreateFrame("Frame")
|
||||||
|
commanderChannelFrame:RegisterEvent("CHAT_MSG_CHANNEL")
|
||||||
|
commanderChannelFrame:SetScript("OnEvent", function(self, event, msg, sender, ...)
|
||||||
|
--if Heimdall_Data.config.commander.debug then
|
||||||
|
-- print(string.format("[%s] Event received", ModuleName))
|
||||||
|
-- shared.dumpTable(Heimdall_Data.config.commander)
|
||||||
|
--end
|
||||||
|
if not Heimdall_Data.config.commander.enabled then
|
||||||
|
--if Heimdall_Data.config.commander.debug then
|
||||||
|
-- print(string.format("[%s] Module disabled, ignoring event", ModuleName))
|
||||||
|
--end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local channelId = select(6, ...)
|
||||||
|
local _, channelname = GetChannelName(channelId)
|
||||||
|
local ok = false
|
||||||
|
for _, channel in pairs(Heimdall_Data.config.commander.channels) do
|
||||||
|
if channel == channelname then
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not ok then
|
||||||
|
if Heimdall_Data.config.commander.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Channel name '%s' does not match any of the channels '%s'",
|
||||||
|
ModuleName,
|
||||||
|
channelname,
|
||||||
|
table.concat(Heimdall_Data.config.commander.channels, ", ")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
sender = string.match(sender, "^[^-]+")
|
||||||
|
if Heimdall_Data.config.commander.debug then
|
||||||
|
print(string.format("[%s] Message from: %s", ModuleName, sender))
|
||||||
|
shared.dumpTable(Heimdall_Data.config.commander)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, command in ipairs(commands) do
|
||||||
|
local enabled = Heimdall_Data.config.commander.commands[command.keywordRe] == true or false
|
||||||
|
if Heimdall_Data.config.commander.debug then
|
||||||
|
print(string.format("[%s] Command match: %s = %s", ModuleName, command.keywordRe, tostring(enabled)))
|
||||||
|
end
|
||||||
|
if
|
||||||
|
enabled
|
||||||
|
and (
|
||||||
|
not command.commanderOnly
|
||||||
|
or (command.commanderOnly and sender == Heimdall_Data.config.commander.commander)
|
||||||
|
)
|
||||||
|
then
|
||||||
|
if msg:match(command.keywordRe) then
|
||||||
|
---@diagnostic disable-next-line: redundant-parameter Currently luals does not support variadic functions as a @field
|
||||||
|
local messages = command.callback({ strsplit(",", msg) })
|
||||||
|
if Heimdall_Data.config.commander.debug then
|
||||||
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
|
print(string.format("[%s] Messages to send: %s", ModuleName, strjoin(", ", unpack(messages))))
|
||||||
|
end
|
||||||
|
for _, message in ipairs(messages) do
|
||||||
|
---@type Message
|
||||||
|
local returnmsg = {
|
||||||
|
channel = "C",
|
||||||
|
data = channelname,
|
||||||
|
message = message,
|
||||||
|
}
|
||||||
|
if Heimdall_Data.config.commander.debug then
|
||||||
|
print(string.format("[%s] Queuing message", ModuleName))
|
||||||
|
shared.dumpTable(msg)
|
||||||
|
end
|
||||||
|
--table.insert(shared.messenger.queue, msg)
|
||||||
|
table.insert(shared.networkMessenger.queue, returnmsg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
print("[Heimdall] Commander module loaded")
|
||||||
|
end
|
2665
Modules/Config.lua
Normal file
2665
Modules/Config.lua
Normal file
File diff suppressed because it is too large
Load Diff
7
Modules/Configurator.lua
Normal file
7
Modules/Configurator.lua
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "Configurator"
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
|
shared.Configurator = {}
|
||||||
|
function shared.Configurator.Init() print(string.format("[Heimdall] %s module loaded", ModuleName)) end
|
252
Modules/DeathReporter.lua
Normal file
252
Modules/DeathReporter.lua
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "DeathReporter"
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
|
shared.DeathReporter = {}
|
||||||
|
function shared.DeathReporter.Init()
|
||||||
|
---@type table<string, number>
|
||||||
|
local recentDeaths = {}
|
||||||
|
---@type table<string, number>
|
||||||
|
local recentDuels = {}
|
||||||
|
|
||||||
|
---@param source string
|
||||||
|
---@param destination string
|
||||||
|
---@param spellName string
|
||||||
|
local function RegisterDeath(source, destination, spellName)
|
||||||
|
if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Processing death event - Source: %s, Target: %s, Spell: %s",
|
||||||
|
ModuleName,
|
||||||
|
source,
|
||||||
|
destination,
|
||||||
|
spellName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not Heimdall_Data.config.deathReporter.enabled then
|
||||||
|
if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
print(string.format("[%s] Module disabled, ignoring death event", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if
|
||||||
|
recentDeaths[destination]
|
||||||
|
and GetTime() - recentDeaths[destination] < Heimdall_Data.config.deathReporter.throttle
|
||||||
|
then
|
||||||
|
if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
local timeLeft = Heimdall_Data.config.deathReporter.throttle - (GetTime() - recentDeaths[destination])
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Death report throttled for %s (%.1f seconds remaining)",
|
||||||
|
ModuleName,
|
||||||
|
destination,
|
||||||
|
timeLeft
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if
|
||||||
|
recentDuels[destination]
|
||||||
|
and GetTime() - recentDuels[destination] < Heimdall_Data.config.deathReporter.duelThrottle
|
||||||
|
then
|
||||||
|
if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Ignoring death report - Recent duel detected for target: %s",
|
||||||
|
ModuleName,
|
||||||
|
destination
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if
|
||||||
|
recentDuels[source]
|
||||||
|
and GetTime() - recentDuels[source] < Heimdall_Data.config.deathReporter.duelThrottle
|
||||||
|
then
|
||||||
|
if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Ignoring death report - Recent duel detected for source: %s",
|
||||||
|
ModuleName,
|
||||||
|
source
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
print(string.format("[%s] Recording death for %s", ModuleName, destination))
|
||||||
|
end
|
||||||
|
recentDeaths[destination] = GetTime()
|
||||||
|
|
||||||
|
C_Timer.NewTimer(3, function()
|
||||||
|
if
|
||||||
|
recentDuels[destination]
|
||||||
|
and GetTime() - recentDuels[destination] < Heimdall_Data.config.deathReporter.duelThrottle
|
||||||
|
then
|
||||||
|
if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Cancelling delayed death report - Recent duel detected for: %s",
|
||||||
|
ModuleName,
|
||||||
|
destination
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if
|
||||||
|
recentDuels[source]
|
||||||
|
and GetTime() - recentDuels[source] < Heimdall_Data.config.deathReporter.duelThrottle
|
||||||
|
then
|
||||||
|
if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Cancelling delayed death report - Recent duel detected for: %s",
|
||||||
|
ModuleName,
|
||||||
|
source
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Sending death report - %s killed %s with %s",
|
||||||
|
ModuleName,
|
||||||
|
source,
|
||||||
|
destination,
|
||||||
|
spellName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local zone, subzone = GetZoneText() or "Unknown", GetSubZoneText() or "Unknown"
|
||||||
|
if Heimdall_Data.config.spotter.zoneOverride then
|
||||||
|
zone = Heimdall_Data.config.spotter.zoneOverride or ""
|
||||||
|
subzone = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
local x, y = GetPlayerMapPosition("player")
|
||||||
|
if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
print(string.format("[%s] Player coordinates: %.2f, %.2f", ModuleName, x * 100, y * 100))
|
||||||
|
end
|
||||||
|
SetMapToCurrentZone()
|
||||||
|
SetMapByID(GetCurrentMapAreaID())
|
||||||
|
local zoneId = GetCurrentMapAreaID()
|
||||||
|
|
||||||
|
for _, channel in pairs(Heimdall_Data.config.deathReporter.channels) do
|
||||||
|
local locale = shared.GetLocaleForChannel(channel)
|
||||||
|
local text = string.format(
|
||||||
|
shared._L("killed", locale),
|
||||||
|
source,
|
||||||
|
destination,
|
||||||
|
shared._L(spellName, locale),
|
||||||
|
shared._L(zone, locale),
|
||||||
|
shared._L(subzone, locale),
|
||||||
|
zoneId,
|
||||||
|
x * 100,
|
||||||
|
y * 100
|
||||||
|
)
|
||||||
|
---@type Message
|
||||||
|
local msg = {
|
||||||
|
channel = "C",
|
||||||
|
data = channel,
|
||||||
|
message = text,
|
||||||
|
}
|
||||||
|
if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
print(string.format("[%s] Queuing death report message", ModuleName))
|
||||||
|
shared.dumpTable(msg)
|
||||||
|
end
|
||||||
|
table.insert(shared.messenger.queue, msg)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local cleuFrame = CreateFrame("Frame")
|
||||||
|
cleuFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
|
||||||
|
cleuFrame:SetScript("OnEvent", function(self, event, ...)
|
||||||
|
-- if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
-- print(string.format("[%s] Received combat log event", ModuleName))
|
||||||
|
-- end
|
||||||
|
if not Heimdall_Data.config.deathReporter.enabled then return end
|
||||||
|
local overkill, source, destination, spellName, sourceGUID, destinationGUID, err
|
||||||
|
overkill, err = CLEUParser.GetOverkill(...)
|
||||||
|
if not err and overkill > 0 then
|
||||||
|
source, err = CLEUParser.GetSourceName(...)
|
||||||
|
if err then
|
||||||
|
source = "unknown"
|
||||||
|
if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
print(string.format("[%s] Error getting source name", ModuleName))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
destination, err = CLEUParser.GetDestName(...)
|
||||||
|
if err then
|
||||||
|
destination = "unknown"
|
||||||
|
if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
print(string.format("[%s] Error getting destination name", ModuleName))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
spellName, err = CLEUParser.GetSpellName(...)
|
||||||
|
if err then
|
||||||
|
spellName = "unknown"
|
||||||
|
if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
print(string.format("[%s] Error getting spell name", ModuleName))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
sourceGUID, err = CLEUParser.GetSourceGUID(...)
|
||||||
|
if err or not string.match(sourceGUID, "Player") then return end
|
||||||
|
destinationGUID, err = CLEUParser.GetDestGUID(...)
|
||||||
|
if err or not string.match(destinationGUID, "Player") then return end
|
||||||
|
RegisterDeath(source, destination, spellName)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local systemMessageFrame = CreateFrame("Frame")
|
||||||
|
systemMessageFrame:RegisterEvent("CHAT_MSG_SYSTEM")
|
||||||
|
systemMessageFrame:SetScript("OnEvent", function(self, event, msg)
|
||||||
|
if not Heimdall_Data.config.deathReporter.enabled then return end
|
||||||
|
local source, destination = string.match(msg, "([^ ]+) has defeated ([^ ]+) in a duel")
|
||||||
|
if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
print(string.format("[%s] Received system message: %s", ModuleName, msg))
|
||||||
|
print(
|
||||||
|
string.format("[%s] Source: %s, Destination: %s", ModuleName, tostring(source), tostring(destination))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
if not source or not destination then return end
|
||||||
|
source = string.match(source, "([^-]+)")
|
||||||
|
destination = string.match(destination, "([^-]+)")
|
||||||
|
if source and destination then
|
||||||
|
if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
print(string.format("[%s] Detected duel between %s and %s", ModuleName, source, destination))
|
||||||
|
end
|
||||||
|
local now = GetTime()
|
||||||
|
recentDuels[source] = now
|
||||||
|
recentDuels[destination] = now
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
if Heimdall_Data.config.deathReporter.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Module initialized with throttle: %.1fs, duel throttle: %.1fs",
|
||||||
|
ModuleName,
|
||||||
|
Heimdall_Data.config.deathReporter.throttle,
|
||||||
|
Heimdall_Data.config.deathReporter.duelThrottle
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
print("[Heimdall] DeathReporter loaded")
|
||||||
|
end
|
55
Modules/Dueler.lua
Normal file
55
Modules/Dueler.lua
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "Dueler"
|
||||||
|
|
||||||
|
---@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 Heimdall_Data.config.dueler.debug then
|
||||||
|
print(string.format("[%s] Duel request received from: %s", ModuleName, sender))
|
||||||
|
end
|
||||||
|
if not Heimdall_Data.config.dueler.enabled then
|
||||||
|
if Heimdall_Data.config.dueler.debug then
|
||||||
|
print(string.format("[%s] Module disabled, ignoring duel request", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.dueler.debug then
|
||||||
|
print(string.format("[%s] Checking if sender '%s' is in agents list", ModuleName, sender))
|
||||||
|
end
|
||||||
|
|
||||||
|
local allow = Heimdall_Data.config.agents[sender]
|
||||||
|
if allow then
|
||||||
|
if Heimdall_Data.config.dueler.debug then
|
||||||
|
print(string.format("[%s] Accepting duel from trusted agent: %s", ModuleName, sender))
|
||||||
|
end
|
||||||
|
AcceptDuel()
|
||||||
|
else
|
||||||
|
if Heimdall_Data.config.dueler.declineOther then
|
||||||
|
if Heimdall_Data.config.dueler.debug then
|
||||||
|
print(string.format("[%s] Auto-declining duel from untrusted sender: %s", ModuleName, sender))
|
||||||
|
end
|
||||||
|
CancelDuel()
|
||||||
|
else
|
||||||
|
if Heimdall_Data.config.dueler.debug then
|
||||||
|
print(string.format("[%s] Leaving duel request from %s for manual response", ModuleName, sender))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
if Heimdall_Data.config.dueler.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Module initialized with auto-decline: %s",
|
||||||
|
ModuleName,
|
||||||
|
tostring(Heimdall_Data.config.dueler.declineOther)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
print("[Heimdall] Dueler loaded")
|
||||||
|
end
|
@@ -1,29 +1,26 @@
|
|||||||
local addonname, data = ...
|
local _, shared = ...
|
||||||
---@cast data HeimdallData
|
---@cast shared HeimdallShared
|
||||||
---@cast addonname string
|
|
||||||
|
if not shared.dumpTable then
|
||||||
if not data.dumpTable then
|
---@param table table
|
||||||
---@param table table
|
---@param depth number?
|
||||||
---@param depth number?
|
shared.dumpTable = function(table, depth)
|
||||||
data.dumpTable = function(table, depth)
|
if not table then
|
||||||
if not table then
|
print(tostring(table))
|
||||||
print(tostring(table))
|
return
|
||||||
return
|
end
|
||||||
end
|
if depth == nil then depth = 0 end
|
||||||
if depth == nil then
|
if depth > 200 then
|
||||||
depth = 0
|
print("Error: Depth > 200 in dumpTable()")
|
||||||
end
|
return
|
||||||
if (depth > 200) then
|
end
|
||||||
print("Error: Depth > 200 in dumpTable()")
|
for k, v in pairs(table) do
|
||||||
return
|
if type(v) == "table" then
|
||||||
end
|
print(string.rep(" ", depth) .. k .. ":")
|
||||||
for k, v in pairs(table) do
|
shared.dumpTable(v, depth + 1)
|
||||||
if (type(v) == "table") then
|
else
|
||||||
print(string.rep(" ", depth) .. k .. ":")
|
print(string.rep(" ", depth) .. k .. ": ", v)
|
||||||
data.dumpTable(v, depth + 1)
|
end
|
||||||
else
|
end
|
||||||
print(string.rep(" ", depth) .. k .. ": ", v)
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
58
Modules/Echoer.lua
Normal file
58
Modules/Echoer.lua
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "Echoer"
|
||||||
|
|
||||||
|
---@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 Heimdall_Data.config.echoer.debug then
|
||||||
|
-- print(string.format("[%s] Channel message received from: %s", ModuleName, sender))
|
||||||
|
--end
|
||||||
|
|
||||||
|
if not Heimdall_Data.config.echoer.enabled then
|
||||||
|
--if Heimdall_Data.config.echoer.debug then
|
||||||
|
-- print(string.format("[%s] Module disabled, ignoring message", ModuleName))
|
||||||
|
--end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local channelId = select(6, ...)
|
||||||
|
local _, channelname = GetChannelName(channelId)
|
||||||
|
local ok = false
|
||||||
|
for _, channel in pairs(Heimdall_Data.config.echoer.channels) do
|
||||||
|
if channel == channelname then
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not ok then
|
||||||
|
if Heimdall_Data.config.echoer.debug then
|
||||||
|
print(string.format("[%s] Channel name does not match any of the channels", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if Heimdall_Data.config.echoer.debug then
|
||||||
|
print(string.format("[%s] Processing message from master channel: %s", ModuleName, sender))
|
||||||
|
shared.dumpTable(Heimdall_Data.config.echoer)
|
||||||
|
end
|
||||||
|
|
||||||
|
if string.find(msg, "^" .. Heimdall_Data.config.echoer.prefix) then
|
||||||
|
if Heimdall_Data.config.echoer.debug then
|
||||||
|
print(string.format("[%s] Found echo command in message: %s", ModuleName, msg))
|
||||||
|
end
|
||||||
|
local echomsg = string.sub(msg, string.len(Heimdall_Data.config.echoer.prefix) + 1)
|
||||||
|
if Heimdall_Data.config.echoer.debug then
|
||||||
|
print(string.format("[%s] Echoing message: %s", ModuleName, echomsg))
|
||||||
|
end
|
||||||
|
SendChatMessage(echomsg, "SAY")
|
||||||
|
elseif Heimdall_Data.config.echoer.debug then
|
||||||
|
print(string.format("[%s] Message does not start with echo prefix", ModuleName))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
if Heimdall_Data.config.echoer.debug then print(string.format("[%s] Module initialized", ModuleName)) end
|
||||||
|
print("[Heimdall] Echoer loaded")
|
||||||
|
end
|
59
Modules/Emoter.lua
Normal file
59
Modules/Emoter.lua
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "Emoter"
|
||||||
|
|
||||||
|
---@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 Heimdall_Data.config.emoter.debug then
|
||||||
|
-- print(string.format("[%s] Channel message received from: %s", ModuleName, sender))
|
||||||
|
--end
|
||||||
|
|
||||||
|
if not Heimdall_Data.config.emoter.enabled then
|
||||||
|
--if Heimdall_Data.config.emoter.debug then
|
||||||
|
-- print(string.format("[%s] Module disabled, ignoring message", ModuleName))
|
||||||
|
--end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local channelId = select(6, ...)
|
||||||
|
local _, channelname = GetChannelName(channelId)
|
||||||
|
local ok = false
|
||||||
|
for _, channel in pairs(Heimdall_Data.config.emoter.channels) do
|
||||||
|
if channel == channelname then
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not ok then
|
||||||
|
if Heimdall_Data.config.emoter.debug then
|
||||||
|
print(string.format("[%s] Channel name does not match any of the channels", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.emoter.debug then
|
||||||
|
print(string.format("[%s] Processing message from master channel: %s", ModuleName, sender))
|
||||||
|
shared.dumpTable(Heimdall_Data.config.emoter)
|
||||||
|
end
|
||||||
|
|
||||||
|
if string.find(msg, "^" .. Heimdall_Data.config.emoter.prefix) then
|
||||||
|
if Heimdall_Data.config.emoter.debug then
|
||||||
|
print(string.format("[%s] Found emote command in message: %s", ModuleName, msg))
|
||||||
|
end
|
||||||
|
local emote = string.sub(msg, string.len(Heimdall_Data.config.emoter.prefix) + 1)
|
||||||
|
if Heimdall_Data.config.emoter.debug then
|
||||||
|
print(string.format("[%s] Performing emote: %s", ModuleName, emote))
|
||||||
|
end
|
||||||
|
DoEmote(emote)
|
||||||
|
elseif Heimdall_Data.config.emoter.debug then
|
||||||
|
print(string.format("[%s] Message does not start with emote prefix", ModuleName))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
if Heimdall_Data.config.emoter.debug then print(string.format("[%s] Module initialized", ModuleName)) end
|
||||||
|
print("[Heimdall] Emoter loaded")
|
||||||
|
end
|
263
Modules/Inviter.lua
Normal file
263
Modules/Inviter.lua
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "Inviter"
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
|
shared.Inviter = {}
|
||||||
|
function shared.Inviter.Init()
|
||||||
|
-- Fallback for old config
|
||||||
|
if type(Heimdall_Data.config.inviter.listeningChannel) == "string" then
|
||||||
|
Heimdall_Data.config.inviter.listeningChannel = {
|
||||||
|
[Heimdall_Data.config.inviter.listeningChannel] = true,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
---@type Timer
|
||||||
|
local updateTimer = nil
|
||||||
|
|
||||||
|
local function FixGroup()
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
print(string.format("[%s] Checking and fixing group configuration", ModuleName))
|
||||||
|
end
|
||||||
|
|
||||||
|
if not IsInRaid() then
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
print(string.format("[%s] Converting party to raid", ModuleName))
|
||||||
|
end
|
||||||
|
ConvertToRaid()
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.inviter.allAssist then
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
print(string.format("[%s] Setting all members to assistant", ModuleName))
|
||||||
|
end
|
||||||
|
SetEveryoneIsAssistant()
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.inviter.agentsAssist then
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
local agentCount = 0
|
||||||
|
for _ in pairs(Heimdall_Data.config.agents) do
|
||||||
|
agentCount = agentCount + 1
|
||||||
|
end
|
||||||
|
print(string.format("[%s] Processing %d agents for assistant promotion", ModuleName, agentCount))
|
||||||
|
end
|
||||||
|
|
||||||
|
for name, _ in pairs(Heimdall_Data.config.agents) do
|
||||||
|
if UnitInParty(name) and not UnitIsGroupLeader(name) and not UnitIsRaidOfficer(name) then
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
print(string.format("[%s] Promoting agent to assistant: %s", ModuleName, name))
|
||||||
|
end
|
||||||
|
PromoteToAssistant(name, true)
|
||||||
|
elseif Heimdall_Data.config.inviter.debug then
|
||||||
|
if not UnitInParty(name) then
|
||||||
|
print(string.format("[%s] Agent not in party: %s", ModuleName, name))
|
||||||
|
elseif UnitIsGroupLeader(name) then
|
||||||
|
print(string.format("[%s] Agent is already leader: %s", ModuleName, name))
|
||||||
|
elseif UnitIsRaidOfficer(name) then
|
||||||
|
print(string.format("[%s] Agent is already assistant: %s", ModuleName, name))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
print(string.format("[%s] Group configuration update complete", ModuleName))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param name string
|
||||||
|
---@return Frame?
|
||||||
|
local function FindPlayerRaidFrame(name)
|
||||||
|
for group = 1, 8 do
|
||||||
|
for player = 1, 5 do
|
||||||
|
local button = _G[string.format("ElvUF_RaidGroup%dUnitButton%d", group, player)]
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
print(string.format("[%s] button = %s", ModuleName, tostring(button)))
|
||||||
|
end
|
||||||
|
|
||||||
|
local unitName = button and button.unit and UnitName(button.unit)
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
print(string.format("[%s] unitName = %s", ModuleName, tostring(unitName)))
|
||||||
|
end
|
||||||
|
if unitName == name then
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
print(string.format("[%s] unitName == name", ModuleName))
|
||||||
|
end
|
||||||
|
return button
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local framePool = {}
|
||||||
|
local playerButtons = {}
|
||||||
|
setmetatable(playerButtons, { __mode = "kv" })
|
||||||
|
---@param players string[]
|
||||||
|
local function OverlayKickButtons(players)
|
||||||
|
for _, frame in pairs(framePool) do
|
||||||
|
frame:Hide()
|
||||||
|
end
|
||||||
|
for _, name in pairs(players) do
|
||||||
|
local frame = FindPlayerRaidFrame(name)
|
||||||
|
if frame then
|
||||||
|
playerButtons[name] = frame
|
||||||
|
-- All of these are ELVUI specific so they won't be in our meta...
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
local button = framePool[frame.unit]
|
||||||
|
or CreateFrame(
|
||||||
|
"Button",
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
string.format("HeimdallKickButton%s", frame.unit, frame, "SecureActionButtonTemplate")
|
||||||
|
)
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
framePool[frame.unit] = button
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
button:SetSize(frame.UNIT_WIDTH / 2, frame.UNIT_HEIGHT / 2)
|
||||||
|
button:SetPoint("CENTER", frame, "CENTER", 0, 0)
|
||||||
|
button:SetNormalTexture("Interface\\Buttons\\UI-GroupLoot-KickIcon")
|
||||||
|
button:SetHighlightTexture("Interface\\Buttons\\UI-GroupLoot-KickIcon")
|
||||||
|
button:SetPushedTexture("Interface\\Buttons\\UI-GroupLoot-KickIcon")
|
||||||
|
button:SetDisabledTexture("Interface\\Buttons\\UI-GroupLoot-KickIcon")
|
||||||
|
button:SetAlpha(0.5)
|
||||||
|
button:Show()
|
||||||
|
button:SetScript("OnClick", function()
|
||||||
|
UninviteUnit(name)
|
||||||
|
button:Hide()
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
print(string.format("[%s] Frame for player %s not found", ModuleName, name))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@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
|
||||||
|
local afkPlayers = {}
|
||||||
|
for name, time in pairs(groupMembers) do
|
||||||
|
if not UnitInParty(name) then
|
||||||
|
print(string.format("%s no longer in party", name))
|
||||||
|
groupMembers[name] = nil
|
||||||
|
else
|
||||||
|
if time < now - Heimdall_Data.config.inviter.afkThreshold then
|
||||||
|
print(string.format("Kicking %s for being offline", name))
|
||||||
|
afkPlayers[#afkPlayers + 1] = name
|
||||||
|
-- Blyat this is protected...
|
||||||
|
-- UninviteUnit(name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
OverlayKickButtons(afkPlayers)
|
||||||
|
end
|
||||||
|
local function Tick()
|
||||||
|
CleanGroups()
|
||||||
|
C_Timer.NewTimer(Heimdall_Data.config.inviter.cleanupInterval, Tick, 1)
|
||||||
|
end
|
||||||
|
Tick()
|
||||||
|
|
||||||
|
local groupRosterUpdateFrame = CreateFrame("Frame")
|
||||||
|
groupRosterUpdateFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
|
||||||
|
groupRosterUpdateFrame:SetScript("OnEvent", function(self, event, ...)
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
print(string.format("[%s] Event received: %s", ModuleName, event))
|
||||||
|
end
|
||||||
|
|
||||||
|
if not Heimdall_Data.config.inviter.enabled then
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
print(string.format("[%s] Module disabled, ignoring event", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
print(string.format("[%s] Group roster changed - Checking configuration", ModuleName))
|
||||||
|
end
|
||||||
|
if updateTimer then updateTimer:Cancel() end
|
||||||
|
updateTimer = C_Timer.NewTimer(Heimdall_Data.config.inviter.throttle, FixGroup)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local inviterChannelFrame = CreateFrame("Frame")
|
||||||
|
inviterChannelFrame:RegisterEvent("CHAT_MSG_CHANNEL")
|
||||||
|
inviterChannelFrame:SetScript("OnEvent", function(self, event, msg, sender, ...)
|
||||||
|
--if Heimdall_Data.config.inviter.debug then
|
||||||
|
-- print(string.format("[%s] Chat message received: %s", ModuleName, msg))
|
||||||
|
-- shared.dumpTable(Heimdall_Data.config.inviter)
|
||||||
|
--end
|
||||||
|
if not Heimdall_Data.config.inviter.enabled then return end
|
||||||
|
local channelId = select(6, ...)
|
||||||
|
local _, channelname = GetChannelName(channelId)
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
print(string.format("[%s] Channel name: %s", ModuleName, channelname))
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok = false
|
||||||
|
for _, channel in pairs(Heimdall_Data.config.inviter.channels) do
|
||||||
|
if channel == channelname then
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not ok then
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
print(string.format("[%s] Channel name does not match any of the channels", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if msg == Heimdall_Data.config.inviter.keyword then
|
||||||
|
if Heimdall_Data.config.inviter.debug then print(string.format("[%s] Inviting %s", ModuleName, sender)) end
|
||||||
|
InviteUnit(sender)
|
||||||
|
else
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
print(string.format("[%s] Message does not match keyword", ModuleName))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Module initialized - All assist: %s, Agents assist: %s",
|
||||||
|
ModuleName,
|
||||||
|
tostring(Heimdall_Data.config.inviter.allAssist),
|
||||||
|
tostring(Heimdall_Data.config.inviter.agentsAssist)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.inviter.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Module initialized - All assist: %s, Agents assist: %s",
|
||||||
|
ModuleName,
|
||||||
|
tostring(Heimdall_Data.config.inviter.allAssist),
|
||||||
|
tostring(Heimdall_Data.config.inviter.agentsAssist)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
print("[Heimdall] Inviter loaded")
|
||||||
|
end
|
101
Modules/Macroer.lua
Normal file
101
Modules/Macroer.lua
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "Macroer"
|
||||||
|
|
||||||
|
---@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
|
||||||
|
|
||||||
|
local function FindOrCreateMacro(macroName)
|
||||||
|
if Heimdall_Data.config.macroer.debug then
|
||||||
|
print(string.format("[%s] Finding or creating macro: %s", ModuleName, macroName))
|
||||||
|
end
|
||||||
|
local idx = GetMacroIndexByName(macroName)
|
||||||
|
if idx == 0 then
|
||||||
|
if Heimdall_Data.config.macroer.debug then
|
||||||
|
print(string.format("[%s] Creating new macro: %s", ModuleName, macroName))
|
||||||
|
end
|
||||||
|
CreateMacro(macroName, "INV_Misc_QuestionMark", "")
|
||||||
|
end
|
||||||
|
idx = GetMacroIndexByName(macroName)
|
||||||
|
if Heimdall_Data.config.macroer.debug then print(string.format("[%s] Macro index: %d", ModuleName, idx)) end
|
||||||
|
return idx
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param stinkies table<string, stinky>
|
||||||
|
local function FixMacro(stinkies)
|
||||||
|
if Heimdall_Data.config.macroer.debug then
|
||||||
|
print(string.format("[%s] Fixing macro with %d stinkies", ModuleName, #stinkies))
|
||||||
|
end
|
||||||
|
if not Heimdall_Data.config.macroer.enabled then
|
||||||
|
if Heimdall_Data.config.macroer.debug then
|
||||||
|
print(string.format("[%s] Module disabled, skipping macro update", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if InCombatLockdown() then
|
||||||
|
if Heimdall_Data.config.macroer.debug then
|
||||||
|
print(string.format("[%s] In combat, skipping macro update", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local priorityMap = {}
|
||||||
|
for priority, className in ipairs(Heimdall_Data.config.macroer.priority) do
|
||||||
|
priorityMap[className] = priority
|
||||||
|
end
|
||||||
|
local minPriority = #Heimdall_Data.config.macroer.priority + 1
|
||||||
|
|
||||||
|
local sortedStinkies = {}
|
||||||
|
for _, stinky in pairs(stinkies) do
|
||||||
|
if not Heimdall_Data.config.agents[stinky.name] then sortedStinkies[#sortedStinkies + 1] = stinky end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.macroer.debug then
|
||||||
|
print(string.format("[%s] Processing %d non-agent stinkies", ModuleName, #sortedStinkies))
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(sortedStinkies, function(a, b)
|
||||||
|
local aPriority = priorityMap[a.class] or minPriority
|
||||||
|
local bPriority = priorityMap[b.class] or minPriority
|
||||||
|
return aPriority > bPriority
|
||||||
|
end)
|
||||||
|
|
||||||
|
if Heimdall_Data.config.macroer.debug then
|
||||||
|
print(string.format("[%s] Sorted stinkies: %d", ModuleName, #sortedStinkies))
|
||||||
|
shared.dumpTable(sortedStinkies)
|
||||||
|
end
|
||||||
|
local lines = { "/targetenemy" }
|
||||||
|
for _, stinky in pairs(sortedStinkies) do
|
||||||
|
if stinky.seenAt > GetTime() - 600 then
|
||||||
|
if Heimdall_Data.config.macroer.debug then
|
||||||
|
print(string.format("[%s] Adding target macro for: %s", ModuleName, stinky.name))
|
||||||
|
end
|
||||||
|
lines[#lines + 1] = string.format("/tar %s", stinky.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local idx = FindOrCreateMacro("HeimdallTarget")
|
||||||
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
|
local body = strjoin("\n", unpack(lines))
|
||||||
|
if Heimdall_Data.config.macroer.debug then
|
||||||
|
print(string.format("[%s] Updating macro with %d lines", ModuleName, #lines))
|
||||||
|
end
|
||||||
|
EditMacro(idx, "HeimdallTarget", "INV_Misc_QuestionMark", body)
|
||||||
|
end
|
||||||
|
|
||||||
|
shared.stinkyTracker.stinkies:onChange(function(value)
|
||||||
|
if Heimdall_Data.config.macroer.debug then
|
||||||
|
print(string.format("[%s] Stinkies changed, updating macro", ModuleName))
|
||||||
|
end
|
||||||
|
FixMacro(value)
|
||||||
|
end)
|
||||||
|
|
||||||
|
if Heimdall_Data.config.macroer.debug then print(string.format("[%s] Module initialized", ModuleName)) end
|
||||||
|
print("[Heimdall] Macroer loaded")
|
||||||
|
end
|
176
Modules/Messenger.lua
Normal file
176
Modules/Messenger.lua
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "Messenger"
|
||||||
|
|
||||||
|
---@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 FindOrJoinChannel(channelName, password)
|
||||||
|
local channelId = GetChannelName(channelName)
|
||||||
|
if channelId == 0 then
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(string.format("[%s] Channel not found, joining: %s", ModuleName, channelName))
|
||||||
|
end
|
||||||
|
if password then
|
||||||
|
JoinPermanentChannel(channelName, password)
|
||||||
|
else
|
||||||
|
JoinPermanentChannel(channelName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
channelId = GetChannelName(channelName)
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(string.format("[%s] Channel found with ID: %s (%s)", ModuleName, channelId, channelName))
|
||||||
|
end
|
||||||
|
return channelId
|
||||||
|
end
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
|
if not shared.messenger then shared.messenger = {} end
|
||||||
|
if not shared.messenger.queue then shared.messenger.queue = {} end
|
||||||
|
if not shared.messenger.ticker then
|
||||||
|
local function DoMessage()
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(string.format("[%s] Processing message queue - Size: %d", ModuleName, #shared.messenger.queue))
|
||||||
|
end
|
||||||
|
|
||||||
|
if not Heimdall_Data.config.messenger.enabled then
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(string.format("[%s] Module disabled, skipping message processing", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type Message
|
||||||
|
local message = shared.messenger.queue[1]
|
||||||
|
if not message then
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(string.format("[%s] Message queue empty", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Processing message - Channel: %s, Data: %s",
|
||||||
|
ModuleName,
|
||||||
|
message.channel or "nil",
|
||||||
|
message.data or "nil"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
print(string.format("[%s] Message content: %s", ModuleName, message.message or "nil"))
|
||||||
|
end
|
||||||
|
|
||||||
|
if not message.message or message.message == "" then
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(string.format("[%s] Invalid message: empty content", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not message.channel or message.channel == "" then
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(string.format("[%s] Invalid message: no channel specified", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if string.find(message.channel, "^C") then
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(string.format("[%s] Converting channel type from C to CHANNEL", ModuleName))
|
||||||
|
end
|
||||||
|
message.channel = "CHANNEL"
|
||||||
|
elseif string.find(message.channel, "^W") then
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(string.format("[%s] Converting channel type from W to WHISPER", ModuleName))
|
||||||
|
end
|
||||||
|
message.channel = "WHISPER"
|
||||||
|
end
|
||||||
|
|
||||||
|
if message.channel == "CHANNEL" and message.data and string.match(message.data, "%D") then
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Processing channel message: '%s' to '%s'",
|
||||||
|
ModuleName,
|
||||||
|
message.message,
|
||||||
|
message.data
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
local channelId = GetChannelName(message.data)
|
||||||
|
if channelId == 0 then
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(string.format("[%s] Channel not found, attempting to join: %s", ModuleName, message.data))
|
||||||
|
end
|
||||||
|
channelId = FindOrJoinChannel(message.data)
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(string.format("[%s] Channel join result - ID: %s", ModuleName, channelId))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
message.data = tostring(channelId)
|
||||||
|
end
|
||||||
|
|
||||||
|
table.remove(shared.messenger.queue, 1)
|
||||||
|
if not message.message or message.message == "" then
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(string.format("[%s] Skipping empty message", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not message.channel or message.channel == "" then
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(string.format("[%s] Skipping message with no channel", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not message.data or message.data == "" then
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(string.format("[%s] Skipping message with no data", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Sending message: '%s' to %s:%s",
|
||||||
|
ModuleName,
|
||||||
|
message.message,
|
||||||
|
message.channel,
|
||||||
|
message.data
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
if string.len(message.message) > 255 then
|
||||||
|
print(string.format("[%s] Message too long!!!!: %s", ModuleName, message.message))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
SendChatMessage(message.message, message.channel, nil, message.data)
|
||||||
|
end
|
||||||
|
local function Tick()
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(string.format("[%s] Tick - Queue size: %d", ModuleName, #shared.messenger.queue))
|
||||||
|
end
|
||||||
|
DoMessage()
|
||||||
|
shared.messenger.ticker = C_Timer.NewTimer(Heimdall_Data.config.messenger.interval, Tick, 1)
|
||||||
|
end
|
||||||
|
Tick()
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.messenger.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Module initialized with interval: %s",
|
||||||
|
ModuleName,
|
||||||
|
Heimdall_Data.config.messenger.interval
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
print("[Heimdall] Messenger loaded")
|
||||||
|
end
|
571
Modules/MinimapTagger.lua
Normal file
571
Modules/MinimapTagger.lua
Normal file
@@ -0,0 +1,571 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "MinimapTagger"
|
||||||
|
local HeimdallRoot = "Interface\\AddOns\\Heimdall\\"
|
||||||
|
local SoundRoot = HeimdallRoot .. "Sounds\\"
|
||||||
|
local TextureRoot = HeimdallRoot .. "Texture\\"
|
||||||
|
--/run local a=GetChannelName("Agent")local b,c=GetPlayerMapPosition("player")b,c=b*100,c*100;local d=string.format("I need help at %s (%s) [%s](%2.2f, %2.2f)",GetZoneText(),GetSubZoneText(),GetCurrentMapAreaID(),b,c)SendChatMessage(d,"CHANNEL",nil,a)
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
|
shared.MinimapTagger = {}
|
||||||
|
function shared.MinimapTagger.Init()
|
||||||
|
---@param x number
|
||||||
|
---@param y number
|
||||||
|
---@param frame Frame
|
||||||
|
---@param scale number?
|
||||||
|
---@param ttl number?
|
||||||
|
local function PlantFrame(x, y, frame, scale, ttl)
|
||||||
|
if not BattlefieldMinimap then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] BattlefieldMinimap not found", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
scale = scale or 1
|
||||||
|
ttl = ttl or 1
|
||||||
|
local w, h = BattlefieldMinimap:GetSize()
|
||||||
|
w, h = w * BattlefieldMinimap:GetEffectiveScale(), h * BattlefieldMinimap:GetEffectiveScale()
|
||||||
|
local maxSize = w > h and w or h
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Minimap size: %d", ModuleName, maxSize))
|
||||||
|
print(string.format("[%s] Scale: %d", ModuleName, scale))
|
||||||
|
print(string.format("[%s] TTL: %d", ModuleName, ttl))
|
||||||
|
end
|
||||||
|
local iconSize = maxSize * 0.05
|
||||||
|
iconSize = iconSize * scale
|
||||||
|
|
||||||
|
x, y = x / 100, y / 100
|
||||||
|
-- Could do with how... I have no idea, but this seems more accurate than without
|
||||||
|
--x, y = x - 0.01, y - 0.01
|
||||||
|
local offsetx, offsety = w * x, h * y
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Alert position: %d, %d", ModuleName, x, y))
|
||||||
|
print(string.format("[%s] Alert offset: %d, %d", ModuleName, offsetx, offsety))
|
||||||
|
end
|
||||||
|
|
||||||
|
frame:Hide()
|
||||||
|
frame:SetSize(iconSize, iconSize)
|
||||||
|
frame:SetFrameStrata("HIGH")
|
||||||
|
frame:SetFrameLevel(100)
|
||||||
|
frame:SetPoint("CENTER", BattlefieldMinimap, "TOPLEFT", offsetx, -offsety)
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Alert frame created, OnUpdate hooked", ModuleName))
|
||||||
|
end
|
||||||
|
frame:SetScript("OnShow", function(self)
|
||||||
|
self:SetAlpha(1)
|
||||||
|
self.custom.busy = true
|
||||||
|
self.custom.progress = 0
|
||||||
|
self:SetScript("OnUpdate", function(selff, elapsed)
|
||||||
|
self.custom.progress = self.custom.progress + elapsed
|
||||||
|
local progress = self.custom.progress / ttl
|
||||||
|
-- if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
-- print(string.format("[%s] Alert progress%%: %f", ModuleName, progress))
|
||||||
|
-- print(string.format("[%s] Alert progress: %f", ModuleName, self.custom.progress))
|
||||||
|
-- print(string.format("[%s] Alert ttl: %d", ModuleName, Heimdall_Data.config.minimapTagger.ttl))
|
||||||
|
-- end
|
||||||
|
self:SetAlpha(1 - progress)
|
||||||
|
|
||||||
|
if progress >= 1 then
|
||||||
|
self:Hide()
|
||||||
|
self.custom.busy = false
|
||||||
|
self:SetScript("OnUpdate", nil)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
frame:Show()
|
||||||
|
end
|
||||||
|
|
||||||
|
--region Alert
|
||||||
|
---@type Frame[]
|
||||||
|
local alertFramePool = {}
|
||||||
|
local alertFramePoolMaxSize = 20
|
||||||
|
for i = 1, alertFramePoolMaxSize do
|
||||||
|
local frame = CreateFrame("Frame")
|
||||||
|
frame.custom = { busy = false }
|
||||||
|
local texture = frame:CreateTexture(nil, "ARTWORK")
|
||||||
|
texture:SetAllPoints(frame)
|
||||||
|
texture:SetTexture(TextureRoot .. Heimdall_Data.config.minimapTagger.alertTextureFile)
|
||||||
|
table.insert(alertFramePool, frame)
|
||||||
|
end
|
||||||
|
local muteAlertUntil = 0
|
||||||
|
---@param x number|nil
|
||||||
|
---@param y number|nil
|
||||||
|
---@param scale number?
|
||||||
|
---@param doTag boolean?
|
||||||
|
local function PlantAlert(x, y, scale, doTag)
|
||||||
|
if x == nil or y == nil then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Alert position is nil, ignoring", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if doTag == nil then doTag = true end
|
||||||
|
local frame = nil
|
||||||
|
for _, alertFrame in ipairs(alertFramePool) do
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
if not alertFrame.custom.busy then
|
||||||
|
frame = alertFrame
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not frame then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Alert frame pool is full and could not get frame", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if Heimdall_Data.config.minimapTagger.alertSound then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Playing alert sound: %s",
|
||||||
|
ModuleName,
|
||||||
|
Heimdall_Data.config.minimapTagger.alertSoundFile
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
if muteAlertUntil > GetTime() then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Alert sound is muted until %d", ModuleName, muteAlertUntil))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
muteAlertUntil = GetTime() + Heimdall_Data.config.minimapTagger.alertSoundThrottle
|
||||||
|
local ok = PlaySoundFile(SoundRoot .. Heimdall_Data.config.minimapTagger.alertSoundFile, "Master")
|
||||||
|
if not ok and Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Failed to play alert sound: %s",
|
||||||
|
ModuleName,
|
||||||
|
Heimdall_Data.config.minimapTagger.alertSoundFile
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if doTag then PlantFrame(x, y, frame, scale, Heimdall_Data.config.minimapTagger.alertTTL) end
|
||||||
|
end
|
||||||
|
--endregion
|
||||||
|
|
||||||
|
--region Tag
|
||||||
|
---@type Frame[]
|
||||||
|
local tagFramePool = {}
|
||||||
|
local tagFramePoolMaxSize = 20
|
||||||
|
for i = 1, tagFramePoolMaxSize do
|
||||||
|
local frame = CreateFrame("Frame")
|
||||||
|
frame.custom = { busy = false }
|
||||||
|
local texture = frame:CreateTexture(nil, "ARTWORK")
|
||||||
|
texture:SetAllPoints(frame)
|
||||||
|
texture:SetTexture(TextureRoot .. Heimdall_Data.config.minimapTagger.tagTextureFile)
|
||||||
|
table.insert(tagFramePool, frame)
|
||||||
|
end
|
||||||
|
local muteTagUntil = 0
|
||||||
|
---@param x number|nil
|
||||||
|
---@param y number|nil
|
||||||
|
---@param scale number?
|
||||||
|
---@param doTag boolean?
|
||||||
|
local function PlantTag(x, y, scale, doTag)
|
||||||
|
if x == nil or y == nil then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Tag position is nil, ignoring", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if doTag == nil then doTag = true end
|
||||||
|
local frame = nil
|
||||||
|
for _, tagFrame in ipairs(tagFramePool) do
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
if not tagFrame.custom.busy then
|
||||||
|
frame = tagFrame
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not frame then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Tag frame pool is full and could not get frame", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if Heimdall_Data.config.minimapTagger.tagSound then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Playing tag sound: %s",
|
||||||
|
ModuleName,
|
||||||
|
Heimdall_Data.config.minimapTagger.tagSoundFile
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
if muteTagUntil > GetTime() then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Tag sound is muted until %d", ModuleName, muteTagUntil))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
muteTagUntil = GetTime() + Heimdall_Data.config.minimapTagger.tagSoundThrottle
|
||||||
|
local ok = PlaySoundFile(SoundRoot .. Heimdall_Data.config.minimapTagger.tagSoundFile, "Master")
|
||||||
|
if not ok and Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Failed to play tag sound: %s",
|
||||||
|
ModuleName,
|
||||||
|
Heimdall_Data.config.minimapTagger.tagSoundFile
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if doTag then PlantFrame(x, y, frame, scale, Heimdall_Data.config.minimapTagger.tagTTL) end
|
||||||
|
end
|
||||||
|
--endregion
|
||||||
|
|
||||||
|
--region Combat
|
||||||
|
---@type Frame[]
|
||||||
|
local combatFramePool = {}
|
||||||
|
local combatFramePoolMaxSize = 20
|
||||||
|
for i = 1, combatFramePoolMaxSize do
|
||||||
|
local frame = CreateFrame("Frame")
|
||||||
|
frame.custom = { busy = false }
|
||||||
|
local texture = frame:CreateTexture(nil, "ARTWORK")
|
||||||
|
texture:SetAllPoints(frame)
|
||||||
|
texture:SetTexture(TextureRoot .. Heimdall_Data.config.minimapTagger.combatTextureFile)
|
||||||
|
table.insert(combatFramePool, frame)
|
||||||
|
end
|
||||||
|
local muteCombatUntil = 0
|
||||||
|
---@param x number|nil
|
||||||
|
---@param y number|nil
|
||||||
|
---@param scale number?
|
||||||
|
---@param doTag boolean?
|
||||||
|
local function PlantCombat(x, y, scale, doTag)
|
||||||
|
if x == nil or y == nil then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Combat position is nil, ignoring", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if doTag == nil then doTag = true end
|
||||||
|
local frame = nil
|
||||||
|
for _, combatFrame in ipairs(combatFramePool) do
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
if not combatFrame.custom.busy then
|
||||||
|
frame = combatFrame
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not frame then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Battle frame pool is full and could not get frame", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if Heimdall_Data.config.minimapTagger.combatSound then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Playing combat sound: %s",
|
||||||
|
ModuleName,
|
||||||
|
Heimdall_Data.config.minimapTagger.combatSoundFile
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
if muteCombatUntil > GetTime() then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Combat sound is muted until %d", ModuleName, muteCombatUntil))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
muteCombatUntil = GetTime() + Heimdall_Data.config.minimapTagger.combatSoundThrottle
|
||||||
|
local ok = PlaySoundFile(SoundRoot .. Heimdall_Data.config.minimapTagger.combatSoundFile, "Master")
|
||||||
|
if not ok and Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Failed to play combat sound: %s",
|
||||||
|
ModuleName,
|
||||||
|
Heimdall_Data.config.minimapTagger.combatSoundFile
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if doTag then PlantFrame(x, y, frame, scale, Heimdall_Data.config.minimapTagger.combatTTL) end
|
||||||
|
end
|
||||||
|
--endregion
|
||||||
|
|
||||||
|
--region Help
|
||||||
|
---@type Frame[]
|
||||||
|
local helpFramePool = {}
|
||||||
|
local helpFramePoolMaxSize = 20
|
||||||
|
for i = 1, helpFramePoolMaxSize do
|
||||||
|
local frame = CreateFrame("Frame")
|
||||||
|
frame.custom = { busy = false }
|
||||||
|
local texture = frame:CreateTexture(nil, "ARTWORK")
|
||||||
|
texture:SetAllPoints(frame)
|
||||||
|
texture:SetTexture(TextureRoot .. Heimdall_Data.config.minimapTagger.helpTextureFile)
|
||||||
|
table.insert(helpFramePool, frame)
|
||||||
|
end
|
||||||
|
local muteHelpUntil = 0
|
||||||
|
---@param x number|nil
|
||||||
|
---@param y number|nil
|
||||||
|
---@param scale number?
|
||||||
|
---@param doTag boolean?
|
||||||
|
local function PlantHelp(x, y, scale, doTag)
|
||||||
|
if x == nil or y == nil then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Help position is nil, ignoring", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if doTag == nil then doTag = true end
|
||||||
|
local frame = nil
|
||||||
|
for _, helpFrame in ipairs(helpFramePool) do
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
if not helpFrame.custom.busy then
|
||||||
|
frame = helpFrame
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not frame then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Help frame pool is full and could not get frame", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if Heimdall_Data.config.minimapTagger.helpSound then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Playing help sound: %s",
|
||||||
|
ModuleName,
|
||||||
|
Heimdall_Data.config.minimapTagger.helpSoundFile
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
if muteHelpUntil > GetTime() then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Help sound is muted until %d", ModuleName, muteHelpUntil))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
muteHelpUntil = GetTime() + Heimdall_Data.config.minimapTagger.helpSoundThrottle
|
||||||
|
local ok = PlaySoundFile(SoundRoot .. Heimdall_Data.config.minimapTagger.helpSoundFile, "Master")
|
||||||
|
if not ok and Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Failed to play help sound: %s",
|
||||||
|
ModuleName,
|
||||||
|
Heimdall_Data.config.minimapTagger.helpSoundFile
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if doTag then PlantFrame(x, y, frame, scale, Heimdall_Data.config.minimapTagger.helpTTL) end
|
||||||
|
end
|
||||||
|
--endregion
|
||||||
|
|
||||||
|
local pauseUntil = 0
|
||||||
|
local frame = CreateFrame("Frame")
|
||||||
|
frame:RegisterEvent("WORLD_MAP_UPDATE")
|
||||||
|
frame:SetScript("OnEvent", function(self, event, addon)
|
||||||
|
if pauseUntil > GetTime() then return end
|
||||||
|
pauseUntil = GetTime() + 1
|
||||||
|
if not BattlefieldMinimap then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] BattlefieldMinimap not found", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not Heimdall_Data.config.minimapTagger.enabled then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] MinimapTagger is disabled", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local scale = Heimdall_Data.config.minimapTagger.scale
|
||||||
|
BattlefieldMinimap:SetScale(scale)
|
||||||
|
BattlefieldMinimap:SetMovable(true)
|
||||||
|
BattlefieldMinimap:EnableMouse(true)
|
||||||
|
BattlefieldMinimap:RegisterForDrag("LeftButton")
|
||||||
|
BattlefieldMinimap:SetScript("OnDragStart", function(selff) selff:StartMoving() end)
|
||||||
|
BattlefieldMinimap:SetScript("OnDragStop", function(selff) selff:StopMovingOrSizing() end)
|
||||||
|
BattlefieldMinimapBackground:Hide()
|
||||||
|
BattlefieldMinimapCloseButton:Hide()
|
||||||
|
BattlefieldMinimapCorner:Hide()
|
||||||
|
BattlefieldMinimap:HookScript("OnHide", function(selff)
|
||||||
|
for _, alertFrame in ipairs(alertFramePool) do
|
||||||
|
alertFrame:Hide()
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
alertFrame.custom.busy = false
|
||||||
|
end
|
||||||
|
for _, tagFrame in ipairs(tagFramePool) do
|
||||||
|
tagFrame:Hide()
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
tagFrame.custom.busy = false
|
||||||
|
end
|
||||||
|
-- What the fuck is this global?
|
||||||
|
for _, battleFrame in ipairs(battleFramePool) do
|
||||||
|
battleFrame:Hide()
|
||||||
|
battleFrame.custom.busy = false
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local chatFrame = CreateFrame("Frame")
|
||||||
|
chatFrame:RegisterEvent("CHAT_MSG_CHANNEL")
|
||||||
|
chatFrame:SetScript("OnEvent", function(self, event, msg, sender, ...)
|
||||||
|
--if Heimdall_Data.config.echoer.debug then
|
||||||
|
-- print(string.format("[%s] Channel message received from: %s", ModuleName, sender))
|
||||||
|
--end
|
||||||
|
|
||||||
|
if not Heimdall_Data.config.minimapTagger.enabled then
|
||||||
|
--if Heimdall_Data.config.echoer.debug then
|
||||||
|
-- print(string.format("[%s] Module disabled, ignoring message", ModuleName))
|
||||||
|
--end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local channelId = select(6, ...)
|
||||||
|
local _, channelname = GetChannelName(channelId)
|
||||||
|
local ok = false
|
||||||
|
for _, channel in pairs(Heimdall_Data.config.minimapTagger.channels) do
|
||||||
|
if channelname == channel then
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not ok then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Ignoring message from non-master channel: %s, need %s",
|
||||||
|
ModuleName,
|
||||||
|
channelname,
|
||||||
|
Heimdall_Data.config.minimapTagger.masterChannel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Processing message from master channel: %s", ModuleName, sender))
|
||||||
|
shared.dumpTable(Heimdall_Data.config.minimapTagger)
|
||||||
|
end
|
||||||
|
|
||||||
|
local doTag = true
|
||||||
|
local messageMapId = string.match(msg, "%[(%d+)%]") or 0
|
||||||
|
if messageMapId then messageMapId = tonumber(messageMapId) end
|
||||||
|
|
||||||
|
local currentMapId = GetCurrentMapAreaID()
|
||||||
|
if currentMapId ~= messageMapId then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Current map ID (%d) does not match message map ID (%d), ignoring message",
|
||||||
|
ModuleName,
|
||||||
|
currentMapId,
|
||||||
|
messageMapId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
doTag = false
|
||||||
|
end
|
||||||
|
|
||||||
|
--region Tag
|
||||||
|
if string.find(msg, "^I see") then
|
||||||
|
if Heimdall_Data.config.minimapTagger.tagTTL == 0 then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Tag TTL is 0, ignoring message: %s", ModuleName, msg))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local x, y = string.match(msg, "%((%d+%.%d+)%s*,%s*(%d+%.%d+)%)")
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Found alert position: %s, %s", ModuleName, tostring(x), tostring(y)))
|
||||||
|
end
|
||||||
|
if x and y then PlantTag(tonumber(x), tonumber(y), 2, doTag) end
|
||||||
|
end
|
||||||
|
--endregion
|
||||||
|
--region Combat
|
||||||
|
if string.find(msg, "^I am in combat with") then
|
||||||
|
if Heimdall_Data.config.minimapTagger.combatTTL == 0 then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Combat TTL is 0, ignoring message: %s", ModuleName, msg))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Found combat alert in message: %s", ModuleName, msg))
|
||||||
|
end
|
||||||
|
local x, y = string.match(msg, "%((%d+%.%d+)%s*,%s*(%d+%.%d+)%)")
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Found combat position: %s, %s", ModuleName, tostring(x), tostring(y)))
|
||||||
|
end
|
||||||
|
if x and y then PlantCombat(tonumber(x), tonumber(y), 2, doTag) end
|
||||||
|
end
|
||||||
|
--endregion
|
||||||
|
--region Death
|
||||||
|
if string.find(msg, " killed ") then
|
||||||
|
if Heimdall_Data.config.minimapTagger.alertTTL == 0 then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Alert TTL is 0, ignoring message: %s", ModuleName, msg))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Found death alert in message: %s", ModuleName, msg))
|
||||||
|
end
|
||||||
|
local x, y = string.match(msg, "%((%d+%.%d+)%s*,%s*(%d+%.%d+)%)")
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Found death position: %s, %s", ModuleName, tostring(x), tostring(y)))
|
||||||
|
end
|
||||||
|
if x and y then PlantAlert(tonumber(x), tonumber(y), 2, doTag) end
|
||||||
|
end
|
||||||
|
--endregion
|
||||||
|
--region Help
|
||||||
|
if string.find(msg, "I need help") then
|
||||||
|
if Heimdall_Data.config.minimapTagger.helpTTL == 0 then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Help TTL is 0, ignoring message: %s", ModuleName, msg))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Found help alert in message: %s", ModuleName, msg))
|
||||||
|
end
|
||||||
|
local x, y = string.match(msg, "%((%d+%.%d+)%s*,%s*(%d+%.%d+)%)")
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Found help position: %s, %s", ModuleName, tostring(x), tostring(y)))
|
||||||
|
end
|
||||||
|
if x and y then
|
||||||
|
x, y = tonumber(x), tonumber(y)
|
||||||
|
PlantHelp(x, y, 1, doTag)
|
||||||
|
---@diagnostic disable-next-line: undefined-global
|
||||||
|
if TomTom then
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Adding help waypoint to TomTom", ModuleName))
|
||||||
|
end
|
||||||
|
local areaId = string.match(msg, "%[(%d+)%]") or 0
|
||||||
|
if areaId then areaId = tonumber(areaId) end
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] Area ID: %s", ModuleName, tostring(areaId)))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: undefined-global
|
||||||
|
TomTom:AddMFWaypoint(areaId, nil, x / 100, y / 100, {
|
||||||
|
title = "Help " .. sender,
|
||||||
|
world = true,
|
||||||
|
from = "Heimdall",
|
||||||
|
crazy = true,
|
||||||
|
})
|
||||||
|
else
|
||||||
|
if Heimdall_Data.config.minimapTagger.debug then
|
||||||
|
print(string.format("[%s] No tomtom no waypoint", ModuleName))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--endregion
|
||||||
|
end)
|
||||||
|
|
||||||
|
print("[Heimdall] MinimapTagger loaded")
|
||||||
|
end
|
||||||
|
|
||||||
|
SlashCmdList["HEIMDALL_MINIMAPTAGGER"] = function(args) shared.MinimapTagger.Init() end
|
||||||
|
SLASH_HEIMDALL_MINIMAPTAGGER1 = "/mf"
|
73
Modules/Network.lua
Normal file
73
Modules/Network.lua
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "Network"
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
|
shared.Network = {}
|
||||||
|
function shared.Network.Init()
|
||||||
|
if not shared.network then shared.network = {} end
|
||||||
|
local updatePending = false
|
||||||
|
|
||||||
|
local function FriendListUpdate()
|
||||||
|
updatePending = false
|
||||||
|
if not Heimdall_Data.config.network.enabled then return end
|
||||||
|
---@type table<string, boolean>
|
||||||
|
local friends = {}
|
||||||
|
for i = 1, GetNumFriends() do
|
||||||
|
local name, _, _, _, connected, _, _, _ = GetFriendInfo(i)
|
||||||
|
if name then
|
||||||
|
friends[name] = connected
|
||||||
|
if Heimdall_Data.config.network.debug then
|
||||||
|
print(string.format("[%s] Friend %s is %s", ModuleName, name, connected and "online" or "offline"))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if Heimdall_Data.config.network.debug then
|
||||||
|
print(string.format("[%s] Friend %s is nil", ModuleName, i))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, member in ipairs(Heimdall_Data.config.network.members) do
|
||||||
|
if friends[member] == nil and member ~= UnitName("player") then
|
||||||
|
if Heimdall_Data.config.network.debug then
|
||||||
|
print(string.format("[%s] Adding friend %s", ModuleName, member))
|
||||||
|
end
|
||||||
|
AddFriend(member)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
friends[UnitName("player")] = true
|
||||||
|
|
||||||
|
shared.networkNodes = {}
|
||||||
|
-- Why are we skipping this again...?
|
||||||
|
-- if false then shared.networkNodes[#shared.networkNodes + 1] = UnitName("player") end
|
||||||
|
for _, player in ipairs(Heimdall_Data.config.network.members) do
|
||||||
|
if friends[player] then
|
||||||
|
shared.networkNodes[#shared.networkNodes + 1] = player
|
||||||
|
if Heimdall_Data.config.network.debug then
|
||||||
|
print(string.format("[%s] Adding network node %s", ModuleName, player))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if Heimdall_Data.config.network.debug then
|
||||||
|
print(string.format("[%s] Network nodes:", ModuleName))
|
||||||
|
shared.dumpTable(shared.networkNodes)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local friendsFrame = CreateFrame("Frame")
|
||||||
|
friendsFrame:RegisterEvent("FRIENDLIST_UPDATE")
|
||||||
|
friendsFrame:SetScript("OnEvent", function(self, event, ...) end)
|
||||||
|
|
||||||
|
local function NetworkTick()
|
||||||
|
if Heimdall_Data.config.network.debug then print("Network module is updating.") end
|
||||||
|
ShowFriends()
|
||||||
|
updatePending = true
|
||||||
|
C_Timer.After(1, function()
|
||||||
|
if updatePending then FriendListUpdate() end
|
||||||
|
end)
|
||||||
|
shared.network.ticker = C_Timer.NewTimer(Heimdall_Data.config.network.updateInterval, NetworkTick, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
NetworkTick()
|
||||||
|
print("[Heimdall] Network module loaded")
|
||||||
|
end
|
183
Modules/NetworkMessenger.lua
Normal file
183
Modules/NetworkMessenger.lua
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "NetworkMessenger"
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
|
shared.NetworkMessenger = {}
|
||||||
|
function shared.NetworkMessenger.Init()
|
||||||
|
RegisterAddonMessagePrefix(Heimdall_Data.config.addonPrefix)
|
||||||
|
|
||||||
|
if not shared.networkMessenger then shared.networkMessenger = {} end
|
||||||
|
if not shared.networkMessenger.queue then shared.networkMessenger.queue = {} end
|
||||||
|
if not shared.networkMessenger.ticker then
|
||||||
|
local function DoMessage()
|
||||||
|
--if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
-- print(string.format("[%s] Processing network message queue", ModuleName))
|
||||||
|
--end
|
||||||
|
if not Heimdall_Data.config.networkMessenger.enabled then
|
||||||
|
--if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
-- print(string.format("[%s] Module disabled, skipping network message processing", ModuleName))
|
||||||
|
--end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
---@type Message
|
||||||
|
local message = shared.networkMessenger.queue[1]
|
||||||
|
if not message then
|
||||||
|
--if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
-- print(string.format("[%s] Network message queue empty", ModuleName))
|
||||||
|
--end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not message.message or message.message == "" then
|
||||||
|
if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
print(string.format("[%s] Invalid network message: empty content", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not message.channel or message.channel == "" then
|
||||||
|
if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
print(string.format("[%s] Invalid network message: no channel specified", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
table.remove(shared.networkMessenger.queue, 1)
|
||||||
|
if not message.message or message.message == "" then
|
||||||
|
if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
print(string.format("[%s] Skipping empty network message", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not message.channel or message.channel == "" then
|
||||||
|
if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
print(string.format("[%s] Skipping network message with no channel", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not message.data or message.data == "" then
|
||||||
|
if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
print(string.format("[%s] Skipping network message with no data", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Sending network message: '%s' to %s:%s",
|
||||||
|
ModuleName,
|
||||||
|
message.message,
|
||||||
|
message.channel,
|
||||||
|
message.data
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
local payload = string.format("dmessage|%s|%s|%s", message.message, message.channel, message.data)
|
||||||
|
if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
print(string.format("[%s] Payload: %s", ModuleName, payload))
|
||||||
|
end
|
||||||
|
if not shared.networkNodes or #shared.networkNodes == 0 then
|
||||||
|
if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
print(string.format("[%s] No network nodes found, wtf????", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local target = shared.networkNodes[1]
|
||||||
|
SendAddonMessage(Heimdall_Data.config.addonPrefix, payload, "WHISPER", target)
|
||||||
|
end
|
||||||
|
local function Tick()
|
||||||
|
--if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
-- local queueSize = #shared.networkMessenger.queue
|
||||||
|
-- print(string.format("[%s] Queue check - Network messages pending: %d", ModuleName, queueSize))
|
||||||
|
--end
|
||||||
|
DoMessage()
|
||||||
|
shared.networkMessenger.ticker = C_Timer.NewTimer(Heimdall_Data.config.networkMessenger.interval, Tick, 1)
|
||||||
|
end
|
||||||
|
Tick()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If we are the leader then we delegate messages (dmessage)
|
||||||
|
-- If we get a "message" command from leader then we send the message
|
||||||
|
|
||||||
|
local nextIdx = 1
|
||||||
|
local addonMsgFrame = CreateFrame("Frame")
|
||||||
|
addonMsgFrame:RegisterEvent("CHAT_MSG_ADDON")
|
||||||
|
addonMsgFrame:SetScript("OnEvent", function(self, event, prefix, message, channel, source)
|
||||||
|
if not Heimdall_Data.config.networkMessenger.enabled then return end
|
||||||
|
if prefix ~= Heimdall_Data.config.addonPrefix then return end
|
||||||
|
source = string.match(source, "[^%-]+")
|
||||||
|
|
||||||
|
if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
print(string.format("[%s] Received message from %s: %s", ModuleName, source, message))
|
||||||
|
end
|
||||||
|
if #shared.networkNodes == 0 then
|
||||||
|
if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
print(string.format("[%s] No network nodes found, wtf????", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- There should always be at least one network node ergo should always exist a leader
|
||||||
|
-- Because the us, the player, is also a node
|
||||||
|
--local networkLeader = shared.networkNodes[1]
|
||||||
|
--if source ~= networkLeader then
|
||||||
|
-- if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
-- print(string.format("[%s] Message from %s is not from the network leader (%s)", ModuleName, source,
|
||||||
|
-- networkLeader))
|
||||||
|
-- end
|
||||||
|
-- return
|
||||||
|
--end
|
||||||
|
|
||||||
|
local parts = shared.Split(message, "|")
|
||||||
|
if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
print(string.format("[%s] Received message parts:", ModuleName))
|
||||||
|
shared.dumpTable(parts)
|
||||||
|
end
|
||||||
|
local command = strtrim(parts[1])
|
||||||
|
if command == "message" then
|
||||||
|
local content = strtrim(tostring(parts[2]))
|
||||||
|
local targetchannel = strtrim(tostring(parts[3]))
|
||||||
|
local target = strtrim(tostring(parts[4]))
|
||||||
|
if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
print(
|
||||||
|
string.format("[%s] Received message command: %s %s %s", ModuleName, content, targetchannel, target)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
---@type Message
|
||||||
|
local msg = {
|
||||||
|
channel = targetchannel,
|
||||||
|
message = content,
|
||||||
|
data = target,
|
||||||
|
}
|
||||||
|
table.insert(shared.messenger.queue, msg)
|
||||||
|
elseif command == "dmessage" then
|
||||||
|
if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
print(string.format("[%s] Received dmessage command", ModuleName))
|
||||||
|
end
|
||||||
|
parts[1] = "message"
|
||||||
|
local content = table.concat(parts, "|")
|
||||||
|
|
||||||
|
if nextIdx > #shared.networkNodes then nextIdx = 1 end
|
||||||
|
local recipient = shared.networkNodes[nextIdx]
|
||||||
|
nextIdx = nextIdx + 1
|
||||||
|
if Heimdall_Data.config.networkMessenger.debug then
|
||||||
|
print(string.format("[%s] Sending message %s to %s", ModuleName, content, recipient))
|
||||||
|
end
|
||||||
|
SendAddonMessage(Heimdall_Data.config.addonPrefix, content, "WHISPER", recipient)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
--/run Heimdall_Data.Test()
|
||||||
|
Heimdall_Data.Test = function()
|
||||||
|
local testmsg = {
|
||||||
|
channel = "W",
|
||||||
|
message = "Hi, mom!",
|
||||||
|
data = "Secundus",
|
||||||
|
}
|
||||||
|
for i = 1, 36 do
|
||||||
|
table.insert(shared.networkMessenger.queue, testmsg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("[Heimdall] NetworkMessenger module loaded")
|
||||||
|
end
|
295
Modules/Noter.lua
Normal file
295
Modules/Noter.lua
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "Noter"
|
||||||
|
|
||||||
|
---@class Note
|
||||||
|
---@field source string
|
||||||
|
---@field for string
|
||||||
|
---@field date string
|
||||||
|
---@field note string
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
|
shared.Noter = {}
|
||||||
|
function shared.Noter.Init()
|
||||||
|
-- ---Hopefully this will not be necessary
|
||||||
|
-- ---@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 array any[]
|
||||||
|
---@return any[]
|
||||||
|
local function Compact(array)
|
||||||
|
local compacted = {}
|
||||||
|
for _, v in pairs(array) do
|
||||||
|
compacted[#compacted + 1] = v
|
||||||
|
end
|
||||||
|
return compacted
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param name string
|
||||||
|
---@param args string[]
|
||||||
|
local function DeleteNotes(name, args)
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Delete note command received for: %s", ModuleName, name))
|
||||||
|
end
|
||||||
|
local range = args[4]
|
||||||
|
if range then
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Range received for delete note: %s", ModuleName, range))
|
||||||
|
end
|
||||||
|
local indices = shared.Split(range, "..")
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Indices for range deletion: %s", ModuleName, table.concat(indices, ", ")))
|
||||||
|
shared.dumpTable(indices)
|
||||||
|
end
|
||||||
|
local start = tonumber(indices[1])
|
||||||
|
local finish = tonumber(indices[2])
|
||||||
|
|
||||||
|
if not start then
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Invalid start range for delete note: %s", ModuleName, tostring(start)))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not finish then finish = start end
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Deleting note range %s to %s for: %s", ModuleName, start, finish, name))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Here, because we are deleting random notes, we lose the "iterative" index property
|
||||||
|
-- Ie it's not longer 1..100, it might be 1..47, 50, 68..100
|
||||||
|
-- Which means that we cannot use ipairs, bad!
|
||||||
|
for i = start, finish do
|
||||||
|
if not Heimdall_Data.config.notes[name] then Heimdall_Data.config.notes[name] = {} end
|
||||||
|
if not Heimdall_Data.config.notes[name][i] then
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Note at index %s does not exist", ModuleName, i))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Deleting note %s at index %s", ModuleName, name, i))
|
||||||
|
shared.dumpTable(Heimdall_Data.config.notes[name][i])
|
||||||
|
end
|
||||||
|
Heimdall_Data.config.notes[name][i] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Heimdall_Data.config.notes[name] = Compact(Heimdall_Data.config.notes[name])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param channel string
|
||||||
|
---@param index number
|
||||||
|
---@param note Note
|
||||||
|
local function PrintNote(channel, index, note)
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Printing note at index %d for: %s", ModuleName, index, note.source))
|
||||||
|
print(string.format("[%s] [%s][%d] %s: %s", ModuleName, note.source, index, note.date, note.note))
|
||||||
|
end
|
||||||
|
---@type Message
|
||||||
|
local msg = {
|
||||||
|
channel = "C",
|
||||||
|
data = channel,
|
||||||
|
message = string.format("[%s][%d] %s: %s", note.source, index, note.date, note.note),
|
||||||
|
}
|
||||||
|
--table.insert(shared.messenger.queue, msg)
|
||||||
|
table.insert(shared.networkMessenger.queue, msg)
|
||||||
|
end
|
||||||
|
---@param name string
|
||||||
|
---@param args string[]
|
||||||
|
local function PrintNotes(channel, name, args)
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Print note command received for: %s", ModuleName, name))
|
||||||
|
end
|
||||||
|
local range = args[3]
|
||||||
|
if not range then
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] No range specified for print note, defaulting to last %d notes",
|
||||||
|
ModuleName,
|
||||||
|
Heimdall_Data.config.noter.lastNotes
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
local notes = Heimdall_Data.config.notes[name] or {}
|
||||||
|
local start = math.max(1, #notes - Heimdall_Data.config.noter.lastNotes + 1)
|
||||||
|
local finish = #notes
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Printing notes from %d to %d for: %s", ModuleName, start, finish, name))
|
||||||
|
end
|
||||||
|
for i = start, finish do
|
||||||
|
PrintNote(channel, i, notes[i])
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if range then
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Range received for print note: %s", ModuleName, range))
|
||||||
|
end
|
||||||
|
local indices = shared.Split(range, "..")
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Indices for range printing: %s", ModuleName, table.concat(indices, ", ")))
|
||||||
|
shared.dumpTable(indices)
|
||||||
|
end
|
||||||
|
local start = tonumber(indices[1])
|
||||||
|
local finish = tonumber(indices[2])
|
||||||
|
|
||||||
|
if not start then
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Invalid start range for print note: %s", ModuleName, tostring(start)))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not finish then finish = start end
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Printing note range %s to %s for: %s", ModuleName, start, finish, name))
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = start, finish do
|
||||||
|
if not Heimdall_Data.config.notes[name] then Heimdall_Data.config.notes[name] = {} end
|
||||||
|
if not Heimdall_Data.config.notes[name][i] then
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Note at index %s does not exist", ModuleName, i))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Printing note %s at index %s", ModuleName, name, i))
|
||||||
|
shared.dumpTable(Heimdall_Data.config.notes[name][i])
|
||||||
|
end
|
||||||
|
PrintNote(channel, i, Heimdall_Data.config.notes[name][i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param name string
|
||||||
|
---@param sender string
|
||||||
|
---@param args string[]
|
||||||
|
local function AddNote(name, sender, args)
|
||||||
|
if not Heimdall_Data.config.notes[name] then Heimdall_Data.config.notes[name] = {} end
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Adding note for: %s from: %s", ModuleName, name, sender))
|
||||||
|
shared.dumpTable(args)
|
||||||
|
end
|
||||||
|
local msgparts = {}
|
||||||
|
for i = 3, #args do
|
||||||
|
msgparts[#msgparts + 1] = args[i]
|
||||||
|
end
|
||||||
|
local msg = table.concat(msgparts, " ")
|
||||||
|
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Adding note for: %s from: %s", ModuleName, name, sender))
|
||||||
|
print(string.format("[%s] Note: %s", ModuleName, msg))
|
||||||
|
end
|
||||||
|
|
||||||
|
local note = {
|
||||||
|
source = sender,
|
||||||
|
date = date("%Y-%m-%dT%H:%M:%S"),
|
||||||
|
note = msg,
|
||||||
|
}
|
||||||
|
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Adding note", ModuleName))
|
||||||
|
shared.dumpTable(note)
|
||||||
|
end
|
||||||
|
table.insert(Heimdall_Data.config.notes[name], note)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Here's the plan:
|
||||||
|
-- Implement a "note" command, that will do everything
|
||||||
|
-- Saying "note <name> <note>" will add a note to the list for the character
|
||||||
|
-- Saying "note <name>" will list last N notes
|
||||||
|
-- Saying "note <name> i" will list the i-th note
|
||||||
|
-- Saying "note <name> i..j" will list notes from i to j
|
||||||
|
-- Saying "note <name> delete i" will delete the i-th note
|
||||||
|
-- Saying "note <name> delete i..j" will delete notes from i to j
|
||||||
|
local noterChannelFrame = CreateFrame("Frame")
|
||||||
|
noterChannelFrame:RegisterEvent("CHAT_MSG_CHANNEL")
|
||||||
|
noterChannelFrame:SetScript("OnEvent", function(self, event, msg, sender, ...)
|
||||||
|
--if Heimdall_Data.config.noter.debug then
|
||||||
|
-- print(string.format("[%s] Event received", ModuleName))
|
||||||
|
-- shared.dumpTable(Heimdall_Data.config.noter)
|
||||||
|
--end
|
||||||
|
if not Heimdall_Data.config.noter.enabled then
|
||||||
|
--if Heimdall_Data.config.noter.debug then
|
||||||
|
-- print(string.format("[%s] Module disabled, ignoring event", ModuleName))
|
||||||
|
--end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local channelId = select(6, ...)
|
||||||
|
local _, channelname = GetChannelName(channelId)
|
||||||
|
local ok = false
|
||||||
|
for _, channel in pairs(Heimdall_Data.config.noter.channels) do
|
||||||
|
if channelname == channel then
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not ok then
|
||||||
|
--if Heimdall_Data.config.noter.debug then
|
||||||
|
-- print(string.format("[%s] Channel %s does not match the master channel %s", ModuleName, channelname, Heimdall_Data.config.noter.masterChannel))
|
||||||
|
--end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
sender = string.match(sender, "^[^-]+")
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Message from: %s", ModuleName, sender))
|
||||||
|
shared.dumpTable(Heimdall_Data.config.noter)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not msg or msg == "" then
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Empty message, ignoring", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local args = { strsplit(" ", msg) }
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Arguments received: %s", ModuleName, table.concat(args, ", ")))
|
||||||
|
shared.dumpTable(args)
|
||||||
|
end
|
||||||
|
local command = args[1]
|
||||||
|
if command == "note" then
|
||||||
|
local name = strtrim(string.lower(args[2] or ""))
|
||||||
|
if Heimdall_Data.config.noter.debug then
|
||||||
|
print(string.format("[%s] Note command received for: %s", ModuleName, name))
|
||||||
|
end
|
||||||
|
local note = strtrim(args[3] or "")
|
||||||
|
if Heimdall_Data.config.noter.debug then print(string.format("[%s] Note: %s", ModuleName, note)) end
|
||||||
|
if note == "delete" then
|
||||||
|
DeleteNotes(name, args)
|
||||||
|
elseif string.find(note, "^[%d%.]*$") then
|
||||||
|
PrintNotes(channelname, name, args)
|
||||||
|
else
|
||||||
|
AddNote(name, sender, args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
print("[Heimdall] Commander module loaded")
|
||||||
|
end
|
609
Modules/ReactiveValue.lua
Normal file
609
Modules/ReactiveValue.lua
Normal file
@@ -0,0 +1,609 @@
|
|||||||
|
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()
|
83
Modules/Sniffer.lua
Normal file
83
Modules/Sniffer.lua
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "Sniffer"
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
|
shared.Sniffer = {}
|
||||||
|
function shared.Sniffer.Init()
|
||||||
|
if Heimdall_Data.config.sniffer.debug then print(string.format("[%s] Module initializing", ModuleName)) end
|
||||||
|
local smellThrottle = {}
|
||||||
|
local SmellStinky = function(stinky)
|
||||||
|
if Heimdall_Data.config.sniffer.debug then
|
||||||
|
print(string.format("%s: SmellStinky", ModuleName))
|
||||||
|
shared.dumpTable(Heimdall_Data.config.sniffer)
|
||||||
|
end
|
||||||
|
if not Heimdall_Data.config.sniffer.enabled then return end
|
||||||
|
if Heimdall_Data.config.sniffer.stinky and not shared.IsStinky(stinky) then
|
||||||
|
if Heimdall_Data.config.sniffer.debug then
|
||||||
|
print(string.format("%s: Stinky not found in config", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if smellThrottle[stinky] and GetTime() - smellThrottle[stinky] < Heimdall_Data.config.sniffer.throttleTime then
|
||||||
|
if Heimdall_Data.config.sniffer.debug then print(string.format("%s: Throttled", ModuleName)) end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
smellThrottle[stinky] = GetTime()
|
||||||
|
|
||||||
|
for _, channel in pairs(Heimdall_Data.config.sniffer.channels) do
|
||||||
|
local locale = shared.GetLocaleForChannel(channel)
|
||||||
|
local text = string.format(shared._L("snifferStinky", locale), stinky)
|
||||||
|
---@type Message
|
||||||
|
local msg = {
|
||||||
|
channel = "C",
|
||||||
|
data = channel,
|
||||||
|
message = text,
|
||||||
|
}
|
||||||
|
if Heimdall_Data.config.sniffer.debug then
|
||||||
|
print(string.format("[%s] Queuing sniffer message", ModuleName))
|
||||||
|
shared.dumpTable(msg)
|
||||||
|
end
|
||||||
|
table.insert(shared.messenger.queue, msg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local cleuFrame = CreateFrame("Frame")
|
||||||
|
cleuFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
|
||||||
|
cleuFrame:SetScript("OnEvent", function(self, event, ...)
|
||||||
|
if Heimdall_Data.config.sniffer.debug then
|
||||||
|
print(string.format("[%s] Received event: %s", ModuleName, event))
|
||||||
|
end
|
||||||
|
if not Heimdall_Data.config.sniffer.enabled then
|
||||||
|
if Heimdall_Data.config.sniffer.debug then
|
||||||
|
print(string.format("[%s] Module disabled, ignoring event", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local source, destination, err
|
||||||
|
source, err = CLEUParser.GetSourceName(...)
|
||||||
|
if Heimdall_Data.config.sniffer.debug then
|
||||||
|
print(string.format("[%s] Processing source: %s", ModuleName, source))
|
||||||
|
end
|
||||||
|
if err then
|
||||||
|
if Heimdall_Data.config.sniffer.debug then
|
||||||
|
print(string.format("[%s] Error parsing source: %s", ModuleName, err))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
SmellStinky(source)
|
||||||
|
destination, err = CLEUParser.GetDestName(...)
|
||||||
|
if Heimdall_Data.config.sniffer.debug then
|
||||||
|
print(string.format("[%s] Processing destination: %s", ModuleName, destination))
|
||||||
|
end
|
||||||
|
if err then
|
||||||
|
if Heimdall_Data.config.sniffer.debug then
|
||||||
|
print(string.format("[%s] Error parsing destination: %s", ModuleName, err))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
SmellStinky(destination)
|
||||||
|
end)
|
||||||
|
if Heimdall_Data.config.sniffer.debug then print(string.format("[%s] Module initialized", ModuleName)) end
|
||||||
|
print("[Heimdall] Sniffer loaded")
|
||||||
|
end
|
224
Modules/Spotter.lua
Normal file
224
Modules/Spotter.lua
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "Spotter"
|
||||||
|
|
||||||
|
---@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)
|
||||||
|
elseif hp > 1e6 then
|
||||||
|
return string.format("%.1fM", hp / 1e6)
|
||||||
|
elseif hp > 1e3 then
|
||||||
|
return string.format("%.1fK", hp / 1e3)
|
||||||
|
else
|
||||||
|
return hp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type table<string, number>
|
||||||
|
local throttleTable = {}
|
||||||
|
|
||||||
|
---@param unit string
|
||||||
|
---@param name string
|
||||||
|
---@param faction string
|
||||||
|
---@param hostile boolean
|
||||||
|
---@return boolean
|
||||||
|
---@return string? error
|
||||||
|
local function ShouldNotify(unit, name, faction, hostile)
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Checking notification criteria for %s (%s)", ModuleName, name, faction))
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.agents[name] then
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Skipping agent: %s", ModuleName, name))
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.spotter.stinky then
|
||||||
|
if shared.IsStinky(name) then
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Notifying - Found stinky: %s", ModuleName, name))
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.spotter.alliance then
|
||||||
|
if faction == "Alliance" then
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Notifying - Found Alliance player: %s", ModuleName, name))
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.spotter.hostile then
|
||||||
|
if hostile then
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Notifying - Found hostile player: %s", ModuleName, name))
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Using everyone setting: %s",
|
||||||
|
ModuleName,
|
||||||
|
tostring(Heimdall_Data.config.spotter.everyone)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
return Heimdall_Data.config.spotter.everyone
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param unit string
|
||||||
|
---@return string?
|
||||||
|
local function NotifySpotted(unit)
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Processing spotted unit: %s", ModuleName, unit))
|
||||||
|
end
|
||||||
|
|
||||||
|
if not unit then return string.format("Could not find unit %s", tostring(unit)) end
|
||||||
|
if not UnitIsPlayer(unit) then
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Ignoring non-player unit: %s", ModuleName, unit))
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local name = UnitName(unit)
|
||||||
|
if not name then return string.format("Could not find name for unit %s", tostring(unit)) end
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Processing player: %s", ModuleName, name))
|
||||||
|
end
|
||||||
|
|
||||||
|
local time = GetTime()
|
||||||
|
if throttleTable[name] and time - throttleTable[name] < Heimdall_Data.config.spotter.throttleTime then
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
local remainingTime = Heimdall_Data.config.spotter.throttleTime - (time - throttleTable[name])
|
||||||
|
print(string.format("[%s] Player %s throttled for %.1f more seconds", ModuleName, name, remainingTime))
|
||||||
|
end
|
||||||
|
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 = shared.raceMap[race]
|
||||||
|
if not faction then return string.format("Could not find faction for race %s", tostring(race)) end
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Player %s is %s (%s)", ModuleName, name, race, faction))
|
||||||
|
end
|
||||||
|
|
||||||
|
local hostile = UnitCanAttack("player", unit)
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Player %s is %s", ModuleName, name, hostile and "hostile" or "friendly"))
|
||||||
|
end
|
||||||
|
|
||||||
|
local doNotify = ShouldNotify(unit, name, faction, hostile)
|
||||||
|
if not doNotify then
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Skipping notification for %s", ModuleName, name))
|
||||||
|
end
|
||||||
|
return string.format("Not notifying for %s", tostring(name))
|
||||||
|
end
|
||||||
|
|
||||||
|
local hp = UnitHealth(unit)
|
||||||
|
if not hp then return string.format("Could not find hp for unit %s", tostring(unit)) end
|
||||||
|
local maxHp = UnitHealthMax(unit)
|
||||||
|
if not maxHp then return string.format("Could not find maxHp for unit %s", tostring(unit)) end
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Player %s health: %s/%s", ModuleName, name, FormatHP(hp), FormatHP(maxHp)))
|
||||||
|
end
|
||||||
|
|
||||||
|
local class = UnitClass(unit)
|
||||||
|
if not class then return string.format("Could not find class for unit %s", tostring(unit)) end
|
||||||
|
|
||||||
|
local zone, subzone = GetZoneText() or "Unknown", GetSubZoneText() or "Unknown"
|
||||||
|
if Heimdall_Data.config.spotter.zoneOverride then
|
||||||
|
zone = Heimdall_Data.config.spotter.zoneOverride or ""
|
||||||
|
subzone = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
local x, y = GetPlayerMapPosition("player")
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Player %s coordinates: %.2f, %.2f", ModuleName, name, x * 100, y * 100))
|
||||||
|
end
|
||||||
|
|
||||||
|
local pvpOn = UnitIsPVP(unit)
|
||||||
|
local stinky = shared.IsStinky(name) or false
|
||||||
|
SetMapToCurrentZone()
|
||||||
|
SetMapByID(GetCurrentMapAreaID())
|
||||||
|
local areaId = tostring(GetCurrentMapAreaID())
|
||||||
|
|
||||||
|
for _, channel in pairs(Heimdall_Data.config.spotter.channels) do
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Processing channel: %s", ModuleName, channel))
|
||||||
|
end
|
||||||
|
local locale = shared.GetLocaleForChannel(channel)
|
||||||
|
local text = string.format(
|
||||||
|
shared._L("spotterSpotted", locale),
|
||||||
|
hostile and shared._L("hostile", locale) or shared._L("friendly", locale),
|
||||||
|
name,
|
||||||
|
shared._L(class, locale),
|
||||||
|
stinky and string.format("(%s)", "!!!!") or "",
|
||||||
|
shared._L(race, locale),
|
||||||
|
shared._L(faction, locale),
|
||||||
|
pvpOn and shared._L("pvpOn", locale) or shared._L("pvpOff", locale),
|
||||||
|
string.gsub(FormatHP(hp), "M", "kk"),
|
||||||
|
string.gsub(FormatHP(maxHp), "M", "kk"),
|
||||||
|
shared._L(zone, locale),
|
||||||
|
shared._L(subzone, locale),
|
||||||
|
areaId,
|
||||||
|
x * 100,
|
||||||
|
y * 100
|
||||||
|
)
|
||||||
|
|
||||||
|
---@type Message
|
||||||
|
local msg = {
|
||||||
|
channel = "C",
|
||||||
|
data = channel,
|
||||||
|
message = text,
|
||||||
|
}
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Queuing spotter message", ModuleName))
|
||||||
|
shared.dumpTable(msg)
|
||||||
|
end
|
||||||
|
table.insert(shared.messenger.queue, msg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local frame = CreateFrame("Frame")
|
||||||
|
frame:RegisterEvent("NAME_PLATE_UNIT_ADDED")
|
||||||
|
frame:RegisterEvent("UNIT_TARGET")
|
||||||
|
frame:SetScript("OnEvent", function(self, event, unit)
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Event received: %s for unit: %s", ModuleName, event, unit or "target"))
|
||||||
|
end
|
||||||
|
|
||||||
|
if not Heimdall_Data.config.spotter.enabled then
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Module disabled, ignoring event", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if event == "UNIT_TARGET" then unit = "target" end
|
||||||
|
|
||||||
|
local err = NotifySpotted(unit)
|
||||||
|
if err then
|
||||||
|
if Heimdall_Data.config.spotter.debug then
|
||||||
|
print(string.format("[%s] Error processing unit %s: %s", ModuleName, unit, err))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
if Heimdall_Data.config.spotter.debug then print(string.format("[%s] Module initialized", ModuleName)) end
|
||||||
|
print("[Heimdall] Spotter loaded")
|
||||||
|
end
|
72
Modules/StinkyCache.lua
Normal file
72
Modules/StinkyCache.lua
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
local addonname, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
---@cast addonname string
|
||||||
|
local ModuleName = "StinkyCache"
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
|
shared.StinkyCache = {}
|
||||||
|
function shared.StinkyCache.Init()
|
||||||
|
shared.stinkyCache = {
|
||||||
|
stinkies = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
---@param name string
|
||||||
|
local function AskCommander(name)
|
||||||
|
if Heimdall_Data.config.stinkyCache.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Asking commander %s about %s",
|
||||||
|
ModuleName,
|
||||||
|
Heimdall_Data.config.stinkyCache.commander,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
local messageParts = { "isstinky", name }
|
||||||
|
local message = table.concat(messageParts, "|")
|
||||||
|
SendAddonMessage(
|
||||||
|
Heimdall_Data.config.addonPrefix,
|
||||||
|
message,
|
||||||
|
"WHISPER",
|
||||||
|
Heimdall_Data.config.stinkyCache.commander
|
||||||
|
)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local addonMessageFrame = CreateFrame("Frame")
|
||||||
|
addonMessageFrame:RegisterEvent("CHAT_MSG_ADDON")
|
||||||
|
addonMessageFrame:SetScript("OnEvent", function(self, event, msg, sender, ...)
|
||||||
|
if sender == Heimdall_Data.config.stinkyCache.commander then
|
||||||
|
if Heimdall_Data.config.stinkyCache.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Received stinky from commander %s: %s",
|
||||||
|
ModuleName,
|
||||||
|
Heimdall_Data.config.stinkyCache.commander,
|
||||||
|
msg
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
local parts = { strsplit("|", msg) }
|
||||||
|
local name, value = parts[1], parts[2]
|
||||||
|
shared.stinkyCache.stinkies[name] = { value = value, timestamp = time() }
|
||||||
|
else
|
||||||
|
if Heimdall_Data.config.stinkyCache.debug then
|
||||||
|
print(string.format("[%s] Received stinky from non-commander %s: %s", ModuleName, sender, msg))
|
||||||
|
end
|
||||||
|
local parts = { strsplit("|", msg) }
|
||||||
|
local command, name = parts[1], parts[2]
|
||||||
|
if parts[1] == "isstinky" then local res = Heimdall_Data.config.stinkies[parts[2]] end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
setmetatable(shared.stinkyCache.stinkies, {
|
||||||
|
__index = function(self, key)
|
||||||
|
local value = rawget(self, key)
|
||||||
|
local now = GetTime()
|
||||||
|
if value == nil or now - value.timestamp > Heimdall_Data.config.stinkyCache.ttl then AskCommander(key) end
|
||||||
|
return rawget(self, key)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
print("[Heimdall] StinkyCache module loaded")
|
||||||
|
end
|
277
Modules/StinkyTracker.lua
Normal file
277
Modules/StinkyTracker.lua
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "StinkyTracker"
|
||||||
|
|
||||||
|
---@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)
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Parsing WHO message: '%s'", ModuleName, msg))
|
||||||
|
end
|
||||||
|
local stinkies = {}
|
||||||
|
for name, class in string.gmatch(msg, whoRegex) do
|
||||||
|
stinkies[name] = {
|
||||||
|
name = name,
|
||||||
|
class = class,
|
||||||
|
seenAt = GetTime(),
|
||||||
|
hostile = true,
|
||||||
|
}
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Found hostile player: %s (%s) at %s",
|
||||||
|
ModuleName,
|
||||||
|
name,
|
||||||
|
class,
|
||||||
|
date("%H:%M:%S", time())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
shared.dumpTable(stinkies)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return stinkies
|
||||||
|
end
|
||||||
|
|
||||||
|
local seeRegex = "I see %((%w+)%) ([^ -/]+)-?%w*/(%w+)"
|
||||||
|
---@param msg string
|
||||||
|
---@return table<string, stinky>
|
||||||
|
local function ParseSee(msg)
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Parsing SEE message: '%s'", ModuleName, msg))
|
||||||
|
end
|
||||||
|
local stinkies = {}
|
||||||
|
local aggression, name, class = string.match(msg, seeRegex)
|
||||||
|
if not name or not class then
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Error: Invalid SEE message format", ModuleName))
|
||||||
|
end
|
||||||
|
return stinkies
|
||||||
|
end
|
||||||
|
local stinky = {
|
||||||
|
name = name,
|
||||||
|
class = class,
|
||||||
|
seenAt = GetTime(),
|
||||||
|
hostile = aggression == "hostile",
|
||||||
|
}
|
||||||
|
stinkies[name] = stinky
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Found stinky in SEE: %s (%s) - %s at %s",
|
||||||
|
ModuleName,
|
||||||
|
name,
|
||||||
|
class,
|
||||||
|
aggression,
|
||||||
|
date("%H:%M:%S", time())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
shared.dumpTable(stinkies)
|
||||||
|
end
|
||||||
|
return stinkies
|
||||||
|
end
|
||||||
|
|
||||||
|
local arrivedRegex = "([^ -/]+)-?%w*; c:([^;]+)"
|
||||||
|
local arrivedRegexAlt = "([^ -/]+)-?%w*%(!!!!%); c:([^;]+)"
|
||||||
|
---@param msg string
|
||||||
|
---@return table<string, stinky>
|
||||||
|
local function ParseArrived(msg)
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("%s: Parsing arrived message: %s", ModuleName, msg))
|
||||||
|
end
|
||||||
|
local stinkies = {}
|
||||||
|
local name, class = string.match(msg, arrivedRegex)
|
||||||
|
if not name or not class then
|
||||||
|
name, class = string.match(msg, arrivedRegexAlt)
|
||||||
|
end
|
||||||
|
if not name or not class then
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("%s: No valid stinky found in arrived message", ModuleName))
|
||||||
|
end
|
||||||
|
return stinkies
|
||||||
|
end
|
||||||
|
local stinky = {
|
||||||
|
name = name,
|
||||||
|
class = class,
|
||||||
|
seenAt = GetTime(),
|
||||||
|
hostile = true,
|
||||||
|
}
|
||||||
|
stinkies[name] = stinky
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("%s: Found stinky in arrived: %s/%s", ModuleName, name, class))
|
||||||
|
shared.dumpTable(stinkies)
|
||||||
|
end
|
||||||
|
return stinkies
|
||||||
|
end
|
||||||
|
|
||||||
|
local frame = CreateFrame("Frame")
|
||||||
|
frame:RegisterEvent("CHAT_MSG_CHANNEL")
|
||||||
|
frame:SetScript("OnEvent", function(self, event, msg, sender, ...)
|
||||||
|
--if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
-- print(string.format("[%s] Event received: %s from %s", ModuleName, event, sender))
|
||||||
|
--end
|
||||||
|
if not Heimdall_Data.config.stinkyTracker.enabled then
|
||||||
|
--if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
-- print(string.format("[%s] Module disabled, ignoring event", ModuleName))
|
||||||
|
--end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local channelId = select(6, ...)
|
||||||
|
local _, channelname = GetChannelName(channelId)
|
||||||
|
local ok = false
|
||||||
|
for _, channel in pairs(Heimdall_Data.config.stinkyTracker.channels) do
|
||||||
|
if channel == channelname then
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not ok then
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Ignoring message from non-master channel: %s", ModuleName, channelname))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Processing message from master channel: %s", ModuleName, sender))
|
||||||
|
shared.dumpTable(Heimdall_Data.config.stinkyTracker)
|
||||||
|
end
|
||||||
|
|
||||||
|
if string.find(msg, "^who:") then
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Processing WHO message from %s", ModuleName, sender))
|
||||||
|
end
|
||||||
|
local whoStinkies = ParseWho(msg)
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Found stinkies in WHO message", ModuleName))
|
||||||
|
end
|
||||||
|
for name, stinky in pairs(whoStinkies) do
|
||||||
|
if stinky.hostile then
|
||||||
|
shared.stinkyTracker.stinkies[name] = stinky
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(
|
||||||
|
string.format("[%s] Added hostile stinky from WHO: %s (%s)", ModuleName, name, stinky.class)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if string.find(msg, "^I see") then
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Processing SEE message from %s", ModuleName, sender))
|
||||||
|
end
|
||||||
|
local seeStinkies = ParseSee(msg)
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Found stinkies in SEE message", ModuleName))
|
||||||
|
end
|
||||||
|
for name, stinky in pairs(seeStinkies) do
|
||||||
|
if stinky.hostile then
|
||||||
|
shared.stinkyTracker.stinkies[name] = stinky
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(
|
||||||
|
string.format("[%s] Added hostile stinky from SEE: %s (%s)", ModuleName, name, stinky.class)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not stinky.hostile then
|
||||||
|
shared.stinkyTracker.stinkies[name] = nil
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Removed non-hostile stinky from SEE: %s", ModuleName, name))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if string.find(msg, "arrived to") or string.find(msg, "moved to") then
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Processing ARRIVED message from %s", ModuleName, sender))
|
||||||
|
end
|
||||||
|
local arrivedStinkies = ParseArrived(msg)
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Found stinkies in ARRIVED message", ModuleName))
|
||||||
|
end
|
||||||
|
for name, stinky in pairs(arrivedStinkies) do
|
||||||
|
shared.stinkyTracker.stinkies[name] = stinky
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Added stinky from ARRIVED: %s (%s)", ModuleName, name, stinky.class))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Log total stinky count after processing
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
local count = 0
|
||||||
|
for _ in pairs(shared.stinkyTracker.stinkies:get()) do
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
print(string.format("[%s] Current total stinkies tracked: %d", ModuleName, count))
|
||||||
|
end
|
||||||
|
|
||||||
|
for name, stinky in pairs(shared.stinkyTracker.stinkies) do
|
||||||
|
if Heimdall_Data.config.agents[name] then
|
||||||
|
shared.stinkyTracker.stinkies[name] = nil
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Removed agent from stinkies: %s", ModuleName, name))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local targetFrame = CreateFrame("Frame")
|
||||||
|
targetFrame:RegisterEvent("UNIT_TARGET")
|
||||||
|
targetFrame:SetScript("OnEvent", function(self, event, unit)
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Event received: %s for unit: %s", ModuleName, event, unit or "target"))
|
||||||
|
end
|
||||||
|
unit = "target"
|
||||||
|
|
||||||
|
if not Heimdall_Data.config.stinkyTracker.enabled then
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Module disabled, ignoring event", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local name = UnitName(unit)
|
||||||
|
if not UnitIsPlayer(unit) then
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Target %s is not a player, nothing to do", ModuleName, name))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local enemy = UnitCanAttack("player", unit)
|
||||||
|
if enemy then
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Target %s is enemy - tracking as stinky", ModuleName, name))
|
||||||
|
end
|
||||||
|
shared.stinkyTracker.stinkies[name] = {
|
||||||
|
name = name,
|
||||||
|
class = UnitClass(unit),
|
||||||
|
seenAt = GetTime(),
|
||||||
|
hostile = true,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not shared.stinkyTracker.stinkies[name] then
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Target %s is friendly and not stinky, nothing to do", ModuleName, name))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then
|
||||||
|
print(string.format("[%s] Target %s is friendly and stinky - removing from stinkies", ModuleName, name))
|
||||||
|
end
|
||||||
|
shared.stinkyTracker.stinkies[name] = nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
if Heimdall_Data.config.stinkyTracker.debug then print(string.format("[%s] Module initialized", ModuleName)) end
|
||||||
|
print("[Heimdall] StinkyTracker loaded")
|
||||||
|
end
|
648
Modules/Whoer.lua
Normal file
648
Modules/Whoer.lua
Normal file
@@ -0,0 +1,648 @@
|
|||||||
|
local _, shared = ...
|
||||||
|
---@cast shared HeimdallShared
|
||||||
|
local ModuleName = "Whoer"
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
|
shared.Whoer = {}
|
||||||
|
function shared.Whoer.Init()
|
||||||
|
if not Heimdall_Data.who then Heimdall_Data.who = {} end
|
||||||
|
if not Heimdall_Data.who.data then Heimdall_Data.who.data = {} end
|
||||||
|
|
||||||
|
---@type table<string, Player>
|
||||||
|
HeimdallStinkies = {}
|
||||||
|
|
||||||
|
---@class Player
|
||||||
|
---@field name string
|
||||||
|
---@field guild string
|
||||||
|
---@field race string
|
||||||
|
---@field class string
|
||||||
|
---@field zone string
|
||||||
|
---@field lastSeenInternal number
|
||||||
|
---@field lastSeen string
|
||||||
|
---@field firstSeen string
|
||||||
|
---@field seenCount number
|
||||||
|
---@field stinky boolean?
|
||||||
|
Player = {
|
||||||
|
---@param name string
|
||||||
|
---@param guild string
|
||||||
|
---@param race string
|
||||||
|
---@param class string
|
||||||
|
---@param zone string
|
||||||
|
---@return Player
|
||||||
|
new = function(name, guild, race, class, zone)
|
||||||
|
local self = setmetatable({}, {
|
||||||
|
__index = Player,
|
||||||
|
})
|
||||||
|
self.name = name
|
||||||
|
self.guild = guild
|
||||||
|
self.race = race
|
||||||
|
self.class = class
|
||||||
|
self.zone = zone
|
||||||
|
self.lastSeenInternal = GetTime()
|
||||||
|
self.lastSeen = "never"
|
||||||
|
self.firstSeen = "never"
|
||||||
|
self.seenCount = 0
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
---@return string
|
||||||
|
ToString = function(self)
|
||||||
|
local out = string.format(
|
||||||
|
"%s %s %s\nFirst: %s Last: %s Seen: %3d",
|
||||||
|
shared.padString(self.name, 16, true),
|
||||||
|
shared.padString(self.guild, 26, false),
|
||||||
|
shared.padString(self.zone, 26, false),
|
||||||
|
shared.padString(self.firstSeen, 10, true),
|
||||||
|
shared.padString(self.lastSeen, 10, true),
|
||||||
|
self.seenCount
|
||||||
|
)
|
||||||
|
return string.format("|cFF%s%s|r", shared.classColors[self.class], out)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
---@class WHOQuery
|
||||||
|
---@field query string
|
||||||
|
---@field filters WHOFilter[]
|
||||||
|
WHOQuery = {
|
||||||
|
---@param query string
|
||||||
|
---@param filters WHOFilter[]
|
||||||
|
---@return WHOQuery
|
||||||
|
new = function(query, filters)
|
||||||
|
local self = setmetatable({}, {
|
||||||
|
__index = WHOQuery,
|
||||||
|
})
|
||||||
|
self.query = query
|
||||||
|
self.filters = filters
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
---@class WHOFilter
|
||||||
|
---@field Run fun(name: string, guild: string, level: number, race: string, class: string, zone: string): boolean
|
||||||
|
---@field key string
|
||||||
|
---@type WHOFilter
|
||||||
|
local NotSiegeOfOrgrimmarFilter = {
|
||||||
|
Run = function(name, guild, level, race, class, zone)
|
||||||
|
if not zone then return false end
|
||||||
|
return zone ~= "Siege of Orgrimmar"
|
||||||
|
end,
|
||||||
|
key = "notsoo",
|
||||||
|
}
|
||||||
|
---@type WHOFilter
|
||||||
|
local AllianceFilter = {
|
||||||
|
Run = function(name, guild, level, race, class, zone)
|
||||||
|
if not race then return false end
|
||||||
|
if not shared.raceMap[race] then return false end
|
||||||
|
return shared.raceMap[race] == "Alliance"
|
||||||
|
end,
|
||||||
|
key = "ally",
|
||||||
|
}
|
||||||
|
|
||||||
|
---@class WhoQueryService
|
||||||
|
---@field queries WHOQuery[]
|
||||||
|
---@field filters WHOFilter[]
|
||||||
|
---@field getFilter fun(key: string): WHOFilter?
|
||||||
|
---@field WhoQueryToString fun(query: WHOQuery): string
|
||||||
|
---@field WhoQueryFromString fun(query: string): WHOQuery
|
||||||
|
---@field WhoQueriesToString fun(queries: WHOQuery[]): string
|
||||||
|
---@field WhoQueriesFromString fun(queries: string): WHOQuery[]
|
||||||
|
shared.WhoQueryService = {
|
||||||
|
queries = {},
|
||||||
|
filters = {
|
||||||
|
NotSiegeOfOrgrimmarFilter,
|
||||||
|
AllianceFilter,
|
||||||
|
},
|
||||||
|
---@param key string
|
||||||
|
---@return WHOFilter?
|
||||||
|
getFilter = function(key)
|
||||||
|
for _, filter in pairs(shared.WhoQueryService.filters) do
|
||||||
|
if filter.key == key then return filter end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end,
|
||||||
|
---@param query WHOQuery
|
||||||
|
---@return string
|
||||||
|
WhoQueryToString = function(query)
|
||||||
|
local ret = ""
|
||||||
|
ret = ret .. query.query
|
||||||
|
ret = ret .. ";"
|
||||||
|
for _, filter in pairs(query.filters) do
|
||||||
|
ret = ret .. filter.key .. ";"
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end,
|
||||||
|
---@param queries WHOQuery[]
|
||||||
|
---@return string
|
||||||
|
WhoQueriesToString = function(queries)
|
||||||
|
local ret = ""
|
||||||
|
for _, query in pairs(queries) do
|
||||||
|
ret = ret .. shared.WhoQueryService.WhoQueryToString(query) .. "\n"
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end,
|
||||||
|
---@param query string
|
||||||
|
---@return WHOQuery
|
||||||
|
WhoQueryFromString = function(query)
|
||||||
|
local queryParts = shared.Split(query, ";")
|
||||||
|
local filters = {}
|
||||||
|
for _, filterKey in pairs(queryParts) do
|
||||||
|
local filter = shared.WhoQueryService.getFilter(filterKey)
|
||||||
|
if not filter then
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] Filter %s not found", ModuleName, filterKey))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] Filter %s found", ModuleName, filterKey))
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(filters, filter)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] WHO query: %s with %d filters", ModuleName, queryParts[1], #filters))
|
||||||
|
end
|
||||||
|
shared.dumpTable(filters)
|
||||||
|
return WHOQuery.new(queryParts[1], filters)
|
||||||
|
end,
|
||||||
|
---@param queryStr string
|
||||||
|
---@return WHOQuery[]
|
||||||
|
WhoQueriesFromString = function(queryStr)
|
||||||
|
local queries = shared.Split(queryStr, "\n")
|
||||||
|
local ret = {}
|
||||||
|
for _, query in pairs(queries) do
|
||||||
|
table.insert(ret, shared.WhoQueryService.WhoQueryFromString(query))
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
shared.WhoQueryService.queries = shared.WhoQueryService.WhoQueriesFromString(Heimdall_Data.config.who.queries)
|
||||||
|
|
||||||
|
---@param inputZone string
|
||||||
|
---@return boolean
|
||||||
|
shared.Whoer.ShouldNotifyForZone = shared.Memoize(function(inputZone)
|
||||||
|
if not Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] ShouldNotifyForZone %s", ModuleName, inputZone))
|
||||||
|
end
|
||||||
|
for zone, _ in pairs(Heimdall_Data.config.who.zoneNotifyFor) do
|
||||||
|
if Heimdall_Data.config.who.debug then print(string.format("[%s] Checking zone %s", ModuleName, zone)) end
|
||||||
|
if zone == "*" then return true end
|
||||||
|
if string.find(inputZone, zone) then
|
||||||
|
if not Heimdall_Data.config.who.debug then
|
||||||
|
print(
|
||||||
|
string.format("[%s] ShouldNotifyForZone %s is true thanks to %s", ModuleName, inputZone, zone)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] ShouldNotifyForZone %s is false", ModuleName, inputZone))
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end)
|
||||||
|
|
||||||
|
-----@type WHOQuery[]
|
||||||
|
--local whoQueries = {
|
||||||
|
-- WHOQuery.new("g-\"БеспредеЛ\"", {}),
|
||||||
|
-- WHOQuery.new("g-\"ЗАО бещёки\"", {}),
|
||||||
|
-- WHOQuery.new("g-\"КОНИЛИНГУСЫ\"", {}),
|
||||||
|
-- --WHOQuery.new("g-\"Dovahkin\"", {}),
|
||||||
|
-- WHOQuery.new(
|
||||||
|
-- "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Human\" r-\"Dwarf\" r-\"Night Elf\"",
|
||||||
|
-- { NotSiegeOfOrgrimmarFilter, AllianceFilter }),
|
||||||
|
-- WHOQuery.new(
|
||||||
|
-- "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Gnome\" r-\"Draenei\" r-\"Worgen\"",
|
||||||
|
-- { NotSiegeOfOrgrimmarFilter, AllianceFilter }),
|
||||||
|
-- WHOQuery.new(
|
||||||
|
-- "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Kul Tiran\" r-\"Dark Iron Dwarf\" r-\"Void Elf\"",
|
||||||
|
-- { NotSiegeOfOrgrimmarFilter, AllianceFilter }),
|
||||||
|
-- WHOQuery.new(
|
||||||
|
-- "z-\"Orgrimmar\" z-\"Durotar\" z-\"Valley of Trials\" r-\"Lightforged Draenei\" r-\"Mechagnome\"",
|
||||||
|
-- { NotSiegeOfOrgrimmarFilter, AllianceFilter }),
|
||||||
|
-- WHOQuery.new("Kekv Firobot Tomoki Mld Alltros", {})
|
||||||
|
--}
|
||||||
|
local whoQueryIdx = 1
|
||||||
|
---@type WHOQuery?
|
||||||
|
local lastQuery = nil
|
||||||
|
|
||||||
|
---@param player Player
|
||||||
|
---@return string?
|
||||||
|
local function Notify(player)
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] Processing notification for player: %s", ModuleName, player.name))
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Player details - Guild: %s, Race: %s, Class: %s, Zone: %s",
|
||||||
|
ModuleName,
|
||||||
|
player.guild,
|
||||||
|
player.race,
|
||||||
|
player.class,
|
||||||
|
player.zone
|
||||||
|
)
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Player history - First seen: %s, Last seen: %s, Seen count: %d",
|
||||||
|
ModuleName,
|
||||||
|
player.firstSeen,
|
||||||
|
player.lastSeen,
|
||||||
|
player.seenCount
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not Heimdall_Data.config.who.enabled then
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] Module disabled, skipping notification", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not player then
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] Error: Cannot notify for nil player", ModuleName))
|
||||||
|
end
|
||||||
|
return string.format("Cannot notify for nil player %s", tostring(player))
|
||||||
|
end
|
||||||
|
|
||||||
|
if not shared.Whoer.ShouldNotifyForZone(player.zone) then
|
||||||
|
--if not Heimdall_Data.config.who.zoneNotifyFor[player.zone] then
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(
|
||||||
|
string.format("[%s] Skipping notification - Zone '%s' not in notify list", ModuleName, player.zone)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
return string.format("Not notifying for zone %s", tostring(player.zone))
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, channel in pairs(Heimdall_Data.config.who.channels) do
|
||||||
|
local locale = shared.GetLocaleForChannel(channel)
|
||||||
|
local text = string.format(
|
||||||
|
shared._L("whoerNew", locale),
|
||||||
|
player.name,
|
||||||
|
player.stinky and "(!!!!)" or "",
|
||||||
|
shared._L(player.class, locale),
|
||||||
|
--shared._L(player.race, locale),
|
||||||
|
shared._L(shared.raceMap[player.race] or "unknown", locale),
|
||||||
|
player.guild,
|
||||||
|
shared._L(player.zone, locale)
|
||||||
|
)
|
||||||
|
---@type Message
|
||||||
|
local msg = {
|
||||||
|
channel = "C",
|
||||||
|
data = channel,
|
||||||
|
message = text,
|
||||||
|
}
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] Queuing channel notification", ModuleName))
|
||||||
|
shared.dumpTable(msg)
|
||||||
|
end
|
||||||
|
table.insert(shared.networkMessenger.queue, msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
--if Heimdall_Data.config.who.doWhisper then
|
||||||
|
-- if Heimdall_Data.config.who.debug then
|
||||||
|
-- print(string.format("[%s] Processing whisper notifications for %d recipients", ModuleName,
|
||||||
|
-- #Heimdall_Data.config.whisperNotify))
|
||||||
|
-- end
|
||||||
|
-- for _, name in pairs(Heimdall_Data.config.whisperNotify) do
|
||||||
|
-- ---@type Message
|
||||||
|
-- local msg = {
|
||||||
|
-- channel = "W",
|
||||||
|
-- data = name,
|
||||||
|
-- message = text
|
||||||
|
-- }
|
||||||
|
-- if Heimdall_Data.config.who.debug then
|
||||||
|
-- print(string.format("[%s] Queuing whisper to %s", ModuleName, name))
|
||||||
|
-- end
|
||||||
|
-- --table.insert(shared.messenger.queue, msg)
|
||||||
|
-- table.insert(shared.networkMessenger.queue, msg)
|
||||||
|
-- end
|
||||||
|
--end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
---@param player Player
|
||||||
|
---@param zone string
|
||||||
|
---@return string?
|
||||||
|
local function NotifyZoneChanged(player, zone)
|
||||||
|
if not Heimdall_Data.config.who.enabled then return end
|
||||||
|
if not player then return string.format("Cannot notify for nil player %s", tostring(player)) end
|
||||||
|
--if not Heimdall_Data.config.who.zoneNotifyFor[zone]
|
||||||
|
-- and not Heimdall_Data.config.who.zoneNotifyFor[player.zone] then
|
||||||
|
if not shared.Whoer.ShouldNotifyForZone(zone) and not shared.Whoer.ShouldNotifyForZone(player.zone) then
|
||||||
|
return string.format("Not notifying for zones %s and %s", tostring(zone), tostring(player.zone))
|
||||||
|
end
|
||||||
|
for _, channel in pairs(Heimdall_Data.config.who.channels) do
|
||||||
|
local locale = shared.GetLocaleForChannel(channel)
|
||||||
|
local text = string.format(
|
||||||
|
shared._L("whoerMoved", locale),
|
||||||
|
player.name,
|
||||||
|
player.stinky and "(!!!!)" or "",
|
||||||
|
shared._L(player.class, locale),
|
||||||
|
--shared._L(player.race, locale),
|
||||||
|
shared._L(shared.raceMap[player.race] or "unknown", locale),
|
||||||
|
player.guild,
|
||||||
|
shared._L(zone, locale)
|
||||||
|
)
|
||||||
|
|
||||||
|
---@type Message
|
||||||
|
local msg = {
|
||||||
|
channel = "C",
|
||||||
|
data = channel,
|
||||||
|
message = text,
|
||||||
|
}
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] Queuing channel notification", ModuleName))
|
||||||
|
shared.dumpTable(msg)
|
||||||
|
end
|
||||||
|
table.insert(shared.networkMessenger.queue, msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
--if Heimdall_Data.config.who.doWhisper then
|
||||||
|
-- for _, name in pairs(Heimdall_Data.config.whisperNotify) do
|
||||||
|
-- ---@type Message
|
||||||
|
-- local msg = {
|
||||||
|
-- channel = "W",
|
||||||
|
-- data = name,
|
||||||
|
-- message = text
|
||||||
|
-- }
|
||||||
|
-- --table.insert(shared.messenger.queue, msg)
|
||||||
|
-- table.insert(shared.networkMessenger.queue, msg)
|
||||||
|
-- end
|
||||||
|
--end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
---@param player Player
|
||||||
|
---@return string?
|
||||||
|
local function NotifyGone(player)
|
||||||
|
if not Heimdall_Data.config.who.enabled then return end
|
||||||
|
if not player then return string.format("Cannot notify for nil player %s", tostring(player)) end
|
||||||
|
--if not Heimdall_Data.config.who.zoneNotifyFor[player.zone] then
|
||||||
|
if not shared.Whoer.ShouldNotifyForZone(player.zone) then
|
||||||
|
return string.format("Not notifying for zone %s", tostring(player.zone))
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, channel in pairs(Heimdall_Data.config.who.channels) do
|
||||||
|
local locale = shared.GetLocaleForChannel(channel)
|
||||||
|
local text = string.format(
|
||||||
|
shared._L("whoerGone", locale),
|
||||||
|
player.name,
|
||||||
|
player.stinky and "(!!!!)" or "",
|
||||||
|
shared._L(player.class, locale),
|
||||||
|
--shared._L(player.race, locale),
|
||||||
|
shared._L(shared.raceMap[player.race] or "unknown", locale),
|
||||||
|
player.guild,
|
||||||
|
shared._L(player.zone, locale)
|
||||||
|
)
|
||||||
|
|
||||||
|
---@type Message
|
||||||
|
local msg = {
|
||||||
|
channel = "C",
|
||||||
|
data = channel,
|
||||||
|
message = text,
|
||||||
|
}
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] Queuing channel notification", ModuleName))
|
||||||
|
shared.dumpTable(msg)
|
||||||
|
end
|
||||||
|
--table.insert(shared.messenger.queue, msg)
|
||||||
|
table.insert(shared.networkMessenger.queue, msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
--if Heimdall_Data.config.who.doWhisper then
|
||||||
|
-- for _, name in pairs(Heimdall_Data.config.whisperNotify) do
|
||||||
|
-- ---@type Message
|
||||||
|
-- local msg = {
|
||||||
|
-- channel = "W",
|
||||||
|
-- data = name,
|
||||||
|
-- message = text
|
||||||
|
-- }
|
||||||
|
-- --table.insert(shared.messenger.queue, msg)
|
||||||
|
-- table.insert(shared.networkMessenger.queue, msg)
|
||||||
|
-- end
|
||||||
|
--end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local frame = CreateFrame("Frame")
|
||||||
|
frame:RegisterEvent("WHO_LIST_UPDATE")
|
||||||
|
frame:SetScript("OnEvent", function(self, event, ...)
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] WHO list update received", ModuleName))
|
||||||
|
print(string.format("[%s] Query index: %d/%d", ModuleName, whoQueryIdx, #shared.WhoQueryService.queries))
|
||||||
|
end
|
||||||
|
|
||||||
|
if not Heimdall_Data.config.who.enabled then
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] Module disabled, ignoring WHO update", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type WHOQuery?
|
||||||
|
local query = lastQuery
|
||||||
|
if not query then
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] Error: No active WHO query found", ModuleName))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local results = GetNumWhoResults()
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] Processing %d WHO results for query: %s", ModuleName, results, query.query))
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, results do
|
||||||
|
local name, guild, level, race, class, zone = GetWhoInfo(i)
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(
|
||||||
|
string.format("[%s] Processing result %d/%d: %s/%s/%s", ModuleName, i, results, name, class, zone)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local continue = false
|
||||||
|
---@type WHOFilter[]
|
||||||
|
local filters = query.filters
|
||||||
|
for _, filter in pairs(filters) do
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(
|
||||||
|
string.format("[%s] Running filter %s on %s/%s/%s", ModuleName, filter.key, name, class, zone)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
if not filter.Run(name, guild, level, race, class, zone) then
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(
|
||||||
|
string.format("[%s] Player %s filtered out by WHO filter %s", ModuleName, name, filter.key)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
continue = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.who.ignored[name] then
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] Ignoring blacklisted player: %s", ModuleName, name))
|
||||||
|
end
|
||||||
|
continue = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] Player %s is not blacklisted", ModuleName, name))
|
||||||
|
end
|
||||||
|
if not continue then
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] Player %s is not filtered out", ModuleName, name))
|
||||||
|
end
|
||||||
|
local timestamp = date("%Y-%m-%dT%H:%M:%S")
|
||||||
|
local player = HeimdallStinkies[name]
|
||||||
|
if not player then
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] New player detected: %s (%s) in %s", ModuleName, name, class, zone))
|
||||||
|
end
|
||||||
|
|
||||||
|
player = Player.new(name, guild, race, class, zone)
|
||||||
|
if not Heimdall_Data.who then Heimdall_Data.who = {} end
|
||||||
|
if not Heimdall_Data.who.data then Heimdall_Data.who.data = {} end
|
||||||
|
local existing = Heimdall_Data.who.data[name]
|
||||||
|
|
||||||
|
if existing then
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Found existing data for %s - Last seen: %s, Count: %d",
|
||||||
|
ModuleName,
|
||||||
|
name,
|
||||||
|
existing.lastSeen or "never",
|
||||||
|
existing.seenCount or 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
player.lastSeen = existing.lastSeen or "never"
|
||||||
|
player.firstSeen = existing.firstSeen or "never"
|
||||||
|
player.seenCount = existing.seenCount or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if player.firstSeen == "never" then
|
||||||
|
player.firstSeen = timestamp
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] First time seeing player: %s at %s", ModuleName, name, timestamp))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local stinky = shared.IsStinky(name)
|
||||||
|
if stinky then
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] Player %s marked as stinky!", ModuleName, name))
|
||||||
|
end
|
||||||
|
player.stinky = true
|
||||||
|
--PlaySoundFile("Interface\\Sounds\\Domination.ogg", "Master")
|
||||||
|
-- else
|
||||||
|
-- PlaySoundFile("Interface\\Sounds\\Cloak.ogg", "Master")
|
||||||
|
end
|
||||||
|
|
||||||
|
local err = Notify(player)
|
||||||
|
if err then
|
||||||
|
print(
|
||||||
|
string.format("[%s] Error notifying for %s: %s", ModuleName, tostring(name), tostring(err))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
player.lastSeen = timestamp
|
||||||
|
player.seenCount = player.seenCount + 1
|
||||||
|
HeimdallStinkies[name] = player
|
||||||
|
end
|
||||||
|
|
||||||
|
player.lastSeenInternal = GetTime()
|
||||||
|
if player.zone ~= zone then
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Player %s zone changed from %s to %s",
|
||||||
|
ModuleName,
|
||||||
|
name,
|
||||||
|
player.zone,
|
||||||
|
zone
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
local err = NotifyZoneChanged(player, zone)
|
||||||
|
if err then print(string.format("Error notifying for %s: %s", tostring(name), tostring(err))) end
|
||||||
|
end
|
||||||
|
player.zone = zone
|
||||||
|
player.lastSeen = timestamp
|
||||||
|
HeimdallStinkies[name] = player
|
||||||
|
if not Heimdall_Data.who then Heimdall_Data.who = {} end
|
||||||
|
if not Heimdall_Data.who.data then Heimdall_Data.who.data = {} end
|
||||||
|
Heimdall_Data.who.data[name] = player
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Turns out WA cannot do this (
|
||||||
|
-- aura_env.UpdateMacro()
|
||||||
|
_G["FriendsFrameCloseButton"]:Click()
|
||||||
|
end)
|
||||||
|
|
||||||
|
do
|
||||||
|
local function UpdateStinkies()
|
||||||
|
for name, player in pairs(HeimdallStinkies) do
|
||||||
|
if player.lastSeenInternal + Heimdall_Data.config.who.ttl < GetTime() then
|
||||||
|
NotifyGone(player)
|
||||||
|
--PlaySoundFile("Interface\\Sounds\\Uncloak.ogg", "Master")
|
||||||
|
HeimdallStinkies[name] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function Tick()
|
||||||
|
UpdateStinkies()
|
||||||
|
C_Timer.NewTimer(0.5, Tick, 1)
|
||||||
|
end
|
||||||
|
Tick()
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local function DoQuery()
|
||||||
|
if not Heimdall_Data.config.who.enabled then return end
|
||||||
|
|
||||||
|
local query = shared.WhoQueryService.queries[whoQueryIdx]
|
||||||
|
if not query then
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(string.format("[%s] Error: No WHO query found to run at index %d", ModuleName, whoQueryIdx))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if Heimdall_Data.config.who.debug then
|
||||||
|
print(
|
||||||
|
string.format(
|
||||||
|
"[%s] Running WHO query %d/%d: %s",
|
||||||
|
ModuleName,
|
||||||
|
whoQueryIdx,
|
||||||
|
#shared.WhoQueryService.queries,
|
||||||
|
query.query
|
||||||
|
)
|
||||||
|
)
|
||||||
|
print(string.format("[%s] Query has %d filters", ModuleName, #query.filters))
|
||||||
|
for i, filter in ipairs(query.filters) do
|
||||||
|
print(string.format("[%s] Filter %d: %s", ModuleName, i, filter.key))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
whoQueryIdx = whoQueryIdx + 1
|
||||||
|
if whoQueryIdx > #shared.WhoQueryService.queries then whoQueryIdx = 1 end
|
||||||
|
lastQuery = query
|
||||||
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
|
SetWhoToUI(1)
|
||||||
|
SendWho(query.query)
|
||||||
|
end
|
||||||
|
local function Tick()
|
||||||
|
DoQuery()
|
||||||
|
C_Timer.NewTimer(1, Tick, 1)
|
||||||
|
end
|
||||||
|
Tick()
|
||||||
|
end
|
||||||
|
|
||||||
|
print("[Heimdall] Whoer loaded")
|
||||||
|
end
|
324
README.md
Normal file
324
README.md
Normal 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. Убедитесь, что аддон включен на экране выбора персонажа
|
BIN
Sounds/MGSSpot.ogg
(Stored with Git LFS)
Normal file
BIN
Sounds/MGSSpot.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Sounds/MedicGangsterParadise.ogg
(Stored with Git LFS)
Normal file
BIN
Sounds/MedicGangsterParadise.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Sounds/OOF.ogg
(Stored with Git LFS)
Normal file
BIN
Sounds/OOF.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Sounds/StarScream.ogg
(Stored with Git LFS)
Normal file
BIN
Sounds/StarScream.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
117
Spotter.lua
117
Spotter.lua
@@ -1,117 +0,0 @@
|
|||||||
local addonname, data = ...
|
|
||||||
---@cast data HeimdallData
|
|
||||||
---@cast addonname string
|
|
||||||
|
|
||||||
data.Spotter = {}
|
|
||||||
function data.Spotter.Init()
|
|
||||||
if not data.config.spotter.enabled then
|
|
||||||
print("Heimdall - Spotter disabled")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local function FormatHP(hp)
|
|
||||||
if hp > 1e9 then
|
|
||||||
return string.format("%.1fB", hp / 1e9)
|
|
||||||
elseif hp > 1e6 then
|
|
||||||
return string.format("%.1fM", hp / 1e6)
|
|
||||||
elseif hp > 1e3 then
|
|
||||||
return string.format("%.1fK", hp / 1e3)
|
|
||||||
else
|
|
||||||
return hp
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@type table<string, number>
|
|
||||||
local throttleTable = {}
|
|
||||||
|
|
||||||
---@param unit string
|
|
||||||
---@param name string
|
|
||||||
---@param faction string
|
|
||||||
---@param hostile boolean
|
|
||||||
---@return boolean
|
|
||||||
---@return string? error
|
|
||||||
local function ShouldNotify(unit, name, faction, hostile)
|
|
||||||
if data.config.spotter.stinky then
|
|
||||||
if data.config.stinkies[name] then return true end
|
|
||||||
end
|
|
||||||
if data.config.spotter.alliance then
|
|
||||||
if faction == "Alliance" then return true end
|
|
||||||
end
|
|
||||||
if data.config.spotter.hostile then
|
|
||||||
if hostile then return true end
|
|
||||||
end
|
|
||||||
return data.config.spotter.everyone
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param unit string
|
|
||||||
---@return string?
|
|
||||||
local function NotifySpotted(unit)
|
|
||||||
if not unit then return string.format("Could not find unit %s", tostring(unit)) end
|
|
||||||
if not UnitIsPlayer(unit) then return nil end
|
|
||||||
|
|
||||||
local name = UnitName(unit)
|
|
||||||
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
|
|
||||||
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]
|
|
||||||
if not faction then return string.format("Could not find faction for race %s", tostring(race)) end
|
|
||||||
|
|
||||||
local hostile = UnitCanAttack("player", unit)
|
|
||||||
local doNotify = ShouldNotify(unit, name, faction, hostile)
|
|
||||||
if not doNotify then return string.format("Not notifying for %s", tostring(name)) end
|
|
||||||
|
|
||||||
local hp = UnitHealth(unit)
|
|
||||||
if not hp then return string.format("Could not find hp for unit %s", tostring(unit)) end
|
|
||||||
|
|
||||||
local maxHp = UnitHealthMax(unit)
|
|
||||||
if not maxHp then return string.format("Could not find maxHp for unit %s", tostring(unit)) end
|
|
||||||
|
|
||||||
local location = data.config.spotter.zoneOverride
|
|
||||||
if not 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()
|
|
||||||
if not subzone then subzone = "" end
|
|
||||||
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",
|
|
||||||
hostile and "Hostile" or "Friendly",
|
|
||||||
stinky and string.format("(%s)", "!!!!") or "",
|
|
||||||
name,
|
|
||||||
race,
|
|
||||||
faction,
|
|
||||||
FormatHP(hp),
|
|
||||||
FormatHP(maxHp),
|
|
||||||
location)
|
|
||||||
|
|
||||||
---@type Message
|
|
||||||
local msg = {
|
|
||||||
channel = "CHANNEL",
|
|
||||||
data = data.config.spotter.notifyChannel,
|
|
||||||
message = text
|
|
||||||
}
|
|
||||||
data.dumpTable(msg)
|
|
||||||
table.insert(data.messenger.queue, msg)
|
|
||||||
end
|
|
||||||
|
|
||||||
local frame = CreateFrame("Frame")
|
|
||||||
frame:RegisterEvent("NAME_PLATE_UNIT_ADDED")
|
|
||||||
frame:RegisterEvent("TARGET_UNIT_CHANGED")
|
|
||||||
frame:SetScript("OnEvent", function(self, event, unit)
|
|
||||||
local err = NotifySpotted(unit)
|
|
||||||
if err then
|
|
||||||
print(string.format("Error notifying %s: %s", tostring(unit), tostring(err)))
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
print("Heimdall - Spotter loaded")
|
|
||||||
end
|
|
BIN
Texture/Aura1.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura1.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura10.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura10.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura100.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura100.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura101.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura101.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura102.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura102.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura103.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura103.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura104.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura104.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura105.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura105.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura106.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura106.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura107.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura107.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura108.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura108.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura109.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura109.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura11.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura11.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura110.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura110.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura111.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura111.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura112.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura112.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura113.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura113.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura114.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura114.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura115.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura115.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura116.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura116.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura117.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura117.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura118.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura118.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura119.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura119.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura12.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura12.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura120.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura120.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura121.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura121.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura122.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura122.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura123.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura123.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura124.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura124.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura125.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura125.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura126.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura126.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura127.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura127.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura128.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura128.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura129.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura129.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura13.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura13.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura130.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura130.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura131.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura131.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura132.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura132.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura133.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura133.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura134.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura134.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura135.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura135.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura136.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura136.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura137.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura137.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura138.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura138.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura139.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura139.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura14.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura14.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura140.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura140.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura141.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura141.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura142.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura142.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura143.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura143.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura144.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura144.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura145.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura145.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura15.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura15.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura16.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura16.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura17.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura17.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Texture/Aura18.tga
(Stored with Git LFS)
Normal file
BIN
Texture/Aura18.tga
(Stored with Git LFS)
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user