1379 lines
55 KiB
Lua
1379 lines
55 KiB
Lua
--------------------------------------------------------------------------------
|
||
-- Nanami-UI: GearScore (GearScore.lua)
|
||
-- 基于乌龟服(Turtle WoW)属性收益理论的装备评分系统
|
||
-- 评分 1-10: 衡量该装备对当前职业各天赋的适配度
|
||
-- 考虑: 属性权重 × 装备类型兼容性 × 等级适配
|
||
--------------------------------------------------------------------------------
|
||
|
||
SFrames.GearScore = {}
|
||
local GS = SFrames.GearScore
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Item budget cost per unit of each stat (for normalization)
|
||
-- Higher cost = stat is "rarer/more expensive" per item budget point
|
||
--------------------------------------------------------------------------------
|
||
|
||
local BUDGET_COST = {
|
||
STR = 1.0, AGI = 1.0, STA = 1.0, INT = 1.0, SPI = 1.0,
|
||
CRIT = 14.0, TOHIT = 15.0, RANGEDCRIT = 14.0,
|
||
SPELLCRIT = 14.0, SPELLTOHIT = 15.0,
|
||
ATTACKPOWER = 0.5, RANGEDATTACKPOWER = 0.5,
|
||
DMG = 0.86, HEAL = 0.58,
|
||
DEFENSE = 1.5, DODGE = 12.0, PARRY = 12.0, BLOCK = 12.0,
|
||
BLOCKVALUE = 0.5, ARMOR = 0.05, BASEARMOR = 0.02,
|
||
HEALTHREG = 2.0, MANAREG = 2.0,
|
||
HEALTH = 0.07, MANA = 0.07,
|
||
WEAPONDPS = 3.0,
|
||
WEAPONSPEED = 1.5,
|
||
}
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- EP Weight Tables (tab = talent tab index for primary spec detection)
|
||
--------------------------------------------------------------------------------
|
||
|
||
local WEIGHTS = {
|
||
-- ================================================================
|
||
-- Pawn-style normalization: primary stat = 1.0
|
||
-- TOHIT/CRIT per 1%; PDF: 1%hit≈18AP, 1%crit≈25AP, 1%spellhit≈14SP
|
||
-- ================================================================
|
||
WARRIOR = {
|
||
specs = {
|
||
{ name = "武器", tab = 1, color = "ffC79C6E",
|
||
w = { STR=1.0, AGI=0.7, STA=0.5, TOHIT=9, CRIT=12,
|
||
ATTACKPOWER=0.5, HEALTH=0.05, WEAPONDPS=5, BASEARMOR=0.005 } },
|
||
{ name = "狂怒", tab = 2, color = "ffC79C6E",
|
||
w = { STR=1.0, AGI=0.6, STA=0.5, TOHIT=10, CRIT=11,
|
||
ATTACKPOWER=0.5, HEALTH=0.05, WEAPONDPS=5, BASEARMOR=0.005 } },
|
||
{ name = "防护", tab = 3, color = "ff69CCF0",
|
||
w = { STA=1.0, STR=0.5, AGI=0.7, TOHIT=5, CRIT=3, ATTACKPOWER=0.2,
|
||
DEFENSE=0.8, DODGE=8, PARRY=7, BLOCK=6, BLOCKVALUE=0.35,
|
||
ARMOR=0.02, HEALTH=0.1, HEALTHREG=1.0, WEAPONDPS=3, BASEARMOR=0.03 } },
|
||
},
|
||
-- 硬核物理(战士型): 耐 > 力 > 敏 > 攻强
|
||
hc = { name = "硬核", color = "ffFF4444",
|
||
w = { STA=1.5, STR=1.0, AGI=0.8, ATTACKPOWER=0.5,
|
||
TOHIT=3, CRIT=3, DEFENSE=0.5, DODGE=5, PARRY=3, BLOCK=3, BLOCKVALUE=0.3,
|
||
ARMOR=0.05, HEALTH=0.1, HEALTHREG=1.5, WEAPONDPS=3, BASEARMOR=0.02 } },
|
||
},
|
||
PALADIN = {
|
||
specs = {
|
||
{ name = "神圣", tab = 1, color = "ff00FF96",
|
||
w = { INT=1.0, SPI=0.3, STA=0.5, HEAL=0.55, DMG=0.1,
|
||
SPELLCRIT=5, MANAREG=1.3, MANA=0.01, BASEARMOR=0.005 } },
|
||
{ name = "防护", tab = 2, color = "ff69CCF0",
|
||
w = { STA=1.0, STR=0.5, AGI=0.5, INT=0.3, TOHIT=5, CRIT=3,
|
||
ATTACKPOWER=0.2, DMG=0.4,
|
||
DEFENSE=0.7, DODGE=7, PARRY=6, BLOCK=6, BLOCKVALUE=0.15,
|
||
ARMOR=0.02, HEALTH=0.1, MANAREG=1.0, WEAPONDPS=2, BASEARMOR=0.03 } },
|
||
{ name = "惩戒", tab = 3, color = "ffF58CBA",
|
||
w = { STR=1.0, AGI=0.6, STA=0.5, INT=0.3,
|
||
TOHIT=8, CRIT=10, SPELLCRIT=5,
|
||
ATTACKPOWER=0.5, DMG=0.3, HEAL=0.05, WEAPONDPS=5, BASEARMOR=0.005 } },
|
||
},
|
||
-- 硬核圣骑士: 耐 > 力 > 智 = 敏 = 精
|
||
hc = { name = "硬核", color = "ffFF4444",
|
||
w = { STA=1.5, STR=1.0, INT=0.8, AGI=0.8, SPI=0.8,
|
||
TOHIT=3, CRIT=3, ATTACKPOWER=0.3, HEAL=0.4, DMG=0.2,
|
||
DEFENSE=0.5, DODGE=3, ARMOR=0.04, HEALTH=0.1,
|
||
HEALTHREG=1.0, MANAREG=1.0, WEAPONDPS=2, BASEARMOR=0.02 } },
|
||
},
|
||
HUNTER = {
|
||
specs = {
|
||
{ name = "野兽", tab = 1, color = "ffABD473",
|
||
w = { AGI=1.0, STR=0.05, STA=0.5, INT=0.8,
|
||
TOHIT=10, CRIT=10, RANGEDCRIT=10,
|
||
ATTACKPOWER=0.4, RANGEDATTACKPOWER=0.5, MANAREG=2.0,
|
||
WEAPONDPS=4, BASEARMOR=0.005 } },
|
||
{ name = "射击", tab = 2, color = "ffABD473",
|
||
w = { AGI=1.0, STR=0.05, STA=0.5, INT=0.9,
|
||
TOHIT=10, CRIT=10, RANGEDCRIT=10,
|
||
ATTACKPOWER=0.4, RANGEDATTACKPOWER=0.5, MANAREG=2.0,
|
||
WEAPONDPS=4, BASEARMOR=0.005 } },
|
||
{ name = "生存", tab = 3, color = "ffABD473",
|
||
w = { AGI=1.0, STR=0.4, STA=0.5, INT=0.3, SPI=0.3,
|
||
TOHIT=8, CRIT=8, RANGEDCRIT=8,
|
||
ATTACKPOWER=0.5, RANGEDATTACKPOWER=0.5, MANAREG=1.0,
|
||
WEAPONDPS=3, BASEARMOR=0.005 } },
|
||
},
|
||
-- 硬核猎人: 耐 > 敏 > 智 > 力 (精神配合灵魂链接有价值)
|
||
hc = { name = "硬核", color = "ffFF4444",
|
||
w = { STA=1.5, AGI=1.0, INT=0.5, STR=0.3, SPI=0.8,
|
||
TOHIT=3, CRIT=3, RANGEDCRIT=3,
|
||
ATTACKPOWER=0.2, RANGEDATTACKPOWER=0.3,
|
||
ARMOR=0.04, HEALTH=0.1, HEALTHREG=1.5, WEAPONDPS=2, BASEARMOR=0.01 } },
|
||
},
|
||
ROGUE = {
|
||
specs = {
|
||
{ name = "刺杀", tab = 1, color = "ffFFF569",
|
||
w = { AGI=1.0, STR=0.5, STA=0.5, TOHIT=10, CRIT=12,
|
||
ATTACKPOWER=0.45, WEAPONDPS=5, BASEARMOR=0.005 } },
|
||
{ name = "战斗", tab = 2, color = "ffFFF569",
|
||
w = { AGI=1.0, STR=0.5, STA=0.5, TOHIT=10, CRIT=11,
|
||
ATTACKPOWER=0.45, WEAPONDPS=6, BASEARMOR=0.005 } },
|
||
{ name = "敏锐", tab = 3, color = "ffFFF569",
|
||
w = { AGI=1.0, STR=0.5, STA=0.5, TOHIT=8, CRIT=10,
|
||
ATTACKPOWER=0.45, DODGE=3, WEAPONDPS=4, BASEARMOR=0.005 } },
|
||
},
|
||
-- 硬核潜行者: 耐 > 敏 > 力 > 攻强
|
||
hc = { name = "硬核", color = "ffFF4444",
|
||
w = { STA=1.5, AGI=1.2, STR=0.8, ATTACKPOWER=0.5,
|
||
TOHIT=3, CRIT=3, DODGE=3, ARMOR=0.04,
|
||
HEALTH=0.1, HEALTHREG=1.5, WEAPONDPS=3, BASEARMOR=0.01 } },
|
||
},
|
||
PRIEST = {
|
||
specs = {
|
||
{ name = "戒律", tab = 1, color = "ff00FF96",
|
||
w = { INT=1.0, SPI=0.5, STA=0.5, HEAL=0.7, DMG=0.1,
|
||
SPELLCRIT=4, MANAREG=1.2, MANA=0.01,
|
||
WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
|
||
{ name = "神圣", tab = 2, color = "ff00FF96",
|
||
w = { INT=1.0, SPI=0.7, STA=0.5, HEAL=0.8, DMG=0.1,
|
||
SPELLCRIT=3, MANAREG=1.35, MANA=0.01,
|
||
WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
|
||
{ name = "暗影", tab = 3, color = "ff9482C9",
|
||
w = { DMG=1.0, INT=0.2, SPI=0.2, STA=0.5,
|
||
SPELLTOHIT=14, SPELLCRIT=8, MANAREG=1.0,
|
||
WEAPONDPS=2.5, WEAPONSPEED=0.6, BASEARMOR=0.003 } },
|
||
},
|
||
-- 硬核法系(牧师): 耐 > 智 = 治疗 > 精
|
||
hc = { name = "硬核", color = "ffFF4444",
|
||
w = { STA=1.5, INT=1.0, HEAL=1.0, DMG=0.8, SPI=0.8,
|
||
SPELLCRIT=2, SPELLTOHIT=3, MANAREG=1.5,
|
||
WEAPONDPS=1.5, WEAPONSPEED=0.3,
|
||
ARMOR=0.04, HEALTH=0.08, HEALTHREG=0.8, BASEARMOR=0.01 } },
|
||
},
|
||
SHAMAN = {
|
||
specs = {
|
||
{ name = "元素", tab = 1, color = "ff0070DE",
|
||
w = { DMG=1.0, INT=0.3, SPI=0.1, STA=0.5,
|
||
SPELLTOHIT=10, SPELLCRIT=8, ATTACKPOWER=0.1,
|
||
MANAREG=1.1, BASEARMOR=0.005 } },
|
||
{ name = "增强", tab = 2, color = "ff0070DE",
|
||
w = { STR=1.0, AGI=0.9, STA=0.5, INT=0.3,
|
||
TOHIT=10, CRIT=11, ATTACKPOWER=0.5,
|
||
DMG=0.3, SPELLCRIT=3, MANAREG=1.0,
|
||
WEAPONDPS=5, BASEARMOR=0.005 } },
|
||
{ name = "恢复", tab = 3, color = "ff00FF96",
|
||
w = { INT=1.0, SPI=0.3, STA=0.5, HEAL=0.9, DMG=0.1,
|
||
SPELLCRIT=5, MANAREG=1.7, MANA=0.01, BASEARMOR=0.005 } },
|
||
},
|
||
-- 硬核萨满(混合): 耐 > 力=敏 > 智=精
|
||
hc = { name = "硬核", color = "ffFF4444",
|
||
w = { STA=1.5, STR=0.8, AGI=0.8, INT=0.5, SPI=0.5,
|
||
TOHIT=3, CRIT=3, ATTACKPOWER=0.3, HEAL=0.4, DMG=0.3,
|
||
MANAREG=1.0, ARMOR=0.04, HEALTH=0.1, HEALTHREG=1.0,
|
||
WEAPONDPS=2, BASEARMOR=0.01 } },
|
||
},
|
||
MAGE = {
|
||
specs = {
|
||
{ name = "奥术", tab = 1, color = "ff69CCF0",
|
||
w = { DMG=1.0, INT=0.46, SPI=0.6, STA=0.3,
|
||
SPELLTOHIT=10, SPELLCRIT=7, MANAREG=1.1,
|
||
MANA=0.04, WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
|
||
{ name = "火焰", tab = 2, color = "ff69CCF0",
|
||
w = { DMG=1.0, INT=0.44, SPI=0.07, STA=0.3,
|
||
SPELLTOHIT=12, SPELLCRIT=9, MANAREG=1.0,
|
||
MANA=0.04, WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
|
||
{ name = "冰霜", tab = 3, color = "ff69CCF0",
|
||
w = { DMG=1.0, INT=0.37, SPI=0.06, STA=0.3,
|
||
SPELLTOHIT=12, SPELLCRIT=7, MANAREG=0.8,
|
||
MANA=0.03, WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
|
||
},
|
||
-- 硬核法系(法师): 耐 > 智 = 法伤 > 精
|
||
hc = { name = "硬核", color = "ffFF4444",
|
||
w = { STA=1.5, INT=1.0, DMG=1.0, SPI=0.8,
|
||
SPELLCRIT=2, SPELLTOHIT=3, MANAREG=1.5,
|
||
WEAPONDPS=1.5, WEAPONSPEED=0.3,
|
||
ARMOR=0.05, HEALTH=0.08, HEALTHREG=0.8, BASEARMOR=0.01 } },
|
||
},
|
||
WARLOCK = {
|
||
specs = {
|
||
{ name = "痛苦", tab = 1, color = "ff9482C9",
|
||
w = { DMG=1.0, INT=0.4, SPI=0.1, STA=0.5,
|
||
SPELLTOHIT=12, SPELLCRIT=4, MANAREG=1.0,
|
||
HEALTH=0.05, WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
|
||
{ name = "恶魔", tab = 2, color = "ff9482C9",
|
||
w = { DMG=1.0, INT=0.4, SPI=0.5, STA=0.5,
|
||
SPELLTOHIT=12, SPELLCRIT=7, MANAREG=1.0,
|
||
WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
|
||
{ name = "毁灭", tab = 3, color = "ff9482C9",
|
||
w = { DMG=1.0, INT=0.34, SPI=0.25, STA=0.5,
|
||
SPELLTOHIT=14, SPELLCRIT=9, MANAREG=0.65,
|
||
WEAPONDPS=2.5, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
|
||
},
|
||
-- 硬核法系(术士): 耐 > 智 = 法伤 > 精 (精神低于法师因有生命分流)
|
||
hc = { name = "硬核", color = "ffFF4444",
|
||
w = { STA=1.5, INT=1.0, DMG=1.0, SPI=0.5,
|
||
SPELLCRIT=2, SPELLTOHIT=3, MANAREG=1.0,
|
||
WEAPONDPS=1.5, WEAPONSPEED=0.3,
|
||
ARMOR=0.04, HEALTH=0.08, HEALTHREG=0.8, BASEARMOR=0.01 } },
|
||
},
|
||
DRUID = {
|
||
specs = {
|
||
{ name = "平衡", tab = 1, color = "ffFF7D0A",
|
||
w = { DMG=1.0, INT=0.38, SPI=0.34, STA=0.5,
|
||
HEAL=0.1, SPELLTOHIT=12, SPELLCRIT=7,
|
||
MANAREG=0.6, MANA=0.03, BASEARMOR=0.005 } },
|
||
{ name = "野猫", tab = 2, color = "ffFF7D0A",
|
||
w = { STR=1.2, AGI=1.0, STA=0.5, TOHIT=10, CRIT=11,
|
||
ATTACKPOWER=0.5, DODGE=1, WEAPONDPS=0.5, BASEARMOR=0.005 } },
|
||
{ name = "野熊", tab = 2, color = "ff69CCF0",
|
||
w = { STA=1.0, AGI=0.5, STR=0.2, TOHIT=3, CRIT=3, ATTACKPOWER=0.35,
|
||
DEFENSE=0.5, DODGE=6, ARMOR=0.1, HEALTH=0.08, BASEARMOR=0.04 } },
|
||
{ name = "恢复", tab = 3, color = "ff00FF96",
|
||
w = { INT=1.0, SPI=0.87, STA=0.5, HEAL=1.2, DMG=0.1,
|
||
SPELLCRIT=4, MANAREG=1.7, MANA=0.01, BASEARMOR=0.005 } },
|
||
},
|
||
-- 硬核德鲁伊(混合偏生存): 耐 > 敏 > 力 > 智 = 精
|
||
hc = { name = "硬核", color = "ffFF4444",
|
||
w = { STA=1.5, AGI=1.0, STR=0.8, INT=0.5, SPI=0.5,
|
||
TOHIT=3, CRIT=3, ATTACKPOWER=0.3, HEAL=0.4, DMG=0.3,
|
||
DODGE=3, ARMOR=0.04, HEALTH=0.1, HEALTHREG=1.0,
|
||
WEAPONDPS=1, BASEARMOR=0.01 } },
|
||
},
|
||
}
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Armor type compatibility (class × armor subclass × level)
|
||
-- 1.0 = ideal, 0 = should never wear this
|
||
--------------------------------------------------------------------------------
|
||
|
||
local ARMOR_COMPAT = {
|
||
WARRIOR = { Plate=1.0, Mail=0.3, Leather=0.1, Cloth=0.05 },
|
||
PALADIN = { Plate=1.0, Mail=0.3, Leather=0.1, Cloth=0.05 },
|
||
HUNTER = { Mail=1.0, Leather=0.5, Cloth=0.1 },
|
||
SHAMAN = { Mail=1.0, Leather=0.5, Cloth=0.1 },
|
||
ROGUE = { Leather=1.0, Cloth=0.3 },
|
||
DRUID = { Leather=1.0, Cloth=0.3 },
|
||
PRIEST = { Cloth=1.0 },
|
||
MAGE = { Cloth=1.0 },
|
||
WARLOCK = { Cloth=1.0 },
|
||
}
|
||
|
||
local ARMOR_COMPAT_LOW = {
|
||
WARRIOR = { Mail=1.0, Leather=0.6, Cloth=0.1 },
|
||
PALADIN = { Mail=1.0, Leather=0.6, Cloth=0.1 },
|
||
HUNTER = { Leather=1.0, Cloth=0.3 },
|
||
SHAMAN = { Leather=1.0, Cloth=0.3 },
|
||
ROGUE = { Leather=1.0, Cloth=0.3 },
|
||
DRUID = { Leather=1.0, Cloth=0.3 },
|
||
PRIEST = { Cloth=1.0 },
|
||
MAGE = { Cloth=1.0 },
|
||
WARLOCK = { Cloth=1.0 },
|
||
}
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Equipment slot compatibility per spec (CLASS_specIdx)
|
||
-- 2H weapons for tanks = 0.05, shields for DPS = low, etc.
|
||
-- Missing entries default to 1.0
|
||
--------------------------------------------------------------------------------
|
||
|
||
local SLOT_COMPAT = {
|
||
WARRIOR_1 = { INVTYPE_SHIELD = 0.1, INVTYPE_HOLDABLE = 0.05 },
|
||
WARRIOR_2 = { INVTYPE_SHIELD = 0.1, INVTYPE_HOLDABLE = 0.05 },
|
||
WARRIOR_3 = { INVTYPE_2HWEAPON = 0.05, INVTYPE_HOLDABLE = 0.05 },
|
||
|
||
PALADIN_1 = { INVTYPE_2HWEAPON = 0.3 },
|
||
PALADIN_2 = { INVTYPE_2HWEAPON = 0.05 },
|
||
PALADIN_3 = { INVTYPE_SHIELD = 0.2, INVTYPE_HOLDABLE = 0.1 },
|
||
|
||
HUNTER_1 = { INVTYPE_SHIELD = 0 },
|
||
HUNTER_2 = { INVTYPE_SHIELD = 0 },
|
||
HUNTER_3 = { INVTYPE_SHIELD = 0 },
|
||
|
||
ROGUE_1 = { INVTYPE_2HWEAPON = 0, INVTYPE_SHIELD = 0, INVTYPE_HOLDABLE = 0 },
|
||
ROGUE_2 = { INVTYPE_2HWEAPON = 0, INVTYPE_SHIELD = 0, INVTYPE_HOLDABLE = 0 },
|
||
ROGUE_3 = { INVTYPE_2HWEAPON = 0, INVTYPE_SHIELD = 0, INVTYPE_HOLDABLE = 0 },
|
||
|
||
SHAMAN_1 = { INVTYPE_2HWEAPON = 0.3 },
|
||
SHAMAN_2 = { INVTYPE_SHIELD = 0.3 },
|
||
|
||
DRUID_1 = { INVTYPE_SHIELD = 0 },
|
||
DRUID_2 = { INVTYPE_SHIELD = 0, INVTYPE_HOLDABLE = 0 },
|
||
DRUID_3 = { INVTYPE_SHIELD = 0, INVTYPE_HOLDABLE = 0 },
|
||
DRUID_4 = { INVTYPE_SHIELD = 0 },
|
||
|
||
MAGE_1 = { INVTYPE_SHIELD = 0 },
|
||
MAGE_2 = { INVTYPE_SHIELD = 0 },
|
||
MAGE_3 = { INVTYPE_SHIELD = 0 },
|
||
WARLOCK_1 = { INVTYPE_SHIELD = 0 },
|
||
WARLOCK_2 = { INVTYPE_SHIELD = 0 },
|
||
WARLOCK_3 = { INVTYPE_SHIELD = 0 },
|
||
PRIEST_1 = { INVTYPE_SHIELD = 0 },
|
||
PRIEST_2 = { INVTYPE_SHIELD = 0 },
|
||
PRIEST_3 = { INVTYPE_SHIELD = 0 },
|
||
}
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Level-based weight adjustments
|
||
--------------------------------------------------------------------------------
|
||
|
||
local function AdjustWeightsForLevel(baseWeights, level, isHC)
|
||
if level >= 55 then return baseWeights end
|
||
local adj = {}
|
||
for k, v in pairs(baseWeights) do adj[k] = v end
|
||
|
||
if isHC then
|
||
-- HC: STA always stays high (survival is the whole point)
|
||
if level <= 20 then
|
||
adj.SPI = math.max((adj.SPI or 0) * 1.3, 0.3)
|
||
adj.TOHIT = (adj.TOHIT or 0) * 0.2
|
||
adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.2
|
||
adj.CRIT = (adj.CRIT or 0) * 0.3
|
||
adj.SPELLCRIT = (adj.SPELLCRIT or 0) * 0.3
|
||
adj.HEALTHREG = (adj.HEALTHREG or 0) + 0.8
|
||
adj.ARMOR = (adj.ARMOR or 0) + 0.02
|
||
elseif level <= 40 then
|
||
adj.TOHIT = (adj.TOHIT or 0) * 0.5
|
||
adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.5
|
||
adj.ARMOR = (adj.ARMOR or 0) + 0.01
|
||
else
|
||
adj.TOHIT = (adj.TOHIT or 0) * 0.8
|
||
adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.8
|
||
end
|
||
else
|
||
if level <= 20 then
|
||
-- PDF: 精神至上 at 1-20; SPI matters for all classes (regen)
|
||
adj.SPI = math.max((adj.SPI or 0) * 1.5, 0.3)
|
||
adj.STA = (adj.STA or 0) * 0.7
|
||
-- SP/HEAL don't exist on items at this level
|
||
adj.DMG = (adj.DMG or 0) * 0.15
|
||
adj.HEAL = (adj.HEAL or 0) * 0.15
|
||
adj.TOHIT = (adj.TOHIT or 0) * 0.2
|
||
adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.2
|
||
adj.CRIT = (adj.CRIT or 0) * 0.2
|
||
adj.SPELLCRIT = (adj.SPELLCRIT or 0) * 0.2
|
||
adj.RANGEDCRIT = (adj.RANGEDCRIT or 0) * 0.2
|
||
adj.HEALTHREG = (adj.HEALTHREG or 0) + 0.8
|
||
adj.ARMOR = (adj.ARMOR or 0) + 0.02
|
||
elseif level <= 40 then
|
||
-- PDF: 40级解锁板甲/锁甲, SP开始出现
|
||
adj.SPI = math.max((adj.SPI or 0) * 1.2, 0.15)
|
||
adj.DMG = (adj.DMG or 0) * 0.5
|
||
adj.HEAL = (adj.HEAL or 0) * 0.5
|
||
adj.TOHIT = (adj.TOHIT or 0) * 0.6
|
||
adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.6
|
||
adj.ARMOR = (adj.ARMOR or 0) + 0.01
|
||
else
|
||
adj.TOHIT = (adj.TOHIT or 0) * 0.85
|
||
adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.85
|
||
end
|
||
end
|
||
return adj
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Player info helpers
|
||
--------------------------------------------------------------------------------
|
||
|
||
local function GetPlayerClassToken()
|
||
local _, classEN = UnitClass("player")
|
||
if classEN then return string.upper(classEN) end
|
||
return nil
|
||
end
|
||
|
||
local function GetPlayerLevel()
|
||
return UnitLevel("player") or 60
|
||
end
|
||
|
||
local function GetPrimaryTabIndex()
|
||
if not GetNumTalentTabs then return 1 end
|
||
local maxPts, maxIdx = 0, 1
|
||
for i = 1, GetNumTalentTabs() do
|
||
local _, _, pts = GetTalentTabInfo(i)
|
||
if pts and pts > maxPts then
|
||
maxPts = pts
|
||
maxIdx = i
|
||
end
|
||
end
|
||
return maxIdx
|
||
end
|
||
|
||
local function IsPlayerHardcore()
|
||
if IsHardcore and IsHardcore("player") then return true end
|
||
if C_TurtleWoW and C_TurtleWoW.IsHardcore and C_TurtleWoW.IsHardcore("player") then
|
||
return true
|
||
end
|
||
return false
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Item link/info utilities
|
||
--------------------------------------------------------------------------------
|
||
|
||
local function ExtractItemString(link)
|
||
if not link then return nil end
|
||
local _, _, itemStr = string.find(link, "(item:[%-?%d:]+)")
|
||
return itemStr
|
||
end
|
||
|
||
-- Item quality detection from link color code
|
||
-- WoW link format: |cffXXXXXX|Hitem:...|h[Name]|h|r
|
||
local QUALITY_FROM_COLOR = {
|
||
["9d9d9d"] = 0, -- Poor (gray)
|
||
["ffffff"] = 1, -- Common (white)
|
||
["1eff00"] = 2, -- Uncommon (green)
|
||
["0070dd"] = 3, -- Rare (blue)
|
||
["a335ee"] = 4, -- Epic (purple)
|
||
["ff8000"] = 5, -- Legendary (orange)
|
||
["e6cc80"] = 6, -- Artifact
|
||
}
|
||
|
||
local QUALITY_MULT = {
|
||
[0] = 0, -- Poor: skip entirely
|
||
[1] = 0.50, -- Common: penalty
|
||
[2] = 0.90, -- Uncommon: slight penalty
|
||
[3] = 0.95, -- Rare: near full
|
||
[4] = 1.0, -- Epic: full
|
||
[5] = 1.0, -- Legendary: full
|
||
[6] = 1.0, -- Artifact: full
|
||
}
|
||
|
||
local function GetQualityFromLink(link)
|
||
if not link then return -1 end
|
||
local _, _, hex = string.find(link, "|cff(%x%x%x%x%x%x)")
|
||
if hex then
|
||
return QUALITY_FROM_COLOR[string.lower(hex)] or 1
|
||
end
|
||
return 1
|
||
end
|
||
|
||
local function GetQualityFromTooltip(tooltip)
|
||
if not tooltip then return 1 end
|
||
local tipName = tooltip:GetName()
|
||
if not tipName then return 1 end
|
||
local nameObj = getglobal(tipName .. "TextLeft1")
|
||
if not nameObj then return 1 end
|
||
local r, g, b = nameObj:GetTextColor()
|
||
if not r then return 1 end
|
||
-- Gray: r≈0.62, g≈0.62, b≈0.62
|
||
if r < 0.7 and g < 0.7 and b < 0.7 then return 0 end
|
||
-- White: r=1, g=1, b=1
|
||
if r > 0.95 and g > 0.95 and b > 0.95 then return 1 end
|
||
-- Green: r≈0.12, g=1, b=0
|
||
if g > 0.9 and r < 0.2 then return 2 end
|
||
-- Blue: r=0, g≈0.44, b≈0.87
|
||
if b > 0.8 and r < 0.1 then return 3 end
|
||
-- Purple: r≈0.64, g≈0.21, b≈0.93
|
||
if b > 0.85 and r > 0.5 then return 4 end
|
||
-- Orange: r=1, g≈0.5, b=0
|
||
if r > 0.9 and g > 0.4 and b < 0.1 then return 5 end
|
||
return 1
|
||
end
|
||
|
||
-- Normalize item class/subclass strings (Chinese client support)
|
||
local ARMOR_CLASS_NAMES = { Armor=true, ["护甲"]=true, ["铠甲"]=true }
|
||
local ARMOR_MISC_NAMES = { Miscellaneous=true, ["其它"]=true, ["杂项"]=true, ["其他"]=true }
|
||
local SHIELD_NAMES = { Shield=true, Shields=true, ["盾牌"]=true, ["盾"]=true }
|
||
local SUBCLASS_NORMALIZE = {
|
||
Plate="Plate", ["板甲"]="Plate", ["铠甲"]="Plate",
|
||
Mail="Mail", ["锁甲"]="Mail", ["链甲"]="Mail",
|
||
Leather="Leather", ["皮甲"]="Leather", ["皮革"]="Leather",
|
||
Cloth="Cloth", ["布甲"]="Cloth", ["布料"]="Cloth",
|
||
}
|
||
|
||
-- Parse equipLoc and armor type directly from tooltip text
|
||
local SLOT_TEXT_TO_INVTYPE = {
|
||
["Head"]="INVTYPE_HEAD", ["头部"]="INVTYPE_HEAD",
|
||
["Neck"]="INVTYPE_NECK", ["颈部"]="INVTYPE_NECK",
|
||
["Shoulder"]="INVTYPE_SHOULDER", ["肩部"]="INVTYPE_SHOULDER",
|
||
["Shirt"]="INVTYPE_BODY", ["衬衣"]="INVTYPE_BODY",
|
||
["Chest"]="INVTYPE_CHEST", ["胸部"]="INVTYPE_CHEST",
|
||
["Robe"]="INVTYPE_ROBE", ["胸甲"]="INVTYPE_ROBE",
|
||
["Waist"]="INVTYPE_WAIST", ["腰部"]="INVTYPE_WAIST",
|
||
["Legs"]="INVTYPE_LEGS", ["腿部"]="INVTYPE_LEGS",
|
||
["Feet"]="INVTYPE_FEET", ["脚"]="INVTYPE_FEET", ["足"]="INVTYPE_FEET",
|
||
["Wrist"]="INVTYPE_WRIST", ["手腕"]="INVTYPE_WRIST",
|
||
["Hands"]="INVTYPE_HAND", ["手"]="INVTYPE_HAND",
|
||
["Finger"]="INVTYPE_FINGER", ["手指"]="INVTYPE_FINGER",
|
||
["Trinket"]="INVTYPE_TRINKET", ["饰品"]="INVTYPE_TRINKET",
|
||
["Back"]="INVTYPE_CLOAK", ["背部"]="INVTYPE_CLOAK",
|
||
["Main Hand"]="INVTYPE_WEAPONMAINHAND", ["主手"]="INVTYPE_WEAPONMAINHAND",
|
||
["One-Hand"]="INVTYPE_WEAPON", ["单手"]="INVTYPE_WEAPON",
|
||
["Off Hand"]="INVTYPE_WEAPONOFFHAND", ["副手"]="INVTYPE_WEAPONOFFHAND",
|
||
["Two-Hand"]="INVTYPE_2HWEAPON", ["双手"]="INVTYPE_2HWEAPON",
|
||
["Held In Off-hand"]="INVTYPE_HOLDABLE", ["副手物品"]="INVTYPE_HOLDABLE",
|
||
["Ranged"]="INVTYPE_RANGED", ["远程"]="INVTYPE_RANGED",
|
||
["Thrown"]="INVTYPE_THROWN", ["投掷"]="INVTYPE_THROWN",
|
||
["Relic"]="INVTYPE_RELIC", ["圣物"]="INVTYPE_RELIC",
|
||
["Shield"]="INVTYPE_SHIELD", ["盾牌"]="INVTYPE_SHIELD",
|
||
}
|
||
|
||
local function ParseEquipLocFromTooltip(tooltip)
|
||
if not tooltip then return nil, nil, nil end
|
||
local tipName = tooltip:GetName()
|
||
if not tipName then return nil, nil, nil end
|
||
local numLines = tooltip:NumLines()
|
||
if not numLines or numLines < 2 then return nil, nil, nil end
|
||
|
||
local foundEquipLoc, foundArmorType, foundItemClass
|
||
for i = 2, math.min(numLines, 8) do
|
||
local leftObj = getglobal(tipName .. "TextLeft" .. i)
|
||
local rightObj = getglobal(tipName .. "TextRight" .. i)
|
||
if leftObj then
|
||
local leftText = leftObj:GetText()
|
||
if leftText and leftText ~= "" then
|
||
local invtype = SLOT_TEXT_TO_INVTYPE[leftText]
|
||
if invtype then
|
||
foundEquipLoc = invtype
|
||
if rightObj then
|
||
local rightText = rightObj:GetText()
|
||
if rightText and rightText ~= "" then
|
||
foundArmorType = rightText
|
||
if ARMOR_CLASS_NAMES[rightText] or SUBCLASS_NORMALIZE[rightText]
|
||
or SHIELD_NAMES[rightText] then
|
||
foundItemClass = "Armor"
|
||
end
|
||
end
|
||
end
|
||
break
|
||
end
|
||
end
|
||
end
|
||
end
|
||
return foundEquipLoc, foundArmorType, foundItemClass
|
||
end
|
||
|
||
local GS_EQUIP_LOCS = {
|
||
INVTYPE_HEAD=true, INVTYPE_NECK=true, INVTYPE_SHOULDER=true,
|
||
INVTYPE_BODY=true, INVTYPE_CHEST=true, INVTYPE_ROBE=true,
|
||
INVTYPE_WAIST=true, INVTYPE_LEGS=true, INVTYPE_FEET=true,
|
||
INVTYPE_WRIST=true, INVTYPE_HAND=true,
|
||
INVTYPE_FINGER=true, INVTYPE_TRINKET=true,
|
||
INVTYPE_CLOAK=true,
|
||
INVTYPE_WEAPON=true, INVTYPE_2HWEAPON=true, INVTYPE_WEAPONMAINHAND=true,
|
||
INVTYPE_SHIELD=true, INVTYPE_WEAPONOFFHAND=true, INVTYPE_HOLDABLE=true,
|
||
INVTYPE_RANGED=true, INVTYPE_RANGEDRIGHT=true, INVTYPE_THROWN=true,
|
||
INVTYPE_RELIC=true,
|
||
}
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Tooltip text parser (zero external dependency)
|
||
--------------------------------------------------------------------------------
|
||
|
||
local STAT_PATTERNS = {
|
||
-- Weapon DPS (float, e.g. "(每秒伤害19.7)" or "(12.3 damage per second)")
|
||
{ p = "%((%d+%.?%d*) damage per second%)", s = "WEAPONDPS" },
|
||
{ p = "每秒伤害(%d+%.?%d*)", s = "WEAPONDPS" },
|
||
{ p = "每秒(%d+%.?%d*)点伤害", s = "WEAPONDPS" },
|
||
|
||
-- Weapon attack speed ("速度 1.50" / "Speed 1.50") — stored as raw value
|
||
{ p = "^速度 (%d+%.?%d*)", s = "WEAPONSPEED" },
|
||
{ p = "^Speed (%d+%.?%d*)", s = "WEAPONSPEED" },
|
||
|
||
-- Base armor value ("63点护甲" / "123 护甲" / "500 Armor", no + prefix)
|
||
{ p = "^(%d+)点护甲", s = "BASEARMOR" },
|
||
{ p = "^(%d+) 点护甲", s = "BASEARMOR" },
|
||
{ p = "^(%d+) 护甲$", s = "BASEARMOR" },
|
||
{ p = "^(%d+) Armor$", s = "BASEARMOR" },
|
||
|
||
-- Bonus armor ("+30 Armor" / "+30 护甲" from enchants)
|
||
{ p = "%+(%d+) Armor", s = "ARMOR" },
|
||
{ p = "%+(%d+) 护甲", s = "ARMOR" },
|
||
{ p = "护甲值提高(%d+)", s = "ARMOR" },
|
||
|
||
-- Primary stats
|
||
{ p = "%+(%d+) Strength", s = "STR" },
|
||
{ p = "%+(%d+) Agility", s = "AGI" },
|
||
{ p = "%+(%d+) Stamina", s = "STA" },
|
||
{ p = "%+(%d+) Intellect", s = "INT" },
|
||
{ p = "%+(%d+) Spirit", s = "SPI" },
|
||
{ p = "%+(%d+) 力量", s = "STR" },
|
||
{ p = "%+(%d+) 敏捷", s = "AGI" },
|
||
{ p = "%+(%d+) 耐力", s = "STA" },
|
||
{ p = "%+(%d+) 智力", s = "INT" },
|
||
{ p = "%+(%d+) 精神", s = "SPI" },
|
||
|
||
-- Crit (spell > ranged > melee, more specific patterns first)
|
||
{ p = "critical strike with spells by (%d+)%%", s = "SPELLCRIT" },
|
||
{ p = "法术暴击.-(%d+)%%", s = "SPELLCRIT" },
|
||
{ p = "法术.-致命一击.-(%d+)%%", s = "SPELLCRIT" },
|
||
{ p = "法术.-爆击.-(%d+)%%", s = "SPELLCRIT" },
|
||
{ p = "critical strike with ranged weapons by (%d+)%%", s = "RANGEDCRIT" },
|
||
{ p = "远程暴击.-(%d+)%%", s = "RANGEDCRIT" },
|
||
{ p = "远程.-致命一击.-(%d+)%%", s = "RANGEDCRIT" },
|
||
{ p = "critical strike by (%d+)%%", s = "CRIT" },
|
||
{ p = "致命一击几率.-(%d+)%%", s = "CRIT" },
|
||
{ p = "致命一击.-提高(%d+)%%", s = "CRIT" },
|
||
{ p = "致命一击.-(%d+)%%", s = "CRIT" },
|
||
{ p = "暴击几率.-(%d+)%%", s = "CRIT" },
|
||
{ p = "暴击.-(%d+)%%", s = "CRIT" },
|
||
|
||
-- Hit (green equip effects: "使你击中目标的几率提高X%", "chance to hit by X%")
|
||
{ p = "hit with spells by (%d+)%%", s = "SPELLTOHIT" },
|
||
{ p = "法术击中.-(%d+)%%", s = "SPELLTOHIT" },
|
||
{ p = "法术命中.-(%d+)%%", s = "SPELLTOHIT" },
|
||
{ p = "用法术击中.-几率.-(%d+)%%", s = "SPELLTOHIT" },
|
||
{ p = "chance to hit by (%d+)%%", s = "TOHIT" },
|
||
{ p = "击中目标.-几率.-(%d+)%%", s = "TOHIT" },
|
||
{ p = "击中.-提高(%d+)%%", s = "TOHIT" },
|
||
{ p = "命中.-(%d+)%%", s = "TOHIT" },
|
||
|
||
-- Attack Power
|
||
{ p = "%+(%d+) ranged Attack Power", s = "RANGEDATTACKPOWER" },
|
||
{ p = "%+(%d+) 远程攻击强度", s = "RANGEDATTACKPOWER" },
|
||
{ p = "远程攻击强度提高(%d+)", s = "RANGEDATTACKPOWER" },
|
||
{ p = "%+(%d+) Attack Power", s = "ATTACKPOWER" },
|
||
{ p = "%+(%d+) 攻击强度", s = "ATTACKPOWER" },
|
||
{ p = "攻击强度提高(%d+)", s = "ATTACKPOWER" },
|
||
|
||
-- Spell damage + healing (combined, MUST be before individual patterns)
|
||
{ p = "damage and healing done by magical spells and effects by up to (%d+)", s = "DMG", a = "HEAL" },
|
||
{ p = "伤害和治疗效果.-最多(%d+)", s = "DMG", a = "HEAL" },
|
||
{ p = "法术伤害和治疗.-(%d+)", s = "DMG", a = "HEAL" },
|
||
|
||
-- Healing only
|
||
{ p = "healing done by spells and effects by up to (%d+)", s = "HEAL" },
|
||
{ p = "Increases healing done by up to (%d+)", s = "HEAL" },
|
||
{ p = "治疗效果.-最多(%d+)", s = "HEAL" },
|
||
{ p = "治疗法术.-最多(%d+)", s = "HEAL" },
|
||
{ p = "治疗量.-最多(%d+)", s = "HEAL" },
|
||
|
||
-- Spell damage only
|
||
{ p = "damage done by magical spells and effects by up to (%d+)", s = "DMG" },
|
||
{ p = "法术伤害.-最多(%d+)", s = "DMG" },
|
||
{ p = "魔法伤害.-最多(%d+)", s = "DMG" },
|
||
|
||
-- Mana/health regen
|
||
{ p = "Restores (%d+) mana per 5 sec", s = "MANAREG" },
|
||
{ p = "(%d+) mana per 5 sec", s = "MANAREG" },
|
||
{ p = "每5秒恢复(%d+)点法力", s = "MANAREG" },
|
||
{ p = "每5秒回复(%d+)点法力", s = "MANAREG" },
|
||
{ p = "5秒回复(%d+)点法力", s = "MANAREG" },
|
||
{ p = "Restores (%d+) health per 5 sec", s = "HEALTHREG" },
|
||
{ p = "每5秒恢复(%d+)点生命", s = "HEALTHREG" },
|
||
{ p = "每5秒回复(%d+)点生命", s = "HEALTHREG" },
|
||
|
||
-- Defense (green equip: "提高你的防御技能X点", "+X Defense")
|
||
{ p = "Increased Defense %+(%d+)", s = "DEFENSE" },
|
||
{ p = "Defense %+(%d+)", s = "DEFENSE" },
|
||
{ p = "%+(%d+) Defense", s = "DEFENSE" },
|
||
{ p = "防御技能提高(%d+)", s = "DEFENSE" },
|
||
{ p = "防御等级提高(%d+)", s = "DEFENSE" },
|
||
{ p = "防御.-提高(%d+)", s = "DEFENSE" },
|
||
|
||
-- Avoidance (green equip: "使你的躲闪几率提高X%")
|
||
{ p = "dodge.-by (%d+)%%", s = "DODGE" },
|
||
{ p = "躲闪几率.-(%d+)%%", s = "DODGE" },
|
||
{ p = "躲闪.-提高(%d+)%%", s = "DODGE" },
|
||
{ p = "躲闪.-(%d+)%%", s = "DODGE" },
|
||
{ p = "parry.-by (%d+)%%", s = "PARRY" },
|
||
{ p = "招架几率.-(%d+)%%", s = "PARRY" },
|
||
{ p = "招架.-提高(%d+)%%", s = "PARRY" },
|
||
{ p = "招架.-(%d+)%%", s = "PARRY" },
|
||
{ p = "block attacks.-by (%d+)%%", s = "BLOCK" },
|
||
{ p = "格挡几率.-(%d+)%%", s = "BLOCK" },
|
||
{ p = "格挡.-提高(%d+)%%", s = "BLOCK" },
|
||
{ p = "格挡率.-(%d+)%%", s = "BLOCK" },
|
||
{ p = "block value.-by (%d+)", s = "BLOCKVALUE" },
|
||
{ p = "格挡值.-(%d+)", s = "BLOCKVALUE" },
|
||
{ p = "盾牌格挡值.-(%d+)", s = "BLOCKVALUE" },
|
||
|
||
-- HP/Mana
|
||
{ p = "%+(%d+) Health", s = "HEALTH" },
|
||
{ p = "%+(%d+) Hit Points", s = "HEALTH" },
|
||
{ p = "%+(%d+) 生命值", s = "HEALTH" },
|
||
{ p = "%+(%d+) Mana", s = "MANA" },
|
||
{ p = "%+(%d+) 法力值", s = "MANA" },
|
||
}
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Debug helper: /gsdebug to toggle
|
||
--------------------------------------------------------------------------------
|
||
local GS_DEBUG = false
|
||
local function GSDebug(msg)
|
||
if GS_DEBUG and DEFAULT_CHAT_FRAME then
|
||
DEFAULT_CHAT_FRAME:AddMessage("|cffFFFF00[GS]|r " .. tostring(msg))
|
||
end
|
||
end
|
||
|
||
SLASH_GSDEBUG1 = "/gsdebug"
|
||
SlashCmdList["GSDEBUG"] = function()
|
||
GS_DEBUG = not GS_DEBUG
|
||
DEFAULT_CHAT_FRAME:AddMessage("|cffFFFF00[GearScore]|r Debug: " .. (GS_DEBUG and "ON" or "OFF"))
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Stat parser: scan a VISIBLE tooltip's text lines directly
|
||
-- This is the most reliable method — no hidden tooltip, no library needed
|
||
--------------------------------------------------------------------------------
|
||
|
||
local function ScanTooltipForStats(tooltip)
|
||
if not tooltip then return nil end
|
||
local tipName = tooltip:GetName()
|
||
if not tipName then return nil end
|
||
|
||
local numLines = tooltip:NumLines()
|
||
if not numLines or numLines < 2 then return nil end
|
||
|
||
local stats = {}
|
||
for i = 2, numLines do
|
||
local lineObj = getglobal(tipName .. "TextLeft" .. i)
|
||
if lineObj then
|
||
local text = lineObj:GetText()
|
||
if text and text ~= "" then
|
||
for _, pat in ipairs(STAT_PATTERNS) do
|
||
local _, _, val = string.find(text, pat.p)
|
||
if val then
|
||
local n = tonumber(val)
|
||
if n and n > 0 then
|
||
stats[pat.s] = (stats[pat.s] or 0) + n
|
||
if pat.a then
|
||
stats[pat.a] = (stats[pat.a] or 0) + n
|
||
end
|
||
GSDebug(" L" .. i .. ": " .. pat.s .. "=" .. n .. " (" .. text .. ")")
|
||
end
|
||
break
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
local hasAny = false
|
||
for _ in pairs(stats) do hasAny = true; break end
|
||
return hasAny and stats or nil
|
||
end
|
||
|
||
local function ParseItemWithLib(link)
|
||
if not link then return nil end
|
||
if not AceLibrary or not AceLibrary.HasInstance then return nil end
|
||
if not AceLibrary:HasInstance("ItemBonusLib-1.0") then return nil end
|
||
local lib = AceLibrary("ItemBonusLib-1.0")
|
||
if not lib or not lib.ScanItem then return nil end
|
||
local ok, result = pcall(function() return lib:ScanItem(link, true) end)
|
||
if ok and result then
|
||
local hasAny = false
|
||
for _ in pairs(result) do hasAny = true; break end
|
||
if hasAny then return result end
|
||
end
|
||
return nil
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Compatibility multipliers
|
||
--------------------------------------------------------------------------------
|
||
|
||
local function GetArmorCompat(classToken, itemClass, itemSubClass, level)
|
||
if not itemClass or not ARMOR_CLASS_NAMES[itemClass] then return 1.0 end
|
||
if not itemSubClass or itemSubClass == "" then return 1.0 end
|
||
if ARMOR_MISC_NAMES[itemSubClass] then return 1.0 end
|
||
if SHIELD_NAMES[itemSubClass] then return 1.0 end
|
||
|
||
local normalizedSub = SUBCLASS_NORMALIZE[itemSubClass]
|
||
if not normalizedSub then return 1.0 end
|
||
|
||
local tbl = (level < 40) and ARMOR_COMPAT_LOW or ARMOR_COMPAT
|
||
local classTbl = tbl[classToken]
|
||
if not classTbl then return 1.0 end
|
||
return classTbl[normalizedSub] or 0.3
|
||
end
|
||
|
||
local function GetSlotCompat(classToken, specIdx, equipLoc)
|
||
if not equipLoc or equipLoc == "" then return 1.0 end
|
||
local key = classToken .. "_" .. specIdx
|
||
local tbl = SLOT_COMPAT[key]
|
||
if not tbl then return 1.0 end
|
||
local val = tbl[equipLoc]
|
||
if val ~= nil then return val end
|
||
return 1.0
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Core scoring: 1-10 normalized scale
|
||
-- Score = (actual_EP / ideal_EP) * 10 * equipment_compat
|
||
-- ideal_EP = total_budget * best_efficiency_for_spec
|
||
--------------------------------------------------------------------------------
|
||
|
||
-- Slot-specific and non-standard stats excluded from reference efficiency
|
||
local EFF_EXCLUDE = { WEAPONDPS=true, WEAPONSPEED=true, BASEARMOR=true, HEALTH=true, MANA=true }
|
||
-- Stats that only appear on higher-level items
|
||
local EFF_LATE_GAME = { DMG=true, HEAL=true, SPELLTOHIT=true, SPELLCRIT=true,
|
||
TOHIT=true, CRIT=true, RANGEDCRIT=true }
|
||
local EFF_MID_GAME = { DMG=true, HEAL=true }
|
||
|
||
local function GetRefEfficiency(weights, level)
|
||
local effs = {}
|
||
for stat, w in pairs(weights) do
|
||
if not EFF_EXCLUDE[stat] then
|
||
local cost = BUDGET_COST[stat]
|
||
if cost and cost > 0 and w > 0 then
|
||
local skip = false
|
||
if level and level < 25 and EFF_LATE_GAME[stat] then skip = true end
|
||
if level and level >= 25 and level < 40 and EFF_MID_GAME[stat] then skip = true end
|
||
if not skip then
|
||
table.insert(effs, w / cost)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
table.sort(effs, function(a,b) return a > b end)
|
||
|
||
local n = table.getn(effs)
|
||
local ref = 0
|
||
if n >= 3 then
|
||
ref = effs[1] * 0.45 + effs[2] * 0.30 + effs[3] * 0.25
|
||
elseif n == 2 then
|
||
ref = effs[1] * 0.55 + effs[2] * 0.45
|
||
elseif n == 1 then
|
||
ref = effs[1]
|
||
end
|
||
|
||
if ref <= 0 then ref = 1.0 end
|
||
return ref
|
||
end
|
||
|
||
local DPS_DAMPEN_MELEE = 0.40
|
||
local DPS_DAMPEN_RANGED = 0.70
|
||
|
||
local function CalcRawEP(bonuses, weights, dpsDampen)
|
||
if not bonuses or not weights then return 0 end
|
||
dpsDampen = dpsDampen or DPS_DAMPEN_MELEE
|
||
local ep = 0
|
||
for stat, value in pairs(bonuses) do
|
||
local w = weights[stat]
|
||
if w and w > 0 then
|
||
if stat == "WEAPONDPS" then
|
||
ep = ep + value * w * dpsDampen
|
||
elseif stat == "WEAPONSPEED" then
|
||
local speedBonus = 3.0 - value
|
||
if speedBonus > 0 then
|
||
ep = ep + speedBonus * w
|
||
end
|
||
else
|
||
ep = ep + value * w
|
||
end
|
||
end
|
||
end
|
||
return ep
|
||
end
|
||
|
||
local function CalcTotalBudget(bonuses)
|
||
if not bonuses then return 0 end
|
||
local total = 0
|
||
for stat, value in pairs(bonuses) do
|
||
local cost = BUDGET_COST[stat]
|
||
if cost and cost > 0 then
|
||
if stat == "WEAPONSPEED" then
|
||
local speedBonus = 3.0 - value
|
||
if speedBonus > 0 then
|
||
total = total + speedBonus * cost
|
||
end
|
||
else
|
||
total = total + math.abs(value) * cost
|
||
end
|
||
end
|
||
end
|
||
return total
|
||
end
|
||
|
||
local GS_RANGED_LOCS = {
|
||
INVTYPE_RANGED = true, INVTYPE_RANGEDRIGHT = true, INVTYPE_THROWN = true,
|
||
}
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Horizontal comparison: reference EP for the best rare item at level/slot
|
||
--------------------------------------------------------------------------------
|
||
|
||
local SLOT_BUDGET_MOD = {
|
||
INVTYPE_HEAD = 1.0, INVTYPE_CHEST = 1.0, INVTYPE_ROBE = 1.0, INVTYPE_LEGS = 1.0,
|
||
INVTYPE_SHOULDER = 0.77, INVTYPE_HAND = 0.77, INVTYPE_WAIST = 0.77, INVTYPE_FEET = 0.77,
|
||
INVTYPE_WRIST = 0.56, INVTYPE_CLOAK = 0.56,
|
||
INVTYPE_NECK = 0.56, INVTYPE_FINGER = 0.56, INVTYPE_TRINKET = 0.56,
|
||
INVTYPE_WEAPON = 0.42, INVTYPE_WEAPONMAINHAND = 0.42, INVTYPE_WEAPONOFFHAND = 0.36,
|
||
INVTYPE_2HWEAPON = 1.0,
|
||
INVTYPE_SHIELD = 0.56, INVTYPE_HOLDABLE = 0.42,
|
||
INVTYPE_RANGED = 0.32, INVTYPE_RANGEDRIGHT = 0.32, INVTYPE_THROWN = 0.32,
|
||
INVTYPE_RELIC = 0.32, INVTYPE_TABARD = 0, INVTYPE_BODY = 0,
|
||
}
|
||
|
||
local WEAPON_EQUIP_LOCS = {
|
||
INVTYPE_WEAPON = true, INVTYPE_WEAPONMAINHAND = true, INVTYPE_WEAPONOFFHAND = true,
|
||
INVTYPE_2HWEAPON = true, INVTYPE_RANGED = true, INVTYPE_RANGEDRIGHT = true,
|
||
INVTYPE_THROWN = true,
|
||
}
|
||
|
||
local ARMOR_EQUIP_LOCS = {
|
||
INVTYPE_HEAD = true, INVTYPE_CHEST = true, INVTYPE_ROBE = true, INVTYPE_LEGS = true,
|
||
INVTYPE_SHOULDER = true, INVTYPE_HAND = true, INVTYPE_WAIST = true, INVTYPE_FEET = true,
|
||
INVTYPE_WRIST = true, INVTYPE_SHIELD = true,
|
||
}
|
||
|
||
local function GetRefWeaponDPS(equipLoc, level)
|
||
local dps
|
||
if level >= 58 then dps = 48
|
||
elseif level >= 40 then dps = 28 + (level - 40) * 0.55
|
||
elseif level >= 20 then dps = 13 + (level - 20) * 0.75
|
||
elseif level >= 10 then dps = 6 + (level - 10) * 0.7
|
||
else dps = 3 + level * 0.3
|
||
end
|
||
if equipLoc == "INVTYPE_2HWEAPON" then dps = dps * 1.3 end
|
||
return dps
|
||
end
|
||
|
||
local function GetReferenceEP(equipLoc, level, weights, refEff)
|
||
local slotMod = SLOT_BUDGET_MOD[equipLoc] or 0.56
|
||
|
||
local refIlvl
|
||
if level >= 58 then refIlvl = 63
|
||
elseif level >= 40 then refIlvl = level + 7
|
||
elseif level >= 20 then refIlvl = level + 5
|
||
else refIlvl = level + 3
|
||
end
|
||
|
||
local statBudget = refIlvl * 0.65 * slotMod
|
||
local ep = statBudget * refEff
|
||
|
||
if WEAPON_EQUIP_LOCS[equipLoc] then
|
||
local wDPS = weights.WEAPONDPS
|
||
if wDPS and wDPS > 0 then
|
||
local refDPS = GetRefWeaponDPS(equipLoc, level)
|
||
local dampen = GS_RANGED_LOCS[equipLoc] and DPS_DAMPEN_RANGED or DPS_DAMPEN_MELEE
|
||
ep = ep + refDPS * wDPS * dampen
|
||
end
|
||
end
|
||
|
||
if ARMOR_EQUIP_LOCS[equipLoc] then
|
||
local wArmor = weights.BASEARMOR
|
||
if wArmor and wArmor > 0 then
|
||
local refArmor = level * 1.5 * slotMod
|
||
ep = ep + refArmor * wArmor
|
||
end
|
||
end
|
||
|
||
return ep
|
||
end
|
||
|
||
local function CalcNormalizedScore(bonuses, weights, armorCompat, slotCompat, level, equipLoc)
|
||
local dpsDampen = DPS_DAMPEN_MELEE
|
||
if equipLoc and GS_RANGED_LOCS[equipLoc] then dpsDampen = DPS_DAMPEN_RANGED end
|
||
local rawEP = CalcRawEP(bonuses, weights, dpsDampen)
|
||
local refEff = GetRefEfficiency(weights, level)
|
||
local refEP = GetReferenceEP(equipLoc, level, weights, refEff)
|
||
|
||
if refEP <= 0 then return 0 end
|
||
|
||
local rawScore = (rawEP / refEP) * 10
|
||
|
||
local compat = (armorCompat or 1.0) * (slotCompat or 1.0)
|
||
local finalScore = rawScore * compat
|
||
|
||
if finalScore < 0 then finalScore = 0 end
|
||
if finalScore > 10 then finalScore = 10 end
|
||
|
||
return math.floor(finalScore * 10 + 0.5) / 10
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Score color (1-10 scale)
|
||
--------------------------------------------------------------------------------
|
||
|
||
local function ScoreColorHex(score)
|
||
if score >= 9 then return "ffFF8800" end
|
||
if score >= 7 then return "ff00FF00" end
|
||
if score >= 5 then return "ffFFFFFF" end
|
||
if score >= 3 then return "ffFFFF00" end
|
||
return "ffFF4444"
|
||
end
|
||
|
||
local function ScoreLabel(score)
|
||
if score >= 9 then return "极品" end
|
||
if score >= 7 then return "优秀" end
|
||
if score >= 5 then return "适用" end
|
||
if score >= 3 then return "一般" end
|
||
return "不适"
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Score tooltip (uses GameTooltipTemplate — proven reliable in this addon)
|
||
-- Created lazily on first use (nil parent like Trade.lua pattern)
|
||
--------------------------------------------------------------------------------
|
||
|
||
local GSTip
|
||
|
||
local function GS_ShowTip(parentTip, scoreLines)
|
||
if not scoreLines or not parentTip then return end
|
||
|
||
if not GSTip then
|
||
GSTip = CreateFrame("GameTooltip", "NanamiGSTooltip", nil, "GameTooltipTemplate")
|
||
GSTip:SetFrameStrata("TOOLTIP")
|
||
GSTip:SetClampedToScreen(true)
|
||
end
|
||
|
||
GSTip:SetOwner(parentTip, "ANCHOR_NONE")
|
||
GSTip:ClearAllPoints()
|
||
|
||
local bottom = parentTip:GetBottom()
|
||
if bottom and bottom > 80 then
|
||
GSTip:SetPoint("TOPLEFT", parentTip, "BOTTOMLEFT", 0, 2)
|
||
else
|
||
GSTip:SetPoint("BOTTOMLEFT", parentTip, "TOPLEFT", 0, -2)
|
||
end
|
||
|
||
for _, entry in ipairs(scoreLines) do
|
||
if entry.left and entry.right then
|
||
GSTip:AddDoubleLine(entry.left, entry.right,
|
||
entry.lr or 1, entry.lg or 1, entry.lb or 1,
|
||
entry.rr or 1, entry.rg or 1, entry.rb or 1)
|
||
else
|
||
GSTip:AddLine(entry.text or "", entry.r or 1, entry.g or 0.84, entry.b or 0)
|
||
end
|
||
end
|
||
|
||
GSTip:Show()
|
||
end
|
||
|
||
local function GS_HideFrame()
|
||
if GSTip then GSTip:Hide() end
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Main tooltip function
|
||
--------------------------------------------------------------------------------
|
||
|
||
local GS_SCORE_CACHE = {}
|
||
local GS_CACHE_SIZE = 0
|
||
local GS_CACHE_MAX = 200
|
||
|
||
function GS:AddScoreToTooltip(tooltip, link)
|
||
if not tooltip or not link then return end
|
||
if SFramesDB and SFramesDB.gearScore == false then return end
|
||
if tooltip._gsScoreAdded then return end
|
||
|
||
local classToken = GetPlayerClassToken()
|
||
if not classToken then return end
|
||
local classData = WEIGHTS[classToken]
|
||
if not classData then return end
|
||
|
||
local cacheKey = classToken .. "|" .. link
|
||
local cached = GS_SCORE_CACHE[cacheKey]
|
||
if cached then
|
||
tooltip._gsScoreAdded = true
|
||
if cached.scoreLines then
|
||
GS_ShowTip(tooltip, cached.scoreLines)
|
||
else
|
||
GS_HideFrame()
|
||
end
|
||
return
|
||
end
|
||
|
||
GSDebug("Processing: " .. tostring(link))
|
||
|
||
local quality = GetQualityFromLink(link)
|
||
if quality < 0 then
|
||
quality = GetQualityFromTooltip(tooltip)
|
||
end
|
||
local qualityMult = QUALITY_MULT[quality] or 0.75
|
||
if quality == 0 then
|
||
GSDebug("Skipped: poor quality (gray) item")
|
||
return
|
||
end
|
||
GSDebug("Quality=" .. quality .. " mult=" .. qualityMult)
|
||
|
||
local equipLoc, itemClass, itemSubClass
|
||
pcall(function()
|
||
local itemStr = ExtractItemString(link)
|
||
local infoArg = itemStr or link
|
||
GSDebug("GetItemInfo arg: " .. tostring(infoArg))
|
||
local results = { GetItemInfo(infoArg) }
|
||
if results[1] then
|
||
itemClass = results[6]
|
||
itemSubClass = results[7]
|
||
for idx = 8, 10 do
|
||
local v = results[idx]
|
||
if type(v) == "string" and (v == "" or string.find(v, "^INVTYPE")) then
|
||
equipLoc = v
|
||
break
|
||
end
|
||
end
|
||
end
|
||
GSDebug("GetItemInfo: equip=" .. tostring(equipLoc)
|
||
.. " class=" .. tostring(itemClass) .. " sub=" .. tostring(itemSubClass))
|
||
end)
|
||
|
||
if not equipLoc then
|
||
local ttEquip, ttArmor, ttClass = ParseEquipLocFromTooltip(tooltip)
|
||
if ttEquip then
|
||
equipLoc = ttEquip
|
||
if ttArmor and not itemSubClass then itemSubClass = ttArmor end
|
||
if ttClass and not itemClass then itemClass = ttClass end
|
||
GSDebug("Parsed from tooltip: equip=" .. tostring(equipLoc) .. " armor=" .. tostring(ttArmor))
|
||
end
|
||
end
|
||
|
||
if equipLoc == "INVTYPE_BAG" or equipLoc == "INVTYPE_AMMO" or equipLoc == "INVTYPE_TABARD" then
|
||
GSDebug("Skipped: bag/ammo/tabard")
|
||
return
|
||
end
|
||
if equipLoc and equipLoc ~= "" and not GS_EQUIP_LOCS[equipLoc] then
|
||
GSDebug("Skipped: not equippable (" .. tostring(equipLoc) .. ")")
|
||
return
|
||
end
|
||
|
||
local bonuses = ParseItemWithLib(link)
|
||
if bonuses then
|
||
GSDebug("Stats from ItemBonusLib")
|
||
else
|
||
bonuses = ScanTooltipForStats(tooltip)
|
||
if bonuses then
|
||
GSDebug("Stats from tooltip text")
|
||
end
|
||
end
|
||
|
||
if not bonuses then
|
||
GSDebug("No stats found, skipping")
|
||
return
|
||
end
|
||
|
||
if not equipLoc or equipLoc == "" then
|
||
GSDebug("No equipLoc, not equipment, skipping")
|
||
return
|
||
end
|
||
|
||
local level = GetPlayerLevel()
|
||
local primaryTab = GetPrimaryTabIndex()
|
||
local isHC = IsPlayerHardcore()
|
||
|
||
local armorCompat = GetArmorCompat(classToken, itemClass, itemSubClass, level)
|
||
GSDebug("ArmorCompat=" .. armorCompat .. " class=" .. classToken
|
||
.. " slot=" .. tostring(equipLoc) .. " lv=" .. level)
|
||
|
||
local specs = classData.specs
|
||
if not specs or table.getn(specs) == 0 then return end
|
||
|
||
local scores = {}
|
||
local anyShow = false
|
||
for i, spec in ipairs(specs) do
|
||
local w = AdjustWeightsForLevel(spec.w, level, false)
|
||
local slotCompat = GetSlotCompat(classToken, i, equipLoc)
|
||
local refEff = GetRefEfficiency(w, level)
|
||
local refEP = GetReferenceEP(equipLoc, level, w, refEff)
|
||
local s = CalcNormalizedScore(bonuses, w, armorCompat, slotCompat, level, equipLoc)
|
||
GSDebug(" " .. spec.name .. ": rawScore=" .. string.format("%.2f", s)
|
||
.. " refEP=" .. string.format("%.1f", refEP) .. " refEff=" .. string.format("%.3f", refEff))
|
||
s = math.floor(s * qualityMult * 10 + 0.5) / 10
|
||
if s < 1.0 and s > 0 then s = 1.0 end
|
||
table.insert(scores, {
|
||
name = spec.name,
|
||
color = spec.color,
|
||
score = s,
|
||
isPrimary = (spec.tab == primaryTab),
|
||
label = ScoreLabel(s),
|
||
})
|
||
if s > 0 then anyShow = true end
|
||
end
|
||
|
||
local hcScore = 0
|
||
if classData.hc then
|
||
local hw = AdjustWeightsForLevel(classData.hc.w, level, true)
|
||
hcScore = CalcNormalizedScore(bonuses, hw, armorCompat, 1.0, level, equipLoc)
|
||
if not bonuses.STA or bonuses.STA <= 0 then
|
||
hcScore = hcScore * 0.35
|
||
end
|
||
hcScore = math.floor(hcScore * qualityMult * 10 + 0.5) / 10
|
||
if hcScore < 1.0 and hcScore > 0 then hcScore = 1.0 end
|
||
if hcScore > 0 then anyShow = true end
|
||
end
|
||
|
||
if not anyShow then
|
||
GS_SCORE_CACHE[cacheKey] = { scoreLines = nil }
|
||
GS_CACHE_SIZE = GS_CACHE_SIZE + 1
|
||
GS_HideFrame()
|
||
return
|
||
end
|
||
|
||
tooltip._gsScoreAdded = true
|
||
|
||
local scoreLines = {}
|
||
table.insert(scoreLines, { text = "── 装备评分 ──", r = 1, g = 0.84, b = 0 })
|
||
|
||
for _, sd in ipairs(scores) do
|
||
local star = sd.isPrimary and "★ " or " "
|
||
local sStr = string.format("%.1f", sd.score)
|
||
local sColor = ScoreColorHex(sd.score)
|
||
table.insert(scoreLines, {
|
||
left = star .. "|c" .. sd.color .. sd.name .. "|r",
|
||
right = "|c" .. sColor .. sStr .. " " .. sd.label .. "|r",
|
||
})
|
||
end
|
||
|
||
if classData.hc and hcScore > 0 then
|
||
local hcSColor = ScoreColorHex(hcScore)
|
||
local hcStar = isHC and "★ " or " "
|
||
table.insert(scoreLines, {
|
||
left = hcStar .. "|c" .. classData.hc.color .. "硬核|r",
|
||
right = "|c" .. hcSColor .. string.format("%.1f", hcScore)
|
||
.. " " .. ScoreLabel(hcScore) .. "|r",
|
||
})
|
||
end
|
||
|
||
GS_ShowTip(tooltip, scoreLines)
|
||
|
||
if GS_CACHE_SIZE >= GS_CACHE_MAX then
|
||
GS_SCORE_CACHE = {}
|
||
GS_CACHE_SIZE = 0
|
||
end
|
||
GS_SCORE_CACHE[cacheKey] = { scoreLines = scoreLines }
|
||
GS_CACHE_SIZE = GS_CACHE_SIZE + 1
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Self-contained tooltip hooks
|
||
--------------------------------------------------------------------------------
|
||
|
||
local function GS_TryEnhance(tooltip, link)
|
||
if not link then return end
|
||
pcall(function() GS:AddScoreToTooltip(tooltip, link) end)
|
||
end
|
||
|
||
function GS:HookTooltips()
|
||
if self._hooked then return end
|
||
self._hooked = true
|
||
|
||
local origHide = GameTooltip:GetScript("OnHide")
|
||
GameTooltip:SetScript("OnHide", function()
|
||
this._gsScoreAdded = nil
|
||
GS_HideFrame()
|
||
if origHide then origHide() end
|
||
end)
|
||
|
||
local origBag = GameTooltip.SetBagItem
|
||
GameTooltip.SetBagItem = function(self, bag, slot)
|
||
self._gsScoreAdded = nil
|
||
local r1, r2, r3 = origBag(self, bag, slot)
|
||
GS_TryEnhance(self, GetContainerItemLink(bag, slot))
|
||
return r1, r2, r3
|
||
end
|
||
|
||
local origInv = GameTooltip.SetInventoryItem
|
||
GameTooltip.SetInventoryItem = function(self, unit, slotId)
|
||
self._gsScoreAdded = nil
|
||
local r1, r2, r3 = origInv(self, unit, slotId)
|
||
if unit and slotId then
|
||
GS_TryEnhance(self, GetInventoryItemLink(unit, slotId))
|
||
end
|
||
return r1, r2, r3
|
||
end
|
||
|
||
if GameTooltip.SetMerchantItem then
|
||
local orig = GameTooltip.SetMerchantItem
|
||
GameTooltip.SetMerchantItem = function(self, idx)
|
||
self._gsScoreAdded = nil
|
||
orig(self, idx)
|
||
if GetMerchantItemLink then GS_TryEnhance(self, GetMerchantItemLink(idx)) end
|
||
end
|
||
end
|
||
|
||
if GameTooltip.SetQuestItem then
|
||
local orig = GameTooltip.SetQuestItem
|
||
GameTooltip.SetQuestItem = function(self, qtype, idx)
|
||
self._gsScoreAdded = nil
|
||
orig(self, qtype, idx)
|
||
if GetQuestItemLink then GS_TryEnhance(self, GetQuestItemLink(qtype, idx)) end
|
||
end
|
||
end
|
||
|
||
if GameTooltip.SetQuestLogItem then
|
||
local orig = GameTooltip.SetQuestLogItem
|
||
GameTooltip.SetQuestLogItem = function(self, itype, idx)
|
||
self._gsScoreAdded = nil
|
||
orig(self, itype, idx)
|
||
if GetQuestLogItemLink then GS_TryEnhance(self, GetQuestLogItemLink(itype, idx)) end
|
||
end
|
||
end
|
||
|
||
if GameTooltip.SetLootItem then
|
||
local orig = GameTooltip.SetLootItem
|
||
GameTooltip.SetLootItem = function(self, idx)
|
||
self._gsScoreAdded = nil
|
||
orig(self, idx)
|
||
if GetLootSlotLink then GS_TryEnhance(self, GetLootSlotLink(idx)) end
|
||
end
|
||
end
|
||
|
||
if GameTooltip.SetLootRollItem then
|
||
local orig = GameTooltip.SetLootRollItem
|
||
GameTooltip.SetLootRollItem = function(self, rollId)
|
||
self._gsScoreAdded = nil
|
||
orig(self, rollId)
|
||
if GetLootRollItemLink then GS_TryEnhance(self, GetLootRollItemLink(rollId)) end
|
||
end
|
||
end
|
||
|
||
if GameTooltip.SetAuctionItem then
|
||
local orig = GameTooltip.SetAuctionItem
|
||
GameTooltip.SetAuctionItem = function(self, atype, idx)
|
||
self._gsScoreAdded = nil
|
||
orig(self, atype, idx)
|
||
if GetAuctionItemLink then GS_TryEnhance(self, GetAuctionItemLink(atype, idx)) end
|
||
end
|
||
end
|
||
|
||
if GameTooltip.SetTradeSkillItem then
|
||
local orig = GameTooltip.SetTradeSkillItem
|
||
GameTooltip.SetTradeSkillItem = function(self, skillIdx, reagentIdx)
|
||
self._gsScoreAdded = nil
|
||
orig(self, skillIdx, reagentIdx)
|
||
local link
|
||
if reagentIdx then
|
||
if GetTradeSkillReagentItemLink then link = GetTradeSkillReagentItemLink(skillIdx, reagentIdx) end
|
||
else
|
||
if GetTradeSkillItemLink then link = GetTradeSkillItemLink(skillIdx) end
|
||
end
|
||
GS_TryEnhance(self, link)
|
||
end
|
||
end
|
||
|
||
if GameTooltip.SetCraftItem then
|
||
local orig = GameTooltip.SetCraftItem
|
||
GameTooltip.SetCraftItem = function(self, skill, slot)
|
||
self._gsScoreAdded = nil
|
||
orig(self, skill, slot)
|
||
if GetCraftReagentItemLink then GS_TryEnhance(self, GetCraftReagentItemLink(skill, slot)) end
|
||
end
|
||
end
|
||
|
||
if GameTooltip.SetTradePlayerItem then
|
||
local orig = GameTooltip.SetTradePlayerItem
|
||
GameTooltip.SetTradePlayerItem = function(self, idx)
|
||
self._gsScoreAdded = nil
|
||
orig(self, idx)
|
||
if GetTradePlayerItemLink then GS_TryEnhance(self, GetTradePlayerItemLink(idx)) end
|
||
end
|
||
end
|
||
|
||
if GameTooltip.SetTradeTargetItem then
|
||
local orig = GameTooltip.SetTradeTargetItem
|
||
GameTooltip.SetTradeTargetItem = function(self, idx)
|
||
self._gsScoreAdded = nil
|
||
orig(self, idx)
|
||
if GetTradeTargetItemLink then GS_TryEnhance(self, GetTradeTargetItemLink(idx)) end
|
||
end
|
||
end
|
||
|
||
if GameTooltip.SetInboxItem then
|
||
local orig = GameTooltip.SetInboxItem
|
||
GameTooltip.SetInboxItem = function(self, mailID, attachIdx)
|
||
self._gsScoreAdded = nil
|
||
orig(self, mailID, attachIdx)
|
||
local left1 = getglobal("GameTooltipTextLeft1")
|
||
if left1 and GetItemLinkByName then
|
||
GS_TryEnhance(self, GetItemLinkByName(left1:GetText()))
|
||
end
|
||
end
|
||
end
|
||
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Initialize
|
||
--------------------------------------------------------------------------------
|
||
|
||
local initFrame = CreateFrame("Frame")
|
||
initFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||
initFrame:SetScript("OnEvent", function()
|
||
initFrame:UnregisterEvent("PLAYER_ENTERING_WORLD")
|
||
GS:HookTooltips()
|
||
end)
|