PageRenderTime 111ms CodeModel.GetById 35ms RepoModel.GetById 1ms app.codeStats 0ms

/code.lua

https://gitlab.com/toreltwiddler/CanIMogIt
Lua | 1488 lines | 976 code | 264 blank | 248 comment | 214 complexity | 915f11b3b271d7bc89f947a6819ffb10 MD5 | raw file
  1. -- This file is loaded from "CanIMogIt.toc"
  2. local L = CanIMogIt.L
  3. CanIMogIt.DressUpModel = CreateFrame('DressUpModel')
  4. CanIMogIt.DressUpModel:SetUnit('player')
  5. -----------------------------
  6. -- Maps --
  7. -----------------------------
  8. ---- Transmog Categories
  9. -- 1 Head
  10. -- 2 Shoulder
  11. -- 3 Back
  12. -- 4 Chest
  13. -- 5 Shirt
  14. -- 6 Tabard
  15. -- 7 Wrist
  16. -- 8 Hands
  17. -- 9 Waist
  18. -- 10 Legs
  19. -- 11 Feet
  20. -- 12 Wand
  21. -- 13 One-Handed Axes
  22. -- 14 One-Handed Swords
  23. -- 15 One-Handed Maces
  24. -- 16 Daggers
  25. -- 17 Fist Weapons
  26. -- 18 Shields
  27. -- 19 Held In Off-hand
  28. -- 20 Two-Handed Axes
  29. -- 21 Two-Handed Swords
  30. -- 22 Two-Handed Maces
  31. -- 23 Staves
  32. -- 24 Polearms
  33. -- 25 Bows
  34. -- 26 Guns
  35. -- 27 Crossbows
  36. -- 28 Warglaives
  37. local HEAD = "INVTYPE_HEAD"
  38. local SHOULDER = "INVTYPE_SHOULDER"
  39. local BODY = "INVTYPE_BODY"
  40. local CHEST = "INVTYPE_CHEST"
  41. local ROBE = "INVTYPE_ROBE"
  42. local WAIST = "INVTYPE_WAIST"
  43. local LEGS = "INVTYPE_LEGS"
  44. local FEET = "INVTYPE_FEET"
  45. local WRIST = "INVTYPE_WRIST"
  46. local HAND = "INVTYPE_HAND"
  47. local CLOAK = "INVTYPE_CLOAK"
  48. local WEAPON = "INVTYPE_WEAPON"
  49. local SHIELD = "INVTYPE_SHIELD"
  50. local WEAPON_2HAND = "INVTYPE_2HWEAPON"
  51. local WEAPON_MAIN_HAND = "INVTYPE_WEAPONMAINHAND"
  52. local RANGED = "INVTYPE_RANGED"
  53. local RANGED_RIGHT = "INVTYPE_RANGEDRIGHT"
  54. local WEAPON_OFF_HAND = "INVTYPE_WEAPONOFFHAND"
  55. local HOLDABLE = "INVTYPE_HOLDABLE"
  56. local TABARD = "INVTYPE_TABARD"
  57. local BAG = "INVTYPE_BAG"
  58. local inventorySlotsMap = {
  59. [HEAD] = {1},
  60. [SHOULDER] = {3},
  61. [BODY] = {4},
  62. [CHEST] = {5},
  63. [ROBE] = {5},
  64. [WAIST] = {6},
  65. [LEGS] = {7},
  66. [FEET] = {8},
  67. [WRIST] = {9},
  68. [HAND] = {10},
  69. [CLOAK] = {15},
  70. [WEAPON] = {16, 17},
  71. [SHIELD] = {17},
  72. [WEAPON_2HAND] = {16, 17},
  73. [WEAPON_MAIN_HAND] = {16},
  74. [RANGED] = {16},
  75. [RANGED_RIGHT] = {16},
  76. [WEAPON_OFF_HAND] = {17},
  77. [HOLDABLE] = {17},
  78. [TABARD] = {19},
  79. }
  80. -- This is a one-time call to get a "transmogLocation" object, which we don't actually care about,
  81. -- but some functions require it now.
  82. local transmogLocation = TransmogUtil.GetTransmogLocation(inventorySlotsMap[HEAD][1], Enum.TransmogType.Appearance, Enum.TransmogModification.Main)
  83. local MISC = 0
  84. local CLOTH = 1
  85. local LEATHER = 2
  86. local MAIL = 3
  87. local PLATE = 4
  88. local COSMETIC = 5
  89. local classArmorTypeMap = {
  90. ["DEATHKNIGHT"] = PLATE,
  91. ["DEMONHUNTER"] = LEATHER,
  92. ["DRUID"] = LEATHER,
  93. ["HUNTER"] = MAIL,
  94. ["MAGE"] = CLOTH,
  95. ["MONK"] = LEATHER,
  96. ["PALADIN"] = PLATE,
  97. ["PRIEST"] = CLOTH,
  98. ["ROGUE"] = LEATHER,
  99. ["SHAMAN"] = MAIL,
  100. ["WARLOCK"] = CLOTH,
  101. ["WARRIOR"] = PLATE,
  102. }
  103. -- Class Masks
  104. local classMask = {
  105. [1] = "WARRIOR",
  106. [2] = "PALADIN",
  107. [4] = "HUNTER",
  108. [8] = "ROGUE",
  109. [16] = "PRIEST",
  110. [32] = "DEATHKNIGHT",
  111. [64] = "SHAMAN",
  112. [128] = "MAGE",
  113. [256] = "WARLOCK",
  114. [512] = "MONK",
  115. [1024] = "DRUID",
  116. [2048] = "DEMONHUNTER",
  117. }
  118. local armorTypeSlots = {
  119. [HEAD] = true,
  120. [SHOULDER] = true,
  121. [CHEST] = true,
  122. [ROBE] = true,
  123. [WRIST] = true,
  124. [HAND] = true,
  125. [WAIST] = true,
  126. [LEGS] = true,
  127. [FEET] = true,
  128. }
  129. local miscArmorExceptions = {
  130. [HOLDABLE] = true,
  131. [BODY] = true,
  132. [TABARD] = true,
  133. }
  134. local APPEARANCES_ITEMS_TAB = 1
  135. local APPEARANCES_SETS_TAB = 2
  136. -- Get the name for Cosmetic. Uses http://www.wowhead.com/item=130064/deadeye-monocle.
  137. local COSMETIC_NAME = select(3, GetItemInfoInstant(130064))
  138. -- Built-in colors
  139. -- TODO: move to constants
  140. local BLIZZARD_RED = "|cffff1919"
  141. local BLIZZARD_GREEN = "|cff19ff19"
  142. local BLIZZARD_DARK_GREEN = "|cff40c040"
  143. local BLIZZARD_YELLOW = "|cffffd100"
  144. -------------------------
  145. -- Text related tables --
  146. -------------------------
  147. -- Maps a text to its simpler version
  148. local simpleTextMap = {
  149. [CanIMogIt.KNOWN_BY_ANOTHER_CHARACTER] = CanIMogIt.KNOWN,
  150. [CanIMogIt.KNOWN_BY_ANOTHER_CHARACTER_BOE] = CanIMogIt.KNOWN_BOE,
  151. [CanIMogIt.KNOWN_BUT_TOO_LOW_LEVEL] = CanIMogIt.KNOWN,
  152. [CanIMogIt.KNOWN_BUT_TOO_LOW_LEVEL_BOE] = CanIMogIt.KNOWN_BOE,
  153. [CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_BUT_TOO_LOW_LEVEL] = CanIMogIt.KNOWN_FROM_ANOTHER_ITEM,
  154. [CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_BUT_TOO_LOW_LEVEL_BOE] = CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_BOE,
  155. [CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_AND_CHARACTER] = CanIMogIt.KNOWN_FROM_ANOTHER_ITEM,
  156. [CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_AND_CHARACTER_BOE] = CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_BOE,
  157. }
  158. -- List of all Known texts
  159. local knownTexts = {
  160. [CanIMogIt.KNOWN] = true,
  161. [CanIMogIt.KNOWN_BOE] = true,
  162. [CanIMogIt.KNOWN_FROM_ANOTHER_ITEM] = true,
  163. [CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_BOE] = true,
  164. [CanIMogIt.KNOWN_BY_ANOTHER_CHARACTER] = true,
  165. [CanIMogIt.KNOWN_BY_ANOTHER_CHARACTER_BOE] = true,
  166. [CanIMogIt.KNOWN_BUT_TOO_LOW_LEVEL] = true,
  167. [CanIMogIt.KNOWN_BUT_TOO_LOW_LEVEL_BOE] = true,
  168. [CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_BUT_TOO_LOW_LEVEL] = true,
  169. [CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_BUT_TOO_LOW_LEVEL_BOE] = true,
  170. [CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_AND_CHARACTER] = true,
  171. [CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_AND_CHARACTER_BOE] = true,
  172. }
  173. local unknownTexts = {
  174. [CanIMogIt.UNKNOWN] = true,
  175. [CanIMogIt.UNKNOWABLE_BY_CHARACTER] = true,
  176. }
  177. -----------------------------
  178. -- Exceptions --
  179. -----------------------------
  180. local exceptionItems = {
  181. [HEAD] = {
  182. -- [134110] = CanIMogIt.KNOWN, -- Hidden Helm
  183. [133320] = CanIMogIt.NOT_TRANSMOGABLE, -- Illidari Blindfold (Alliance)
  184. [112450] = CanIMogIt.NOT_TRANSMOGABLE, -- Illidari Blindfold (Horde)
  185. -- [150726] = CanIMogIt.NOT_TRANSMOGABLE, -- Illidari Blindfold (Alliance) - starting item
  186. -- [150716] = CanIMogIt.NOT_TRANSMOGABLE, -- Illidari Blindfold (Horde) - starting item
  187. [130064] = CanIMogIt.NOT_TRANSMOGABLE, -- Deadeye Monocle
  188. },
  189. [SHOULDER] = {
  190. [119556] = CanIMogIt.NOT_TRANSMOGABLE, -- Trailseeker Spaulders - 100 Salvage Yard ilvl 610
  191. [117106] = CanIMogIt.NOT_TRANSMOGABLE, -- Trailseeker Spaulders - 90 boost ilvl 483
  192. [129714] = CanIMogIt.NOT_TRANSMOGABLE, -- Trailseeker Spaulders - 100 trial/boost ilvl 640
  193. [150642] = CanIMogIt.NOT_TRANSMOGABLE, -- Trailseeker Spaulders - 100 trial/boost ilvl 600
  194. [153810] = CanIMogIt.NOT_TRANSMOGABLE, -- Trailseeker Spaulders - 110 trial/boost ilvl 870
  195. [162796] = CanIMogIt.NOT_TRANSMOGABLE, -- Wildguard Spaulders - 8.0 BfA Pre-Patch event
  196. [119588] = CanIMogIt.NOT_TRANSMOGABLE, -- Mistdancer Pauldrons - 100 Salvage Yard ilvl 610
  197. [117138] = CanIMogIt.NOT_TRANSMOGABLE, -- Mistdancer Pauldrons - 90 boost ilvl 483
  198. [129485] = CanIMogIt.NOT_TRANSMOGABLE, -- Mistdancer Pauldrons - 100 trial/boost ilvl 640
  199. [150658] = CanIMogIt.NOT_TRANSMOGABLE, -- Mistdancer Pauldrons - 100 trial/boost ilvl 600
  200. [153842] = CanIMogIt.NOT_TRANSMOGABLE, -- Mistdancer Pauldrons - 110 trial/boost ilvl 870
  201. [162812] = CanIMogIt.NOT_TRANSMOGABLE, -- Serene Disciple's Padding - 8.0 BfA Pre-Patch event
  202. [134112] = CanIMogIt.KNOWN, -- Hidden Shoulders
  203. },
  204. [BODY] = {},
  205. [CHEST] = {},
  206. [ROBE] = {},
  207. [WAIST] = {
  208. [143539] = CanIMogIt.KNOWN, -- Hidden Belt
  209. },
  210. [LEGS] = {},
  211. [FEET] = {},
  212. [WRIST] = {},
  213. [HAND] = {
  214. [119585] = CanIMogIt.NOT_TRANSMOGABLE, -- Mistdancer Handguards - 100 Salvage Yard ilvl 610
  215. [117135] = CanIMogIt.NOT_TRANSMOGABLE, -- Mistdancer Handguards - 90 boost ilvl 483
  216. [129482] = CanIMogIt.NOT_TRANSMOGABLE, -- Mistdancer Handguards - 100 trial/boost ilvl 640
  217. [150655] = CanIMogIt.NOT_TRANSMOGABLE, -- Mistdancer Handguards - 100 trial/boost ilvl 600
  218. [153839] = CanIMogIt.NOT_TRANSMOGABLE, -- Mistdancer Handguards - 110 trial/boost ilvl 870
  219. [162809] = CanIMogIt.NOT_TRANSMOGABLE, -- Serene Disciple's Handguards - 8.0 BfA Pre-Patch event
  220. },
  221. [CLOAK] = {
  222. -- [134111] = CanIMogIt.KNOWN, -- Hidden Cloak
  223. [112462] = CanIMogIt.NOT_TRANSMOGABLE, -- Illidari Drape
  224. },
  225. [WEAPON] = {},
  226. [SHIELD] = {},
  227. [WEAPON_2HAND] = {},
  228. [WEAPON_MAIN_HAND] = {},
  229. [RANGED] = {},
  230. [RANGED_RIGHT] = {},
  231. [WEAPON_OFF_HAND] = {},
  232. [HOLDABLE] = {},
  233. [TABARD] = {
  234. -- [142504] = CanIMogIt.KNOWN, -- Hidden Tabard
  235. },
  236. }
  237. -----------------------------
  238. -- Helper functions --
  239. -----------------------------
  240. CanIMogIt.Utils = {}
  241. function CanIMogIt.Utils.pairsByKeys (t, f)
  242. -- returns a sorted iterator for a table.
  243. -- https://www.lua.org/pil/19.3.html
  244. -- Why is it not a built in function? ¯\_(ツ)_/¯
  245. local a = {}
  246. for n in pairs(t) do table.insert(a, n) end
  247. table.sort(a, f)
  248. local i = 0 -- iterator variable
  249. local iter = function () -- iterator function
  250. i = i + 1
  251. if a[i] == nil then return nil
  252. else return a[i], t[a[i]]
  253. end
  254. end
  255. return iter
  256. end
  257. function CanIMogIt.Utils.copyTable (t)
  258. -- shallow-copy a table
  259. if type(t) ~= "table" then return t end
  260. local target = {}
  261. for k, v in pairs(t) do target[k] = v end
  262. return target
  263. end
  264. function CanIMogIt.Utils.spairs(t, order)
  265. -- Returns an iterator that is a sorted table. order is the function to sort by.
  266. -- http://stackoverflow.com/questions/15706270/sort-a-table-in-lua
  267. -- Again, why is this not a built in function? ¯\_(ツ)_/¯
  268. -- collect the keys
  269. local keys = {}
  270. for k in pairs(t) do keys[#keys+1] = k end
  271. -- if order function given, sort by it by passing the table and keys a, b,
  272. -- otherwise just sort the keys
  273. if order then
  274. table.sort(keys, function(a,b) return order(t, a, b) end)
  275. else
  276. table.sort(keys)
  277. end
  278. -- return the iterator function
  279. local i = 0
  280. return function()
  281. i = i + 1
  282. if keys[i] then
  283. return keys[i], t[keys[i]]
  284. end
  285. end
  286. end
  287. function CanIMogIt.Utils.strsplit(delimiter, text)
  288. -- from http://lua-users.org/wiki/SplitJoin
  289. -- Split text into a list consisting of the strings in text,
  290. -- separated by strings matching delimiter (which may be a pattern).
  291. -- example: strsplit(",%s*", "Anna, Bob, Charlie,Dolores")
  292. local list = {}
  293. local pos = 1
  294. if string.find("", delimiter, 1) then -- this would result in endless loops
  295. error("delimiter matches empty string!")
  296. end
  297. while 1 do
  298. local first, last = string.find(text, delimiter, pos)
  299. if first then -- found?
  300. table.insert(list, string.sub(text, pos, first-1))
  301. pos = last+1
  302. else
  303. table.insert(list, string.sub(text, pos))
  304. break
  305. end
  306. end
  307. return list
  308. end
  309. function CanIMogIt.Utils.tablelength(T)
  310. -- Count the number of keys in a table, because tables don't bother
  311. -- counting themselves if it's filled with key-value pairs...
  312. -- ¯\_(ツ)_/¯
  313. local count = 0
  314. for _ in pairs(T) do count = count + 1 end
  315. return count
  316. end
  317. -----------------------------
  318. -- CanIMogIt Core methods --
  319. -----------------------------
  320. -- Variables for managing updating appearances.
  321. local appearanceIndex = 0
  322. local sourceIndex = 0
  323. local getAppearancesDone = false;
  324. local sourceCount = 0
  325. local appearanceCount = 0
  326. local buffer = 0
  327. local sourcesAdded = 0
  328. local sourcesRemoved = 0
  329. local loadingScreen = true
  330. local appearancesTable = {}
  331. local removeAppearancesTable = nil
  332. local appearancesTableGotten = false
  333. local doneAppearances = {}
  334. local function LoadingScreenStarted(event)
  335. if event ~= "LOADING_SCREEN_ENABLED" then return end
  336. loadingScreen = true
  337. end
  338. CanIMogIt.frame:AddEventFunction(LoadingScreenStarted)
  339. local function LoadingScreenEnded(event)
  340. if event ~= "LOADING_SCREEN_DISABLED" then return end
  341. loadingScreen = false
  342. end
  343. CanIMogIt.frame:AddEventFunction(LoadingScreenEnded)
  344. local function GetAppearancesTable()
  345. -- Sort the C_TransmogCollection.GetCategoryAppearances tables into something
  346. -- more usable.
  347. if appearancesTableGotten then return end
  348. for categoryID=1,28 do
  349. local categoryAppearances = C_TransmogCollection.GetCategoryAppearances(categoryID, transmogLocation)
  350. for i, categoryAppearance in pairs(categoryAppearances) do
  351. if categoryAppearance.isCollected then
  352. appearanceCount = appearanceCount + 1
  353. appearancesTable[categoryAppearance.visualID] = true
  354. end
  355. end
  356. end
  357. appearancesTableGotten = true
  358. end
  359. local function AddSource(source, appearanceID)
  360. -- Adds the source to the database, and increments the buffer.
  361. buffer = buffer + 1
  362. sourceCount = sourceCount + 1
  363. local sourceID = source.sourceID
  364. local sourceItemLink = CanIMogIt:GetItemLinkFromSourceID(sourceID)
  365. local added = CanIMogIt:DBAddItem(sourceItemLink, appearanceID, sourceID)
  366. if added then
  367. sourcesAdded = sourcesAdded + 1
  368. end
  369. end
  370. local function AddAppearance(appearanceID)
  371. -- Adds all of the sources for this appearanceID to the database.
  372. -- returns early if the buffer is reached.
  373. local sources = C_TransmogCollection.GetAppearanceSources(appearanceID, 1, transmogLocation)
  374. if sources then
  375. for i, source in pairs(sources) do
  376. if source.isCollected then
  377. AddSource(source, appearanceID)
  378. end
  379. end
  380. end
  381. end
  382. -- Remembering iterators for later
  383. local appearancesIter, removeIter = nil, nil
  384. local function _GetAppearances()
  385. -- Core logic for getting the appearances.
  386. if getAppearancesDone then return end
  387. C_TransmogCollection.ClearSearch(APPEARANCES_ITEMS_TAB)
  388. GetAppearancesTable()
  389. buffer = 0
  390. if appearancesIter == nil then appearancesIter = CanIMogIt.Utils.pairsByKeys(appearancesTable) end
  391. -- Add new appearances learned.
  392. for appearanceID, collected in appearancesIter do
  393. AddAppearance(appearanceID)
  394. if buffer >= CanIMogIt.bufferMax then return end
  395. appearancesTable[appearanceID] = nil
  396. end
  397. if removeIter == nil then removeIter = CanIMogIt.Utils.pairsByKeys(removeAppearancesTable) end
  398. -- Remove appearances that are no longer learned.
  399. for appearanceHash, sources in removeIter do
  400. for sourceID, source in pairs(sources.sources) do
  401. if not C_TransmogCollection.PlayerHasTransmogItemModifiedAppearance(sourceID) then
  402. local itemLink = CanIMogIt:GetItemLinkFromSourceID(sourceID)
  403. local appearanceID = CanIMogIt:GetAppearanceIDFromSourceID(sourceID)
  404. CanIMogIt:DBRemoveItem(appearanceID, sourceID, itemLink, appearanceHash)
  405. sourcesRemoved = sourcesRemoved + 1
  406. end
  407. buffer = buffer + 1
  408. end
  409. if buffer >= CanIMogIt.bufferMax then return end
  410. removeAppearancesTable[appearanceHash] = nil
  411. end
  412. getAppearancesDone = true
  413. appearancesTable = {} -- cleanup
  414. CanIMogIt:ResetCache()
  415. appearancesIter = nil
  416. removeIter = nil
  417. CanIMogIt.frame:SetScript("OnUpdate", nil)
  418. if CanIMogItOptions["printDatabaseScan"] then
  419. CanIMogIt:Print(CanIMogIt.DATABASE_DONE_UPDATE_TEXT..CanIMogIt.BLUE.."+" .. sourcesAdded .. ", "..CanIMogIt.ORANGE.."-".. sourcesRemoved)
  420. end
  421. end
  422. local timer = 0
  423. local function GetAppearancesOnUpdate(self, elapsed)
  424. -- OnUpdate function with a reset timer to throttle getting appearances.
  425. -- We also don't run things if the loading screen is currently up, as some
  426. -- functions don't return values when loading.
  427. timer = timer + elapsed
  428. if timer >= CanIMogIt.throttleTime and not loadingScreen then
  429. _GetAppearances()
  430. timer = 0
  431. end
  432. end
  433. function CanIMogIt:GetAppearances()
  434. -- Gets a table of all the appearances known to
  435. -- a character and adds it to the database.
  436. if CanIMogItOptions["printDatabaseScan"] then
  437. CanIMogIt:Print(CanIMogIt.DATABASE_START_UPDATE_TEXT)
  438. end
  439. removeAppearancesTable = CanIMogIt.Utils.copyTable(CanIMogIt.db.global.appearances)
  440. CanIMogIt.frame:SetScript("OnUpdate", GetAppearancesOnUpdate)
  441. end
  442. function CIMI_GetVariantSets(setID)
  443. --[[
  444. It seems that C_TransmogSets.GetVariantSets(setID) returns a number
  445. (instead of the expected table of sets) if it can't find a matching
  446. base set. We currently are checking that it's returning a table first
  447. to prevent issues.
  448. ]]
  449. local variantSets = C_TransmogSets.GetVariantSets(setID)
  450. if type(variantSets) == "table" then
  451. return variantSets
  452. end
  453. return {}
  454. end
  455. function CanIMogIt:GetSets()
  456. -- Gets a table of all of the sets available to the character,
  457. -- along with their items, and adds them to the sets database.
  458. C_TransmogCollection.ClearSearch(APPEARANCES_SETS_TAB)
  459. for i, set in pairs(C_TransmogSets.GetAllSets()) do
  460. -- This is a base set, so we need to get the variant sets as well
  461. for i, sourceID in pairs(C_TransmogSets.GetAllSourceIDs(set.setID)) do
  462. CanIMogIt:SetsDBAddSetItem(set, sourceID)
  463. end
  464. for i, variantSet in pairs(CIMI_GetVariantSets(set.setID)) do
  465. for i, sourceID in pairs(C_TransmogSets.GetAllSourceIDs(variantSet.setID)) do
  466. CanIMogIt:SetsDBAddSetItem(variantSet, sourceID)
  467. end
  468. end
  469. end
  470. end
  471. function CanIMogIt:_GetRatio(setID)
  472. -- Gets the count of known and total sources for the given setID.
  473. local have = 0
  474. local total = 0
  475. for _, knownSource in pairs(C_TransmogSets.GetSetSources(setID)) do
  476. total = total + 1
  477. if knownSource then
  478. have = have + 1
  479. end
  480. end
  481. return have, total
  482. end
  483. function CanIMogIt:_GetRatioTextColor(have, total)
  484. if have == total then
  485. return CanIMogIt.BLUE
  486. elseif have > 0 then
  487. return CanIMogIt.RED_ORANGE
  488. else
  489. return CanIMogIt.GRAY
  490. end
  491. end
  492. function CanIMogIt:_GetRatioText(setID)
  493. -- Gets the ratio text (and color) of known/total for the given setID.
  494. local have, total = CanIMogIt:_GetRatio(setID)
  495. local ratioText = CanIMogIt:_GetRatioTextColor(have, total)
  496. ratioText = ratioText .. "(" .. have .. "/" .. total .. ")"
  497. return ratioText
  498. end
  499. function CanIMogIt:GetSetClass(set)
  500. --[[
  501. Returns the set's class. If it belongs to more than one class,
  502. return an empty string.
  503. This is done based on the player's sex.
  504. Player's sex
  505. 1 = Neutrum / Unknown
  506. 2 = Male
  507. 3 = Female
  508. ]]
  509. local playerSex = UnitSex("player")
  510. local className
  511. if playerSex == 2 then
  512. className = LOCALIZED_CLASS_NAMES_MALE[classMask[set.classMask]]
  513. else
  514. className = LOCALIZED_CLASS_NAMES_FEMALE[classMask[set.classMask]]
  515. end
  516. return className or ""
  517. end
  518. local classSetIDs = nil
  519. function CanIMogIt:CalculateSetsText(itemLink)
  520. --[[
  521. Gets the two lines of text to display on the tooltip related to sets.
  522. Example:
  523. Demon Hunter: Garb of the Something or Other
  524. Ulduar: 25 Man Normal (2/8)
  525. This function is not cached, so avoid calling often!
  526. Use GetSetsText whenever possible!
  527. ]]
  528. local sourceID = CanIMogIt:GetSourceID(itemLink)
  529. if not sourceID then return end
  530. local setID = CanIMogIt:SetsDBGetSetFromSourceID(sourceID)
  531. if not setID then return end
  532. local set = C_TransmogSets.GetSetInfo(setID)
  533. local ratioText = CanIMogIt:_GetRatioText(setID)
  534. -- Build the classSetIDs table, if it hasn't been built yet.
  535. if classSetIDs == nil then
  536. classSetIDs = {}
  537. for i, baseSet in pairs(C_TransmogSets.GetBaseSets()) do
  538. classSetIDs[baseSet.setID] = true
  539. for i, variantSet in pairs(C_TransmogSets.GetVariantSets(baseSet.setID)) do
  540. classSetIDs[variantSet.setID] = true
  541. end
  542. end
  543. end
  544. local setNameColor, otherClass
  545. if classSetIDs[set.setID] then
  546. setNameColor = CanIMogIt.WHITE
  547. otherClass = ""
  548. else
  549. setNameColor = CanIMogIt.GRAY
  550. otherClass = CanIMogIt:GetSetClass(set) .. ": "
  551. end
  552. local secondLineText = ""
  553. if set.label and set.description then
  554. secondLineText = CanIMogIt.WHITE .. set.label .. ": " .. BLIZZARD_GREEN .. set.description .. " "
  555. elseif set.label then
  556. secondLineText = CanIMogIt.WHITE .. set.label .. " "
  557. elseif set.description then
  558. secondLineText = BLIZZARD_GREEN .. set.description .. " "
  559. end
  560. -- TODO: replace CanIMogIt.WHITE with setNameColor, add otherClass
  561. -- e.g.: setNameColor .. otherClass .. set.name
  562. return CanIMogIt.WHITE .. set.name, secondLineText .. ratioText
  563. end
  564. function CanIMogIt:GetSetsText(itemLink)
  565. -- Gets the cached text regarding the sets info for the given item.
  566. local line1, line2;
  567. if CanIMogIt.cache:GetSetsInfoTextValue(itemLink) then
  568. line1, line2 = unpack(CanIMogIt.cache:GetSetsInfoTextValue(itemLink))
  569. return line1, line2
  570. end
  571. line1, line2 = CanIMogIt:CalculateSetsText(itemLink)
  572. CanIMogIt.cache:SetSetsInfoTextValue(itemLink, {line1, line2})
  573. return line1, line2
  574. end
  575. function CanIMogIt:CalculateSetsVariantText(setID)
  576. --[[
  577. Given a setID, calculate the sum of all known sources for this set
  578. and it's variants.
  579. ]]
  580. local baseSetID = C_TransmogSets.GetBaseSetID(setID)
  581. local variantSets = {C_TransmogSets.GetSetInfo(baseSetID)}
  582. for i, variantSet in ipairs(CIMI_GetVariantSets(baseSetID)) do
  583. variantSets[#variantSets+1] = variantSet
  584. end
  585. local variantsText = ""
  586. for i, variantSet in CanIMogIt.Utils.spairs(variantSets, function(t,a,b) return t[a].uiOrder < t[b].uiOrder end) do
  587. local variantHave, variantTotal = CanIMogIt:_GetRatio(variantSet.setID)
  588. variantsText = variantsText .. CanIMogIt:_GetRatioTextColor(variantHave, variantTotal)
  589. -- There is intentionally an extra space before the newline, for positioning.
  590. variantsText = variantsText .. variantHave .. "/" .. variantTotal .. " \n"
  591. end
  592. -- uncomment for debug
  593. -- variantsText = variantsText .. "setID: " .. setID
  594. return string.sub(variantsText, 1, -2)
  595. end
  596. function CanIMogIt:GetSetsVariantText(setID)
  597. -- Gets the cached text regarding the sets info for the given item.
  598. if not setID then return end
  599. local line1;
  600. if CanIMogIt.cache:GetSetsSumRatioTextValue(setID) then
  601. line1 = CanIMogIt.cache:GetSetsSumRatioTextValue(setID)
  602. return line1
  603. end
  604. line1 = CanIMogIt:CalculateSetsVariantText(setID)
  605. CanIMogIt.cache:SetSetsSumRatioTextValue(setID, line1)
  606. return line1
  607. end
  608. function CanIMogIt:ResetCache()
  609. -- Resets the cache, and calls things relying on the cache being reset.
  610. CanIMogIt.cache:Clear()
  611. CanIMogIt:SendMessage("ResetCache")
  612. -- Fake a BAG_UPDATE event to updating the icons. TODO: Replace this with message
  613. CanIMogIt.frame:ItemOverlayEvents("BAG_UPDATE")
  614. end
  615. function CanIMogIt:CalculateSourceLocationText(itemLink)
  616. --[[
  617. Calculates the sources for this item.
  618. This function is not cached, so avoid calling often!
  619. Use GetSourceLocationText whenever possible!
  620. ]]
  621. local output = ""
  622. local appearanceID = CanIMogIt:GetAppearanceID(itemLink)
  623. if appearanceID == nil then return end
  624. local sources = C_TransmogCollection.GetAppearanceSources(appearanceID, 1, transmogLocation)
  625. if sources then
  626. local totalSourceTypes = { 0, 0, 0, 0, 0, 0 }
  627. local knownSourceTypes = { 0, 0, 0, 0, 0, 0 }
  628. local totalUnknownType = 0
  629. local knownUnknownType = 0
  630. for _, source in pairs(sources) do
  631. if source.sourceType ~= 0 and source.sourceType ~= nil then
  632. totalSourceTypes[source.sourceType] = totalSourceTypes[source.sourceType] + 1
  633. if source.isCollected then
  634. knownSourceTypes[source.sourceType] = knownSourceTypes[source.sourceType] + 1
  635. end
  636. elseif source.sourceType == 0 and source.isCollected then
  637. totalUnknownType = totalUnknownType + 1
  638. knownUnknownType = knownUnknownType + 1
  639. end
  640. end
  641. for sourceType, totalCount in ipairs(totalSourceTypes) do
  642. if (totalCount > 0) then
  643. local knownCount = knownSourceTypes[sourceType]
  644. local knownColor = CanIMogIt.RED_ORANGE
  645. if knownCount == totalCount then
  646. knownColor = CanIMogIt.GRAY
  647. elseif knownCount > 0 then
  648. knownColor = CanIMogIt.BLUE
  649. end
  650. output = string.format("%s"..knownColor.."%s ("..knownColor.."%i/%i"..knownColor..")"..CanIMogIt.WHITE..", ",
  651. output, _G["TRANSMOG_SOURCE_"..sourceType], knownCount, totalCount)
  652. end
  653. end
  654. if totalUnknownType > 0 then
  655. output = string.format("%s"..CanIMogIt.GRAY.."Unobtainable ("..CanIMogIt.GRAY.."%i/%i"..CanIMogIt.GRAY..")"..CanIMogIt.WHITE..", ",
  656. output, knownUnknownType, totalUnknownType)
  657. end
  658. output = string.sub(output, 1, -3)
  659. end
  660. return output
  661. end
  662. function CanIMogIt:GetSourceLocationText(itemLink)
  663. -- Returns string of the all the types of sources which can provide an item with this appearance.
  664. cached_value = CanIMogIt.cache:GetItemSourcesValue(itemLink)
  665. if cached_value then
  666. return cached_value
  667. end
  668. local output = CanIMogIt:CalculateSourceLocationText(itemLink)
  669. CanIMogIt.cache:SetItemSourcesValue(itemLink, output)
  670. return output
  671. end
  672. function CanIMogIt:GetPlayerArmorTypeName()
  673. local playerArmorTypeID = classArmorTypeMap[select(2, UnitClass("player"))]
  674. return select(1, GetItemSubClassInfo(4, playerArmorTypeID))
  675. end
  676. function CanIMogIt:GetItemID(itemLink)
  677. return tonumber(itemLink:match("item:(%d+)"))
  678. end
  679. function CanIMogIt:GetItemLinkFromSourceID(sourceID)
  680. return select(6, C_TransmogCollection.GetAppearanceSourceInfo(sourceID))
  681. end
  682. function CanIMogIt:GetItemQuality(itemID)
  683. return select(3, GetItemInfo(itemID))
  684. end
  685. function CanIMogIt:GetItemExpansion(itemID)
  686. return select(15, GetItemInfo(itemID))
  687. end
  688. function CanIMogIt:GetItemMinTransmogLevel(itemID)
  689. -- Returns the minimum level required to transmog the item.
  690. -- This uses the expansion ID of the item to figure it out.
  691. -- Expansions before Shadowlands are all opened at level 10
  692. -- as of 9.0. Shadowlands is opened at level 48.
  693. local expansion = CanIMogIt:GetItemExpansion(itemID)
  694. if expansion == nil or expansion == 0 then return end
  695. if expansion < CanIMogIt.Expansions.SHADOWLANDS then
  696. return CanIMogIt.MIN_TRANSMOG_LEVEL
  697. else
  698. return CanIMogIt.MIN_TRANSMOG_LEVEL_SHADOWLANDS
  699. end
  700. end
  701. function CanIMogIt:GetItemClassName(itemLink)
  702. return select(2, GetItemInfoInstant(itemLink))
  703. end
  704. function CanIMogIt:GetItemSubClassName(itemLink)
  705. return select(3, GetItemInfoInstant(itemLink))
  706. end
  707. function CanIMogIt:GetItemSlotName(itemLink)
  708. return select(4, GetItemInfoInstant(itemLink))
  709. end
  710. function CanIMogIt:IsReadyForCalculations(itemLink)
  711. -- Returns true of the item's GetItemInfo is ready, or if it's a keystone,
  712. -- or if it's a battlepet.
  713. local itemInfo = GetItemInfo(itemLink)
  714. local isKeystone = string.match(itemLink, ".*(keystone):.*") == "keystone"
  715. local isBattlepet = string.match(itemLink, ".*(battlepet):.*") == "battlepet"
  716. if itemInfo or isKeystone or isBattlepet then
  717. return true
  718. end
  719. return false
  720. end
  721. function CanIMogIt:IsItemArmor(itemLink)
  722. local itemClass = CanIMogIt:GetItemClassName(itemLink)
  723. if itemClass == nil then return end
  724. return GetItemClassInfo(4) == itemClass
  725. end
  726. function CanIMogIt:IsArmorSubClassID(subClassID, itemLink)
  727. local itemSubClass = CanIMogIt:GetItemSubClassName(itemLink)
  728. if itemSubClass == nil then return end
  729. return select(1, GetItemSubClassInfo(4, subClassID)) == itemSubClass
  730. end
  731. function CanIMogIt:IsArmorSubClassName(subClassName, itemLink)
  732. local itemSubClass = CanIMogIt:GetItemSubClassName(itemLink)
  733. if itemSubClass == nil then return end
  734. return subClassName == itemSubClass
  735. end
  736. function CanIMogIt:IsItemSubClassIdentical(itemLinkA, itemLinkB)
  737. local subClassA = CanIMogIt:GetItemSubClassName(itemLinkA)
  738. local subClassB = CanIMogIt:GetItemSubClassName(itemLinkB)
  739. if subClassA == nil or subClassB == nil then return end
  740. return subClassA == subClassB
  741. end
  742. function CanIMogIt:IsArmorCosmetic(itemLink)
  743. return CanIMogIt:IsArmorSubClassID(COSMETIC, itemLink)
  744. end
  745. function CanIMogIt:IsArmorAppropriateForPlayer(itemLink)
  746. local playerArmorTypeID = CanIMogIt:GetPlayerArmorTypeName()
  747. local slotName = CanIMogIt:GetItemSlotName(itemLink)
  748. if slotName == nil then return end
  749. local isArmorCosmetic = CanIMogIt:IsArmorCosmetic(itemLink)
  750. if isArmorCosmetic == nil then return end
  751. if armorTypeSlots[slotName] and isArmorCosmetic == false then
  752. return playerArmorTypeID == CanIMogIt:GetItemSubClassName(itemLink)
  753. else
  754. return true
  755. end
  756. end
  757. local function IsHeirloomRedText(redText, itemLink)
  758. local itemID = CanIMogIt:GetItemID(itemLink)
  759. if redText == _G["ITEM_SPELL_KNOWN"] and C_Heirloom.IsItemHeirloom(itemID) then
  760. return true
  761. end
  762. end
  763. local function IsLevelRequirementRedText(redText)
  764. if string.match(redText, _G["ITEM_MIN_LEVEL"]) then
  765. return true
  766. end
  767. end
  768. function CanIMogIt:CharacterCanEquipItem(itemLink)
  769. -- Can the character equip this item eventually? (excluding level)
  770. if CanIMogIt:IsItemArmor(itemLink) and CanIMogIt:IsArmorCosmetic(itemLink) then
  771. return true
  772. end
  773. local redText = CanIMogItTooltipScanner:GetRedText(itemLink)
  774. if redText == "" or redText == nil then
  775. return true
  776. end
  777. if IsHeirloomRedText(redText, itemLink) then
  778. -- Special case for heirloom items. They always have red text if it was learned.
  779. return true
  780. end
  781. if IsLevelRequirementRedText(redText) then
  782. -- We ignore the level, since it will be equipable eventually.
  783. return true
  784. end
  785. return false
  786. end
  787. function CanIMogIt:IsValidAppearanceForCharacter(itemLink)
  788. -- Can the character transmog this appearance right now?
  789. if not CanIMogIt:CharacterIsHighEnoughLevelForTransmog(itemLink) then
  790. return false
  791. end
  792. if CanIMogIt:CharacterCanEquipItem(itemLink) then
  793. if CanIMogIt:IsItemArmor(itemLink) then
  794. return CanIMogIt:IsArmorAppropriateForPlayer(itemLink)
  795. else
  796. return true
  797. end
  798. else
  799. return false
  800. end
  801. end
  802. function CanIMogIt:CharacterIsHighEnoughLevelForTransmog(itemLink)
  803. local minLevel = CanIMogIt:GetItemMinTransmogLevel(itemLink)
  804. if minLevel == nil then return true end
  805. return UnitLevel("player") > minLevel
  806. end
  807. function CanIMogIt:IsItemSoulbound(itemLink, bag, slot)
  808. return CanIMogItTooltipScanner:IsItemSoulbound(itemLink, bag, slot)
  809. end
  810. function CanIMogIt:IsItemBindOnEquip(itemLink, bag, slot)
  811. return CanIMogItTooltipScanner:IsItemBindOnEquip(itemLink, bag, slot)
  812. end
  813. function CanIMogIt:GetItemClassRestrictions(itemLink)
  814. if not itemLink then return end
  815. return CanIMogItTooltipScanner:GetClassesRequired(itemLink)
  816. end
  817. function CanIMogIt:GetExceptionText(itemLink)
  818. -- Returns the exception text for this item, if it has one.
  819. local itemID = CanIMogIt:GetItemID(itemLink)
  820. local slotName = CanIMogIt:GetItemSlotName(itemLink)
  821. if slotName == nil then return end
  822. local slotExceptions = exceptionItems[slotName]
  823. if slotExceptions then
  824. return slotExceptions[itemID]
  825. end
  826. end
  827. function CanIMogIt:IsEquippable(itemLink)
  828. -- Returns whether the item is equippable or not (exluding bags)
  829. local slotName = CanIMogIt:GetItemSlotName(itemLink)
  830. if slotName == nil then return end
  831. return slotName ~= "" and slotName ~= BAG
  832. end
  833. function CanIMogIt:GetSourceID(itemLink)
  834. local sourceID = select(2, C_TransmogCollection.GetItemInfo(itemLink))
  835. if sourceID then
  836. return sourceID, "C_TransmogCollection.GetItemInfo"
  837. end
  838. -- Some items don't have the C_TransmogCollection.GetItemInfo data,
  839. -- so use the old way to find the sourceID (using the DressUpModel).
  840. local itemID, _, _, slotName = GetItemInfoInstant(itemLink)
  841. local slots = inventorySlotsMap[slotName]
  842. if slots == nil or slots == false or C_Item.IsDressableItemByID(itemID) == false then return end
  843. local cached_source = CanIMogIt.cache:GetDressUpModelSource(itemLink)
  844. if cached_source then
  845. return cached_source, "DressUpModel:GetItemTransmogInfo cache"
  846. end
  847. CanIMogIt.DressUpModel:SetUnit('player')
  848. CanIMogIt.DressUpModel:Undress()
  849. for i, slot in pairs(slots) do
  850. CanIMogIt.DressUpModel:TryOn(itemLink, slot)
  851. local transmogInfo = CanIMogIt.DressUpModel:GetItemTransmogInfo(slot)
  852. if transmogInfo and
  853. transmogInfo.appearanceID ~= nil and
  854. transmogInfo.appearanceID ~= 0 then
  855. -- Yes, that's right, we are setting `appearanceID` to the `sourceID`. Blizzard messed
  856. -- up the DressUpModel functions, so _they_ don't even know what they do anymore.
  857. -- The `appearanceID` field from `DressUpModel:GetItemTransmogInfo` is actually its
  858. -- source ID, not it's appearance ID.
  859. sourceID = transmogInfo.appearanceID
  860. if not CanIMogIt:IsSourceIDFromItemLink(sourceID, itemLink) then
  861. -- This likely means that the game hasn't finished loading things
  862. -- yet, so let's wait until we get good data before caching it.
  863. return
  864. end
  865. CanIMogIt.cache:SetDressUpModelSource(itemLink, sourceID)
  866. return sourceID, "DressUpModel:GetItemTransmogInfo"
  867. end
  868. end
  869. end
  870. function CanIMogIt:IsSourceIDFromItemLink(sourceID, itemLink)
  871. -- Returns whether the source ID given matches the itemLink.
  872. local sourceItemLink = select(6, C_TransmogCollection.GetAppearanceSourceInfo(sourceID))
  873. if not sourceItemLink then return false end
  874. return CanIMogIt:DoItemIDsMatch(sourceItemLink, itemLink)
  875. end
  876. function CanIMogIt:DoItemIDsMatch(itemLinkA, itemLinkB)
  877. return CanIMogIt:GetItemID(itemLinkA) == CanIMogIt:GetItemID(itemLinkB)
  878. end
  879. function CanIMogIt:GetAppearanceID(itemLink)
  880. -- Gets the appearanceID of the given item. Also returns the sourceID, for convenience.
  881. local sourceID = CanIMogIt:GetSourceID(itemLink)
  882. return CanIMogIt:GetAppearanceIDFromSourceID(sourceID), sourceID
  883. end
  884. function CanIMogIt:GetAppearanceIDFromSourceID(sourceID)
  885. -- Gets the appearanceID from the sourceID.
  886. if sourceID ~= nil then
  887. local appearanceID = select(2, C_TransmogCollection.GetAppearanceSourceInfo(sourceID))
  888. return appearanceID
  889. end
  890. end
  891. function CanIMogIt:_PlayerKnowsTransmog(itemLink, appearanceID)
  892. -- Internal logic for determining if the item is known or not.
  893. -- Does not use the CIMI database.
  894. if itemLink == nil or appearanceID == nil then return end
  895. local sources = C_TransmogCollection.GetAppearanceSources(appearanceID, 1, transmogLocation)
  896. if sources then
  897. for i, source in pairs(sources) do
  898. local sourceItemLink = CanIMogIt:GetItemLinkFromSourceID(source.sourceID)
  899. if CanIMogIt:IsItemSubClassIdentical(itemLink, sourceItemLink) and source.isCollected then
  900. return true
  901. end
  902. end
  903. end
  904. return false
  905. end
  906. function CanIMogIt:PlayerKnowsTransmog(itemLink)
  907. -- Returns whether this item's appearance is already known by the player.
  908. local appearanceID = CanIMogIt:GetAppearanceID(itemLink)
  909. if appearanceID == nil then return false end
  910. local requirements = CanIMogIt.Requirements:GetRequirements()
  911. if CanIMogIt:DBHasAppearanceForRequirements(appearanceID, itemLink, requirements) then
  912. if CanIMogIt:IsItemArmor(itemLink) then
  913. -- The character knows the appearance, check that it's from the same armor type.
  914. for sourceID, knownItem in pairs(CanIMogIt:DBGetSources(appearanceID, itemLink)) do
  915. if CanIMogIt:IsArmorSubClassName(knownItem.subClass, itemLink)
  916. or knownItem.subClass == COSMETIC_NAME then
  917. return true
  918. end
  919. end
  920. else
  921. -- Is not armor, don't worry about same appearance for different types
  922. return true
  923. end
  924. end
  925. -- Don't know from the database, try using the API.
  926. local knowsTransmog = CanIMogIt:_PlayerKnowsTransmog(itemLink, appearanceID)
  927. if knowsTransmog then
  928. CanIMogIt:DBAddItem(itemLink)
  929. end
  930. return knowsTransmog
  931. end
  932. function CanIMogIt:PlayerKnowsTransmogFromItem(itemLink)
  933. -- Returns whether the transmog is known from this item specifically.
  934. local slotName = CanIMogIt:GetItemSlotName(itemLink)
  935. if slotName == TABARD then
  936. local itemID = CanIMogIt:GetItemID(itemLink)
  937. return C_TransmogCollection.PlayerHasTransmog(itemID)
  938. end
  939. local appearanceID, sourceID = CanIMogIt:GetAppearanceID(itemLink)
  940. if sourceID == nil then return end
  941. -- First check the Database
  942. if CanIMogIt:DBHasSource(appearanceID, sourceID, itemLink) then
  943. return true
  944. end
  945. local hasTransmog;
  946. hasTransmog = C_TransmogCollection.PlayerHasTransmogItemModifiedAppearance(sourceID)
  947. -- Update Database
  948. if hasTransmog then
  949. CanIMogIt:DBAddItem(itemLink, appearanceID, sourceID)
  950. end
  951. return hasTransmog
  952. end
  953. function CanIMogIt:CharacterCanLearnTransmog(itemLink)
  954. -- Returns whether the player can learn the item or not.
  955. local slotName = CanIMogIt:GetItemSlotName(itemLink)
  956. if slotName == TABARD then return true end
  957. local sourceID = CanIMogIt:GetSourceID(itemLink)
  958. if sourceID == nil then return end
  959. if select(2, C_TransmogCollection.PlayerCanCollectSource(sourceID)) then
  960. return true
  961. end
  962. return false
  963. end
  964. function CanIMogIt:GetReason(itemLink)
  965. local reason = CanIMogItTooltipScanner:GetRedText(itemLink)
  966. if reason == "" then
  967. reason = CanIMogIt:GetItemSubClassName(itemLink)
  968. end
  969. return reason
  970. end
  971. function CanIMogIt:IsTransmogable(itemLink)
  972. -- Returns whether the item is transmoggable or not.
  973. -- White items are not transmoggable.
  974. local quality = CanIMogIt:GetItemQuality(itemLink)
  975. if quality == nil then return end
  976. if quality <= 1 then
  977. return false
  978. end
  979. local is_misc_subclass = CanIMogIt:IsArmorSubClassID(MISC, itemLink)
  980. if is_misc_subclass and miscArmorExceptions[CanIMogIt:GetItemSlotName(itemLink)] == nil then
  981. return false
  982. end
  983. local itemID, _, _, slotName = GetItemInfoInstant(itemLink)
  984. -- See if the game considers it transmoggable
  985. local transmoggable = select(3, C_Transmog.CanTransmogItem(itemID))
  986. if transmoggable == false then
  987. return false
  988. end
  989. -- See if the item is in a valid transmoggable slot
  990. if inventorySlotsMap[slotName] == nil then
  991. return false
  992. end
  993. return true
  994. end
  995. function CanIMogIt:TextIsKnown(text)
  996. -- Returns whether the text is considered to be a KNOWN value or not.
  997. return knownTexts[text] or false
  998. end
  999. function CanIMogIt:TextIsUnknown(unmodifiedText)
  1000. -- Returns whether the text is considered to be an UNKNOWN value or not.
  1001. return unknownTexts[unmodifiedText] or false
  1002. end
  1003. function CanIMogIt:PreLogicOptionsContinue(itemLink)
  1004. -- Apply the options. Returns false if it should stop the logic.
  1005. if CanIMogItOptions["showEquippableOnly"] and
  1006. not CanIMogIt:IsEquippable(itemLink) then
  1007. -- Don't bother if it's not equipable.
  1008. return false
  1009. end
  1010. return true
  1011. end
  1012. function CanIMogIt:PostLogicOptionsText(text, unmodifiedText)
  1013. -- Apply the options to the text. Returns the relevant text.
  1014. if CanIMogItOptions["showUnknownOnly"] and not CanIMogIt:TextIsUnknown(unmodifiedText) then
  1015. -- We don't want to show the tooltip if it's already known.
  1016. return "", ""
  1017. end
  1018. if CanIMogItOptions["showTransmoggableOnly"]
  1019. and (unmodifiedText == CanIMogIt.NOT_TRANSMOGABLE
  1020. or unmodifiedText == CanIMogIt.NOT_TRANSMOGABLE_BOE) then
  1021. -- If we don't want to show the tooltip if it's not transmoggable
  1022. return "", ""
  1023. end
  1024. if not CanIMogItOptions["showVerboseText"] then
  1025. text = simpleTextMap[text] or text
  1026. end
  1027. return text, unmodifiedText
  1028. end
  1029. function CanIMogIt:CalculateTooltipText(itemLink, bag, slot)
  1030. --[[
  1031. Calculate the tooltip text.
  1032. No caching is done here, so don't call this often!
  1033. Use GetTooltipText whenever possible!
  1034. ]]
  1035. local exception_text = CanIMogIt:GetExceptionText(itemLink)
  1036. if exception_text then
  1037. return exception_text, exception_text
  1038. end
  1039. local isTransmogable = CanIMogIt:IsTransmogable(itemLink)
  1040. -- if isTransmogable == nil then return end
  1041. local playerKnowsTransmogFromItem, isValidAppearanceForCharacter,
  1042. characterIsHighEnoughLevelToTransmog, playerKnowsTransmog, characterCanLearnTransmog,
  1043. isItemSoulbound, text, unmodifiedText;
  1044. local isItemSoulbound = CanIMogIt:IsItemSoulbound(itemLink, bag, slot)
  1045. -- Is the item transmogable?
  1046. if isTransmogable then
  1047. --Calculating the logic for each rule
  1048. -- If the item is transmogable, bug didn't give a result for soulbound state, it's
  1049. -- probably not ready yet.
  1050. if isItemSoulbound == nil then return end
  1051. playerKnowsTransmogFromItem = CanIMogIt:PlayerKnowsTransmogFromItem(itemLink)
  1052. if playerKnowsTransmogFromItem == nil then return end
  1053. isValidAppearanceForCharacter = CanIMogIt:IsValidAppearanceForCharacter(itemLink)
  1054. if isValidAppearanceForCharacter == nil then return end
  1055. playerKnowsTransmog = CanIMogIt:PlayerKnowsTransmog(itemLink)
  1056. if playerKnowsTransmog == nil then return end
  1057. characterCanLearnTransmog = CanIMogIt:CharacterCanLearnTransmog(itemLink)
  1058. if characterCanLearnTransmog == nil then return end
  1059. -- Is the item transmogable?
  1060. if playerKnowsTransmogFromItem then
  1061. -- Is this an appearance that the character can use now?
  1062. if isValidAppearanceForCharacter then
  1063. -- The player knows the appearance from this item
  1064. -- and the character can transmog it.
  1065. text = CanIMogIt.KNOWN
  1066. unmodifiedText = CanIMogIt.KNOWN
  1067. else
  1068. -- Can the character use the appearance eventually?
  1069. if characterCanLearnTransmog then
  1070. -- The player knows the appearance from this item, but
  1071. -- the character is too low level to use the appearance.
  1072. text = CanIMogIt.KNOWN_BUT_TOO_LOW_LEVEL
  1073. unmodifiedText = CanIMogIt.KNOWN_BUT_TOO_LOW_LEVEL
  1074. else
  1075. -- The player knows the appearance from this item, but
  1076. -- the character can never use it.
  1077. text = CanIMogIt.KNOWN_BY_ANOTHER_CHARACTER
  1078. unmodifiedText = CanIMogIt.KNOWN_BY_ANOTHER_CHARACTER
  1079. end
  1080. end
  1081. -- Does the player know the appearance from a different item?
  1082. elseif playerKnowsTransmog then
  1083. -- Is this an appearance that the character can use/learn now?
  1084. if isValidAppearanceForCharacter then
  1085. -- The player knows the appearance from another item, and
  1086. -- the character can use it.
  1087. text = CanIMogIt.KNOWN_FROM_ANOTHER_ITEM
  1088. unmodifiedText = CanIMogIt.KNOWN_FROM_ANOTHER_ITEM
  1089. else
  1090. -- Can the character use/learn the appearance from the item eventually?
  1091. if characterCanLearnTransmog then
  1092. -- The player knows the appearance from another item, but
  1093. -- the character is too low level to use/learn the appareance.
  1094. text = CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_BUT_TOO_LOW_LEVEL
  1095. unmodifiedText = CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_BUT_TOO_LOW_LEVEL
  1096. else
  1097. -- The player knows the appearance from another item, but
  1098. -- this charater can never use/learn the apperance.
  1099. text = CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_AND_CHARACTER
  1100. unmodifiedText = CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_AND_CHARACTER
  1101. end
  1102. end
  1103. else
  1104. -- Can the character learn the appearance?
  1105. if characterCanLearnTransmog then
  1106. -- The player does not know the appearance and the character
  1107. -- can learn this appearance.
  1108. text = CanIMogIt.UNKNOWN
  1109. unmodifiedText = CanIMogIt.UNKNOWN
  1110. else
  1111. -- Is the item Soulbound?
  1112. if isItemSoulbound then
  1113. -- The player does not know the appearance, the character
  1114. -- cannot use the appearance, and the item cannot be mailed
  1115. -- because it is soulbound.
  1116. text = CanIMogIt.UNKNOWABLE_SOULBOUND
  1117. .. BLIZZARD_RED .. CanIMogIt:GetReason(itemLink)
  1118. unmodifiedText = CanIMogIt.UNKNOWABLE_SOULBOUND
  1119. else
  1120. -- The player does not know the apperance, and the character
  1121. -- cannot use/learn the appearance.
  1122. text = CanIMogIt.UNKNOWABLE_BY_CHARACTER
  1123. .. BLIZZARD_RED .. CanIMogIt:GetReason(itemLink)
  1124. unmodifiedText = CanIMogIt.UNKNOWABLE_BY_CHARACTER
  1125. end
  1126. end
  1127. end
  1128. else
  1129. -- This item is never transmogable.
  1130. text = CanIMogIt.NOT_TRANSMOGABLE
  1131. unmodifiedText = CanIMogIt.NOT_TRANSMOGABLE
  1132. end
  1133. -- if CanIMogItOptions["showBoEColors"] then
  1134. -- -- Apply the option, if it is enabled then check item bind.
  1135. -- text, unmodifiedText = CanIMogIt:CheckItemBindType(text, unmodifiedText, itemLink, bag, slot)
  1136. -- end
  1137. return text, unmodifiedText
  1138. end
  1139. function CanIMogIt:CheckItemBindType(text, unmodifiedText, itemLink, bag, slot)
  1140. --[[
  1141. Check what binding text is used on the tooltip and then
  1142. change the Can I Mog It text where appropirate.
  1143. ]]
  1144. local isItemBindOnEquip = CanIMogIt:IsItemBindOnEquip(itemLink, bag, slot)
  1145. if isItemBindOnEquip == nil then return end
  1146. if isItemBindOnEquip then
  1147. if unmodifiedText == CanIMogIt.KNOWN then
  1148. text = CanIMogIt.KNOWN_BOE
  1149. unmodifiedText = CanIMogIt.KNOWN_BOE
  1150. elseif unmodifiedText == CanIMogIt.KNOWN_BUT_TOO_LOW_LEVEL then
  1151. text = CanIMogIt.KNOWN_BUT_TOO_LOW_LEVEL_BOE
  1152. unmodifiedText = CanIMogIt.KNOWN_BUT_TOO_LOW_LEVEL_BOE
  1153. elseif unmodifiedText == CanIMogIt.KNOWN_BY_ANOTHER_CHARACTER then
  1154. text = CanIMogIt.KNOWN_BY_ANOTHER_CHARACTER_BOE
  1155. unmodifiedText = CanIMogIt.KNOWN_BY_ANOTHER_CHARACTER_BOE
  1156. elseif unmodifiedText == CanIMogIt.KNOWN_FROM_ANOTHER_ITEM then
  1157. text = CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_BOE
  1158. unmodifiedText = CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_BOE
  1159. elseif unmodifiedText == CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_BUT_TOO_LOW_LEVEL then
  1160. text = CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_BUT_TOO_LOW_LEVEL_BOE
  1161. unmodifiedText = CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_BUT_TOO_LOW_LEVEL_BOE
  1162. elseif unmodifiedText == CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_AND_CHARACTER then
  1163. text = CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_AND_CHARACTER_BOE
  1164. unmodifiedText = CanIMogIt.KNOWN_FROM_ANOTHER_ITEM_AND_CHARACTER_BOE
  1165. elseif unmodifiedText == CanIMogIt.NOT_TRANSMOGABLE then
  1166. text = CanIMogIt.NOT_TRANSMOGABLE_BOE
  1167. unmodifiedText = CanIMogIt.NOT_TRANSMOGABLE_BOE
  1168. end
  1169. -- elseif BoA
  1170. end
  1171. return text, unmodifiedText
  1172. end
  1173. local foundAnItemFromBags = false
  1174. function CanIMogIt:GetTooltipText(itemLink, bag, slot)
  1175. --[[
  1176. Gets the text to display on the tooltip from the itemLink.
  1177. If bag and slot are given, this will use the itemLink from
  1178. bag and slot instead.
  1179. Returns two things:
  1180. the text to display.
  1181. the unmodifiedText that can be used for lookup values.
  1182. ]]
  1183. if bag and slot then
  1184. itemLink = GetContainerItemLink(bag, slot)
  1185. if not itemLink then
  1186. if foundAnItemFromBags then
  1187. return "", ""
  1188. else
  1189. -- If we haven't found any items in the bags yet, then
  1190. -- it's likely that the inventory hasn't been loaded yet.
  1191. return nil
  1192. end
  1193. else
  1194. foundAnItemFromBags = true
  1195. end
  1196. end
  1197. if not itemLink then return "", "" end
  1198. if not CanIMogIt:IsReadyForCalculations(itemLink) then
  1199. return
  1200. end
  1201. local text = ""
  1202. local unmodifiedText = ""
  1203. if not CanIMogIt:PreLogicOptionsContinue(itemLink) then return "", "" end
  1204. -- Return cached items
  1205. local cachedData = CanIMogIt.cache:GetItemTextValue(itemLink)
  1206. if cachedData then
  1207. local cachedText, cachedUnmodifiedText = unpack(cachedData)
  1208. return cachedText, cachedUnmodifiedText
  1209. end
  1210. text, unmodifiedText = CanIMogIt:CalculateTooltipText(itemLink, bag, slot)
  1211. text = CanIMogIt:PostLogicOptionsText(text, unmodifiedText)
  1212. -- Update cached items
  1213. if text ~= nil then
  1214. CanIMogIt.cache:SetItemTextValue(itemLink, {text, unmodifiedText})
  1215. end
  1216. return text, unmodifiedText
  1217. end
  1218. function CanIMogIt:GetIconText(item