· 9 years ago · Nov 29, 2016, 01:16 AM
1SimpleStats = LibStub("AceAddon-3.0"):NewAddon("SimpleStats", "AceConsole-3.0", "AceEvent-3.0", "AceHook-3.0")
2
3-- Retrieve localization table
4local ADDON_NAME,namespace = ...
5local L = namespace.L
6
7SimpleStats.defaults = { -- Default settings
8 profile = {
9 RESISTANCE0_NAME = false,
10 ITEM_MOD_DAMAGE_PER_SECOND_SHORT = false,
11 ITEM_MOD_STAMINA_SHORT = true,
12 ITEM_MOD_AGILITY_SHORT = true,
13 ITEM_MOD_STRENGTH_SHORT = true,
14 ITEM_MOD_INTELLECT_SHORT = true,
15 ITEM_MOD_SPIRIT_SHORT = true,
16 ITEM_MOD_CRIT_RATING_SHORT = true,
17 ITEM_MOD_HASTE_RATING_SHORT = true,
18 ITEM_MOD_DODGE_RATING_SHORT = true,
19 ITEM_MOD_PARRY_RATING_SHORT = true,
20 ITEM_MOD_MASTERY_RATING_SHORT = true,
21 ITEM_MOD_CR_MULTISTRIKE_SHORT = true,
22 ITEM_MOD_VERSATILITY = true,
23 ITEM_MOD_CR_LIFESTEAL_SHORT = true,
24 ITEM_MOD_CR_AVOIDANCE_SHORT = true,
25 ITEM_MOD_CR_SPEED_SHORT = true,
26 --ITEM_MOD_CR_AMPLIFY_SHORT = true,
27 --ITEM_MOD_CR_CLEAVE_SHORT = true,
28 ITEM_MOD_SPELL_POWER_SHORT = true,
29 ITEM_MOD_PVP_POWER_SHORT = true,
30 ITEM_MOD_RESILIENCE_RATING_SHORT = true,
31 ITEM_MOD_CR_STURDINESS_SHORT = true,
32 resistance = true,
33 sockets = true,
34
35 usableweapons = 4,
36 usablearmor = 3,
37 minquality = 2,
38 showitemlevel = true,
39 hideondisabledprimaries = false,
40 hideonlowerilevel = false,
41 }
42}
43SimpleStats.options = { -- Settings GUI table
44 name = "SimpleStats",
45 handler = SimpleStats,
46 type = "group",
47 get = "get",
48 set = "set",
49
50 args = {
51 minquality = {
52 type = "select",
53 name = L["Minimum item quality"],
54 desc = L["Stat comparisons will only be shown on items with this quality or higher"],
55 values = {ITEM_QUALITY1_DESC, ITEM_QUALITY2_DESC, ITEM_QUALITY3_DESC, ITEM_QUALITY4_DESC},
56 order = 10,
57 width = "double",
58 },
59 usableweapons = {
60 type = "select",
61 name = L["Show on weapon types:"],
62 desc = L["Controls which types of weapons stat comparisons will be shown on"],
63 values = {L["All"], L["Only usable by current class"], L["Only useful to current class"], L["Only useful to current specialization"]},
64 order = 20,
65 width = "double",
66 },
67 usablearmor = {
68 type = "select",
69 name = L["Show on armor types:"],
70 desc = L["Controls which types of armor stat comparisons will be shown on"],
71 values = {L["All"], L["Only wearable by current class"], L["Only useful to current class"]},
72 order = 30,
73 width = "double",
74 },
75 hideondisabledprimaries = {
76 type = "toggle",
77 name = L["Hide comparison if disabled primary stat exists"],
78 desc = L["For example, hide comparisons on items with Agility if you're not showing Agility.\n\nComparisons will still be shown if the item also has a primary stat you're showing.\n\nOnly affects Strength, Agility, and Intellect."],
79 order = 40,
80 width = "full"
81 },
82 hideonlowerilevel = {
83 type = "toggle",
84 name = L["Hide comparison if item level is lower"],
85 order = 50,
86 width = "full"
87 },
88
89 header2 = {type = "header", name = L["Primary Stats"], order = 90},
90
91 ITEM_MOD_STRENGTH_SHORT = {
92 type = "toggle",
93 name = ITEM_MOD_STRENGTH_SHORT,
94 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_STRENGTH_SHORT),
95 order = 100,
96 width = "half"
97 },
98 ITEM_MOD_AGILITY_SHORT = {
99 type = "toggle",
100 name = ITEM_MOD_AGILITY_SHORT,
101 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_AGILITY_SHORT),
102 order = 110,
103 width = "half"
104 },
105 ITEM_MOD_INTELLECT_SHORT = {
106 type = "toggle",
107 name = ITEM_MOD_INTELLECT_SHORT,
108 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_INTELLECT_SHORT),
109 order = 120,
110 width = "half"
111 },
112 ITEM_MOD_STAMINA_SHORT = {
113 type = "toggle",
114 name = ITEM_MOD_STAMINA_SHORT,
115 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_STAMINA_SHORT),
116 order = 130,
117 width = "double"
118 },
119 RESISTANCE0_NAME = {
120 type = "toggle",
121 name = RESISTANCE0_NAME,
122 desc = format(L["Show %s in stat comparisons"], RESISTANCE0_NAME),
123 order = 140,
124 width = "half"
125 },
126 ITEM_MOD_DAMAGE_PER_SECOND_SHORT = {
127 type = "toggle",
128 name = STAT_DPS_SHORT,
129 desc = format(L["Show %s in stat comparisons"], STAT_DPS_SHORT),
130 order = 150,
131 width = "half"
132 },
133 showitemlevel = {
134 type = "toggle",
135 name = STAT_AVERAGE_ITEM_LEVEL,
136 desc = format(L["Show %s in stat comparisons"], STAT_AVERAGE_ITEM_LEVEL:lower()),
137 order = 155,
138 },
139
140 header3 = {type = "header", name = L["Secondary Stats"], order = 160},
141
142 ITEM_MOD_CRIT_RATING_SHORT = {
143 type = "toggle",
144 name = ITEM_MOD_CRIT_RATING_SHORT,
145 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_CRIT_RATING_SHORT),
146 order = 170,
147 },
148 ITEM_MOD_HASTE_RATING_SHORT = {
149 type = "toggle",
150 name = ITEM_MOD_HASTE_RATING_SHORT,
151 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_HASTE_RATING_SHORT),
152 order = 180,
153 },
154 ITEM_MOD_MASTERY_RATING_SHORT = {
155 type = "toggle",
156 name = ITEM_MOD_MASTERY_RATING_SHORT,
157 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_MASTERY_RATING_SHORT),
158 order = 190,
159 },
160 --[[ITEM_MOD_CR_MULTISTRIKE_SHORT = {
161 type = "toggle",
162 name = ITEM_MOD_CR_MULTISTRIKE_SHORT,
163 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_CR_MULTISTRIKE_SHORT),
164 order = 200,
165 },]]
166 ITEM_MOD_VERSATILITY = {
167 type = "toggle",
168 name = ITEM_MOD_VERSATILITY,
169 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_VERSATILITY),
170 order = 210,
171 },
172 resistance = {
173 type = "toggle",
174 name = RESISTANCE_LABEL,
175 desc = format(L["Show %s in stat comparisons"], RESISTANCE_LABEL:lower()),
176 order = 220,
177 },
178 --[[ITEM_MOD_SPIRIT_SHORT = {
179 type = "toggle",
180 name = ITEM_MOD_SPIRIT_SHORT,
181 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_SPIRIT_SHORT),
182 order = 230,
183 },]]
184 ITEM_MOD_DODGE_RATING_SHORT = {
185 type = "toggle",
186 name = ITEM_MOD_DODGE_RATING_SHORT,
187 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_DODGE_RATING_SHORT),
188 order = 240,
189 },
190 ITEM_MOD_PVP_POWER_SHORT = {
191 type = "toggle",
192 name = ITEM_MOD_PVP_POWER_SHORT,
193 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_PVP_POWER_SHORT),
194 order = 250,
195 },
196 ITEM_MOD_SPELL_POWER_SHORT = {
197 type = "toggle",
198 name = ITEM_MOD_SPELL_POWER_SHORT,
199 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_SPELL_POWER_SHORT),
200 order = 260,
201 },
202 ITEM_MOD_PARRY_RATING_SHORT = {
203 type = "toggle",
204 name = ITEM_MOD_PARRY_RATING_SHORT,
205 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_PARRY_RATING_SHORT),
206 order = 270,
207 },
208 ITEM_MOD_RESILIENCE_RATING_SHORT = {
209 type = "toggle",
210 name = ITEM_MOD_RESILIENCE_RATING_SHORT,
211 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_RESILIENCE_RATING_SHORT),
212 order = 280,
213 },
214
215
216 header4 = {type = "header", name = L["Minor Stats"], order = 290},
217
218 sockets = {
219 type = "toggle",
220 name = L["Gem Sockets"],
221 desc = format(L["Show %s in stat comparisons"], L["Gem Sockets"]:lower()),
222 order = 300,
223 },
224 ITEM_MOD_CR_STURDINESS_SHORT = {
225 type = "toggle",
226 name = ITEM_MOD_CR_STURDINESS_SHORT,
227 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_CR_STURDINESS_SHORT),
228 order = 310,
229 },
230 ITEM_MOD_CR_LIFESTEAL_SHORT = {
231 type = "toggle",
232 name = ITEM_MOD_CR_LIFESTEAL_SHORT,
233 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_CR_LIFESTEAL_SHORT),
234 order = 320,
235 },
236 ITEM_MOD_CR_AVOIDANCE_SHORT = {
237 type = "toggle",
238 name = ITEM_MOD_CR_AVOIDANCE_SHORT,
239 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_CR_AVOIDANCE_SHORT),
240 order = 330,
241 },
242 ITEM_MOD_CR_SPEED_SHORT = {
243 type = "toggle",
244 name = ITEM_MOD_CR_SPEED_SHORT,
245 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_CR_SPEED_SHORT),
246 order = 340,
247 },
248 --[[ITEM_MOD_CR_AMPLIFY_SHORT = {
249 type = "toggle",a
250 name = ITEM_MOD_CR_AMPLIFY_SHORT,
251 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_CR_AMPLIFY_SHORT),
252 order = 350,
253 },
254 ITEM_MOD_CR_CLEAVE_SHORT = {
255 type = "toggle",
256 name = ITEM_MOD_CR_CLEAVE_SHORT,
257 desc = format(L["Show %s in stat comparisons"], ITEM_MOD_CR_CLEAVE_SHORT),
258 order = 360,
259 },]]
260 }
261}
262function SimpleStats:get(key) -- Getter function for settings, used by options table
263 return self.db.profile[key[1]]
264end
265function SimpleStats:set(key,value) -- Setter function for settings, used by options table
266 self.db.profile[key[1]] = value
267end
268
269function SimpleStats:ChatCommand(input) -- Chat command handler
270 input = input:trim()
271 if not input or input == "" then
272 InterfaceOptionsFrame_OpenToCategory(self.optionsFrame)
273 InterfaceOptionsFrame_OpenToCategory(self.optionsFrame)
274
275 elseif (input == "v" or input == "ver" or input == "version") then
276 self:Print(GAME_VERSION_LABEL..": "..GetAddOnMetadata("SimpleStats","Version"))
277
278 elseif (input == "profile" or input == "profiles") then
279 InterfaceOptionsFrame_OpenToCategory(self.profileFrame)
280 InterfaceOptionsFrame_OpenToCategory(self.profileFrame)
281
282 else
283 LibStub("AceConfigCmd-3.0").HandleCommand(self, "ss", "SimpleStats", input)
284 end
285end
286
287function SimpleStats:SortStats(changedStats) -- Takes a table of stat changes, returns a new table sorted by logical stat order and with positives first
288 local increasedStats = {} -- positive increases
289 local decreasedStats = {} -- negative increases
290 local sortedStats = {} -- final returned table
291
292 -- Sort into two tables, positives and negatives
293 for statName,statChange in pairs(changedStats) do
294 if statChange > 0 then
295 increasedStats[statName] = statChange
296 elseif statChange < 0 then
297 decreasedStats[statName] = statChange
298 end
299 end
300
301 -- Add the positive stat changes, in the correct position based on the 'order' table, to the final table
302 for statName,statChange in pairs(increasedStats) do
303 if statName == "ITEM_MOD_DAMAGE_PER_SECOND_SHORT" then
304 statChange = round(statChange, 2)
305 else
306 statChange = math.floor(statChange)
307 end
308
309 if self.order[statName] then
310 local pos = self.order[statName]
311 sortedStats[pos] = {statName,statChange}
312 end
313 end
314
315 -- Finally, do the same thing for the negative stat changes, except offset them by #self.order so they always appear after the positive changes
316 for statName,statChange in pairs(decreasedStats) do
317 if statName == "ITEM_MOD_DAMAGE_PER_SECOND_SHORT" then
318 statChange = round(statChange, 2)
319 else
320 statChange = math.floor(statChange)
321 end
322 if self.order[statName] then
323 local pos = self.order[statName] + self.order.count
324 sortedStats[pos] = {statName,statChange}
325 end
326 end
327
328 -- Compress the table indexes
329 local cleanStats = {}
330 for k,stat in orderedPairs(sortedStats) do
331 tinsert(cleanStats, stat)
332 end
333
334 return cleanStats
335end
336
337function SimpleStats:StatIsEnabled(statName) -- Returns whether the given stat should be shown. Resistances and gem sockets are handled specially
338 if strmatch(statName,"RESISTANCE_SHORT") then
339 return self.db.profile.resistance
340 elseif strmatch(statName, "EMPTY_SOCKET") then
341 return self.db.profile.sockets
342 elseif statName == "STAT_AVERAGE_ITEM_LEVEL" then
343 return self.db.profile.showitemlevel
344 else
345 return self.db.profile[statName]
346 end
347end
348
349function SimpleStats:GetStatChangeLines(newStats,currentStats, doIndent)-- Takes new stats, existing stats, and whether to indent (optional) and returns the stat change lines
350 local statChanges = {}
351
352 -- Add each new stat into the statChanges table
353 for statName,statValue in pairs(newStats) do
354 if currentStats[statName] then -- Stat exists on both, do subtraction
355 statChanges[statName] = newStats[statName] - currentStats[statName]
356 else -- New stat, just use the value
357 statChanges[statName] = newStats[statName]
358 end
359 end
360
361 -- For each current stat that doesn't exist on new, add a -xxx stat change to the statChanges table
362 for statName,statValue in pairs(currentStats) do
363 if not newStats[statName] then
364 statChanges[statName] = -statValue
365 end
366 end
367
368 -- Store item level change external to statChanges table since it's much harder to extract after sorting
369 local itemLevelChange = statChanges.STAT_AVERAGE_ITEM_LEVEL
370
371 -- Sort the stats in the proper order (primary first, then secondary, etc.)
372 statChanges = self:SortStats(statChanges)
373
374 -- If there's no stat changes, don't bother printing anything
375 if #statChanges == 0 then return {} end
376
377 local lines = {}
378 local indent = doIndent and " " or ""
379
380 if self.db.profile.showitemlevel then
381 if itemLevelChange > 0 then
382 table.insert(lines, {text=indent.."|cffbbff00+"..itemLevelChange.." |cffffe100"..STAT_AVERAGE_ITEM_LEVEL})
383 elseif itemLevelChange < 0 then
384 table.insert(lines, {text=indent.."|cffff7423"..itemLevelChange.." |cffffe100"..STAT_AVERAGE_ITEM_LEVEL})
385 end
386 end
387
388 -- Quit if the item level is lower than what we have, and that option is enabled
389 if SimpleStats.db.profile.hideonlowerilevel and itemLevelChange < 0 then
390 return lines
391 end
392
393 -- Used later to determine if any stats were actually shown; if not, don't show the header line ("Trinket 1:", etc.)
394 local aStatIsShown = false
395
396 for k,stat in orderedPairs(statChanges) do
397 statName = stat[1]
398
399 -- If it's a resistance, the localized strings are wrong and need to be converted to a second style to work
400 convertedName = self.resistanceNames[statName] or statName
401
402 statValue = stat[2]+0
403 if self:StatIsEnabled(statName) and statName ~= "STAT_AVERAGE_ITEM_LEVEL" then
404 aStatIsShown = true
405 if (statValue > 0) then
406 if self.noNumberStats[convertedName] then
407 table.insert(lines, {text=indent.."|cff00ff00+".._G[convertedName]})
408 else
409 table.insert(lines, {text=indent.."|cff00ff00+"..statValue.."|cffffffff ".._G[convertedName]})
410 end
411 elseif (statValue < 0) then
412 if self.noNumberStats[convertedName] then
413 table.insert(lines, {text=indent.."|cffff0000-".._G[convertedName]})
414 else
415 table.insert(lines, {text=indent.."|cffff0000"..statValue.."|cffffffff ".._G[convertedName]})
416 end
417 end
418 end
419 end
420
421 return lines
422end
423
424function SimpleStats:CombineItemStats(itemLink1,itemLink2) -- Returns a table containing the combined stat bonuses of zero, one, or two items
425 local combinedStats = {}
426 if itemLink1 then
427 combinedStats = GetItemStats(itemLink1)
428
429 -- Add item level to stat table
430 local itemLevel1 = SimpleStats:GetTrueItemLevel(itemLink1)
431 combinedStats["STAT_AVERAGE_ITEM_LEVEL"] = itemLevel1
432
433 if itemLink2 then
434 -- Set item level to the average of the two items
435 local itemLevel2 = SimpleStats:GetTrueItemLevel(itemLink2)
436 combinedStats["STAT_AVERAGE_ITEM_LEVEL"] = math.floor((itemLevel1+itemLevel2)/2)
437
438 for statName,statValue in pairs(GetItemStats(itemLink2)) do
439 if combinedStats[statName] then
440 combinedStats[statName] = combinedStats[statName] + statValue
441 else
442 combinedStats[statName] = statValue
443 end
444 end
445 end
446 end
447
448 return combinedStats
449end
450
451function SimpleStats:CheckWeaponType(weaponType) -- Determines whether this weapon type should have stats printed for it (taking into account settings)
452 local _,class = UnitClass("player")
453 local specSlot = GetSpecialization()
454 local usability, specID
455
456 if specSlot then
457 local specID = GetSpecializationInfo(specSlot)
458 if self.usableWeapons[class][specID] and self.usableWeapons[class][specID][weaponType] then
459 usability = self.usableWeapons[class][specID][weaponType]
460 -- weapon matches our spec and is usable
461 else
462 usability = self.usableWeapons[class][weaponType]
463 -- weapon doesn't match our spec, but is still usable
464 end
465 else
466 usability = self.usableWeapons[class][weaponType]
467 -- we don't have a spec, weapon is usable
468 end
469
470 if not usability and self.db.profile.usableweapons > 1 then
471 return false
472 end
473
474 if self.db.profile.usableweapons == 1 then -- Show on all weapons
475 return true
476 elseif self.db.profile.usableweapons == 2 then -- Show on usable weapons
477 return usability >= 1
478 elseif self.db.profile.usableweapons == 3 then -- Show on useful weapons
479 return usability >= 2
480 elseif self.db.profile.usableweapons == 4 then -- Show on weapons useful to current spec
481 return usability == 3 or (usability == 2 and not specID)
482 end
483end
484
485function SimpleStats:CheckArmorType(armorType) -- Determines whether this armor type should have stats printed for it (taking into account settings)
486 if armorType == LE_ITEM_ARMOR_GENERIC then
487 return true -- If it's misc. armor (trinket, ring, etc.), always show
488 end
489
490 local _,class = UnitClass("player")
491 local usability = self.usableArmor[class][armorType]
492
493 if not usability and self.db.profile.usablearmor > 1 then
494 return false
495 end
496
497 if self.db.profile.usablearmor == 1 then -- Show on all armor
498 return true
499 elseif self.db.profile.usablearmor == 2 then -- Show on wearable armor
500 return usability >= 1
501 elseif self.db.profile.usablearmor == 3 then -- Show on useful armor
502 return usability == 2
503 end
504end
505
506function SimpleStats:IsWeapon2H(itemLink) -- Returns whether the given weapon takes up both weapon slots
507 if itemLink then
508 local _,_,_,_,_,_,_,_,invType,_,_,classID,subclassID = GetItemInfo(itemLink)
509
510 -- RANGEDRIGHT requires an additional check since wands are RANGEDRIGHT but CAN be wielded with an off-hand
511 if invType == "INVTYPE_2HWEAPON"
512 or invType == "INVTYPE_RANGED"
513 or (invType == "INVTYPE_RANGEDRIGHT" and subclassID ~= LE_ITEM_WEAPON_WAND) then
514 return true
515 end
516 end
517
518 return false
519end
520
521function SimpleStats:AreIdentical(itemLink1, itemLink2) -- Determines whether two items are identical (takes into account id, suffix, difficulty, and bonuses)
522 -- If either item is missing, they're obviously not identical
523 if not itemLink1 or not itemLink2 then
524 return false
525 end
526
527 -- Convert itemLinks to itemStrings
528 itemLink1 = string.match(itemLink1, "item[%-?%d:]+")
529 itemLink2 = string.match(itemLink2, "item[%-?%d:]+")
530
531 local _, itemID1, _, _, _, _, _, suffixID1, _, _, _, upgradeType1, instanceDifficultyID1 = strsplit(":", itemLink1)
532 local _, itemID2, _, _, _, _, _, suffixID2, _, _, _, upgradeType2, instanceDifficultyID2 = strsplit(":", itemLink2)
533
534 local bonuses1 = string.match(itemLink1, "item:%d*:%d*:%d*:%d*:%d*:%d*:%d*:%d*:%d*:%d*:%d*:%d*:(.*)")
535 local bonuses2 = string.match(itemLink2, "item:%d*:%d*:%d*:%d*:%d*:%d*:%d*:%d*:%d*:%d*:%d*:%d*:(.*)")
536
537 return (
538 itemID1 == itemID2
539 and suffixID1 == suffixID2
540 and upgradeType1 == upgradeType2
541 and instanceDifficultyID1 == instanceDifficultyID2
542 and bonuses1 == bonuses2
543 )
544end
545
546function SimpleStats:GetTrueItemLevel(itemString) -- Scans an item tooltip for the TRUE itemlevel (for heirlooms and Timewalker gear, for example)
547 -- Copied and slightly modified from LibItemUpgradeInfo by eridius
548 local _, itemLink, rarity, itemLevel = GetItemInfo(itemString)
549 local ilvl = self.heirloomCache[itemLink]
550
551 if ilvl ~= nil then
552 return ilvl, true
553 end
554
555 if not self.scanningTooltip then
556 self.scanningTooltip = _G.CreateFrame("GameTooltip", "LibItemUpgradeInfoTooltip", nil, "GameTooltipTemplate")
557 self.scanningTooltip:SetOwner(_G.WorldFrame, "ANCHOR_NONE")
558 end
559
560 self.scanningTooltip:ClearLines()
561 self.scanningTooltip:SetHyperlink(itemLink)
562
563 for i = 2, 4 do
564 local label, text = _G["LibItemUpgradeInfoTooltipTextLeft"..i], nil
565 if label then text=label:GetText() end
566 if text then
567 ilvl = tonumber(text:match(self.itemLevelPattern))
568 if ilvl ~= nil then
569 self.heirloomCache[itemLink] = ilvl
570 self.scanningTooltip:Hide()
571 return ilvl, true
572 end
573 end
574 end
575
576 self.scanningTooltip:Hide()
577 return itemLevel, false
578end
579
580function SimpleStats:MergeTooltipLines(lines, newLines, headerLine) -- Takes a table of lines and a (possibly blank) table of new lines and appends them, adding an optional header line if there's any new lines
581 if newLines then
582 if headerLine then
583 tinsert(lines, headerLine)
584 end
585
586 for k,line in pairs(newLines) do
587 tinsert(lines, line)
588 end
589 end
590 return lines
591end
592
593function SimpleStats:CanDualWield() -- Determines whether the character can dual-wield
594 local _,class = UnitClass("player")
595 local level = UnitLevel("player")
596 local specSlot = GetSpecialization()
597 local specID
598
599 if specSlot then
600 specID = GetSpecializationInfo(specSlot)
601 end
602
603 if class == "HUNTER" -- Hunters can always DW
604 or class == "DEATHKNIGHT" -- DKs can always DW
605 or class == "ROGUE" -- Rogues can always DW
606 or (class == "WARRIOR" and specID == 72) -- Fury warriors can DW
607 or (class == "SHAMAN" and specID == 263) -- Enhance shamans can DW
608 or (class == "MONK" and (specID == 268 or specID == 269)) then -- Brewmaster and Windwalker monks can DW
609 return true
610 else
611 return false
612 end
613end
614
615function SimpleStats:AddLinesToTooltip(tooltip,lines) -- Adds the given lines to the given tooltip
616 local printedSeparator = false
617
618 for k,line in pairs(lines) do
619 -- If it has child line elements, print as a header and print all the children indented
620 if line.lines and #line.lines > 0 then
621 -- Print a separator if we haven't already
622 if not printedSeparator then
623 tooltip:AddLine(" ")
624 printedSeparator = true
625 end
626
627 tooltip:AddLine(line.text)
628
629 for k,subline in pairs(line.lines) do
630 tooltip:AddLine(" "..subline.text)
631 end
632 elseif not line.lines then -- not is there so above if doesn't default to this if line.lines exists and #line.lines == 0
633 -- Print a separator if we haven't already
634 if not printedSeparator then
635 tooltip:AddLine(" ")
636 printedSeparator = true
637 end
638
639 tooltip:AddLine(line.text)
640 end
641 end
642
643 -- :Show()ing the tooltip forces it to recalculate its sized based on contents
644 tooltip:Show()
645end
646
647function SimpleStats:HandleTooltip(self, ...) -- Tooltip handler, parses a tooltip and modifies it with the stat changes
648 local _,itemLink = self:GetItem()
649 if not itemLink then return end -- Quit if this isn't an item tooltip
650
651 local _,_,rarity,_,_,_,_,_,invType,_,_,classID,subclassID = GetItemInfo(itemLink)
652 local itemID = tonumber(strmatch(itemLink,"item:(%d+):"))
653
654 -- Quit if: -- Quit if:
655 if (classID ~= LE_ITEM_CLASS_ARMOR and classID ~= LE_ITEM_CLASS_WEAPON) -- It's not armor or a weapon
656 or (invType == "INVTYPE_TABARD" or invType == "INVTYPE_BODY") -- It's a shirt or tabard
657 or (rarity < SimpleStats.db.profile.minquality) -- It's below our current quality threshold
658 or (classID == LE_ITEM_CLASS_ARMOR and invType ~= "INVTYPE_CLOAK" and not SimpleStats:CheckArmorType(subclassID)) -- It's armor and doesn't match our armor settings (but always show cloth->cloaks)
659 or (classID == LE_ITEM_CLASS_WEAPON and not SimpleStats:CheckWeaponType(subclassID)) -- It's a weapon and doesn't match our weapon settings
660 or (SimpleStats.blacklistedItems[itemID]) -- It's a blacklisted item
661 or (strmatch(GetMouseFocus() and GetMouseFocus():GetName() or "", "^Character.*Slot$")) then -- We're looking at an equipped item
662 return
663 end
664
665 -- Get data on currently equipped items
666 local equippedItems = {[1]={level=0},[2]={slotExists=false, level=0}}
667 local firstItemLink = GetInventoryItemLink("player",SimpleStats.invTypes[invType])
668 if (firstItemLink) then
669 local firstItemLevel = SimpleStats:GetTrueItemLevel(firstItemLink)
670
671 equippedItems[1] = {
672 id = tonumber(strmatch(firstItemLink,"item:(%d+):")),
673 level = firstItemLevel,
674 link = firstItemLink
675 }
676 end
677
678 -- Get data on second equipped item if in a dual slot (ring, trinket, weapon)
679
680 if SimpleStats.secondSlotID[SimpleStats.invTypes[invType]] then
681 equippedItems[2].slotExists = true
682
683 local secondItemLink = GetInventoryItemLink("player",SimpleStats.secondSlotID[SimpleStats.invTypes[invType]])
684 if (secondItemLink) then
685 local secondItemLevel = SimpleStats:GetTrueItemLevel(secondItemLink)
686
687 equippedItems[2] = {
688 id = tonumber(strmatch(secondItemLink,"item:(%d+):")),
689 level = secondItemLevel,
690 link = secondItemLink,
691 slotExists = true
692 }
693 end
694 end
695
696 -- Quit if the item is identical to the only (most things)/both (rings, trinkets, etc.) equipped item
697 if SimpleStats:AreIdentical(itemLink, equippedItems[1].link)
698 and (SimpleStats:AreIdentical(itemLink, equippedItems[2].link) or not equippedItems[2].slotExists) then
699 return
700 end
701
702 -- Retrieve data about the main-hand weapon for use later
703 local currentMainHand = {}
704 if GetInventoryItemLink("player",16) then
705 local _,mhItemLink,_,_,_,_,mhSubType,_,mhInvType = GetItemInfo(GetInventoryItemLink("player",16))
706 local mhItemLevel = SimpleStats:GetTrueItemLevel(GetInventoryItemLink("player",16))
707
708 currentMainHand = {
709 link = mhItemLink,
710 level = mhItemLevel,
711 subType = mhSubType,
712 invType = mhInvType,
713 }
714 end
715
716 local equippedIs2HWeapon = SimpleStats:IsWeapon2H(currentMainHand.link)
717 local newStats = GetItemStats(itemLink)
718
719 local itemLevel = SimpleStats:GetTrueItemLevel(itemLink)
720 newStats["STAT_AVERAGE_ITEM_LEVEL"] = itemLevel
721
722 -- Collect primary stats on the item for use below
723 local primaryStats = {agi=0, str=0, int=0}
724 for statName,statValue in pairs(newStats) do
725 if statName == "ITEM_MOD_AGILITY_SHORT" then primaryStats.agi = statValue
726 elseif statName == "ITEM_MOD_STRENGTH_SHORT" then primaryStats.str = statValue
727 elseif statName == "ITEM_MOD_INTELLECT_SHORT" then primaryStats.int = statValue
728 end
729 end
730
731 -- Hide comparison if it has primary stats, has primary stats we're not showing, and DOESN'T have stats we're showing
732 -- So an Int item is hidden if we're not showing Int, but if it also has Agi and we're showing that, show it since it's still useful
733 -- But if it has no primary stats at all (like trinkets, often), never hide it
734 if (primaryStats.agi > 0 or primaryStats.int > 0 or primaryStats.str > 0)
735 and SimpleStats.db.profile.hideondisabledprimaries
736 and ((primaryStats.agi > 0 and not SimpleStats.db.profile.ITEM_MOD_AGILITY_SHORT) or primaryStats.agi == 0)
737 and ((primaryStats.str > 0 and not SimpleStats.db.profile.ITEM_MOD_STRENGTH_SHORT) or primaryStats.str == 0)
738 and ((primaryStats.int > 0 and not SimpleStats.db.profile.ITEM_MOD_INTELLECT_SHORT) or primaryStats.int == 0) then
739 return
740 end
741
742 local tooltipLines = {}
743
744 -- If the item is a trinket, show stat changes for both trinkets, but if we don't have any trinkets, just show one stat comparison ('else' below)
745 if invType == "INVTYPE_TRINKET" and (equippedItems[1].link or equippedItems[2].link) then
746 tinsert(tooltipLines, {text="Trinket 1:", lines= SimpleStats:GetStatChangeLines(newStats, SimpleStats:CombineItemStats(equippedItems[1].link), true) })
747 tinsert(tooltipLines, {text="Trinket 2:", lines= SimpleStats:GetStatChangeLines(newStats, SimpleStats:CombineItemStats(equippedItems[2].link), true) })
748
749 -- If the item is a ring, show stat changes for both rings, but if we don't have any rings, just show one stat comparison ('else' below)
750 elseif invType == "INVTYPE_FINGER" and (equippedItems[1].link or equippedItems[2].link) then
751 tinsert(tooltipLines, {text="Ring 1:", lines= SimpleStats:GetStatChangeLines(newStats, SimpleStats:CombineItemStats(equippedItems[1].link), true) })
752 tinsert(tooltipLines, {text="Ring 2:", lines= SimpleStats:GetStatChangeLines(newStats, SimpleStats:CombineItemStats(equippedItems[2].link), true) })
753
754 -- If the item is a 1H weapon, show stat changes for both weapon slots, but if we don't have any weapons, just show one stat comparison ('else' below)
755 -- Also, if we're wielding a 2H, show a standard comparison against it ('else' below)
756 elseif invType == "INVTYPE_WEAPON" and (equippedItems[1].link or equippedItems[2].link) and not equippedIs2HWeapon then
757 -- If the character can't dual wield, don't show two comparisons since it can only go in the first slot
758 if SimpleStats:CanDualWield() then
759 tinsert(tooltipLines, {text="Weapon 1:", lines= SimpleStats:GetStatChangeLines(newStats, SimpleStats:CombineItemStats(equippedItems[1].link), true) })
760 tinsert(tooltipLines, {text="Weapon 2:", lines= SimpleStats:GetStatChangeLines(newStats, SimpleStats:CombineItemStats(equippedItems[2].link), true) })
761 else -- Functionally the same as 'else' below
762 tooltipLines = SimpleStats:GetStatChangeLines(newStats, SimpleStats:CombineItemStats(equippedItems[1].link))
763 end
764
765 -- Looking at a 2H and two 1H are equipped, so combine their stats
766 elseif SimpleStats:IsWeapon2H(itemLink) and equippedItems[1].id and equippedItems[2].id then
767 tooltipLines = SimpleStats:GetStatChangeLines(newStats, SimpleStats:CombineItemStats(equippedItems[1].link, equippedItems[2].link))
768
769 -- Looking at a 2H and something is in the off-hand slot, so compare to that
770 elseif SimpleStats:IsWeapon2H(itemLink) and GetInventoryItemLink("player",17) then
771 tooltipLines = SimpleStats:GetStatChangeLines(newStats, SimpleStats:CombineItemStats(GetInventoryItemLink("player",17)))
772
773 -- Looking at an off-hand, and a 2h weapon is in the first wep slot, so compare to that
774 elseif SimpleStats.invTypes[invType] == 17 and equippedIs2HWeapon then
775 tooltipLines = SimpleStats:GetStatChangeLines(newStats, SimpleStats:CombineItemStats(currentMainHand.link))
776
777 -- Otherwise, print a single stat comparison
778 else
779 tooltipLines = SimpleStats:GetStatChangeLines(newStats, SimpleStats:CombineItemStats(equippedItems[1].link))
780 end
781
782 -- Finally, augment the tooltip
783 SimpleStats:AddLinesToTooltip(self,tooltipLines)
784end
785
786function SimpleStats:SetupTables() -- Sets up all of the utility/data tables used by the addon
787
788 -- These items should never show a stat comparison (most of them are Use: items with a classID of LE_ITEM_CLASS_ARMOR/WEAPON)
789 local blacklist = {
790 114052,114053,114094,114096,114097,114098,114099,114100,114101,114105,114108, -- 512-610 mission gear
791 114057,114058,114059,114063,114066,114068,114109, -- 615 mission gear
792 114069,114070,114071,114075,114078,114080,114110, -- 630 mission gear
793 114082,114083,114084,114085,114086,114087,114112, -- 645 mission gear
794 114745,114808,114822,114807,114806,114746, -- Follower armor upgrades
795 114128,114129,114131,114616,114081,114622, -- Follower weapon upgrades
796 120313, -- Sanketsu
797
798 124550,124551,124552,124553,124554,124555,124556,124557,124558,124559,124560,
799 124561,124562,127779,127780,127783,127784,127792,127793,127796,127797,127799, -- Baleful tokens
800 127800,127805,127806,127809,127810,127818,127819,127822,127823,128348,
801
802 128225, -- Empowered Apexis Fragment
803 }
804
805 -- Inverse the blacklist so we can do self.blacklistedItems[itemID]
806 self.blacklistedItems = {}
807 for k,itemID in pairs(blacklist) do
808 self.blacklistedItems[itemID] = true
809 end
810
811 -- Maps between INVTYPEs and slot IDs
812 self.invTypes = {
813 INVTYPE_HEAD = 1,
814 INVTYPE_NECK = 2,
815 INVTYPE_SHOULDER = 3,
816 INVTYPE_BODY = 4,
817 INVTYPE_CHEST = 5,
818 INVTYPE_ROBE = 5,
819 INVTYPE_WAIST = 6,
820 INVTYPE_LEGS = 7,
821 INVTYPE_FEET = 8,
822 INVTYPE_WRIST = 9,
823 INVTYPE_HAND = 10,
824 INVTYPE_FINGER = 11, -- also 12
825 INVTYPE_TRINKET = 13, -- also 14
826 INVTYPE_CLOAK = 15,
827 INVTYPE_WEAPON = 16, -- also 17
828 INVTYPE_SHIELD = 17,
829 INVTYPE_2HWEAPON = 16,
830 INVTYPE_WEAPONMAINHAND = 16,
831 INVTYPE_WEAPONOFFHAND = 17,
832 INVTYPE_HOLDABLE = 17,
833 INVTYPE_RANGED = 16,
834 INVTYPE_RANGEDRIGHT = 16,
835 INVTYPE_TABARD = 19,
836 }
837
838 -- Used both to determine whether a slot has a sibling slot, and if so, what its slotID is
839 self.secondSlotID = {
840 [11] = 12,
841 [12] = 11,
842 [13] = 14,
843 [14] = 13,
844 [16] = 17,
845 [17] = 16,
846 }
847
848 -- Define the order that stats should appear in in tooltips
849 local orderInverse = {
850 "STAT_AVERAGE_ITEM_LEVEL",
851 "RESISTANCE0_NAME",
852 "ITEM_MOD_DAMAGE_PER_SECOND_SHORT",
853 "ITEM_MOD_AGILITY_SHORT",
854 "ITEM_MOD_STRENGTH_SHORT",
855 "ITEM_MOD_INTELLECT_SHORT",
856 "ITEM_MOD_STAMINA_SHORT",
857 "ITEM_MOD_SPIRIT_SHORT",
858 "ITEM_MOD_HIT_RATING_SHORT",
859 "ITEM_MOD_CRIT_RATING_SHORT",
860 "ITEM_MOD_HASTE_RATING_SHORT",
861 "ITEM_MOD_EXPERTISE_RATING_SHORT",
862 "ITEM_MOD_DODGE_RATING_SHORT",
863 "ITEM_MOD_PARRY_RATING_SHORT",
864 "ITEM_MOD_MASTERY_RATING_SHORT",
865 "ITEM_MOD_SPELL_POWER_SHORT",
866 "ITEM_MOD_SPELL_PENETRATION_SHORT",
867 "ITEM_MOD_ATTACK_POWER_SHORT",
868 "ITEM_MOD_CR_MULTISTRIKE_SHORT",
869 "ITEM_MOD_VERSATILITY",
870 "ITEM_MOD_PVP_POWER_SHORT",
871 "ITEM_MOD_RESILIENCE_RATING_SHORT",
872 "RESISTANCE1_NAME",
873 "RESISTANCE2_NAME",
874 "RESISTANCE3_NAME",
875 "RESISTANCE4_NAME",
876 "RESISTANCE5_NAME",
877 "RESISTANCE6_NAME",
878 "EMPTY_SOCKET_META",
879 "EMPTY_SOCKET_PRISMATIC",
880 "EMPTY_SOCKET_COGWHEEL",
881 "EMPTY_SOCKET_RED",
882 "EMPTY_SOCKET_BLUE",
883 "EMPTY_SOCKET_YELLOW",
884 "ITEM_MOD_CR_STURDINESS_SHORT",
885 "ITEM_MOD_CR_LIFESTEAL_SHORT",
886 "ITEM_MOD_CR_AVOIDANCE_SHORT",
887 "ITEM_MOD_CR_SPEED_SHORT",
888 --"ITEM_MOD_CR_AMPLIFY_SHORT",
889 --"ITEM_MOD_CR_CLEAVE_SHORT",
890 }
891
892 -- Convert order table to "STAT_NAME" => index instead of "STAT_NAME"
893 self.order = {}
894
895 local count = 0
896 for k,statName in pairs(orderInverse) do
897 count = count+1
898 self.order[statName] = k
899 end
900
901 -- Store a count of items in self.order for later use in SortStats()
902 self.order.count = count
903
904 -- Create mapping between the two different resistance names used
905 self.resistanceNames = {
906 ITEM_MOD_HOLY_RESISTANCE_SHORT = "RESISTANCE1_NAME",
907 ITEM_MOD_FIRE_RESISTANCE_SHORT = "RESISTANCE2_NAME",
908 ITEM_MOD_NATURE_RESISTANCE_SHORT = "RESISTANCE3_NAME",
909 ITEM_MOD_FROST_RESISTANCE_SHORT = "RESISTANCE4_NAME",
910 ITEM_MOD_SHADOW_RESISTANCE_SHORT = "RESISTANCE5_NAME",
911 ITEM_MOD_ARCANE_RESISTANCE_SHORT = "RESISTANCE6_NAME",
912 RESISTANCE1_NAME = "ITEM_MOD_HOLY_RESISTANCE_SHORT",
913 RESISTANCE2_NAME = "ITEM_MOD_FIRE_RESISTANCE_SHORT",
914 RESISTANCE3_NAME = "ITEM_MOD_NATURE_RESISTANCE_SHORT",
915 RESISTANCE4_NAME = "ITEM_MOD_FROST_RESISTANCE_SHORT",
916 RESISTANCE5_NAME = "ITEM_MOD_SHADOW_RESISTANCE_SHORT",
917 RESISTANCE6_NAME = "ITEM_MOD_ARCANE_RESISTANCE_SHORT"
918 }
919
920 -- The following stats should not show a number (+Indestructible instead of +57 Indestructible, for example)
921 self.noNumberStats = {
922 ITEM_MOD_CR_STURDINESS_SHORT = true,
923 }
924
925 -- 3=Useful for spec, 2=Useful for class, 1=Usable
926 self.usableWeapons = {
927 WARLOCK = {
928 [LE_ITEM_WEAPON_SWORD1H] = 3,
929 [LE_ITEM_WEAPON_DAGGER] = 3,
930 [LE_ITEM_WEAPON_STAFF] = 3,
931 [LE_ITEM_WEAPON_WAND] = 3,
932 },
933 MAGE = {
934 [LE_ITEM_WEAPON_SWORD1H] = 3,
935 [LE_ITEM_WEAPON_DAGGER] = 3,
936 [LE_ITEM_WEAPON_STAFF] = 3,
937 [LE_ITEM_WEAPON_WAND] = 3,
938 },
939 PRIEST = {
940 [LE_ITEM_WEAPON_MACE1H] = 3,
941 [LE_ITEM_WEAPON_DAGGER] = 3,
942 [LE_ITEM_WEAPON_STAFF] = 3,
943 [LE_ITEM_WEAPON_WAND] = 3,
944 },
945 DRUID = {
946 [LE_ITEM_WEAPON_MACE1H] = 2,
947 [LE_ITEM_WEAPON_MACE2H] = 2,
948 [LE_ITEM_WEAPON_DAGGER] = 2,
949 [LE_ITEM_WEAPON_UNARMED] = 2,
950 [LE_ITEM_WEAPON_POLEARM] = 2,
951 [LE_ITEM_WEAPON_STAFF] = 2,
952
953 [102] = { -- Balance
954 [LE_ITEM_WEAPON_MACE1H] = 3,
955 [LE_ITEM_WEAPON_MACE2H] = 3,
956 [LE_ITEM_WEAPON_DAGGER] = 3,
957 [LE_ITEM_WEAPON_UNARMED] = 3,
958 [LE_ITEM_WEAPON_POLEARM] = 3,
959 [LE_ITEM_WEAPON_STAFF] = 3,
960 },
961 [103] = { -- Feral
962 [LE_ITEM_WEAPON_MACE2H] = 3,
963 [LE_ITEM_WEAPON_POLEARM] = 3,
964 [LE_ITEM_WEAPON_STAFF] = 3,
965 },
966 [104] = { -- Guardian
967 [LE_ITEM_WEAPON_MACE2H] = 3,
968 [LE_ITEM_WEAPON_POLEARM] = 3,
969 [LE_ITEM_WEAPON_STAFF] = 3,
970 },
971 [105] = { -- Restoration
972 [LE_ITEM_WEAPON_MACE1H] = 3,
973 [LE_ITEM_WEAPON_MACE2H] = 3,
974 [LE_ITEM_WEAPON_DAGGER] = 3,
975 [LE_ITEM_WEAPON_UNARMED] = 3,
976 [LE_ITEM_WEAPON_POLEARM] = 3,
977 [LE_ITEM_WEAPON_STAFF] = 3,
978 },
979 },
980 ROGUE = {
981 [LE_ITEM_WEAPON_BOWS] = 1,
982 [LE_ITEM_WEAPON_CROSSBOW] = 1,
983 [LE_ITEM_WEAPON_GUNS] = 1,
984
985 [LE_ITEM_WEAPON_AXE1H] = 2,
986 [LE_ITEM_WEAPON_MACE1H] = 2,
987 [LE_ITEM_WEAPON_SWORD1H] = 2,
988 [LE_ITEM_WEAPON_DAGGER] = 2,
989 [LE_ITEM_WEAPON_UNARMED] = 2,
990
991 [259] = { -- Assassination
992 [LE_ITEM_WEAPON_DAGGER] = 3,
993 },
994 [260] = { -- Combat
995 [LE_ITEM_WEAPON_AXE1H] = 3,
996 [LE_ITEM_WEAPON_MACE1H] = 3,
997 [LE_ITEM_WEAPON_SWORD1H] = 3,
998 [LE_ITEM_WEAPON_DAGGER] = 3,
999 [LE_ITEM_WEAPON_UNARMED] = 3,
1000 },
1001 [261] = { -- Subtlety
1002 [LE_ITEM_WEAPON_AXE1H] = 3,
1003 [LE_ITEM_WEAPON_MACE1H] = 3,
1004 [LE_ITEM_WEAPON_SWORD1H] = 3,
1005 [LE_ITEM_WEAPON_DAGGER] = 3,
1006 [LE_ITEM_WEAPON_UNARMED] = 3,
1007 },
1008 },
1009 MONK = {
1010 [LE_ITEM_WEAPON_AXE1H] = 3,
1011 [LE_ITEM_WEAPON_MACE1H] = 3,
1012 [LE_ITEM_WEAPON_SWORD1H] = 3,
1013 [LE_ITEM_WEAPON_UNARMED] = 3,
1014 [LE_ITEM_WEAPON_POLEARM] = 3,
1015 [LE_ITEM_WEAPON_STAFF] = 3,
1016 },
1017 SHAMAN = {
1018 [LE_ITEM_WEAPON_AXE1H] = 2,
1019 [LE_ITEM_WEAPON_MACE1H] = 2,
1020 [LE_ITEM_WEAPON_AXE2H] = 2,
1021 [LE_ITEM_WEAPON_MACE2H] = 2,
1022 [LE_ITEM_WEAPON_DAGGER] = 2,
1023 [LE_ITEM_WEAPON_UNARMED] = 2,
1024 [LE_ITEM_WEAPON_STAFF] = 2,
1025
1026 [262] = { -- Elemental
1027 [LE_ITEM_WEAPON_AXE1H] = 3,
1028 [LE_ITEM_WEAPON_MACE1H] = 3,
1029 [LE_ITEM_WEAPON_AXE2H] = 3,
1030 [LE_ITEM_WEAPON_MACE2H] = 3,
1031 [LE_ITEM_WEAPON_DAGGER] = 3,
1032 [LE_ITEM_WEAPON_UNARMED] = 3,
1033 [LE_ITEM_WEAPON_STAFF] = 3,
1034 },
1035 [263] = { -- Enhancement
1036 [LE_ITEM_WEAPON_AXE1H] = 3,
1037 [LE_ITEM_WEAPON_MACE1H] = 3,
1038 [LE_ITEM_WEAPON_DAGGER] = 3,
1039 [LE_ITEM_WEAPON_UNARMED] = 3,
1040 },
1041 [264] = { -- Restoration
1042 [LE_ITEM_WEAPON_AXE1H] = 3,
1043 [LE_ITEM_WEAPON_MACE1H] = 3,
1044 [LE_ITEM_WEAPON_AXE2H] = 3,
1045 [LE_ITEM_WEAPON_MACE2H] = 3,
1046 [LE_ITEM_WEAPON_DAGGER] = 3,
1047 [LE_ITEM_WEAPON_UNARMED] = 3,
1048 [LE_ITEM_WEAPON_STAFF] = 3,
1049 },
1050 },
1051 HUNTER = {
1052 [LE_ITEM_WEAPON_AXE1H] = 1,
1053 [LE_ITEM_WEAPON_SWORD1H] = 1,
1054 [LE_ITEM_WEAPON_AXE2H] = 1,
1055 [LE_ITEM_WEAPON_SWORD2H] = 1,
1056 [LE_ITEM_WEAPON_DAGGER] = 1,
1057 [LE_ITEM_WEAPON_UNARMED] = 1,
1058 [LE_ITEM_WEAPON_POLEARM] = 1,
1059 [LE_ITEM_WEAPON_STAFF] = 1,
1060
1061 [LE_ITEM_WEAPON_GUNS] = 2,
1062 [LE_ITEM_WEAPON_BOWS] = 2,
1063 [LE_ITEM_WEAPON_CROSSBOW] = 2,
1064
1065 [253] = { -- Beast Mastery
1066 [LE_ITEM_WEAPON_GUNS] = 3,
1067 [LE_ITEM_WEAPON_BOWS] = 3,
1068 [LE_ITEM_WEAPON_CROSSBOW] = 3,
1069 },
1070
1071 [254] = { -- Marksmanship
1072 [LE_ITEM_WEAPON_GUNS] = 3,
1073 [LE_ITEM_WEAPON_BOWS] = 3,
1074 [LE_ITEM_WEAPON_CROSSBOW] = 3,
1075 },
1076
1077 [255] = { -- Survival
1078 [LE_ITEM_WEAPON_AXE2H] = 3,
1079 [LE_ITEM_WEAPON_SWORD2H] = 3,
1080 [LE_ITEM_WEAPON_POLEARM] = 3,
1081 [LE_ITEM_WEAPON_STAFF] = 3,
1082 },
1083
1084
1085 },
1086 WARRIOR = {
1087 [LE_ITEM_WEAPON_BOWS] = 1,
1088 [LE_ITEM_WEAPON_CROSSBOW] = 1,
1089 [LE_ITEM_WEAPON_GUNS] = 1,
1090
1091 [LE_ITEM_WEAPON_AXE1H] = 2,
1092 [LE_ITEM_WEAPON_MACE1H] = 2,
1093 [LE_ITEM_WEAPON_SWORD1H] = 2,
1094 [LE_ITEM_WEAPON_AXE2H] = 2,
1095 [LE_ITEM_WEAPON_MACE2H] = 2,
1096 [LE_ITEM_WEAPON_SWORD2H] = 2,
1097 [LE_ITEM_WEAPON_DAGGER] = 2,
1098 [LE_ITEM_WEAPON_UNARMED] = 2,
1099 [LE_ITEM_WEAPON_POLEARM] = 2,
1100 [LE_ITEM_WEAPON_STAFF] = 2,
1101
1102 [71] = { -- Arms
1103 [LE_ITEM_WEAPON_AXE2H] = 3,
1104 [LE_ITEM_WEAPON_MACE2H] = 3,
1105 [LE_ITEM_WEAPON_SWORD2H] = 3,
1106 [LE_ITEM_WEAPON_POLEARM] = 3,
1107 [LE_ITEM_WEAPON_STAFF] = 3,
1108 },
1109 [72] = { -- Fury
1110 [LE_ITEM_WEAPON_AXE1H] = 3,
1111 [LE_ITEM_WEAPON_MACE1H] = 3,
1112 [LE_ITEM_WEAPON_SWORD1H] = 3,
1113 [LE_ITEM_WEAPON_DAGGER] = 3,
1114 [LE_ITEM_WEAPON_UNARMED] = 3,
1115 },
1116 [73] = { -- Protection
1117 [LE_ITEM_WEAPON_AXE1H] = 3,
1118 [LE_ITEM_WEAPON_MACE1H] = 3,
1119 [LE_ITEM_WEAPON_SWORD1H] = 3,
1120 [LE_ITEM_WEAPON_DAGGER] = 3,
1121 [LE_ITEM_WEAPON_UNARMED] = 3,
1122 },
1123 },
1124 PALADIN = {
1125 [LE_ITEM_WEAPON_AXE1H] = 2,
1126 [LE_ITEM_WEAPON_MACE1H] = 2,
1127 [LE_ITEM_WEAPON_SWORD1H] = 2,
1128 [LE_ITEM_WEAPON_AXE2H] = 2,
1129 [LE_ITEM_WEAPON_MACE2H] = 2,
1130 [LE_ITEM_WEAPON_SWORD2H] = 2,
1131 [LE_ITEM_WEAPON_POLEARM] = 2,
1132
1133 [67] = { -- Retribution
1134 [LE_ITEM_WEAPON_AXE2H] = 3,
1135 [LE_ITEM_WEAPON_MACE2H] = 3,
1136 [LE_ITEM_WEAPON_SWORD2H] = 3,
1137 [LE_ITEM_WEAPON_POLEARM] = 3,
1138 },
1139 [66] = { -- Protection
1140 [LE_ITEM_WEAPON_AXE1H] = 3,
1141 [LE_ITEM_WEAPON_MACE1H] = 3,
1142 [LE_ITEM_WEAPON_SWORD1H] = 3,
1143 },
1144 [65] = { -- Holy
1145 [LE_ITEM_WEAPON_AXE1H] = 3,
1146 [LE_ITEM_WEAPON_MACE1H] = 3,
1147 [LE_ITEM_WEAPON_SWORD1H] = 3,
1148 [LE_ITEM_WEAPON_AXE2H] = 3,
1149 [LE_ITEM_WEAPON_MACE2H] = 3,
1150 [LE_ITEM_WEAPON_SWORD2H] = 3,
1151 [LE_ITEM_WEAPON_POLEARM] = 3,
1152 },
1153 },
1154 DEATHKNIGHT = {
1155 [LE_ITEM_WEAPON_AXE1H] = 3,
1156 [LE_ITEM_WEAPON_MACE1H] = 3,
1157 [LE_ITEM_WEAPON_SWORD1H] = 3,
1158 [LE_ITEM_WEAPON_AXE2H] = 3,
1159 [LE_ITEM_WEAPON_MACE2H] = 3,
1160 [LE_ITEM_WEAPON_SWORD2H] = 3,
1161 [LE_ITEM_WEAPON_POLEARM] = 3,
1162 },
1163 DEMONHUNTER = {
1164 [LE_ITEM_WEAPON_DAGGER] = 1,
1165 [LE_ITEM_WEAPON_UNARMED] = 1,
1166 [LE_ITEM_WEAPON_AXE1H] = 1,
1167 [LE_ITEM_WEAPON_MACE1H] = 1,
1168 [LE_ITEM_WEAPON_SWORD1H] = 1,
1169 [LE_ITEM_WEAPON_WARGLAIVE] = 3,
1170 }
1171 }
1172
1173 -- 2=Useful for class, 1=Usable
1174 self.usableArmor = {
1175 WARLOCK = {
1176 [LE_ITEM_ARMOR_CLOTH] = 2,
1177 },
1178 MAGE = {
1179 [LE_ITEM_ARMOR_CLOTH] = 2,
1180 },
1181 PRIEST = {
1182 [LE_ITEM_ARMOR_CLOTH] = 2,
1183 },
1184 DRUID = {
1185 [LE_ITEM_ARMOR_CLOTH] = 1,
1186 [LE_ITEM_ARMOR_LEATHER] = 2,
1187 },
1188 ROGUE = {
1189 [LE_ITEM_ARMOR_CLOTH] = 1,
1190 [LE_ITEM_ARMOR_LEATHER] = 2,
1191 },
1192 MONK = {
1193 [LE_ITEM_ARMOR_CLOTH] = 1,
1194 [LE_ITEM_ARMOR_LEATHER] = 2,
1195 },
1196 SHAMAN = {
1197 [LE_ITEM_ARMOR_CLOTH] = 1,
1198 [LE_ITEM_ARMOR_LEATHER] = 1,
1199 [LE_ITEM_ARMOR_MAIL] = 2,
1200 [LE_ITEM_ARMOR_SHIELD] = 2,
1201 },
1202 HUNTER = {
1203 [LE_ITEM_ARMOR_CLOTH] = 1,
1204 [LE_ITEM_ARMOR_LEATHER] = 1,
1205 [LE_ITEM_ARMOR_MAIL] = 2,
1206 },
1207 WARRIOR = {
1208 [LE_ITEM_ARMOR_CLOTH] = 1,
1209 [LE_ITEM_ARMOR_LEATHER] = 1,
1210 [LE_ITEM_ARMOR_MAIL] = 1,
1211 [LE_ITEM_ARMOR_PLATE] = 2,
1212 [LE_ITEM_ARMOR_SHIELD] = 2,
1213 },
1214 PALADIN = {
1215 [LE_ITEM_ARMOR_CLOTH] = 1,
1216 [LE_ITEM_ARMOR_LEATHER] = 1,
1217 [LE_ITEM_ARMOR_MAIL] = 1,
1218 [LE_ITEM_ARMOR_PLATE] = 2,
1219 [LE_ITEM_ARMOR_SHIELD] = 2,
1220 },
1221 DEATHKNIGHT = {
1222 [LE_ITEM_ARMOR_CLOTH] = 1,
1223 [LE_ITEM_ARMOR_LEATHER] = 1,
1224 [LE_ITEM_ARMOR_MAIL] = 1,
1225 [LE_ITEM_ARMOR_PLATE] = 2,
1226 },
1227 DEMONHUNTER = {
1228 [LE_ITEM_ARMOR_CLOTH] = 1,
1229 [LE_ITEM_ARMOR_LEATHER] = 2,
1230 }
1231 }
1232end
1233
1234function SimpleStats:OnInitialize() -- Runs when addon is initialized
1235 self.db = LibStub("AceDB-3.0"):New("SimpleStatsDB", self.defaults)
1236 local profileOptions = LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db)
1237
1238 LibStub("AceConfig-3.0"):RegisterOptionsTable("SimpleStats", self.options)
1239 LibStub("AceConfig-3.0"):RegisterOptionsTable("SimpleStats Profile", profileOptions)
1240 self.optionsFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("SimpleStats","SimpleStats")
1241 self.profileFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("SimpleStats Profile", "Profiles", "SimpleStats")
1242
1243 self:RegisterChatCommand("ss", "ChatCommand")
1244 self:RegisterChatCommand("simplestats", "ChatCommand")
1245
1246 self:SetupTables()
1247
1248 self:HookScript(GameTooltip, "OnTooltipSetItem", "HandleTooltip")
1249 self:HookScript(ItemRefTooltip, "OnTooltipSetItem", "HandleTooltip")
1250
1251 -- for GetTrueItemLevel()
1252 self.itemLevelPattern = _G["ITEM_LEVEL"]:gsub("%%d", "(%%d+)")
1253 self.scanningTooltip = false
1254 self.heirloomCache = {}
1255end
1256
1257--- Public Functions ---
1258function SimpleStats:ShowStatChanges(tooltip,stats1,stats2) -- Shows stat changes from stats1 -> stats2 in the given tooltip. Pass it either itemstrings or the result of GetItemStats()
1259 -- If given an item link instead of a stats table, extract the stats from the link
1260 if type(stats1) == "string" and strmatch(stats1, "item:") then
1261 stats1 = GetItemStats(stats1)
1262 end
1263 if type(stats2) == "string" and strmatch(stats2, "item:") then
1264 stats2 = GetItemStats(stats2)
1265 end
1266
1267 local lines = SimpleStats:GetStatChangeLines(stats2,stats1)
1268 SimpleStats:AddLinesToTooltip(tooltip, lines)
1269end
1270
1271--- Misc Lua Functions ---
1272if not print_r then -- Debug function to print the contents of a table
1273function print_r ( t )
1274 local print_r_cache={}
1275 local function sub_print_r(t,indent)
1276 if (print_r_cache[tostring(t)]) then
1277 print(indent.."*"..tostring(t))
1278 else
1279 print_r_cache[tostring(t)]=true
1280 if (type(t)=="table") then
1281 for pos,val in pairs(t) do
1282 if (type(val)=="table") then
1283 print(indent.."["..pos.."] => "..tostring(t).." {")
1284 sub_print_r(val,indent..string.rep(" ",string.len(pos)+8))
1285 print(indent..string.rep(" ",string.len(pos)+6).."}")
1286 else
1287 print(indent.."["..pos.."] => "..tostring(val))
1288 end
1289 end
1290 else
1291 print(indent..tostring(t))
1292 end
1293 end
1294 end
1295 sub_print_r(t," ")
1296end
1297end
1298function __genOrderedIndex(t) -- Used by orderedNext()
1299 local orderedIndex = {}
1300 for key in pairs(t) do table.insert( orderedIndex, key ) end
1301 table.sort( orderedIndex )
1302 return orderedIndex
1303end
1304function orderedNext(t, state) -- Used by orderedPairs()
1305 if state == nil then
1306 t.__orderedIndex = __genOrderedIndex( t )
1307 key = t.__orderedIndex[1]
1308 return key, t[key]
1309 end
1310 key = nil
1311 for i = 1,table.getn(t.__orderedIndex) do
1312 if t.__orderedIndex[i] == state then
1313 key = t.__orderedIndex[i+1]
1314 end
1315 end
1316 if key then return key, t[key] end
1317 t.__orderedIndex = nil
1318 return
1319end
1320function orderedPairs(t) -- Same as pairs(table), but keys are returned sorted by key (alphabetically/numerically) instead of whatever order lua wants
1321 return orderedNext, t, nil
1322end
1323function round(num, idp) -- Rounds a number to x places
1324 local mult = 10^(idp or 0)
1325 return math.floor(num * mult + 0.5) / mult
1326end