-
Notifications
You must be signed in to change notification settings - Fork 21
/
core.lua
8682 lines (7918 loc) · 358 KB
/
core.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
if WOW_PROJECT_ID ~= WOW_PROJECT_MAINLINE then return end
local addonName = ... ---@type string @The name of the addon.
local ns = select(2, ...) ---@type ns @The addon namespace.
local L = ns.L
local arshift = bit.arshift
local band = bit.band
local bnot = bit.bnot
local bor = bit.bor
local bxor = bit.bxor
local lshift = bit.lshift
local mod = bit.mod
local rshift = bit.rshift
-- constants.lua (ns)
-- dependencies: none
do
---@class ns
---@field public Print function @Prints yellow text to the default chat frame. Behaves otherwise same way as AddMessage does
---@field public EXPANSION number @The currently accessible expansion to the playerbase
---@field public MAX_LEVEL number @The currently accessible expansion max level to the playerbase
---@field public REGION_TO_LTD string[] @Region ID to LTD conversion table
---@field public FACTION_TO_ID number[] @Faction group string to ID conversion table
---@field public PLAYER_REGION string @`us`, `kr`, `eu`, `tw`, `cn`
---@field public PLAYER_REGION_ID number @`1` (us), `2` (kr), `3` (eu), `4` (tw), `5` (cn)
---@field public PLAYER_FACTION number @`1` (alliance), `2` (horde), `3` (neutral)
---@field public PLAYER_FACTION_TEXT string @`Alliance`, `Horde`, `Neutral`
---@field public PLAYER_NAME string @The name of the player character
---@field public PLAYER_REALM string @The realm of the player character
---@field public PLAYER_REALM_SLUG string @The realm slug of the player character
---@field public OUTDATED_CUTOFF number @Seconds before we start looking at the data as out-of-date
---@field public OUTDATED_BLOCK_CUTOFF number @Seconds before we block future score showing
---@field public PROVIDER_DATA_TYPE number[] @Data Type enum
---@field public LOOKUP_MAX_SIZE number @The maximum index we can use in a table before we start to get errors
---@field public CURRENT_SEASON number @The current mythic keystone season
---@field public HEADLINE_MODE table<string, number> @Enum over headline modes
---@field public ROLE_ICONS RoleIcons @Collection of roles and their icons
---@field public KEYSTONE_LEVEL_PATTERN table<number, string> @Table over patterns matching keystone levels in strings
---@field public KEYSTONE_LEVEL_TO_SCORE table<number, number> @Table over keystone levels and the base score for that level
---@field public RAID_DIFFICULTY table<number, RaidDifficulty> @Table of 1=normal, 2=heroic, 3=mythic difficulties and their names and colors
---@field public PREVIOUS_SEASON_SCORE_RELEVANCE_THRESHOLD number @Threshold that current season must surpass from previous season to be considered better and shown as primary in addon
---@field public PREVIOUS_SEASON_MAIN_SCORE_RELEVANCE_THRESHOLD number @Threshold that current season current character must surpass from previous season main to be considered better and shown as primary in addon
---@field public REGIONS_RESET_TIME table<string, number> @Maps each region string to their weekly reset timer
---@field public KEYSTONE_AFFIX_SCHEDULE number[] @Maps each weekly rotation, primarily for Tyrannical (`9`) and Fortified (`10`) tracking
---@field public KEYSTONE_AFFIX_INTERNAL table<number, string> @Maps each affix ID to a internal string version like `tyrannical` (`9`) and `fortified` (`10`)
---@field public KEYSTONE_AFFIX_TEXTURE table<number, string> @Maps each affix to a texture string Tyrannical (`9`/`-9`) and Fortified (`10`/`-10`)
ns.Print = function(text, r, g, b, ...)
r, g, b = r or 1, g or 1, b or 0
DEFAULT_CHAT_FRAME:AddMessage(tostring(text), r, g, b, ...)
end
ns.EXPANSION = max(LE_EXPANSION_BATTLE_FOR_AZEROTH, GetExpansionLevel() - 1)
ns.MAX_LEVEL = GetMaxLevelForExpansionLevel(ns.EXPANSION)
ns.REGION_TO_LTD = {"us", "kr", "eu", "tw", "cn"}
ns.FACTION_TO_ID = {Alliance = 1, Horde = 2, Neutral = 3}
ns.PLAYER_REGION = nil
ns.PLAYER_REGION_ID = nil
ns.PLAYER_FACTION = nil
ns.PLAYER_FACTION_TEXT = nil
ns.OUTDATED_CUTOFF = 86400 * 3 -- number of seconds before we start warning about stale data (warning the user should update their addon)
ns.OUTDATED_BLOCK_CUTOFF = 86400 * 7 -- number of seconds before we hide the data (block showing score as its most likely inaccurate)
ns.PROVIDER_DATA_TYPE = {MythicKeystone = 1, Raid = 2, PvP = 3}
ns.LOOKUP_MAX_SIZE = floor(2^18-1)
ns.CURRENT_SEASON = 1
ns.RAIDERIO_ADDON_DOWNLOAD_URL = "https://rio.gg/addon"
ns.HEADLINE_MODE = {
CURRENT_SEASON = 0,
BEST_SEASON = 1,
BEST_RUN = 2
}
-- threshold for comparing current character's previous season score to current score
-- meaning: once current score exceeds this fraction of previous season, then show current season
ns.PREVIOUS_SEASON_SCORE_RELEVANCE_THRESHOLD = 0.75
ns.PREVIOUS_SEASON_MAIN_SCORE_RELEVANCE_THRESHOLD = 0.75
ns.REGIONS_RESET_TIME = {
us = 1135695600,
eu = 1135753200,
tw = 1135810800,
kr = 1135810800,
cn = 1135810800,
}
ns.KEYSTONE_AFFIX_SCHEDULE = {
9, -- Tyrannical
10, -- Fortified
}
ns.KEYSTONE_AFFIX_INTERNAL = {
[9] = "tyrannical",
[10] = "fortified",
}
ns.KEYSTONE_AFFIX_TEXTURE = {
[-9] = CreateTextureMarkup("Interface\\AddOns\\RaiderIO\\icons\\affixes", 32, 32, 0, 0, 0.5, 1, 0.5, 1, 0, 0),
[-10] = CreateTextureMarkup("Interface\\AddOns\\RaiderIO\\icons\\affixes", 32, 32, 0, 0, 0.5, 1, 0, 0.5, 0, 0),
[9] = CreateTextureMarkup("Interface\\AddOns\\RaiderIO\\icons\\affixes", 32, 32, 0, 0, 0, 0.5, 0.5, 1, 0, 0),
[10] = CreateTextureMarkup("Interface\\AddOns\\RaiderIO\\icons\\affixes", 32, 32, 0, 0, 0, 0.5, 0, 0.5, 0, 0),
}
---@class RoleIcon
---@field full string @The full icon in "|T|t" syntax
---@field partial string @The partial icon in "|T|t" syntax
---@class RoleIcons
---@field public dps RoleIcon
---@field public healer RoleIcon
---@field public tank RoleIcon
ns.ROLE_ICONS = {
dps = {
full = "|TInterface\\AddOns\\RaiderIO\\icons\\roles:14:14:0:0:64:64:0:18:0:18|t",
partial = "|TInterface\\AddOns\\RaiderIO\\icons\\roles:14:14:0:0:64:64:0:18:36:54|t"
},
healer = {
full = "|TInterface\\AddOns\\RaiderIO\\icons\\roles:14:14:0:0:64:64:19:37:0:18|t",
partial = "|TInterface\\AddOns\\RaiderIO\\icons\\roles:14:14:0:0:64:64:19:37:36:54|t"
},
tank = {
full = "|TInterface\\AddOns\\RaiderIO\\icons\\roles:14:14:0:0:64:64:38:56:0:18|t",
partial = "|TInterface\\AddOns\\RaiderIO\\icons\\roles:14:14:0:0:64:64:38:56:36:54|t"
}
}
ns.KEYSTONE_LEVEL_PATTERN = {
"(%d+)%+",
"%+%s*(%d+)",
"(%d+)%s*%+",
"(%d+)"
}
ns.KEYSTONE_LEVEL_TO_SCORE = {
[2] = 40,
[3] = 45,
[4] = 55,
[5] = 60,
[6] = 65,
[7] = 75,
[8] = 80,
[9] = 85,
[10] = 100,
[11] = 105,
[12] = 110,
[13] = 115,
[14] = 120,
[15] = 125,
[16] = 130,
[17] = 135,
[18] = 140,
[19] = 145,
[20] = 150,
[21] = 155,
[22] = 160,
[23] = 165,
[24] = 170,
[25] = 175,
[26] = 180,
[27] = 185,
[28] = 190,
[29] = 195,
[30] = 200
}
---@class RaidDifficultyColor : table
---@field public pos1 number @red (0-1.0) - this table can be unpacked to get r, g, b
---@field public pos2 number @green (0-1.0) - this table can be unpacked to get r, g, b
---@field public pos3 number @blue (0-1.0) - this table can be unpacked to get r, g, b
---@field public hex string @hex (000000-ffffff) - this table can be unpacked to get r, g, b
---@class RaidDifficulty
---@field public suffix string
---@field public name string
---@field public color RaidDifficultyColor
ns.RAID_DIFFICULTY = {
[1] = {
suffix = L.RAID_DIFFICULTY_SUFFIX_NORMAL,
name = L.RAID_DIFFICULTY_NAME_NORMAL,
color = { 0.12, 1.00, 0.00, hex = "1eff00" }
},
[2] = {
suffix = L.RAID_DIFFICULTY_SUFFIX_HEROIC,
name = L.RAID_DIFFICULTY_NAME_HEROIC,
color = { 0.00, 0.44, 0.87, hex = "0070dd" }
},
[3] = {
suffix = L.RAID_DIFFICULTY_SUFFIX_MYTHIC,
name = L.RAID_DIFFICULTY_NAME_MYTHIC,
color = { 0.64, 0.21, 0.93, hex = "a335ee" }
}
}
end
-- data.lua (ns)
-- dependencies: constants
do
---@class CharacterProfile
---@field public name string
---@field public realm string
---@field public faction string @"alliance", "horde"
---@field public race number
---@field public class number
---@class CharacterMythicKeystoneRun
---@field public zone_id number
---@field public level number
---@field public upgrades number
---@field public fraction number
---@field public score number
---@field public url string
---@class CharacterCollection
---@field public profile CharacterProfile
---@field public mythic_keystone CharacterCollectionKeystones
---@class CharacterCollectionKeystones
---@field public all CharacterCollectionKeystoneProfile
---@class CharacterCollectionKeystoneProfile
---@field public score number
---@field public best CharacterMythicKeystoneRun
---@field public runs CharacterMythicKeystoneRun[]
---@class Character
---@return Character<string, CharacterCollection>
function ns:GetClientData()
return ns.CLIENT_CHARACTERS
end
---@class ScoreColor
---@field public score number
---@field public color number[]
---@class ScoreColorCollection
---@return ScoreColorCollection<number, ScoreColor>
function ns:GetClientColorData()
return ns.CLIENT_COLORS
end
---@class GuildProfile
---@field public name string
---@field public realm string
---@field public faction string @"alliance", "horde"
---@class GuildMythicKeystoneRunMember
---@field public name string
---@field public role string @"tank", "heal", "dps"
---@field public class_id number
---@class GuildMythicKeystoneRun
---@field public zone_id number
---@field public level number
---@field public upgrades number
---@field public fraction number
---@field public clear_time string
---@field public party GuildMythicKeystoneRunMember[]
---@class GuildCollection
---@field public profile GuildProfile
---@field public season_best GuildMythicKeystoneRun[]
---@field public weekly_best GuildMythicKeystoneRun[]
---@class Guild
---@return Guild<string, GuildCollection>
function ns:GetClientGuildData()
return ns.GUILD_BEST_DATA
end
---@class ClientConfig
---@field public lastModified string @A date like "2017-06-03T00:41:07Z"
---@field public enableCombatLogTracking boolean
---@field public syncMode string @"all"
---@field public syncAmericasHorde boolean
---@field public syncEuropeHorde boolean
---@field public syncKoreaHorde boolean
---@field public syncTaiwanHorde boolean
---@field public syncAmericasAlliance boolean
---@field public syncEuropeAlliance boolean
---@field public syncKoreaAlliance boolean
---@field public syncTaiwanAlliance boolean
---@return ClientConfig
function ns:GetClientConfig()
return ns.CLIENT_CONFIG
end
---@class DungeonInstance
---@field public id number
---@field public instance_map_id number
---@field public lfd_activity_ids number[]
---@field public name string
---@field public shortName string
---@class Dungeon : DungeonInstance
---@field public keystone_instance number
---@field public shortNameLocale string @Assigned dynamically based on the user preference regarding the short dungeon names.
---@field public index number @Assigned dynamically based on the index of the dungeon in the table.
---@class DungeonRaid : DungeonInstance
---@field public index number @Assigned dynamically based on the index of the raid in the table.
---@type Dungeon[]
local DUNGEONS = ns.DUNGEONS or ns.dungeons -- DEPRECATED: ns.dungeons
for i = 1, #DUNGEONS do
local dungeon = DUNGEONS[i] ---@type Dungeon
dungeon.index = i
end
---@type DungeonRaid[]
local RAIDS = ns.RAIDS or ns.raids -- DEPRECATED: ns.raids
for i = 1, #RAIDS do
local raid = RAIDS[i] ---@type DungeonRaid
raid.index = i
end
---@return Dungeon[]
function ns:GetDungeonData()
return DUNGEONS
end
---@return DungeonRaid[]
function ns:GetDungeonRaidData()
return RAIDS
end
---@class RealmCollection
---@return RealmCollection<string, string>
function ns:GetRealmData()
return ns.REALMS or ns.realmSlugs -- DEPRECATED: ns.realmSlugs
end
---@class RegionCollection
---@return RegionCollection<number, number>
function ns:GetRegionData()
return ns.REGIONS or ns.regionIDs -- DEPRECATED: ns.regionIDs
end
---@class ScoreStatsCollection
---@return ScoreStatsCollection<number, number>
function ns:GetScoreStatsData()
return ns.SCORE_STATS or ns.scoreLevelStats -- DEPRECATED: ns.scoreLevelStats
end
---@return ScoreColorCollection<number, ScoreColor>
function ns:GetScoreTiersData()
return ns.SCORE_TIERS or ns.scoreTiers -- DEPRECATED: ns.scoreTiers
end
---@class ScoreTierSimple
---@field public score number
---@field public quality number
---@class ScoreTiersSimpleCollection
---@return ScoreTiersSimpleCollection<number, ScoreTierSimple>
function ns:GetScoreTiersSimpleData()
return ns.SCORE_TIERS_SIMPLE or ns.scoreTiersSimple -- DEPRECATED: ns.scoreTiersSimple
end
---@return ScoreColorCollection<number, ScoreColor>
function ns:GetScoreTiersPrevData()
return ns.SCORE_TIERS_PREV or ns.previousScoreTiers -- DEPRECATED ns.previousScoreTiers
end
---@return ScoreTiersSimpleCollection<number, ScoreTierSimple>
function ns:GetScoreTiersSimplePrevData()
return ns.SCORE_TIERS_SIMPLE_PREV or ns.previousScoreTiersSimple -- DEPRECATED: ns.previousScoreTiersSimple
end
end
-- module.lua (ns)
-- dependencies: none
do
---@type Module<string, Module>
local modules = {}
local moduleIndex = 0
---@class Module
-- private properties for internal use only
---@field private id string @Required and unique string to identify the module.
---@field private index number @Automatically assigned a number based on the creation order.
---@field private loaded boolean @Flag indicates if the module is loaded.
---@field private enabled boolean @Flag indicates if the module is enabled.
---@field private dependencies string[] @List over dependencies before we can Load the module.
-- private functions that should never be called
---@field private SetLoaded function @Internal function should not be called manually.
---@field private Load function @Internal function should not be called manually.
---@field private SetEnabled function @Internal function should not be called manually.
-- protected functions that can be called but should never be overridden
---@field protected IsLoaded function @Internal function, can be called but do not override.
---@field protected IsEnabled function @Internal function, can be called but do not override.
---@field protected Enable function @Internal function, can be called but do not override.
---@field protected Disable function @Internal function, can be called but do not override.
---@field protected SetDependencies function @Internal function, can be called but do not override.
---@field protected HasDependencies function @Internal function, can be called but do not override.
---@field protected GetDependencies function @Internal function, can be called but do not override. Returns a table using the same order as the dependencies table. Returns the modules or nil depending if they are available or not.
-- public functions that can be overridden
---@field public CanLoad function @If it returns true the module will be loaded, otherwise postponed for later. Override to define your modules load criteria that have to be met before loading.
---@field public OnLoad function @Once the module loads this function is executed. Use this to setup further logic for your module. The args provided are the module references as described in the dependencies table.
---@field public OnEnable function @This function is executed when the module is set to enabled state. Use this to setup and prepare.
---@field public OnDisable function @This function is executed when the module is set to disabled state. Use this for cleanup purposes.
---@type Module
local module = {}
---@return nil
function module:SetLoaded(state)
self.loaded = state
end
---@return boolean
function module:Load()
if not self:CanLoad() then
return false
end
self:SetLoaded(true)
self:OnLoad(unpack(self:GetDependencies()))
return true
end
---@return nil
function module:SetEnabled(state)
self.enabled = state
end
---@return boolean
function module:IsLoaded()
return self.loaded
end
---@return boolean
function module:IsEnabled()
return self.enabled
end
---@return boolean
function module:Enable()
if self:IsEnabled() then
return false
end
self:SetEnabled(true)
self:OnEnable()
return true
end
---@return boolean
function module:Disable()
if not self:IsEnabled() then
return false
end
self:SetEnabled(false)
self:OnDisable()
return true
end
---@return nil
function module:SetDependencies(dependencies)
self.dependencies = dependencies
end
---@return boolean
function module:HasDependencies()
if type(self.dependencies) == "string" then
local m = modules[self.dependencies]
return m and m:IsLoaded()
end
if type(self.dependencies) == "table" then
for _, id in ipairs(self.dependencies) do
local m = modules[id]
if not m or not m:IsLoaded() then
return false
end
end
end
return true
end
---@return Module[]
function module:GetDependencies()
local temp = {}
local index = 0
if type(self.dependencies) == "string" then
index = index + 1
temp[index] = modules[self.dependencies]
end
if type(self.dependencies) == "table" then
for _, id in ipairs(self.dependencies) do
index = index + 1
temp[index] = modules[id]
end
end
return temp
end
---@return boolean
function module:CanLoad()
return not self:IsLoaded()
end
---@vararg Module
---@return nil
function module:OnLoad(...)
self:Enable()
end
---@return nil
function module:OnEnable()
end
---@return nil
function module:OnDisable()
end
---@param id string @Unique module ID reference.
---@param data Module @Optional table with properties to copy into the newly created module.
function ns:NewModule(id, data)
assert(type(id) == "string", "Raider.IO Module expects NewModule(id[, data]) where id is a string, data is optional table.")
assert(not modules[id], "Raider.IO Module expects NewModule(id[, data]) where id is a string, that is unique and not already taken.")
---@type Module
local m = {}
for k, v in pairs(module) do
m[k] = v
end
moduleIndex = moduleIndex + 1
m.index = moduleIndex
m.id = id
m:SetLoaded(false)
m:SetEnabled(false)
m:SetDependencies()
if type(data) == "table" then
for k, v in pairs(data) do
m[k] = v
end
end
modules[id] = m
return m
end
---@param a Module
---@param b Module
local function SortModules(a, b)
return a.index < b.index
end
---@return Module[]
function ns:GetModules()
local ordered = {}
local index = 0
for _, module in pairs(modules) do
index = index + 1
ordered[index] = module
end
table.sort(ordered, SortModules)
return ordered
end
---@param id string @Unique module ID reference.
---@param silent boolean @Ommit to throw if module doesn't exists.
function ns:GetModule(id, silent)
assert(type(id) == "string", "Raider.IO Module expects GetModule(id) where id is a string.")
for _, module in pairs(modules) do
if module.id == id then
return module
end
end
assert(silent, "Raider.IO Module expects GetModule(id) where id is a string, and the module must exists, or the silent param must be set to avoid this throw.")
end
end
-- callback.lua
-- dependencies: module
do
---@class CallbackModule : Module
local callback = ns:NewModule("Callback") ---@type CallbackModule
local callbacks = {}
local callbackOnce = {}
local handler = CreateFrame("Frame")
handler:SetScript("OnEvent", function(handler, event, ...)
if event == "COMBAT_LOG_EVENT_UNFILTERED" or event == "COMBAT_LOG_EVENT" then
callback:SendEvent(event, CombatLogGetCurrentEventInfo())
else
callback:SendEvent(event, ...)
end
end)
---@param callbackFunc function
function callback:RegisterEvent(callbackFunc, ...)
assert(type(callbackFunc) == "function", "Raider.IO Callback expects RegisterEvent(callback[, ...events])")
local events = {...}
for _, event in ipairs(events) do
if not callbacks[event] then
callbacks[event] = {}
end
table.insert(callbacks[event], callbackFunc)
pcall(handler.RegisterEvent, handler, event)
end
end
---@param callbackFunc function
---@param event string
function callback:RegisterUnitEvent(callbackFunc, event, ...)
assert(type(callbackFunc) == "function" and type(event) == "string", "Raider.IO Callback expects RegisterUnitEvent(callback, event, ...units)")
if not callbacks[event] then
callbacks[event] = {}
end
table.insert(callbacks[event], callbackFunc)
handler:RegisterUnitEvent(event, ...)
end
function callback:UnregisterEvent(callbackFunc, ...)
assert(type(callbackFunc) == "function", "Raider.IO Callback expects UnregisterEvent(callback, ...events)")
local events = {...}
callbackOnce[callbackFunc] = nil
for _, event in ipairs(events) do
local eventCallbacks = callbacks[event]
for i = #eventCallbacks, 1, -1 do
local eventCallback = eventCallbacks[i]
if eventCallback == callbackFunc then
table.remove(eventCallbacks, i)
end
end
if not eventCallbacks[1] then
pcall(handler.UnregisterEvent, handler, event)
end
end
end
---@param callbackFunc function
function callback:UnregisterCallback(callbackFunc)
assert(type(callbackFunc) == "function", "Raider.IO Callback expects UnregisterCallback(callback)")
for event, _ in pairs(callbacks) do
self:UnregisterEvent(callbackFunc, event)
end
end
---@param event string
function callback:SendEvent(event, ...)
assert(type(event) == "string", "Raider.IO Callback expects SendEvent(event[, ...args])")
local eventCallbacks = callbacks[event]
if not eventCallbacks then
return
end
-- execute in correct sequence but note if any are to be removed later
local remove
for i = 1, #eventCallbacks do
local callbackFunc = eventCallbacks[i]
callbackFunc(event, ...)
if callbackOnce[callbackFunc] then
callbackOnce[callbackFunc] = nil
if not remove then
remove = {}
end
table.insert(remove, i)
end
end
-- if we have callbacks to remove iterate backwards and remove those indices
if remove then
for i = #remove, 1, -1 do
table.remove(eventCallbacks, remove[i])
end
end
end
---@param callbackFunc function
function callback:RegisterEventOnce(callbackFunc, ...)
assert(type(callbackFunc) == "function", "Raider.IO Callback expects RegisterEventOnce(callback[, ...events])")
callbackOnce[callbackFunc] = true
callback:RegisterEvent(callbackFunc, ...)
end
end
-- config.lua
-- dependencies: module, callback
do
---@class ConfigModule : Module
---@field public SavedVariablesLoaded boolean This is etonce the SV are loaded to indicate we are ready to read from the settings table.
local config = ns:NewModule("Config") ---@type ConfigModule
local callback = ns:GetModule("Callback") ---@type CallbackModule
-- fallback saved variables
local fallbackConfig = {
enableUnitTooltips = true,
enableLFGTooltips = true,
enableFriendsTooltips = true,
enableLFGDropdown = true,
enableWhoTooltips = true,
enableWhoMessages = true,
enableGuildTooltips = true,
enableKeystoneTooltips = true,
mplusHeadlineMode = 1,
useEnglishAbbreviations = false,
showMainsScore = true,
showMainBestScore = true,
showDropDownCopyURL = true,
showSimpleScoreColors = false,
showScoreInCombat = true,
showScoreModifier = false, -- NEW in 9.0
disableScoreColors = false,
enableClientEnhancements = true,
showClientGuildBest = true,
displayWeeklyGuildBest = false,
allowClientToControlCombatLog = true,
enableCombatLogTracking = false,
showRaiderIOProfile = true,
hidePersonalRaiderIOProfile = false,
showRaidEncountersInProfile = true,
enableProfileModifier = true,
inverseProfileModifier = false,
positionProfileAuto = true,
lockProfile = false,
showRoleIcons = true,
profilePoint = { point = nil, x = 0, y = 0 },
debugMode = false,
rwfMode = false, -- NEW in 9.1
showMedalsInsteadOfText = false,-- NEW in 9.1.5
}
-- fallback metatable looks up missing keys into the fallback config table
local fallbackMetatable = {
__index = function(_, key)
return fallbackConfig[key]
end
}
-- the global saved variables table used when setting up fresh installations
RaiderIO_Config = setmetatable({}, fallbackMetatable)
local function OnPlayerLogin()
if type(RaiderIO_Config) ~= "table" then
RaiderIO_Config = {}
end
setmetatable(RaiderIO_Config, fallbackMetatable)
config:Enable()
if config:Get("debugMode") then
ns.Print(format(L.WARNING_DEBUG_MODE_ENABLE, addonName))
end
if config:Get("rwfMode") then
ns.Print(format(L.WARNING_RWF_MODE_ENABLE, addonName))
end
callback:SendEvent("RAIDERIO_CONFIG_READY")
end
function config:CanLoad()
return not self:IsLoaded() and self.SavedVariablesLoaded
end
function config:OnLoad()
callback:RegisterEventOnce(OnPlayerLogin, "RAIDERIO_PLAYER_LOGIN")
end
function config:Set(key, val)
assert(self:IsEnabled(), "Raider.IO Config expects Set(key, val) to only be used after the addon saved variables have been loaded.")
RaiderIO_Config[key] = val
end
function config:Get(key, fallback)
assert(self:IsEnabled(), "Raider.IO Config expects Get(key[, fallback]) to only be used after the addon saved variables have been loaded.")
local val = RaiderIO_Config[key]
if val == nil then
return fallback
end
return val
end
end
-- util.lua
-- dependencies: module, config
do
---@class UtilModule : Module
local util = ns:NewModule("Util") ---@type UtilModule
local callback = ns:GetModule("Callback") ---@type CallbackModule
local config = ns:GetModule("Config") ---@type ConfigModule
local DUNGEONS = ns:GetDungeonData()
local SORTED_DUNGEONS = {} ---@type Dungeon[]
do
for i = 1, #DUNGEONS do
SORTED_DUNGEONS[i] = DUNGEONS[i]
end
end
-- update the dungeon properties for shortNameLocale at the appropriate events
local function OnSettingsChanged()
if not config:IsEnabled() then
return
end
local useEnglishAbbreviations = config:Get("useEnglishAbbreviations")
for i = 1, #DUNGEONS do
local dungeon = DUNGEONS[i]
if useEnglishAbbreviations then
dungeon.shortNameLocale = dungeon.shortName
else
dungeon.shortNameLocale = L["DUNGEON_SHORT_NAME_" .. dungeon.shortName] or dungeon.shortName
end
end
table.sort(SORTED_DUNGEONS, function(a, b)
return a.shortNameLocale < b.shortNameLocale
end)
end
callback:RegisterEvent(OnSettingsChanged, "RAIDERIO_CONFIG_READY")
callback:RegisterEvent(OnSettingsChanged, "RAIDERIO_SETTINGS_SAVED")
---@return Dungeon[]
function util:GetSortedDungeons()
return SORTED_DUNGEONS
end
---@return Dungeon|nil
function util:GetDungeonByIndex(index)
return DUNGEONS[index]
end
---@return Dungeon|nil
function util:GetDungeonByLFDActivityID(id)
for i = 1, #DUNGEONS do
local dungeon = DUNGEONS[i]
for j = 1, #dungeon.lfd_activity_ids do
local activityID = dungeon.lfd_activity_ids[j]
if activityID == id then
return dungeon
end
end
end
end
---@return Dungeon|nil
function util:GetDungeonByKeyValue(key, value)
for i = 1, #DUNGEONS do
local dungeon = DUNGEONS[i]
if dungeon[key] == value then
return dungeon
end
end
end
---@return Dungeon|nil
function util:GetDungeonByID(id)
return util:GetDungeonByKeyValue("id", id)
end
---@return Dungeon|nil
function util:GetDungeonByInstanceMapID(id)
return util:GetDungeonByKeyValue("instance_map_id", id)
end
---@return Dungeon|nil
function util:GetDungeonByKeystoneID(id)
return util:GetDungeonByKeyValue("keystone_instance", id)
end
---@return Dungeon|nil
function util:GetDungeonByName(name)
return util:GetDungeonByKeyValue("name", name)
end
---@return Dungeon|nil
function util:GetDungeonByShortName(name)
return util:GetDungeonByKeyValue("shortName", name) or util:GetDungeonByKeyValue("shortNameLocale", name)
end
---@param object Region @Any interface widget object that supports the methods GetScript.
---@param handler string @The script handler like OnEnter, OnClick, etc.
---@return boolean|nil @If successfully executed returns true, otherwise false if nothing has been called. nil if the widget had no handler to execute.
function util:ExecuteWidgetHandler(object, handler, ...)
if type(object) ~= "table" or type(object.GetScript) ~= "function" then
return false
end
local func = object:GetScript(handler)
if type(func) ~= "function" then
return
end
if not pcall(func, object, ...) then
return false
end
return true
end
---@param object Region @Any interface widget object that supports the methods GetOwner.
---@param owner Region @Any interface widget object.
---@param anchor string @`ANCHOR_TOPLEFT`, `ANCHOR_NONE`, `ANCHOR_CURSOR`, etc.
---@param offsetX number @Optional offset X for some of the anchors.
---@param offsetY number @Optional offset Y for some of the anchors.
---@return boolean, boolean, boolean @If owner was set arg1 is true. If owner was updated arg2 is true. Otherwise both will be set to face to indicate we did not update the Owner of the widget. If the owner is set to the preferred owner arg3 is true.
function util:SetOwnerSafely(object, owner, anchor, offsetX, offsetY)
if type(object) ~= "table" or type(object.GetOwner) ~= "function" then
return
end
local currentOwner = object:GetOwner()
if not currentOwner then
object:SetOwner(owner, anchor, offsetX, offsetY)
return true, false, true
end
offsetX, offsetY = offsetX or 0, offsetY or 0
local currentAnchor, currentOffsetX, currentOffsetY = object:GetAnchorType()
currentOffsetX, currentOffsetY = currentOffsetX or 0, currentOffsetY or 0
if currentAnchor ~= anchor or (currentOffsetX ~= offsetX and abs(currentOffsetX - offsetX) > 0.01) or (currentOffsetY ~= offsetY and abs(currentOffsetY - offsetY) > 0.01) then
object:SetOwner(owner, anchor, offsetX, offsetY)
return true, true, true
end
return false, true, currentOwner == owner
end
---@param text string @The format string like "Greetings %s! How are you?"
---@return string @Returns a pattern like "Greetings (.-)%! How are you%?"
function util:FormatToPattern(text)
if type(text) ~= "string" then
return
end
text = text:gsub("%%", "%%%%")
text = text:gsub("%.", "%%%.")
text = text:gsub("%?", "%%%?")
text = text:gsub("%+", "%%%+")
text = text:gsub("%-", "%%%-")
text = text:gsub("%(", "%%%(")
text = text:gsub("%)", "%%%)")
text = text:gsub("%[", "%%%[")
text = text:gsub("%]", "%%%]")
text = text:gsub("%%%%s", "(.-)")
text = text:gsub("%%%%d", "(%%d+)")
text = text:gsub("%%%%%%[%d%.%,]+f", "([%%d%%.%%,]+)")
return text
end
---@param ts number @A time() number
---@return number @seconds difference between time and utc
function util:GetTimeZoneOffset(ts)
local utc = date("!*t", ts)
local loc = date("*t", ts)
loc.isdst = false
return difftime(time(loc), time(utc))
end
---@param dateString string @A date like "2017-06-03T00:41:07Z"
---@return number @A time() number
function util:GetTimeFromDateString(dateString)
local year, month, day, hours, minutes, seconds = dateString:match("^(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+).*Z$")
return time({ year = year, month = month, day = day, hour = hours, min = minutes, sec = seconds })
end
local REGION = ns:GetRegionData()
---@return any, number @arg1 can be nil (no data), false (server is unknown), string (the ltd). arg2 can be nil (no data), or region ID.
function util:GetRegion()
local guid = UnitGUID("player")
if not guid then
return
end
local serverId = tonumber(strmatch(guid, "^Player%-(%d+)") or 0) or 0
local regionId = REGION[serverId]
if not regionId then
regionId = GetCurrentRegion()
ns.Print(format(L.UNKNOWN_SERVER_FOUND, addonName, guid or "N/A", GetNormalizedRealmName() or "N/A"))
end
if not regionId then
return false
end
local ltd = ns.REGION_TO_LTD[regionId]
if not ltd then
return false, regionId
end
return ltd, regionId
end
---@return any, number @arg1 can be nil (no data), false (server is unknown), string (the ltd). arg2 can be nil (no data), or region ID.
function util:GetRegionForServerId(serverId)
if not serverId then
return
end
local regionId = REGION[serverId]
if not regionId then
return
end
local ltd = ns.REGION_TO_LTD[regionId]
if not ltd then