-
Notifications
You must be signed in to change notification settings - Fork 111
Expand file tree
/
Copy pathAllTheThings.lua
More file actions
1704 lines (1595 loc) · 61.4 KB
/
AllTheThings.lua
File metadata and controls
1704 lines (1595 loc) · 61.4 KB
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
--------------------------------------------------------------------------------
-- A L L T H E T H I N G S --
--------------------------------------------------------------------------------
-- Copyright 2017-2025 Dylan Fortune (Crieve-Sargeras) --
--------------------------------------------------------------------------------
-- App locals
local appName, app = ...;
local L = app.L;
-- Abbreviations
L.ABBREVIATIONS[L.UNSORTED .. " %> " .. L.UNSORTED] = "|T" .. app.asset("WindowIcon_Unsorted") .. ":0|t " .. L.SHORTTITLE .. " %> " .. L.UNSORTED;
-- Binding Localizations
BINDING_HEADER_ALLTHETHINGS = L.TITLE
BINDING_NAME_ALLTHETHINGS_TOGGLEACCOUNTMODE = L.TOGGLE_ACCOUNT_MODE
BINDING_NAME_ALLTHETHINGS_TOGGLECOMPLETIONISTMODE = L.TOGGLE_COMPLETIONIST_MODE
BINDING_NAME_ALLTHETHINGS_TOGGLEDEBUGMODE = L.TOGGLE_DEBUG_MODE
BINDING_NAME_ALLTHETHINGS_TOGGLEFACTIONMODE = L.TOGGLE_FACTION_MODE
BINDING_NAME_ALLTHETHINGS_TOGGLELOOTMODE = L.TOGGLE_LOOT_MODE
BINDING_HEADER_ALLTHETHINGS_PREFERENCES = PREFERENCES
BINDING_NAME_ALLTHETHINGS_TOGGLECOMPLETEDTHINGS = L.TOGGLE_COMPLETEDTHINGS
BINDING_NAME_ALLTHETHINGS_TOGGLECOMPLETEDGROUPS = L.TOGGLE_COMPLETEDGROUPS
BINDING_NAME_ALLTHETHINGS_TOGGLECOLLECTEDTHINGS = L.TOGGLE_COLLECTEDTHINGS
BINDING_NAME_ALLTHETHINGS_TOGGLEBOEITEMS = L.TOGGLE_BOEITEMS
BINDING_NAME_ALLTHETHINGS_TOGGLESOURCETEXT = L.TOGGLE_SOURCETEXT
BINDING_HEADER_ALLTHETHINGS_MODULES = L.MODULES
BINDING_NAME_ALLTHETHINGS_TOGGLEMAINLIST = L.TOGGLE_MAINLIST
BINDING_NAME_ALLTHETHINGS_TOGGLEMINILIST = L.TOGGLE_MINILIST
BINDING_NAME_ALLTHETHINGS_TOGGLE_PROFESSION_LIST = L.TOGGLE_PROFESSION_LIST
BINDING_NAME_ALLTHETHINGS_TOGGLE_RAID_ASSISTANT = L.TOGGLE_RAID_ASSISTANT
BINDING_NAME_ALLTHETHINGS_TOGGLE_WORLD_QUESTS_LIST = L.TOGGLE_WORLD_QUESTS_LIST
BINDING_NAME_ALLTHETHINGS_TOGGLERANDOM = L.TOGGLE_RANDOM
BINDING_NAME_ALLTHETHINGS_REROLL_RANDOM = L.REROLL_RANDOM
-- Performance Cache
local print,rawget,rawset,tostring,ipairs,pairs,tonumber,wipe,select,setmetatable,getmetatable,tinsert,tremove,type,math_floor,GetTime
= print,rawget,rawset,tostring,ipairs,pairs,tonumber,wipe,select,setmetatable,getmetatable,tinsert,tremove,type,math.floor,GetTime
-- Global WoW API Cache
local C_Map_GetMapInfo = C_Map.GetMapInfo;
-- App & Module locals
local SearchForField, SearchForObject
= app.SearchForField, app.SearchForObject
local IsRetrieving = app.Modules.RetrievingData.IsRetrieving;
local TryColorizeName = app.TryColorizeName;
local MergeProperties = app.MergeProperties
local DESCRIPTION_SEPARATOR = app.DESCRIPTION_SEPARATOR;
local GetRelativeValue = app.GetRelativeValue
local
CreateObject,
MergeObject,
NestObject,
MergeObjects,
NestObjects,
PriorityNestObjects
=
app.__CreateObject,
app.MergeObject,
app.NestObject,
app.MergeObjects,
app.NestObjects,
app.PriorityNestObjects
-- Coroutine Helper Functions
local Callback = app.CallbackHandlers.Callback;
app.FillRunner = app.CreateRunner("fill");
-- Data Lib
local AllTheThingsAD = {}; -- For account-wide data.
local GetGroupItemIDWithModID, GroupMatchesParams
= app.GetGroupItemIDWithModID, app.GroupMatchesParams
do
local ContainsLimit, ContainsExceeded;
local GetProgressTextForRow, GetUnobtainableTexture
app.AddEventHandler("OnLoad", function()
GetProgressTextForRow = app.GetProgressTextForRow
GetUnobtainableTexture = app.GetUnobtainableTexture
end)
local MaxLayer = 4
local Indents = {
" ",
}
for i=2,MaxLayer do
Indents[i] = Indents[i-1].." "
end
local ContainsTypesIndicators
app.AddEventHandler("OnStartup", function() ContainsTypesIndicators = app.Modules.Fill.Settings.Icons end)
local function BuildContainsInfo(root, entries, indent, layer)
local subgroups = root and root.g
if not subgroups or #subgroups == 0 then return end
for _,group in ipairs(subgroups) do
-- If there's progress to display for a non-sourceIgnored group, then let's summarize a bit better.
if group.visible and not group.sourceIgnored and not group.skipContains then
-- Special case to ignore 'container' layers where the container is a Header which matches the ItemID of the parent
if group.headerID and group.headerID == root.itemID then
BuildContainsInfo(group, entries, indent, layer)
else
-- Count it, but don't actually add it to entries if it meets the limit
if #entries >= ContainsLimit then
ContainsExceeded = ContainsExceeded + 1;
else
-- Insert into the display.
-- app.PrintDebug("INCLUDE",app.Debugging,GetProgressTextForRow(group),group.hash,group.key,group.key and group[group.key])
local o = { group = group, right = GetProgressTextForRow(group, true) };
local indicator = ContainsTypesIndicators[group.filledType] or app.GetIndicatorIcon(group);
o.prefix = indicator and (Indents[indent]:sub(3) .. "|T" .. indicator .. ":0|t ") or Indents[indent]
entries[#entries + 1] = o
end
-- Only go down one more level.
if layer < MaxLayer then
BuildContainsInfo(group, entries, indent + 1, layer + 1);
end
end
-- else
-- app.PrintDebug("EXCLUDE",app.Debugging,GetProgressTextForRow(group),group.hash,group.key,group.key and group[group.key])
end
end
end
-- Fields on groups which can be utilized in tooltips to show additional Source location info for that group (by order of priority)
local TooltipSourceFields = {
"professionID",
"instanceID",
"mapID",
"maps",
"npcID",
"questID"
};
local SourceLocationSettingsKey = setmetatable({
creatureID = "SourceLocations:Creatures",
npcID = "SourceLocations:Creatures",
}, {
__index = function(t, key)
return "SourceLocations:Things";
end
});
local UnobtainableTexture = " |T"..L.UNOBTAINABLE_ITEM_TEXTURES[1]..":0|t"
local NotCurrentCharacterTexture = " |T"..L.UNOBTAINABLE_ITEM_TEXTURES[0]..":0|t"
local RETRIEVING_DATA = RETRIEVING_DATA
local function GenerateSourcePath(group, l)
local parent = group.sourceParent or group.parent;
if parent then
if l < 1 then
return GenerateSourcePath(parent, l + 1);
else
return GenerateSourcePath(parent, l + 1) .. " > " .. (group.sourceText or group.text or RETRIEVING_DATA);
end
end
return group.text or RETRIEVING_DATA;
end
local function AddContainsData(group, tooltipInfo)
local key = group.key
local thingCheck = app.ThingKeys[key]
-- only show Contains on Things
if not thingCheck or (app.ActiveRowReference and thingCheck ~= true) then return end
local working = group.working
-- Sort by the heirarchy of the group if not the raw group of an ATT list
if not working and not app.ActiveRowReference then
app.Sort(group.g, app.SortDefaults.Hierarchy, true);
end
-- app.PrintDebug("SummarizeThings",app:SearchLink(group),group.g and #group.g,working)
local entries = {};
-- app.Debugging = "CONTAINS-"..group.hash;
ContainsLimit = app.Settings:GetTooltipSetting("ContainsCount") or 25;
ContainsExceeded = 0;
BuildContainsInfo(group, entries, 1, 1)
-- app.Debugging = nil;
-- app.PrintDebug(entries and #entries,"contains entries")
if #entries > 0 then
local left, right;
tinsert(tooltipInfo, { left = L.CONTAINS });
local item, entry;
for i=1,#entries do
item = entries[i];
entry = item.group;
left = entry.text;
if not left or IsRetrieving(left) then
left = RETRIEVING_DATA
if not working then
local AsyncRefreshFunc = entry.AsyncRefreshFunc
if AsyncRefreshFunc then
AsyncRefreshFunc(entry)
else
-- app.PrintDebug("No Async Refresh Func for TT Type!",entry.__type)
app.ReshowGametooltip()
end
end
working = true
end
left = TryColorizeName(entry, left);
-- app.PrintDebug("Entry#",i,app:SearchLink(entry),GenerateSourcePath(entry, 1))
-- If this entry has a specific Class requirement and is not itself a 'Class' header, tack that on as well
if entry.c and entry.key ~= "classID" and #entry.c == 1 then
left = left .. " [" .. TryColorizeName(entry, app.ClassInfoByID[entry.c[1]].name) .. "]";
end
if entry.icon then item.prefix = item.prefix .. "|T" .. entry.icon .. ":0|t "; end
-- If this entry has specialization requirements, let's attempt to show the specialization icons.
right = item.right;
local specs = entry.specs;
if specs and #specs > 0 then
right = app.GetSpecsString(specs, false, false) .. right;
else
local c = entry.c;
if c and #c > 0 then
right = app.GetClassesString(c, false, false) .. right;
end
end
-- If this entry has customCollect requirements, list them for clarity
if entry.customCollect then
for i,c in ipairs(entry.customCollect) do
local reason = L.CUSTOM_COLLECTS_REASONS[c];
local icon_color_str = reason.icon.." |c"..reason.color..reason.text;
if i > 1 then
right = icon_color_str .. " / " .. right;
else
right = icon_color_str .. " " .. right;
end
end
end
-- If this entry is an Item, show additional Source information for that Item (since it needs to be acquired in a specific location most-likely)
if entry.itemID and key ~= "npcID" and key ~= "encounterID" then
-- Add the Zone name
local field, id;
for _,v in ipairs(TooltipSourceFields) do
id = GetRelativeValue(entry, v, true);
-- app.PrintDebug("check",v,id)
if id then
field = v;
break;
end
end
if field then
local locationGroup, locationName;
-- convert maps
if field == "maps" then
-- if only a few maps, list them all
local count = #id;
if count == 1 then
locationName = app.GetMapName(id[1]);
else
-- instead of listing individual zone names, just list zone count for brevity
local names = {__count=0}
local name
for j=1,count,1 do
name = app.GetMapName(id[j]);
if name and not names[name] then
names.__count = names.__count + 1
end
end
locationName = "["..names.__count.." "..BRAWL_TOOLTIP_MAPS.."]"
-- old: list 3 zones/+++
-- local mapsConcat, names, name = {}, {}, nil;
-- for j=1,count,1 do
-- name = app.GetMapName(id[j]);
-- if name and not names[name] then
-- names[name] = true;
-- mapsConcat[#mapsConcat + 1] = name
-- end
-- end
-- -- 1 unique map name displayed
-- if #mapsConcat < 2 then
-- locationName = app.TableConcat(mapsConcat, nil, nil, "/");
-- else
-- mapsConcat[2] = "+"..(count - 1);
-- locationName = app.TableConcat(mapsConcat, nil, nil, "/", 1, 2);
-- end
end
else
locationGroup = SearchForObject(field, id, "field") or (id and field == "mapID" and C_Map_GetMapInfo(id));
locationName = locationGroup and TryColorizeName(locationGroup, locationGroup.name);
end
-- print("contains info",entry.itemID,field,id,locationGroup,locationName)
if locationName then
-- Add the immediate parent group Vendor name
local rawParent, sParent = rawget(entry, "parent"), entry.sourceParent;
-- the source entry is different from the raw parent and the search context, then show the source parent text for reference
if sParent and sParent.text and not GroupMatchesParams(rawParent, sParent.key, sParent[sParent.key]) and not GroupMatchesParams(sParent, key, id) then
local parentText = sParent.text;
if IsRetrieving(parentText) then
working = true;
end
right = locationName .. " > " .. parentText .. " " .. right;
else
right = locationName .. " " .. right;
end
-- else
-- print("No Location name for item",entry.itemID,id,field)
end
end
end
-- If this entry is an Achievement Criteria (whose raw parent is not the Achievement) then show the Achievement
if entry.criteriaID and entry.achievementID then
local rawParent = rawget(entry, "parent");
if not rawParent or rawParent.achievementID ~= entry.achievementID then
local critAch = SearchForObject("achievementID", entry.achievementID, "key");
left = left .. " > " .. (critAch and critAch.text or "???");
end
end
tinsert(tooltipInfo, { left = item.prefix .. left, right = right });
end
if ContainsExceeded > 0 then
tinsert(tooltipInfo, { left = (L.AND_MORE):format(ContainsExceeded) });
end
end
return working
end
app.AddEventHandler("OnLoad", function()
app.Settings.CreateInformationType("SummarizeThings", {
text = "SummarizeThings",
priority = 2.9, HideCheckBox = true,
Process = function(t, reference, tooltipInfo)
if reference.g then
if AddContainsData(reference, tooltipInfo) then
reference.working = true
end
end
end
})
end)
local SourceSearcher = app.SourceSearcher
local function AddSourceLinesForTooltip(tooltipInfo, paramA, paramB)
-- Create a list of sources
-- app.PrintDebug("SourceLocations",paramA,paramB,SourceLocationSettingsKey[paramA])
if not app.ThingKeys[paramA] then return end
local settings = app.Settings
if not settings:GetTooltipSetting("SourceLocations") or not settings:GetTooltipSetting(SourceLocationSettingsKey[paramA]) then return end
local text, parent, right
local character, unavailable, unobtainable = {}, {}, {}
local showUnsorted = settings:GetTooltipSetting("SourceLocations:Unsorted");
local showCompleted = settings:GetTooltipSetting("SourceLocations:Completed");
local issecretvalue = app.WOWAPI.issecretvalue
local FilterSettings, FilterInGame, FilterCharacter, FirstParent
= app.RecursiveGroupRequirementsFilter, app.Modules.Filter.Filters.InGame, app.RecursiveCharacterRequirementsFilter, app.GetRelativeGroup
local abbrevs = L.ABBREVIATIONS;
local sourcesToShow
-- paramB is the modItemID for itemID searches, so we may have to fallback to the base itemID if nothing sourced for the modItemID
-- TODO: Rings from raid showing all difficulties, need fallback matching for items... modItemID, modID, itemID
-- using a second return, directSources, to indicate the SourceSearcher has returned the Sources rather than the Things
local allReferences, directSources = SourceSearcher[paramA](paramA,paramB)
-- app.PrintDebug(directSources and "Source count" or "Search count",#allReferences,paramA,paramB,GetItemIDAndModID(paramB))
for _,j in ipairs(allReferences or app.EmptyTable) do
parent = directSources and j or j.parent
-- app.PrintDebug("source:",app:SearchLink(j),parent and parent.parent,showCompleted or not app.IsComplete(j))
if parent and parent.parent
and (showCompleted or not app.IsComplete(j))
then
text = GenerateSourcePath(parent, parent.objectiveID and 0 or 1);
-- app.PrintDebug("SourceLocation",text,FilterInGame(j),FilterSettings(parent),FilterCharacter(parent))
if showUnsorted or issecretvalue(text) or (not text:match(L.UNSORTED) and not text:match(L.HIDDEN_QUEST_TRIGGERS)) then
-- doesn't meet current unobtainable filters from the Thing itself and its parent chain
if not FilterInGame(j) or not FilterInGame(parent) then
unobtainable[#unobtainable + 1] = text..UnobtainableTexture
else
-- something user would currently see in a list or not
sourcesToShow = FilterSettings(parent) and character or unavailable
-- from obtainable, different character source
if not FilterCharacter(parent) then
sourcesToShow[#sourcesToShow + 1] = text..NotCurrentCharacterTexture
else
-- check if this needs a status icon even though it's being shown
right = GetUnobtainableTexture(FirstParent(parent, "e", true) or FirstParent(parent, "u", true) or parent)
or (parent.rwp and app.asset("status-prerequisites"))
if right then
sourcesToShow[#sourcesToShow + 1] = text.." |T" .. right .. ":0|t"
else
sourcesToShow[#sourcesToShow + 1] = text
end
end
end
end
end
end
-- app.PrintDebug("Sources count",#character,#unobtainable)
-- if in Debug, add any unobtainable & unavailable sources
if app.MODE_DEBUG then
-- app.PrintDebug("+unavailable",#unavailable,"+unobtainable",#unobtainable)
app.ArrayAppend(character, unavailable, unobtainable)
elseif #character == 0 and not (paramA == "npcID" or paramA == "creatureID" or paramA == "encounterID") then
-- no sources available to the character, add any unavailable/unobtainable sources
if #unavailable > 0 then
-- app.PrintDebug("+unavailable",#unavailable)
app.ArrayAppend(character, unavailable)
elseif #unobtainable > 0 then
-- app.PrintDebug("+unobtainable",#unobtainable)
app.ArrayAppend(character, unobtainable)
end
end
if #character > 0 then
local listing = {}
local secretListings = {}
local maximum = settings:GetTooltipSetting("Locations");
local count = 0;
app.Sort(character, app.SortDefaults.Strings);
for _,text in ipairs(character) do
-- since the strings are sorted, we only need to add ones that are not equal to the previously-added one
-- instead of checking all existing strings
if issecretvalue(text) then
count = count + 1
if count <= maximum then
secretListings[#secretListings + 1] = text
end
elseif listing[#listing] ~= text then
count = count + 1;
if count <= maximum then
listing[#listing + 1] = text
-- app.PrintDebug("add source",text)
end
-- else app.PrintDebug("exclude source by last match",text)
end
end
if count > maximum then
listing[#listing + 1] = (L.AND_OTHER_SOURCES):format(count - maximum)
end
if #listing > 0 then
local wrap = settings:GetTooltipSetting("SourceLocations:Wrapping");
local working
for _,text in ipairs(listing) do
for source,replacement in pairs(abbrevs) do
text = text:gsub(source, replacement);
end
if not working and IsRetrieving(text) then working = true; end
local left, right = DESCRIPTION_SEPARATOR:split(text);
tooltipInfo[#tooltipInfo + 1] = { left = left, right = right, wrap = wrap }
end
tooltipInfo.hasSourceLocations = true;
return working
end
if #secretListings > 0 then
local wrap = settings:GetTooltipSetting("SourceLocations:Wrapping");
for i=1,#secretListings do
tooltipInfo[#tooltipInfo + 1] = { left = secretListings[i], wrap = wrap }
end
end
end
end
app.AddEventHandler("OnLoad", function()
local SourceShowKeys = app.CloneDictionary(app.ThingKeys, {
-- Specific keys which we don't want to list Sources but are considered Things
npcID = false,
creatureID = false,
encounterID = false,
explorationID = false,
})
app.Settings.CreateInformationType("SourceLocations", {
priority = 2.7,
text = "Source Locations",
HideCheckBox = true,
Process = function(t, reference, tooltipInfo)
local key = reference.key
local id = key == "itemID" and reference.modItemID or reference[key]
if key and id and SourceShowKeys[key] then
if tooltipInfo.hasSourceLocations then return end
if AddSourceLinesForTooltip(tooltipInfo, key, id) then
reference.working = true
end
end
end
})
end)
local unpack = unpack
local function GetSearchResults(method, paramA, paramB, options)
-- app.PrintDebug("GetSearchResults",method,paramA,paramB)
if not method then
print("GetSearchResults: Invalid method: nil");
return nil, true;
end
if not paramA then
print("GetSearchResults: Invalid paramA: nil");
return nil, true;
end
-- If we are searching for only one parameter, it is a raw link.
local rawlink;
if paramB then paramB = tonumber(paramB);
else rawlink = paramA; end
local RecursiveCharacterRequirementsFilter, RecursiveGroupRequirementsFilter
= app.RecursiveCharacterRequirementsFilter, app.RecursiveGroupRequirementsFilter
-- Call to the method to search the database.
local group, a, b
if options and options.AppendSearchParams then
group, a, b = method(paramA, paramB, unpack(options.AppendSearchParams))
else
group, a, b = method(paramA, paramB)
end
-- app.PrintDebug("GetSearchResults:method",group and #group,a,b,paramA,paramB)
if group then
if a then paramA = a; end
if b then paramB = b; end
if paramA == "modItemID" then paramA = "itemID" end
-- Move all post processing here?
if #group > 0 then
-- For Creatures, Objects and Encounters that are inside of an instance, we only want the data relevant for the instance + difficulty.
if paramA == "npcID" or paramA == "creatureID" or paramA == "encounterID" or paramA == "objectID" then
local subgroup = {};
for _,j in ipairs(group) do
if not j.ShouldExcludeFromTooltip then
tinsert(subgroup, j);
end
end
group = subgroup;
elseif paramA == "azeriteessenceID" then
local regroup = {};
local rank = options and options.Rank
if app.MODE_ACCOUNT then
for i,j in ipairs(group) do
if j.rank == rank and app.RecursiveUnobtainableFilter(j) then
if j.mapID or j.parent == nil or j.parent.parent == nil then
tinsert(regroup, setmetatable({["g"] = {}}, { __index = j }));
else
tinsert(regroup, j);
end
end
end
else
for i,j in ipairs(group) do
if j.rank == rank and RecursiveCharacterRequirementsFilter(j) and app.RecursiveUnobtainableFilter(j) and RecursiveGroupRequirementsFilter(j) then
if j.mapID or j.parent == nil or j.parent.parent == nil then
tinsert(regroup, setmetatable({["g"] = {}}, { __index = j }));
else
tinsert(regroup, j);
end
end
end
end
group = regroup;
elseif paramA == "titleID" or paramA == "followerID" then
-- Don't do anything
local regroup = {};
if app.MODE_ACCOUNT then
for i,j in ipairs(group) do
if app.RecursiveUnobtainableFilter(j) then
tinsert(regroup, setmetatable({["g"] = {}}, { __index = j }));
end
end
else
for i,j in ipairs(group) do
if RecursiveCharacterRequirementsFilter(j) and app.RecursiveUnobtainableFilter(j) and RecursiveGroupRequirementsFilter(j) then
tinsert(regroup, setmetatable({["g"] = {}}, { __index = j }));
end
end
end
group = regroup;
end
end
else
group = {};
end
-- Determine if this is a cache for an item
if rawlink and not paramB then
local itemString = app.Modules.Item.CleanLink(rawlink)
if itemString:match("item") then
-- app.PrintDebug("Rawlink SourceID",sourceID,rawlink)
local _, itemID, enchantId, gemId1, gemId2, gemId3, gemId4, suffixId, uniqueId, linkLevel, specializationID, upgradeId, linkModID, numBonusIds, bonusID1 = (":"):split(itemString);
if itemID then
itemID = tonumber(itemID);
local modID = tonumber(linkModID) or 0;
if modID == 0 then modID = nil; end
local bonusID = (tonumber(numBonusIds) or 0) > 0 and tonumber(bonusID1) or 3524;
if bonusID == 3524 then bonusID = nil; end
local sourceID = app.GetSourceID(rawlink);
if sourceID then
paramA = "sourceID"
paramB = sourceID
-- app.PrintDebug("use sourceID params",paramA,paramB)
else
paramA = "itemID";
paramB = GetGroupItemIDWithModID(nil, itemID, modID, bonusID) or itemID;
-- app.PrintDebug("use itemID params",paramA,paramB)
end
end
else
local kind, id = (":"):split(rawlink);
kind = kind:lower();
if id then id = tonumber(id); end
if kind == "itemid" then
paramA = "itemID";
paramB = id;
elseif kind == "questid" then
paramA = "questID";
paramB = id;
elseif kind == "creatureid" or kind == "npcid" then
paramA = "npcID";
paramB = id;
elseif kind == "achievementid" then
paramA = "achievementID";
paramB = id;
end
end
end
-- Create clones of the search results
if not group.g then
-- Clone all the non-ignored groups so that things don't get modified in the Source
-- app.PrintDebug("Cloning Roots for",paramA,paramB,"#group",group and #group);
local cloned = {};
for _,o in ipairs(group) do
-- app.PrintDebug("Clone:",app:SearchLink(o),GetRelativeValue(o, "sourceIgnored"),app.GetRelativeRawWithField(o, "sourceIgnored"),GenerateSourcePath(o, 1))
if not GetRelativeValue(o, "sourceIgnored") then
cloned[#cloned + 1] = CreateObject(o)
end
end
-- replace the Source references with the cloned references
group = cloned;
local clearSourceParent = #group > 1;
-- Find or Create the root group for the search results, and capture the results which need to be nested instead
local root, filtered
local nested = {};
-- app.PrintDebug("Find Root for",paramA,paramB,"#group",group and #group);
-- check for Item groups in a special way to account for extra ID's
if paramA == "itemID" then
local refinedMatches = app.GroupBestMatchingItems(group, paramB);
if refinedMatches then
-- move from depth 3 to depth 1 to find the set of items which best matches for the root
for depth=3,1,-1 do
if refinedMatches[depth] then
-- app.PrintDebug("refined",depth,#refinedMatches[depth])
if not root then
for _,o in ipairs(refinedMatches[depth]) do
-- object meets filter criteria and is exactly what is being searched
if RecursiveCharacterRequirementsFilter(o) then
-- app.PrintDebug("filtered root");
if root then
if filtered then
-- app.PrintDebug("merge root",app:SearchLink(o));
-- app.PrintTable(o)
MergeProperties(root, o, filtered);
-- other root content will be nested after
MergeObjects(nested, o.g);
else
local otherRoot = root;
-- app.PrintDebug("replace root",app:SearchLink(otherRoot));
root = o;
MergeProperties(root, otherRoot);
-- previous root content will be nested after
MergeObjects(nested, otherRoot.g);
end
else
root = o;
-- app.PrintDebug("first root",app:SearchLink(o));
end
filtered = true
else
-- app.PrintDebug("unfiltered root",app:SearchLink(o),o.modItemID,paramB);
if root then MergeProperties(root, o, true);
else root = o; end
end
end
else
for _,o in ipairs(refinedMatches[depth]) do
-- Not accurate matched enough to be the root, so it will be nested
-- app.PrintDebug("nested",app:SearchLink(o))
nested[#nested + 1] = o
end
end
end
end
end
else
for _,o in ipairs(group) do
-- If the obj "is" the root obj
-- app.PrintDebug(o.key,o[o.key],o.modItemID,"=parent>",o.parent and o.parent.key,o.parent and o.parent.key and o.parent[o.parent.key],o.parent and o.parent.text);
if GroupMatchesParams(o, paramA, paramB) then
-- object meets filter criteria and is exactly what is being searched
if RecursiveCharacterRequirementsFilter(o) then
-- app.PrintDebug("filtered root");
if root then
if filtered then
-- app.PrintDebug("merge root",o.key,o[o.key]);
-- app.PrintTable(o)
MergeProperties(root, o, filtered);
-- other root content will be nested after
MergeObjects(nested, o.g);
else
local otherRoot = root;
-- app.PrintDebug("replace root",otherRoot.key,otherRoot[otherRoot.key]);
-- app.PrintTable(o)
root = o;
MergeProperties(root, otherRoot);
-- previous root content will be nested after
MergeObjects(nested, otherRoot.g);
end
else
-- app.PrintDebug("first root",o.key,o[o.key]);
-- app.PrintTable(o)
root = o;
end
filtered = true
else
-- app.PrintDebug("unfiltered root",o.key,o[o.key],o.modItemID,paramB);
if root then MergeProperties(root, o, true);
else root = o; end
end
else
-- Not the root, so it will be nested
-- app.PrintDebug("nested")
nested[#nested + 1] = o
end
end
end
if not root then
-- app.PrintDebug("Create New Root",paramA,paramB)
if paramA == "criteriaID" then
local critID, achID = (":"):split(paramB)
root = CreateObject({ [paramA] = tonumber(critID), achievementID = tonumber(achID) })
else
root = CreateObject({ [paramA] = paramB })
end
root.missing = true
end
-- If rawLink exists, import it into the root
if rawlink then app.ImportRawLink(root, rawlink); end
-- Ensure the param values are consistent with the new root object values (basically only affects creatureID)
paramA, paramB = root.key, root[root.key];
-- Special Case for itemID, need to use the modItemID for accuracy in item matching
if root.itemID then
if paramA ~= "sourceID" then
paramA = "itemID"
paramB = root.modItemID or paramB
end
-- if our item root has a bonusID, then we will rely on upgrade module to provide any upgrade
-- raw groups with 'up' will never be sourced with a bonusID
local bonusID = root.bonusID
if bonusID ~= 3524 and bonusID or 0 > 0 then
root.up = nil
end
end
-- app.PrintDebug("Root",root.key,root[root.key],root.modItemID,root.up,root._up);
-- app.PrintTable(root)
-- app.PrintDebug("Root Collect",root.collectible,root.collected,root.collectibleAsCost,root.hasUpgrade);
-- app.PrintDebug("params",paramA,paramB);
-- app.PrintDebug(#nested,"Nested total");
if #nested > 0 then
-- Nest the objects by matching filter priority if it's not a currency
if paramA ~= "currencyID" then
PriorityNestObjects(root, nested, nil, RecursiveCharacterRequirementsFilter, RecursiveGroupRequirementsFilter)
else
-- do roughly the same logic for currency, but will not add the skipped objects afterwards
local added = {};
for i,o in ipairs(nested) do
-- If the obj meets the recursive group filter
if RecursiveCharacterRequirementsFilter(o) then
-- Merge the obj into the merged results
-- app.PrintDebug("Merge object",o.key,o[o.key])
added[#added + 1] = o
end
end
-- Nest the added objects
NestObjects(root, added)
end
end
-- if not root.key then
-- app.PrintDebug("UNKNOWN ROOT GROUP",paramA,paramB)
-- app.PrintTable(root)
-- end
-- Single group which matches the root, then collapse it
-- This could only happen if a Thing is literally listed underneath itself...
if root.g and #root.g == 1 then
local o = root.g[1];
-- if not o.key then
-- app.PrintDebug("UNKNOWN OBJECT GROUP",paramA,paramB)
-- app.PrintTable(o)
-- end
if o.key then
local okey, rootkey = o.key, root.key
-- print("Check Single",root.key,root.key and root[root.key],o.key and root[o.key],o.key,o.key and o[o.key],root.key and o[root.key])
-- Heroic Tusks of Mannoroth triggers this logic
if (root[okey] == o[okey]) or (root[rootkey] == o[rootkey]) then
-- print("Single group")
root.g = nil;
MergeProperties(root, o, true);
end
end
end
-- Replace as the group
group = root;
-- Ensure some specific relative values are captured in the base group
-- can make this a loop if there ends up being more needed...
group.difficultyID = GetRelativeValue(group, "difficultyID");
-- Ensure no weird parent references attached to the base search result if there were multiple search results
group.parent = nil;
if clearSourceParent then
group.sourceParent = nil;
end
-- app.PrintDebug(group.g and #group.g,"Merge total");
-- app.PrintDebug("Final Group",group.key,group[group.key],group.collectible,group.collected,app:SearchLink(group.sourceParent));
-- app.PrintDebug("Group Type",group.__type)
-- Special cases
-- This was added in https://github.com/ATTWoWAddon/AllTheThings/commit/97dfc7dd9d228f149635e7fcbccd1c22549316a4
-- in Dec 2020. I'm trying to figure out why other than forcibly reducing some Achievement tooltip size...
-- Going to remove this and see if any complaints since showing more accurate Achievement information than Blizz
-- default tooltip is probably a nicety - Runaway 2025-10-18
-- Don't show nested criteria of achievements (unless loading popout/row content)
-- if group.g and group.key == "achievementID" and app.GetSkipLevel() < 2 then
-- local noCrits = {};
-- -- print("achieve group",#group.g)
-- for i=1,#group.g do
-- if group.g[i].key ~= "criteriaID" then
-- tinsert(noCrits, group.g[i]);
-- end
-- end
-- group.g = noCrits;
-- -- print("achieve nocrits",#group.g)
-- end
-- Fill the search result but not if the search itself was skipped (Mark of Honor) or indicated to skip
if not options or not options.SkipFill then
-- Fill up the group
app.FillGroups(group)
end
-- Only need to build groups from the top level
app.AssignChildren(group);
-- delete sub-groups if there are none
elseif #group.g == 0 then
group.g = nil;
end
app.TopLevelUpdateGroup(group, true);
group.isBaseSearchResult = true;
return group, group.working
end
app.GetCachedSearchResults = function(method, paramA, paramB, options)
-- app.print("GCSR",paramA,paramB,options and options.IgnoreCache)
if options then
if options.IgnoreCache then
return GetSearchResults(method, paramA, paramB, options)
end
-- add a 10sec cache window to lookups
-- probably long enough that any repeated use is smoother, but short enough that user actions would typically result in fresh lookups
if options.ShortCache then
local time = math_floor(GetTime() / 10)
return app.GetCachedData((paramB and paramA..":"..paramB or paramA)..time, GetSearchResults, method, paramA, paramB, options);
end
end
return app.GetCachedData(paramB and paramA..":"..paramB or paramA, GetSearchResults, method, paramA, paramB, options);
end
end -- Search results Lib
(function()
-- Keys for groups which are in-game 'Things'
app.ThingKeys = {
-- filterID = true,
flightpathID = true,
-- professionID = true,
-- categoryID = true,
-- mapID = true,
conduitID = true,
currencyID = true,
itemID = true,
toyID = true,
sourceID = true,
speciesID = true,
recipeID = true,
runeforgepowerID = true,
spellID = true,
missionID = true,
mountID = true,
mountmodID = true,
illusionID = true,
questID = true,
objectID = true,
artifactID = true,
azeriteessenceID = true,
followerID = true,
factionID = true,
titleID = true,
campsiteID = true,
decorID = true,
firstcraftID = true,
garrisonbuildingID = true,
professionnodeID = true,
achievementID = true, -- special handling
criteriaID = true, -- special handling
-- 1 - Specific keys which we don't want to list Contains data on row reference tooltips but are considered Things
npcID = 1,
creatureID = 1,
encounterID = 1,
explorationID = 1,
pvprankID = 1,
};
local SpecificSources = {
headerID = {
[app.HeaderConstants.COMMON_BOSS_DROPS] = true,
[app.HeaderConstants.COMMON_VENDOR_ITEMS] = true,
},
}
if rawget(app.HeaderConstants, "DROPS") then
SpecificSources.headerID[app.HeaderConstants.DROPS] = true
end
local KeepSourced = {
criteriaID = true
}
local SourceSearcher = app.SourceSearcher
local function GetThingSources(field, value)
if field == "achievementID" then
return SearchForField(field, value)
end
if field == "itemID" then
-- allow extra return val (indicates directSources)
return SourceSearcher.itemID(field, value)
end
-- ignore extra return vals
local results = app.SearchForLink(field..":"..value)
return results
end
-- TODO: probably have parser generate CraftedItemDB for simpler use
local function GetCraftingOutputRecipes(thing)
local recipeIDs
local itemID = thing.itemID
for reagent,recipes in pairs(app.ReagentsDB) do
for recipeID,info in pairs(recipes) do
if info[1] == itemID then
if recipeIDs then recipeIDs[#recipeIDs + 1] = recipeID
else recipeIDs = { recipeID } end
end
end
end
return recipeIDs
end
-- Builds a 'Source' group from the parent of the group (or other listings of this group) and lists it under the group itself for
local function BuildSourceParent(group)
-- only show sources for Things or specific of other types
if not group or not group.key then return; end
local groupKey, thingKeys = group.key, app.ThingKeys;
local thingCheck = thingKeys[groupKey];
local specificSource = SpecificSources[groupKey]
if specificSource then
specificSource = specificSource[group[groupKey]];
end
-- group with some Source-able data can be treated as specific Source
if not specificSource and (
group.npcID or group.crs or group.providers
) then
specificSource = true;
end
if not thingCheck and not specificSource then return; end
-- pull all listings of this 'Thing'
local keyValue = group[groupKey];
local isDirectSources
local things = specificSource and { group }
if not things then
things, isDirectSources = GetThingSources(groupKey, keyValue)
end
-- app.PrintDebug("BuildSourceParent",group.hash,thingCheck,specificSource,keyValue,#things,isDirectSources)
-- if app.Debugging then
-- local sourceGroup = app.CreateRawText("DEBUG THINGS", {
-- ["OnUpdate"] = app.AlwaysShowUpdate,
-- ["skipFill"] = true,
-- ["g"] = {},
-- })
-- NestObjects(sourceGroup, things, true)
-- NestObject(group, sourceGroup, nil, 1)
-- end
if things then
local groupHash = group.hash;
local parents = {};
local isAchievement = groupKey == "achievementID";
local parentKey, parent;
-- collect all possible parent groups for all instances of this Thing
for _,thing in ipairs(things) do
if isDirectSources then
parents[#parents + 1] = CreateObject(thing)
elseif isAchievement or GroupMatchesParams(thing, groupKey, keyValue) then
---@class ATTTempParentObject
---@field key string