Repository navigation
Expand file tree
/
Copy pathDialect.lua
More file actions
665 lines (596 loc) · 26.4 KB
/
Copy pathDialect.lua
File metadata and controls
665 lines (596 loc) · 26.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
-- WickCore
-- Dialect.lua — one calling convention over two API generations.
--
-- The Forever probe (1.60.1.69893) showed GetItemInfo, GetSpellInfo,
-- GetSpellCooldown, UnitAura and the container globals are nil, replaced by
-- C_Item, C_Spell, C_UnitAuras and C_Container. TBC Anniversary still has the
-- legacy globals. Each function here resolves once at load and returns a
-- table with the same field names on either client.
local ADDON = ...
local Core = LibStub("WickCore-1.0", true)
if not Core or Core._sourceAddon ~= ADDON then return end
local D = {}
Core.Dialect = D
D.paths = {}
local R = Core.Restrict
local pack = Core.pack
local function ns(name) return rawget(_G, name) end
local function fn(space, method)
local t = ns(space)
if type(t) == "table" and type(t[method]) == "function" then return t[method] end
end
local function glob(name)
local v = rawget(_G, name)
if type(v) == "function" then return v end
end
-- Record which path a label took, for /wickcore dialect.
local function chose(label, modern, legacy)
if modern then D.paths[label] = "modern"; return modern, true end
if legacy then D.paths[label] = "legacy"; return legacy, false end
D.paths[label] = "missing"
end
-- ============================================================
-- Items
-- ============================================================
local itemInfo, itemInfoModern = chose("GetItemInfo", fn("C_Item", "GetItemInfo"), glob("GetItemInfo"))
local ITEM_FIELDS = {
"name", "link", "quality", "itemLevel", "minLevel", "itemType", "itemSubType",
"stackCount", "equipLoc", "icon", "sellPrice", "classID", "subclassID",
"bindType", "expansionID", "setID", "isCraftingReagent",
}
-- Returns nil when the item is not cached yet (same as the game does).
function D.GetItemInfo(item)
if not itemInfo or item == nil then return nil end
local r = pack(itemInfo(item))
if r[1] == nil then return nil end
local t = {}
for i, f in ipairs(ITEM_FIELDS) do t[f] = r[i] end
return t
end
local itemInstant = chose("GetItemInfoInstant", fn("C_Item", "GetItemInfoInstant"), glob("GetItemInfoInstant"))
function D.GetItemInfoInstant(item)
if not itemInstant or item == nil then return nil end
local id, itemType, subType, equipLoc, icon, classID, subclassID = itemInstant(item)
if not id then return nil end
return { itemID = id, itemType = itemType, itemSubType = subType, equipLoc = equipLoc,
icon = icon, classID = classID, subclassID = subclassID }
end
-- The name alone, which this client will give for an item it has
-- never sent, where the full GetItemInfo will not. On the older client
-- there is no such shortcut, so the full one answers instead.
local itemNameByID = fn("C_Item", "GetItemNameByID")
function D.GetItemNameByID(id)
if id == nil then return nil end
if itemNameByID then
local ok, n = pcall(itemNameByID, id)
if ok and type(n) == "string" and n ~= "" then return n end
return nil
end
local t = D.GetItemInfo(id)
return t and t.name
end
-- What is coating a weapon: a poison, a stone, an oil. Three clients,
-- three shapes, and no way to tell from here which one answers, so all
-- three are tried and the winner is named in the result. A caller that
-- picks one and finds it missing has no second path, which is how a
-- rogue ended up looking at two bare blades with poison on both.
--
-- Returns nil when nothing is on the weapon, or
-- { msLeft, charges, enchantID, icon, via } when something is. Any
-- field may be nil on a client that will not say.
local tempPaperDoll = fn("C_PaperDollInfo", "GetTemporaryEnchantmentInfo")
local tempItem = fn("C_Item", "GetWeaponEnchantInfo")
local tempClassic = glob("GetWeaponEnchantInfo")
function D.GetTempEnchant(slot)
if slot == nil then return nil end
-- A table per slot, whichever module offers it. In order, because
-- pairs would pick a different one between runs and which call
-- answered is a thing the product reports.
for _, p in ipairs({ { "C_Item", tempItem }, { "C_PaperDollInfo", tempPaperDoll } }) do
local via, f = p[1], p[2]
if f then
local ok, info = pcall(f, slot)
if ok and type(info) == "table" then
-- Some builds wrap the answer in a list.
local e = info.enchants and info.enchants[1] or info
if e.remainingTimeMs or e.timeLeft or e.enchantID or e.charges
or e.chargesRemaining then
return { msLeft = e.remainingTimeMs or e.timeLeft,
charges = e.chargesRemaining or e.charges,
enchantID = e.enchantID,
icon = e.enchantIconID or e.icon,
via = via }
end
end
end
end
-- Nothing answered. The game still writes it on the item, as a
-- line like "Instant Poison (23 min) (60 Charges)", so read that.
-- Last, because parsing a sentence is the worst way to learn
-- something a function will tell you, and first choice when the
-- functions are all present and all silent, which is what this
-- client does.
local function fromTooltip(slot)
local lines
local info = C_TooltipInfo and C_TooltipInfo.GetInventoryItem
if info then
local ok, t = pcall(info, "player", slot)
if ok and type(t) == "table" then lines = t.lines end
end
if not lines then
-- The older way: pour it into a tooltip of our own and read
-- the font strings back off it.
local tt = D._scanTip
if not tt and CreateFrame then
tt = CreateFrame("GameTooltip", "WickCoreScanTip", nil, "GameTooltipTemplate")
if tt and tt.SetOwner then
tt:SetOwner(UIParent, "ANCHOR_NONE")
D._scanTip = tt
end
end
if not (tt and tt.SetInventoryItem) then return nil end
tt:ClearLines()
pcall(tt.SetInventoryItem, tt, "player", slot)
lines = {}
for i = 1, (tt.NumLines and tt:NumLines() or 0) do
local fs = _G["WickCoreScanTipTextLeft" .. i]
local txt = fs and fs.GetText and fs:GetText()
if txt then lines[#lines + 1] = { leftText = txt } end
end
end
if not lines then return nil end
for _, line in ipairs(lines) do
local txt = line.leftText or line
if type(txt) == "string" then
-- The minutes in brackets are what marks the temporary
-- line out from a permanent enchant, which has none.
local name, mins = txt:match("^(.-)%s*%((%d+)%s*[Mm][Ii][Nn]%)")
if name and name ~= "" then
local charges = txt:match("%((%d+)%s*[Cc]harge")
return { msLeft = tonumber(mins) * 60000,
charges = tonumber(charges),
name = name, via = "tooltip" }
end
end
end
return nil
end
-- The classic global: no arguments, and every slot in one flat run
-- of values. Main hand first, then off hand, then ranged.
if tempClassic then
local ok, r = pcall(function() return { tempClassic() } end)
if ok and type(r) == "table" and #r >= 4 then
local at = (slot == 16 and 0) or (slot == 17 and 4) or (slot == 18 and 8)
if at and r[at + 1] then
return { msLeft = r[at + 2], charges = r[at + 3],
enchantID = r[at + 4], via = "GetWeaponEnchantInfo" }
end
if at then return fromTooltip(slot) end
end
end
return fromTooltip(slot)
end
local itemCount = chose("GetItemCount", fn("C_Item", "GetItemCount"), glob("GetItemCount"))
function D.GetItemCount(item, includeBank, includeUses)
if not itemCount or item == nil then return 0 end
return itemCount(item, includeBank, includeUses) or 0
end
local itemIconByID = fn("C_Item", "GetItemIconByID")
local itemIconLegacy = glob("GetItemIcon")
chose("GetItemIcon", itemIconByID, itemIconLegacy)
function D.GetItemIcon(item)
if itemIconByID then return itemIconByID(item) end
if itemIconLegacy then return itemIconLegacy(item) end
local t = D.GetItemInfoInstant(item)
return t and t.icon
end
local itemStats = chose("GetItemStats", fn("C_Item", "GetItemStats"), glob("GetItemStats"))
-- Returns the ITEM_MOD_* -> value map, or nil.
function D.GetItemStats(link)
if not itemStats or not link then return nil end
local ok, t = pcall(itemStats, link)
if ok and type(t) == "table" then return t end
return nil
end
local itemSpell = chose("GetItemSpell", fn("C_Item", "GetItemSpell"), glob("GetItemSpell"))
function D.GetItemSpell(item)
if not itemSpell or item == nil then return nil end
local name, id = itemSpell(item)
if not name then return nil end
return { name = name, spellID = id }
end
local qualityColor = chose("GetItemQualityColor", fn("C_Item", "GetItemQualityColor"), glob("GetItemQualityColor"))
function D.GetItemQualityColor(quality)
if qualityColor then
local r, g, b, hex = qualityColor(quality or 1)
return r, g, b, hex
end
local c = ITEM_QUALITY_COLORS and ITEM_QUALITY_COLORS[quality or 1]
if c then return c.r, c.g, c.b, c.hex end
return 1, 1, 1, "|cffffffff"
end
-- ============================================================
-- Spells
-- ============================================================
local spellInfoModern = fn("C_Spell", "GetSpellInfo")
local spellInfoLegacy = glob("GetSpellInfo")
chose("GetSpellInfo", spellInfoModern, spellInfoLegacy)
function D.GetSpellInfo(spell)
if spell == nil then return nil end
if spellInfoModern then
local t = spellInfoModern(spell)
if not t then return nil end
return { name = t.name, icon = t.iconID, originalIcon = t.originalIconID,
castTime = t.castTime, minRange = t.minRange, maxRange = t.maxRange,
spellID = t.spellID }
elseif spellInfoLegacy then
local name, rank, icon, castTime, minRange, maxRange, spellID, originalIcon = spellInfoLegacy(spell)
if not name then return nil end
return { name = name, rank = rank, icon = icon, originalIcon = originalIcon,
castTime = castTime, minRange = minRange, maxRange = maxRange,
spellID = spellID }
end
end
local spellName = fn("C_Spell", "GetSpellName")
function D.GetSpellName(spell)
if spellName then return spellName(spell) end
local t = D.GetSpellInfo(spell)
return t and t.name
end
local spellTexture = chose("GetSpellTexture", fn("C_Spell", "GetSpellTexture"), glob("GetSpellTexture"))
function D.GetSpellTexture(spell)
if spellTexture then return spellTexture(spell) end
local t = D.GetSpellInfo(spell)
return t and t.icon
end
local cdModern = fn("C_Spell", "GetSpellCooldown")
local cdLegacy = glob("GetSpellCooldown")
chose("GetSpellCooldown", cdModern, cdLegacy)
-- Values may be secret in combat on a secrets client. They are still safe to
-- pass to Cooldown:SetCooldown; use Restrict:IsSecret before doing math.
function D.GetSpellCooldown(spell)
if spell == nil then return nil end
if cdModern then
local ok, t = pcall(cdModern, spell)
if not ok or not t then return nil end
-- isActive stays a plain boolean under combat restrictions
-- while the times go secret, so it is the one part of a
-- cooldown a caller may actually look at mid-fight.
return { start = t.startTime, duration = t.duration, enabled = t.isEnabled,
modRate = t.modRate, active = t.isActive }
elseif cdLegacy then
local ok, start, duration, enabled, modRate = pcall(cdLegacy, spell)
if not ok or start == nil then return nil end
-- No isActive here, but nothing is secret either, so the same
-- answer can be worked out rather than left missing.
local active
if type(duration) == "number" then active = duration > 0 end
return { start = start, duration = duration, enabled = enabled,
modRate = modRate, active = active }
end
end
local spellKnown, spellKnownModern = chose("IsSpellKnown", fn("C_SpellBook", "IsSpellKnown"), glob("IsSpellKnown"))
function D.IsSpellKnown(spellID, isPet)
if not spellKnown then return false end
local second = isPet
if spellKnownModern then
-- C_SpellBook.IsSpellKnown takes a spell bank, not a pet flag; a
-- boolean there throws and the pcall answered false for every
-- pet spell.
local banks = rawget(_G, "Enum") and Enum.SpellBookSpellBank
second = isPet and ((banks and banks.Pet) or 1) or nil
end
local ok, v = pcall(spellKnown, spellID, second)
return ok and v == true
end
-- ============================================================
-- The spellbook
-- ============================================================
--
-- Asking what the player can cast, rather than asking about a spell you
-- already knew the name of. The mage kit needs it to find the teleports
-- and portals a character actually has: Forever is Classic plus its own
-- changes, so a list of cities written here would be a guess.
--
-- The two clients disagree about all of it. Modern has skill lines, a
-- numeric bank enum, and getters that return a table. Legacy has tabs,
-- the bank as the string "spell", and tuples.
local numSkillLines = fn("C_SpellBook", "GetNumSpellBookSkillLines")
local skillLineInfo = fn("C_SpellBook", "GetSpellBookSkillLineInfo")
local bookItemInfoModern = fn("C_SpellBook", "GetSpellBookItemInfo")
local bookItemNameModern = fn("C_SpellBook", "GetSpellBookItemName")
local bookItemTexModern = fn("C_SpellBook", "GetSpellBookItemTexture")
local numSpellTabs = glob("GetNumSpellTabs")
local spellTabInfo = glob("GetSpellTabInfo")
local bookItemNameLegacy = glob("GetSpellBookItemName")
local bookItemTexLegacy = glob("GetSpellBookItemTexture")
local bookItemInfoLegacy = glob("GetSpellBookItemInfo")
chose("SpellBook", numSkillLines and bookItemNameModern,
numSpellTabs and bookItemNameLegacy)
-- The player's own book, never the pet's.
local function playerBank()
local e = rawget(_G, "Enum")
local bank = e and e.SpellBookSpellBank
-- Player is 0 on every build that has the enum; the fallback is for
-- one that has the functions but not the table.
return (bank and bank.Player) or 0
end
local function modernItem(index, bank)
if bookItemInfoModern then
local ok, t = pcall(bookItemInfoModern, index, bank)
if ok and type(t) == "table" and t.name then
return { index = index, name = t.name, rank = t.subName,
icon = t.iconID, spellID = t.spellID,
isPassive = t.isPassive == true }
end
end
local ok, name, rank = pcall(bookItemNameModern, index, bank)
if not ok or not name then return nil end
local icon
if bookItemTexModern then
local okTex, tex = pcall(bookItemTexModern, index, bank)
icon = okTex and tex or nil
end
return { index = index, name = name, rank = rank, icon = icon }
end
local function legacyItem(index)
local ok, name, rank = pcall(bookItemNameLegacy, index, "spell")
if not ok or not name then return nil end
local icon, spellID
if bookItemTexLegacy then
local okTex, tex = pcall(bookItemTexLegacy, index, "spell")
icon = okTex and tex or nil
end
if bookItemInfoLegacy then
-- (skillType, id), and the id is only a spell id for a SPELL.
local okInfo, kind, id = pcall(bookItemInfoLegacy, index, "spell")
if okInfo and kind == "SPELL" then spellID = id end
end
return { index = index, name = name, rank = rank, icon = icon, spellID = spellID }
end
-- Every spell in the player's book, in book order.
--
-- Ranks are left alone rather than folded together: the caller knows
-- whether it wants one entry per rank, and a mage's teleports have no
-- ranks anyway.
function D.SpellBookSpells()
local out = {}
if numSkillLines and bookItemNameModern then
local bank = playerBank()
local okN, lines = pcall(numSkillLines)
if not okN or not lines then return out end
for line = 1, lines do
local info
if skillLineInfo then
local okLine, t = pcall(skillLineInfo, line)
if okLine then info = t end
end
-- A guild or off-spec line is not this character's own
-- casting, so it does not belong in the answer.
if type(info) == "table" and not info.isGuild and not info.shouldHide then
local offset = info.itemIndexOffset or 0
for i = 1, (info.numSpellBookItems or 0) do
local item = modernItem(offset + i, bank)
if item then out[#out + 1] = item end
end
end
end
return out
end
if numSpellTabs and bookItemNameLegacy then
local okN, tabs = pcall(numSpellTabs)
if not okN or not tabs then return out end
for tab = 1, tabs do
local okT, _, _, offset, count = pcall(spellTabInfo, tab)
if okT and count then
for i = 1, count do
local item = legacyItem((offset or 0) + i)
if item then out[#out + 1] = item end
end
end
end
end
return out
end
-- ============================================================
-- Containers
-- ============================================================
local numSlots = chose("GetContainerNumSlots", fn("C_Container", "GetContainerNumSlots"), glob("GetContainerNumSlots"))
function D.GetContainerNumSlots(bag)
if not numSlots then return 0 end
return numSlots(bag) or 0
end
local freeSlots = chose("GetContainerNumFreeSlots", fn("C_Container", "GetContainerNumFreeSlots"), glob("GetContainerNumFreeSlots"))
function D.GetContainerNumFreeSlots(bag)
if not freeSlots then return 0, 0 end
return freeSlots(bag)
end
local ciModern = fn("C_Container", "GetContainerItemInfo")
local ciLegacy = glob("GetContainerItemInfo")
chose("GetContainerItemInfo", ciModern, ciLegacy)
-- Field names follow C_Container's ContainerItemInfo table.
function D.GetContainerItemInfo(bag, slot)
if ciModern then
return ciModern(bag, slot)
elseif ciLegacy then
local icon, count, locked, quality, readable, lootable, link, filtered, noValue, itemID, bound = ciLegacy(bag, slot)
if icon == nil and link == nil then return nil end
return { iconFileID = icon, stackCount = count, isLocked = locked, quality = quality,
isReadable = readable, hasLoot = lootable, hyperlink = link, isFiltered = filtered,
hasNoValue = noValue, itemID = itemID, isBound = bound }
end
end
local ciLink = chose("GetContainerItemLink", fn("C_Container", "GetContainerItemLink"), glob("GetContainerItemLink"))
function D.GetContainerItemLink(bag, slot)
if not ciLink then return nil end
return ciLink(bag, slot)
end
local ciID = chose("GetContainerItemID", fn("C_Container", "GetContainerItemID"), glob("GetContainerItemID"))
function D.GetContainerItemID(bag, slot)
if not ciID then return nil end
return ciID(bag, slot)
end
local useItem = chose("UseContainerItem", fn("C_Container", "UseContainerItem"), glob("UseContainerItem"))
function D.UseContainerItem(bag, slot, target)
if useItem then return useItem(bag, slot, target) end
end
local pickupItem = chose("PickupContainerItem", fn("C_Container", "PickupContainerItem"), glob("PickupContainerItem"))
function D.PickupContainerItem(bag, slot)
if pickupItem then return pickupItem(bag, slot) end
end
-- ============================================================
-- Auras (always guarded)
-- ============================================================
local auraModern = fn("C_UnitAuras", "GetAuraDataByIndex")
local auraLegacy = glob("UnitAura")
chose("UnitAura", auraModern, auraLegacy)
local function legacyAura(unit, index, filter)
local name, icon, count, dispel, duration, expires, source, stealable, showPersonal, spellId = auraLegacy(unit, index, filter)
if not name then return nil end
return { name = name, icon = icon, applications = count, dispelName = dispel,
duration = duration, expirationTime = expires, sourceUnit = source,
isStealable = stealable, nameplateShowPersonal = showPersonal, spellId = spellId }
end
-- Returns the aura table, or nil plus a reason ("restricted" / error text).
function D.GetAura(unit, index, filter)
if auraModern then
return R:Guard(auraModern, unit, index, filter or "HELPFUL")
elseif auraLegacy then
return R:Guard(legacyAura, unit, index, filter or "HELPFUL")
end
return nil, "missing"
end
local auraByName = fn("C_UnitAuras", "GetAuraDataBySpellName")
function D.GetAuraBySpellName(unit, name, filter)
if auraByName then
return R:Guard(auraByName, unit, name, filter or "HELPFUL")
end
for aura in D.IterateAuras(unit, filter) do
if aura.name == name then return aura end
end
return nil
end
-- for aura in D.IterateAuras("player", "HELPFUL") do ... end
-- Yields nothing at all while auras are blocked.
function D.IterateAuras(unit, filter, max)
local i, limit = 0, max or 40
return function()
while i < limit do
i = i + 1
local aura = D.GetAura(unit, i, filter)
if aura == nil then return nil end
return aura
end
end
end
-- ============================================================
-- Quest log
-- ============================================================
local numQuests = chose("GetNumQuestLogEntries", fn("C_QuestLog", "GetNumQuestLogEntries"), glob("GetNumQuestLogEntries"))
function D.GetNumQuestLogEntries()
if not numQuests then return 0, 0 end
return numQuests()
end
local questInfo = fn("C_QuestLog", "GetInfo")
local questTitle = glob("GetQuestLogTitle")
chose("GetQuestLogTitle", questInfo, questTitle)
function D.GetQuestLogEntry(index)
if questInfo then
local t = questInfo(index)
if not t then return nil end
return { title = t.title, level = t.level, isHeader = t.isHeader, isComplete = t.isComplete,
questID = t.questID, index = t.questLogIndex or index }
elseif questTitle then
local title, level, _, isHeader, _, isComplete, _, questID = questTitle(index)
if not title then return nil end
return { title = title, level = level, isHeader = isHeader, isComplete = isComplete,
questID = questID, index = index }
end
end
-- ============================================================
-- Group, money, chat
-- ============================================================
local groupMembers = glob("GetNumGroupMembers")
local raidMembers, partyMembers = glob("GetNumRaidMembers"), glob("GetNumPartyMembers")
chose("GetNumGroupMembers", groupMembers, raidMembers)
function D.GetNumGroupMembers()
if groupMembers then return groupMembers() end
if raidMembers and partyMembers then
local r = raidMembers()
if r > 0 then return r end
local p = partyMembers()
return p > 0 and p + 1 or 0
end
return 0
end
local coinString = chose("GetCoinTextureString", fn("C_CurrencyInfo", "GetCoinTextureString"), glob("GetCoinTextureString"))
function D.CoinString(copper)
copper = tonumber(copper) or 0
if coinString then return coinString(copper) end
local g, s, c = math.floor(copper / 10000), math.floor(copper / 100) % 100, copper % 100
return string.format("%dg %ds %dc", g, s, c)
end
local regPrefix = chose("RegisterAddonMessagePrefix", fn("C_ChatInfo", "RegisterAddonMessagePrefix"), glob("RegisterAddonMessagePrefix"))
function D.RegisterAddonPrefix(prefix)
if regPrefix then return pcall(regPrefix, prefix) end
return false
end
local sendMsg = chose("SendAddonMessage", fn("C_ChatInfo", "SendAddonMessage"), glob("SendAddonMessage"))
-- Returns true if the message was handed to the client, false if blocked.
function D.SendAddonMessage(prefix, text, channel, target)
if not sendMsg or R:ChatBlocked() then return false end
local ok = pcall(sendMsg, prefix, text, channel, target)
return ok
end
-- ============================================================
-- Professions, totems, talents
-- ============================================================
local profInfo = fn("C_TradeSkillUI", "GetBaseProfessionInfo")
local tsLine = glob("GetTradeSkillLine")
chose("GetTradeSkillLine", profInfo, tsLine)
-- Only meaningful while a profession window is open, on either client.
function D.GetTradeSkillLine()
if profInfo then
local ok, t = pcall(profInfo)
if ok and t then
return { name = t.professionName, rank = t.skillLevel, maxRank = t.maxSkillLevel,
professionID = t.professionID, skillLineID = t.profession }
end
elseif tsLine then
local name, rank, maxRank = tsLine()
if name then return { name = name, rank = rank, maxRank = maxRank } end
end
return nil
end
local totemInfo = glob("GetTotemInfo")
chose("GetTotemInfo", totemInfo, nil)
-- Fields may be secret in combat on a secrets client; .secret says so.
function D.GetTotemInfo(slot)
if not totemInfo then return nil end
local ok, have, name, start, duration, icon, modRate, spellID = pcall(totemInfo, slot)
if not ok then return nil end
return { haveTotem = have, name = name, startTime = start, duration = duration,
icon = icon, modRate = modRate, spellID = spellID, secret = R:IsSecret(have) }
end
-- Talents are the retail trait system on Forever; the legacy tab API is nil.
D.hasTraits = Core.Client.hasTraits
chose("Talents", D.hasTraits and fn("C_ClassTalents", "GetActiveConfigID") or nil, glob("GetTalentInfo"))
function D.GetActiveTalentConfigID()
local f = fn("C_ClassTalents", "GetActiveConfigID")
if not f then return nil end
local ok, id = pcall(f)
return ok and id or nil
end
-- ============================================================
-- Report
-- ============================================================
function D:Report()
local labels = {}
for label in pairs(self.paths) do labels[#labels + 1] = label end
table.sort(labels)
local out = {}
for _, label in ipairs(labels) do
out[#out + 1] = string.format("%-24s %s", label, self.paths[label])
end
return out
end