-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathDataFunctions.lua
More file actions
586 lines (510 loc) * 18.2 KB
/
DataFunctions.lua
File metadata and controls
586 lines (510 loc) * 18.2 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
--
-- Merchant Plus
-- A Modern Scrollable UI for Merchants
--
-- Copyright 2023 - 2024 SimGuy
--
-- Use of this source code is governed by an MIT-style
-- license that can be found in the LICENSE file or at
-- https://opensource.org/licenses/MIT.
--
local _, Shared = ...
-- Push us into shared object
local Data = {}
Shared.Data = Data
-- Init an empty trace function to replace later
local trace = function() end
-- From Locales/Locales.lua
-- Not used yet
--local L = Shared.Locale
-- List of additional functions to call to populate data to be filled later
Data.Functions = {}
-- This is a list of Collectable states to be filled in later
Data.CollectableState = {}
-- Item Categories: Find a localized string that will help identify some special items
--
-- The idea here is to not have to hardcode known localized strings so we can match non-English
-- clients without having to test in every language or get help from translators
Data.ItemCategoriesQueried = 0
Data.ItemCategoriesReturned = 0
Data.ItemCategories = {
DrakewatcherManuscript = 196970,
AirshipSchematic = 235691,
}
-- This is an enum of things that a collectable can be
Data.CollectableType = {
None = 0,
Toy = 1,
Pet = 2,
Mount = 3,
Decor = 4,
Recipe = 5,
Heirloom = 6,
Transmog = 7,
Ensemble = 8,
Special = 9,
}
-- Sync updated Merchant information
function Data:UpdateMerchant()
SetMerchantFilter(LE_LOOT_FILTER_ALL)
local count = GetMerchantNumItems()
local MerchantItems = {}
trace("called: UpdateMerchant", count, #Data.Functions)
for i = 1, count do
local item = Data:GetMerchantItemInfo(i)
for _, func in ipairs(Data.Functions) do
MergeTable(item, func(i, item.link, item))
end
MerchantItems[i] = item
end
return MerchantItems
end
-- Fetch number of merchant items available
function Data:GetMerchantCount()
SetMerchantFilter(LE_LOOT_FILTER_ALL)
return GetMerchantNumItems()
end
-- Fetch the data for a single item by Merchant index
function Data:GetMerchantItemInfo(index)
local item = C_MerchantFrame.GetItemInfo(index)
item.quantity = item.stackCount
item.extendedCost = item.hasExtendedCost
item.itemID = GetMerchantItemID(index)
item.link = GetMerchantItemLink(index)
item.itemKey = { itemID = item.itemID }
item.index = index
return item
end
-- Fetch extended item data for a single item by item link
function Data:GetItemInfo(link)
local item = {}
local _
if link then
_, _, item.quality, item.level, item.minLevel, item.itemType, item.itemSubType,
item.stackCount, item.equipLoc, _, item.sellPrice, item.classID, item.subclassID,
item.bindType, item.expacID, item.setID, item.isCraftingReagent,
item.itemDescription = C_Item.GetItemInfo(link)
end
return item
end
-- Fetch tooltip item data for a single item by Merchant index
function Data:GetMerchantItemTooltip()
-- index ends up in self due to the way this is called
local index = self
local item = {}
item.tooltip = C_TooltipInfo.GetMerchantItem(index)
return item
end
-- Finish gathering tooltip data for ItemCategory entries after preload is done
function Data:FinishItemCategories()
Data.ItemCategoriesReturned = Data.ItemCategoriesReturned + 1
if Data.ItemCategoriesReturned == Data.ItemCategoriesQueried then
for name, id in pairs(Data.ItemCategories) do
local tooltip = C_TooltipInfo.GetItemByID(id)
Data.ItemCategories[name] = Data:GetItemCategory(tooltip)
end
end
end
-- Find the Item Category string in the tooltip
function Data:GetItemCategory(tooltip)
for _, line in ipairs(tooltip.lines) do
-- This is hacky: look for the first line with light blue text
--
-- Usually this will be something like "Crafting Reagent" or another
-- type of description indicating a special category of item
if string.find(line.leftText, "|cFF66BBFF") then
return string.sub(line.leftText, 11)
end
end
return nil
end
-- Find the Item Known state of the tooltip
function Data:GetItemKnown(tooltip)
for _, line in ipairs(tooltip.lines) do
if line.type == Enum.TooltipDataLineType.UsageRequirement and line.leftText == ITEM_SPELL_KNOWN then
return true
end
end
return false
end
-- Find the Item Profession state of the tooltip
function Data:GetItemProfession(tooltip)
-- Initialize the list of professions once, they won't change while we're logged in!
if not Data.professionInfo then
Data.professionInfo = {}
for _, id in pairs(Enum.Profession) do
local skillLineID = C_TradeSkillUI.GetProfessionSkillLineID(id)
local professionInfo = C_TradeSkillUI.GetProfessionInfoBySkillLineID(skillLineID)
if professionInfo.professionName ~= "" then
table.insert(Data.professionInfo, professionInfo.professionName)
end
end
end
local profText = nil
for _, line in ipairs(tooltip.lines) do
-- Search for a UsageRequirement line, if there is one
-- If there is more than one, we want the last one
if line.type == Enum.TooltipDataLineType.UsageRequirement then
profText = line.leftText
end
end
if not profText then
return false
end
-- Search for a profession name in the UsageRequirement line
for _, professionName in ipairs(Data.professionInfo) do
if string.find(profText, professionName) then
return professionName
end
end
return false
end
-- Check every source for an appearance to decide if the player has learned the appearance
function Data:AppearanceKnownFromAnySource(sourceid)
if C_TransmogCollection.PlayerHasTransmogItemModifiedAppearance(sourceid) then
trace("logic: AppearanceKnownFromAnySource: marked known based on sourceid", sourceid)
return true
else
trace("logic: AppearanceKnownFromAnySource: not known based on sourceid", sourceid)
local sourceinfo = C_TransmogCollection.GetAppearanceSourceInfo(sourceid)
local altsources = C_TransmogCollection.GetAllAppearanceSources(sourceinfo.itemAppearanceID)
if altsources then
for _, altsourceid in ipairs(altsources) do
trace("logic: AppearanceKnownFromAnySource: checking additional sourceid", altsourceid)
if C_TransmogCollection.PlayerHasTransmogItemModifiedAppearance(altsourceid) then
trace("logic: AppearanceKnownFromAnySource: marked known from additional sourceid", altsourceid)
return true
else
trace("logic: AppearanceKnownFromAnySource: not known from additional sourceid", altsourceid)
end
end
end
end
trace("logic: AppearanceKnownFromAnySource: not known from any sourceid")
return false
end
-- Check if this item is a toy and return information about it
--
-- If we have it, we have it, if the toy is usable, we can collect it, otherwise
-- we probably can't collect it yet.
--
function Data:GetToyInfo(itemdata)
local item = {}
local itemid = itemdata.itemID
local toyid = C_ToyBox.GetToyInfo(itemid)
if toyid then
item.collectableType = Data.CollectableType.Toy
if PlayerHasToy(toyid) then
item.collectable = Data.CollectableState.Known
elseif C_ToyBox.IsToyUsable(toyid) then
item.collectable = Data.CollectableState.Collectable
else
item.collectable = Data.CollectableState.Restricted
end
return item
else
return false
end
end
-- Check if this item is a pet and return information about it
--
-- If the pet collected at all, we know it, if it's usable and we don't know it
-- we can collect it, othewise we probably just can't collect it yet
--
-- We're not storing enough data here when we have fewer than max to tell if we
-- can collect more on this character
--
-- It's possible we could find a merchant pet that isn't collectable by this
-- character (class or faction locked), but I didn't find any examples to test
--
function Data:GetPetInfo(itemdata)
local item = {}
local itemid = itemdata.itemID
local petinfo = { C_PetJournal.GetPetInfoByItemID(itemid) }
local speciesID = petinfo[13] -- This field could move; look for speciesID index
if speciesID then
item.collectableType = Data.CollectableType.Pet
local count, max = C_PetJournal.GetNumCollectedInfo(speciesID)
-- This is special metadata just for pets, because we might want to use this
-- in our display, sorting, or filtering functionality since we're looking it
-- up anyway
item.collectedPets = { count = count, max = max }
if count == 0 and itemdata.isUsable then
item.collectable = Data.CollectableState.Collectable
elseif count > 0 then
item.collectable = Data.CollectableState.Known
else
item.collectable = Data.CollectableState.Restricted
end
return item
else
return false
end
end
-- Check if this item is a mount and return information about it
--
-- If collected, then we know it, if it's usable we can collect it, otherwise
-- we probably can't collect it yet
--
-- It's possible we could find a merchant mount that isn't collectable by this
-- character (class or faction locked), but I didn't find any examples to test
--
function Data:GetMountInfo(itemdata)
local item = {}
local itemid = itemdata.itemID
local mountid = C_MountJournal.GetMountFromItem(itemid)
if mountid then
item.collectableType = Data.CollectableType.Mount
local mountinfo = { C_MountJournal.GetMountInfoByID(mountid) }
-- This field could move; look for isCollected index
if mountinfo[11] then
item.collectable = Data.CollectableState.Known
elseif itemdata.isUsable then
item.collectable = Data.CollectableState.Collectable
else
item.collectable = Data.CollectableState.Restricted
end
return item
else
return false
end
end
-- Check if this item is housing decor and return information about it
--
-- Check if the player has any of these anywhere, if not, then they are uncollected
-- Housing items are always collectable, so we're counting having even one copy as "known"
--
function Data:GetDecorInfo(link, itemdata)
local item = {}
local itemid = itemdata.itemID
if C_Item.IsDecorItem(itemid) then
local decoritem = C_HousingCatalog.GetCatalogEntryInfoByItem(link, true)
-- This really shouldn't happen, but there are some merchant items that
-- claim to be decor and somehow have no catalog info associated with them
-- We'll just have to pretend not to notice for now
if decoritem then
item.collectableType = Data.CollectableType.Decor
item.collectedDecor = { stored = decoritem.quantity + decoritem.remainingRedeemable,
placed = decoritem.numPlaced }
if decoritem.quantity + decoritem.numPlaced + decoritem.remainingRedeemable == 0 then
item.collectable = Data.CollectableState.Collectable
else
item.collectable = Data.CollectableState.Known
end
return item
else
return false
end
else
return false
end
end
-- Check if this item is a recipe and return information about it
--
-- We are looking for a Requires skill line and spell ID 483 (Learning)
-- This should hopefully rule out non-profession things that can be learned
-- without missing legitimate recipes
--
-- If this item is not known or usable, it is restricted (unsatified conditions)
-- If it's not for our professions, it's unavailable
--
function Data:GetRecipeInfo(link, itemdata)
local item = {}
local _, spellID = C_Item.GetItemSpell(link)
if spellID == 483 then
local itemProf = Data:GetItemProfession(itemdata.tooltip)
local profs = { GetProfessions() }
local profMatch = false
for _, prof in pairs(profs) do
local profName = GetProfessionInfo(prof)
if itemProf == profName then
profMatch = true
break
end
end
item.collectableType = Data.CollectableType.Recipe
if profMatch then
if Data:GetItemKnown(itemdata.tooltip) then
item.collectable = Data.CollectableState.Known
elseif itemdata.isUsable then
item.collectable = Data.CollectableState.Collectable
else
item.collectable = Data.CollectableState.Restricted
end
else
item.collectable = Data.CollectableState.Unavailable
end
return item
else
return false
end
end
-- Check if this item is an heirloom and return information about it
--
-- If this is an heirloom, the whole item is collectable unless it's known
--
function Data:GetHeirloomInfo(itemdata)
local item = {}
local itemid = itemdata.itemID
if C_Heirloom.GetHeirloomInfo(itemid) then
item.collectableType = Data.CollectableType.Heirloom
if C_Heirloom.PlayerHasHeirloom(itemid) then
item.collectable = Data.CollectableState.Known
-- Heirlooms that aren't known are always collectable
else
item.collectable = Data.CollectableState.Collectable
end
return item
else
return false
end
end
-- Check if this item has a transmog appearance and return information about it
--
-- Try to find an appearance for this item by checking sourceid (aka ItemModifiedAppearanceId)
-- of the item link, which should give us the version of the item actually appearing on the
-- merchant. If we can't get a source from that, we'll try the itemID directly, which may work
-- for some older items that have only one appearance type.
--
-- To make sure that we count an item as collected if we have an alternate source, we'll scan
-- those sources if the first one comes back negative.
--
-- Starting with Warbands as long as the item is collectable it should be available to collect
-- even if the armor type doesn't match the player's class.
--
function Data:GetTransmogInfo(link, itemdata)
local item = {}
local itemid = itemdata.itemID
if C_Item.IsDressableItemByID(link) then
local _, sourceid = C_TransmogCollection.GetItemInfo(link)
-- Fall back if the item link doesn't give us anything
if not sourceid then
trace("logic: GetTransmogInfo: fell back to item ID on appearance source", itemid)
_, sourceid = C_TransmogCollection.GetItemInfo(itemid)
end
-- If we fail this check the item likely isn't a single piece of equipment
if sourceid then
item.collectableType = Data.CollectableType.Transmog
if Data:AppearanceKnownFromAnySource(sourceid) then
item.collectable = Data.CollectableState.Known
else
local _, collectable = C_TransmogCollection.PlayerCanCollectSource(sourceid)
if collectable then
item.collectable = Data.CollectableState.Collectable
else
item.collectable = Data.CollectableState.Unavailable
end
end
return item
else
return false
end
else
return false
end
end
-- Check if this item is an ensemble and return information about it
--
-- Here we check to see if the item is actually an ensemble instead of a regular
-- piece of equipment. We check to see if the player owns every appearance in this
-- set and if they are missing even one, we mark it as available to collect, provided
-- it's not otherwise restricted.
--
function Data:GetEnsembleInfo(link, itemdata)
local item = {}
if C_Item.IsDressableItemByID(link) then
local setid = C_Item.GetItemLearnTransmogSet(link)
if setid then
item.collectableType = Data.CollectableType.Ensemble
local setTotal = 0
local setKnown = 0
local setSources = C_Transmog.GetAllSetAppearancesByID(setid)
for _, sourceinfo in ipairs(setSources) do
setTotal = setTotal + 1
if Data:AppearanceKnownFromAnySource(sourceinfo.itemModifiedAppearanceID) then
setKnown = setKnown + 1
end
end
item.collectedEnsemble = { known = setKnown, total = setTotal }
if setKnown == setTotal then
item.collectable = Data.CollectableState.Known
elseif itemdata.isUsable then
item.collectable = Data.CollectableState.Collectable
else
item.collectable = Data.CollectableState.Restricted
end
return item
else
return false
end
else
return false
end
end
-- Check if this item is an special item type and return information about it
--
-- These are usually customization parts
--
-- With some tooltip magic we tried to guess if this item has a special category.
-- Hopefully doing it this way is localization agnostic.
--
-- If this is on the vendor and usable, it's collectable
--
-- We shouldn't ever see these kinds of items on the merchant if they can't be
-- collected by this character or are known, but we'll try to check just
-- in case.
--
-- There are probably a few more types of items that work like this, if you find
-- this comment, feel free to submit suggestions.
--
function Data:GetSpecialItemInfo(itemdata)
local item = {}
local itemcategory = Data:GetItemCategory(itemdata.tooltip)
if itemcategory == Data.ItemCategories.DrakewatcherManuscript or
itemcategory == Data.ItemCategories.AirshipSchematic then
if Data:GetItemKnown(itemdata.tooltip) then
item.collectable = Data.CollectableState.Known
elseif itemdata.isUsable then
item.collectable = Data.CollectableState.Collectable
else
item.collectable = Data.CollectableState.Restricted
end
return item
else
return false
end
end
-- Look at the item and determine if the item is collectable or known
--
-- Blizzard can't be trusted to put items in the right categories, so we're just
-- going to have to test for everything.
--
function Data:GetCollectable(link, itemdata)
local emptyItem = { collectable = Data.CollectableState.Unsupported,
collectabletype = Data.CollectableType.None }
if not link then
local itemid = itemdata.itemID
trace("logic: GetCollectable: item link was nil; may have tried to fetch info too early", itemid)
return emptyItem
end
return Data:GetToyInfo(itemdata)
or Data:GetPetInfo(itemdata)
or Data:GetMountInfo(itemdata)
or Data:GetDecorInfo(link, itemdata)
or Data:GetRecipeInfo(link, itemdata)
or Data:GetHeirloomInfo(itemdata)
or Data:GetTransmogInfo(link, itemdata)
or Data:GetEnsembleInfo(link, itemdata)
or Data:GetSpecialItemInfo(itemdata)
or emptyItem
end
-- Since this code runs before MerchantPlus.lua we need to reset the trace function after init
-- Also initialize the ItemCategories data now
function Data:Init()
trace = Shared.Trace or function() end
for _, id in pairs(Data.ItemCategories) do
Data.ItemCategoriesQueried = Data.ItemCategoriesQueried + 1
local item = Item:CreateFromItemID(id)
item:ContinueOnItemLoad(Data.FinishItemCategories)
end
end