1485 lines
51 KiB
Lua
1485 lines
51 KiB
Lua
-- NanamiPlates Spell Database
|
|
-- Debuff duration tracking with rank support (ShaguPlates-style)
|
|
|
|
-- Performance: Upvalue frequently used globals
|
|
local pairs = pairs
|
|
local type = type
|
|
local tonumber = tonumber
|
|
local string_gfind = string.gfind
|
|
local string_sub = string.sub
|
|
local GetTime = GetTime
|
|
|
|
NanamiPlates_SpellDB = {}
|
|
NanamiPlates_SpellDB.scanner = nil
|
|
|
|
-- Chinese → English spell name mapping (for localized tooltip scanning)
|
|
NanamiPlates_SpellDB.localeMap = {
|
|
-- WARRIOR
|
|
["撕裂"] = "Rend", ["雷霆一击"] = "Thunder Clap", ["破甲攻击"] = "Sunder Armor",
|
|
["缴械"] = "Disarm", ["断筋"] = "Hamstring", ["挫志怒吼"] = "Demoralizing Shout",
|
|
["破胆怒吼"] = "Intimidating Shout", ["震荡猛击"] = "Concussion Blow",
|
|
["嘲讽打击"] = "Mocking Blow", ["刺耳怒吼"] = "Piercing Howl",
|
|
["致死打击"] = "Mortal Strike", ["重伤"] = "Deep Wound",
|
|
["冲锋昏迷"] = "Charge Stun", ["冲锋"] = "Charge",
|
|
["拦截昏迷"] = "Intercept Stun", ["拦截"] = "Intercept",
|
|
["挑战怒吼"] = "Challenging Shout",
|
|
["嘲讽"] = "Taunt",
|
|
-- ROGUE
|
|
["偷袭"] = "Cheap Shot", ["肾击"] = "Kidney Shot", ["闷棍"] = "Sap",
|
|
["致盲"] = "Blind", ["凿击"] = "Gouge", ["割裂"] = "Rupture",
|
|
["绞喉"] = "Garrote", ["破甲"] = "Expose Armor",
|
|
["致残毒药"] = "Crippling Poison", ["致残毒药 II"] = "Crippling Poison II",
|
|
["致命毒药"] = "Deadly Poison", ["致命毒药 II"] = "Deadly Poison II",
|
|
["致命毒药 III"] = "Deadly Poison III", ["致命毒药 IV"] = "Deadly Poison IV",
|
|
["致命毒药 V"] = "Deadly Poison V",
|
|
["麻痹毒药"] = "Mind-numbing Poison", ["麻痹毒药 II"] = "Mind-numbing Poison II",
|
|
["麻痹毒药 III"] = "Mind-numbing Poison III",
|
|
["伤残毒药"] = "Wound Poison", ["伤残毒药 II"] = "Wound Poison II",
|
|
["伤残毒药 III"] = "Wound Poison III", ["伤残毒药 IV"] = "Wound Poison IV",
|
|
["速效毒药"] = "Instant Poison",
|
|
["脚踢 - 沉默"] = "Kick - Silenced",
|
|
-- Turtle WoW rogue variants
|
|
["腐蚀毒药"] = "Crippling Poison",
|
|
["腐蚀毒药 II"] = "Crippling Poison II",
|
|
-- MAGE
|
|
["冰霜新星"] = "Frost Nova", ["变形术"] = "Polymorph",
|
|
["变形术:猪"] = "Polymorph: Pig", ["变形术:龟"] = "Polymorph: Turtle",
|
|
["变形术:牛"] = "Polymorph: Cow",
|
|
["寒冰箭"] = "Frostbolt", ["冰锥术"] = "Cone of Cold",
|
|
["冰霜撕咬"] = "Frostbite", ["法术反制 - 沉默"] = "Counterspell - Silenced",
|
|
["冬天的寒意"] = "Winter's Chill", ["火球术"] = "Fireball",
|
|
["炎爆术"] = "Pyroblast", ["点燃"] = "Ignite",
|
|
-- WARLOCK
|
|
["腐蚀术"] = "Corruption", ["献祭"] = "Immolate",
|
|
["恐惧术"] = "Fear", ["恐惧嚎叫"] = "Howl of Terror",
|
|
["死亡缠绕"] = "Death Coil", ["痛苦诅咒"] = "Curse of Agony",
|
|
["虚弱诅咒"] = "Curse of Weakness", ["鲁莽诅咒"] = "Curse of Recklessness",
|
|
["语言诅咒"] = "Curse of Tongues", ["元素诅咒"] = "Curse of the Elements",
|
|
["暗影诅咒"] = "Curse of Shadow", ["疲劳诅咒"] = "Curse of Exhaustion",
|
|
["末日诅咒"] = "Curse of Doom", ["生命虹吸"] = "Siphon Life",
|
|
["生命吸取"] = "Drain Life", ["法力吸取"] = "Drain Mana",
|
|
["灵魂吸取"] = "Drain Soul", ["放逐术"] = "Banish",
|
|
["奴役恶魔"] = "Enslave Demon", ["魅惑"] = "Seduction",
|
|
["暗影易伤"] = "Shadow Vulnerability",
|
|
["法术封锁"] = "Spell Lock",
|
|
-- PRIEST
|
|
["暗言术:痛"] = "Shadow Word: Pain", ["心灵尖啸"] = "Psychic Scream",
|
|
["精神鞭笞"] = "Mind Flay", ["精神控制"] = "Mind Control",
|
|
["沉默"] = "Silence", ["虚弱灵魂"] = "Weakened Soul",
|
|
["噬灵瘟疫"] = "Devouring Plague", ["吸血鬼的拥抱"] = "Vampiric Embrace",
|
|
["昏厥"] = "Blackout",
|
|
-- HUNTER
|
|
["毒蛇钉刺"] = "Serpent Sting", ["蝰蛇钉刺"] = "Viper Sting",
|
|
["毒蝎钉刺"] = "Scorpid Sting", ["震荡射击"] = "Concussive Shot",
|
|
["驱散射击"] = "Scatter Shot", ["摔绊"] = "Wing Clip",
|
|
["猎人印记"] = "Hunter's Mark", ["反击"] = "Counterattack",
|
|
["翼龙钉刺"] = "Wyvern Sting", ["冰冻陷阱效果"] = "Freezing Trap Effect",
|
|
["爆炸陷阱效果"] = "Explosive Trap Effect", ["献祭陷阱效果"] = "Immolation Trap Effect",
|
|
["冰霜陷阱光环"] = "Frost Trap Aura", ["胁迫"] = "Intimidation",
|
|
["诱捕"] = "Entrapment", ["恐吓野兽"] = "Scare Beast",
|
|
-- DRUID
|
|
["月火术"] = "Moonfire", ["纠缠根须"] = "Entangling Roots",
|
|
["重击"] = "Bash", ["精灵之火"] = "Faerie Fire",
|
|
["精灵之火(野性)"] = "Faerie Fire (Feral)",
|
|
["撕碎"] = "Rake", ["撕扯"] = "Rip", ["突袭流血"] = "Pounce Bleed",
|
|
["突袭"] = "Pounce", ["虫群"] = "Insect Swarm",
|
|
["休眠"] = "Hibernate", ["野性冲锋效果"] = "Feral Charge Effect",
|
|
["低吼"] = "Growl", ["挑战咆哮"] = "Challenging Roar",
|
|
["挫志咆哮"] = "Demoralizing Roar",
|
|
-- PALADIN
|
|
["制裁之锤"] = "Hammer of Justice", ["忏悔"] = "Repentance",
|
|
["超度亡灵"] = "Turn Undead",
|
|
["十字军审判"] = "Judgement of the Crusader",
|
|
["光明审判"] = "Judgement of Light", ["智慧审判"] = "Judgement of Wisdom",
|
|
["公正审判"] = "Judgement of Justice", ["审判"] = "Judgement",
|
|
-- SHAMAN
|
|
["冰霜震击"] = "Frost Shock", ["地震术"] = "Earth Shock",
|
|
["烈焰震击"] = "Flame Shock", ["地缚"] = "Earthbind",
|
|
["风暴打击"] = "Stormstrike",
|
|
["报应"] = "Vindication",
|
|
["十字军打击"] = "Crusader Strike",
|
|
-- SHAMAN (extra)
|
|
["石爪昏迷"] = "Stoneclaw Stun",
|
|
-- OTHER
|
|
["战争践踏"] = "War Stomp", ["眩晕"] = "Dazed",
|
|
["锤击昏迷效果"] = "Mace Stun Effect",
|
|
["盾击 - 沉默"] = "Shield Bash - Silenced",
|
|
["复仇昏迷"] = "Revenge Stun",
|
|
["撕裂(宠物)"] = "Lacerate",
|
|
["冲击"] = "Impact",
|
|
["余波"] = "Aftermath",
|
|
["火焰风暴"] = "Pyroclasm",
|
|
["还击"] = "Riposte",
|
|
-- ENGINEERING / ITEMS
|
|
["铁皮手雷"] = "Iron Grenade",
|
|
["瑟银手雷"] = "Thorium Grenade",
|
|
["闪光弹"] = "Flash Bomb",
|
|
["简易炸药"] = "Ez-Thro Dynamite",
|
|
["简易炸药 II"] = "Ez-Thro Dynamite II",
|
|
["致密炸药"] = "Dense Dynamite",
|
|
["固体炸药"] = "Solid Dynamite",
|
|
["高爆炸弹"] = "Hi-Explosive Bomb",
|
|
["黑铁炸弹"] = "Dark Iron Bomb",
|
|
["地精工兵炸药"] = "Goblin Sapper Charge",
|
|
["地精迫击炮"] = "Goblin Mortar",
|
|
["大炸弹"] = "The Big One",
|
|
["电网发射器"] = "Net-o-Matic",
|
|
["鲁莽冲锋"] = "Reckless Charge",
|
|
["侏儒精神控制帽"] = "Gnomish Mind Control Cap",
|
|
["侏儒电网发射器"] = "Gnomish Net-o-Matic",
|
|
["侏儒死亡射线"] = "Gnomish Death Ray",
|
|
["地精火箭头盔"] = "Goblin Rocket Helmet",
|
|
["冰霜手雷"] = "Frost Grenade",
|
|
["粗制铜质炸弹"] = "Rough Copper Bomb",
|
|
["大型铜质炸弹"] = "Large Copper Bomb",
|
|
["小型青铜炸弹"] = "Small Bronze Bomb",
|
|
["大型青铜炸弹"] = "Big Bronze Bomb",
|
|
["大型铁质炸弹"] = "Big Iron Bomb",
|
|
["地精地雷"] = "Goblin Land Mine",
|
|
["混乱射线"] = "Discombobulator Ray",
|
|
["大绳索网"] = "Large Rope Net",
|
|
["陷阱"] = "Trap",
|
|
["自由行动药水"] = "Free Action Potion",
|
|
["活力行动药水"] = "Living Action Potion",
|
|
["潮汐护符"] = "Tidal Charm",
|
|
["昏迷"] = "Stun",
|
|
}
|
|
|
|
-- ONLY unique texture → spell mappings here.
|
|
-- Ambiguous textures (shared by multiple spells) are omitted; tooltip + locale handles them.
|
|
NanamiPlates_SpellDB.textureToSpell = {
|
|
-- Warrior (unique only)
|
|
["Interface\\Icons\\Ability_Rend"] = "Rend",
|
|
["Interface\\Icons\\Ability_Warrior_Decimate"] = "Improved Hamstring",
|
|
["Interface\\Icons\\Ability_Warrior_Sunder"] = "Sunder Armor",
|
|
["Interface\\Icons\\Ability_BullRush"] = "Challenging Shout",
|
|
|
|
-- Warlock (unique only)
|
|
["Interface\\Icons\\Spell_Shadow_LifeDrain"] = "Tainted Blood Effect",
|
|
["Interface\\Icons\\Spell_Shadow_SoulLeech"] = "Dark Harvest",
|
|
["Interface\\Icons\\Spell_Shadow_AbominationExplosion"] = "Corruption",
|
|
["Interface\\Icons\\Spell_Shadow_CurseOfSargeras"] = "Curse of Agony",
|
|
["Interface\\Icons\\Spell_Shadow_CurseOfMannoroth"] = "Curse of Weakness",
|
|
["Interface\\Icons\\Spell_Shadow_UnholyStrength"] = "Curse of Recklessness",
|
|
["Interface\\Icons\\Spell_Shadow_CurseOfTounges"] = "Curse of Tongues",
|
|
["Interface\\Icons\\Spell_Shadow_ChillTouch"] = "Curse of the Elements",
|
|
["Interface\\Icons\\Spell_Shadow_CurseOfAchimonde"] = "Curse of Shadow",
|
|
["Interface\\Icons\\Spell_Shadow_GrimWard"] = "Curse of Exhaustion",
|
|
["Interface\\Icons\\Spell_Shadow_AuraOfDarkness"] = "Curse of Doom",
|
|
["Interface\\Icons\\Spell_Shadow_MindRot"] = "Curse of Idiocy",
|
|
["Interface\\Icons\\Spell_Fire_Immolation"] = "Immolate",
|
|
["Interface\\Icons\\Spell_Shadow_Requiem"] = "Siphon Life",
|
|
["Interface\\Icons\\Spell_Shadow_Cripple"] = "Banish",
|
|
|
|
-- Druid (unique only)
|
|
["Interface\\Icons\\Spell_Nature_FaerieFire"] = "Faerie Fire",
|
|
["Interface\\Icons\\Ability_Druid_Disembowel"] = "Rake",
|
|
["Interface\\Icons\\Ability_Druid_Rip"] = "Rip",
|
|
["Interface\\Icons\\Ability_Druid_SupriseAttack"] = "Pounce Bleed",
|
|
["Interface\\Icons\\Ability_Druid_ChallangingRoar"] = "Challenging Roar",
|
|
["Interface\\Icons\\Spell_Nature_StarFall"] = "Moonfire",
|
|
["Interface\\Icons\\Spell_Nature_StrangleVines"] = "Entangling Roots",
|
|
["Interface\\Icons\\Spell_Nature_InsectSwarm"] = "Insect Swarm",
|
|
["Interface\\Icons\\Ability_Druid_DemoralizingRoar"] = "Demoralizing Roar",
|
|
["Interface\\Icons\\Ability_Druid_Mangle"] = "Mangle",
|
|
|
|
-- Paladin (unique only)
|
|
["Interface\\Icons\\Spell_Holy_Vindication"] = "Vindication",
|
|
|
|
-- Hunter (unique only)
|
|
["Interface\\Icons\\spell_lacerate_1C"] = "Lacerate",
|
|
["Interface\\Icons\\Spell_Frost_ChainsOfIce"] = "Freezing Trap Effect",
|
|
["Interface\\Icons\\Spell_Fire_SelfDestruct"] = "Explosive Trap Effect",
|
|
["Interface\\Icons\\Spell_Frost_FreezingBreath"] = "Frost Trap Aura",
|
|
|
|
-- Rogue (unique only)
|
|
["Interface\\Icons\\Ability_CheapShot"] = "Cheap Shot",
|
|
["Interface\\Icons\\Ability_Rogue_KidneyShot"] = "Kidney Shot",
|
|
["Interface\\Icons\\Ability_Sap"] = "Sap",
|
|
["Interface\\Icons\\Ability_Rogue_Rupture"] = "Rupture",
|
|
["Interface\\Icons\\Ability_Rogue_Garrote"] = "Garrote",
|
|
|
|
-- Other (unique only)
|
|
["Interface\\Icons\\Spell_Nature_Cyclone"] = "Thunderfury's Blessing",
|
|
}
|
|
|
|
-- Preferred names for textures when encountered as DEBUFFS (context-aware priority)
|
|
NanamiPlates_SpellDB.debuffPriority = {
|
|
["Interface\\Icons\\Spell_Holy_HealingAura"] = "Judgement of Light",
|
|
["Interface\\Icons\\Spell_Holy_RighteousnessAura"] = "Judgement of Wisdom",
|
|
["Interface\\Icons\\Spell_Holy_HolySmite"] = "Judgement of the Crusader",
|
|
["Interface\\Icons\\Spell_Holy_SealOfWrath"] = "Judgement of Justice",
|
|
}
|
|
|
|
-- ============================================
|
|
-- DEBUFF DURATIONS BY SPELL NAME AND RANK
|
|
-- Format: ["Spell Name"] = { [rank] = duration, [0] = default/max }
|
|
-- ============================================
|
|
NanamiPlates_SpellDB.DEBUFFS = {
|
|
-- WARRIOR
|
|
["Rend"] = {[1]=9, [2]=12, [3]=15, [4]=18, [5]=21, [6]=21, [7]=21, [0]=21},
|
|
["Thunder Clap"] = {[1]=10, [2]=14, [3]=18, [4]=22, [5]=26, [6]=30, [0]=30},
|
|
["Sunder Armor"] = {[0]=30},
|
|
["Disarm"] = {[0]=10},
|
|
["Hamstring"] = {[0]=15},
|
|
["Improved Hamstring"] = {[0]=5},
|
|
["Demoralizing Shout"] = {[0]=30},
|
|
["Intimidating Shout"] = {[0]=8},
|
|
["Concussion Blow"] = {[0]=5},
|
|
["Mocking Blow"] = {[0]=6},
|
|
["Piercing Howl"] = {[0]=6},
|
|
["Mortal Strike"] = {[0]=10},
|
|
["Deep Wound"] = {[0]=12},
|
|
["Charge"] = {[0]=1},
|
|
["Charge Stun"] = {[0]=1},
|
|
["Intercept"] = {[0]=3},
|
|
["Intercept Stun"] = {[0]=3},
|
|
["Challenging Shout"] = {[0]=6},
|
|
["Demoralizing Roar"] = {[0]=30},
|
|
["Dazed"] = {[0]=4},
|
|
["Taunt"] = {[0]=3},
|
|
|
|
-- ROGUE
|
|
["Cheap Shot"] = {[0]=4},
|
|
["Kidney Shot"] = {[1]=0, [2]=1, [0]=1}, -- Rank1: 0+1s/CP, Rank2: 1+1s/CP
|
|
["Sap"] = {[1]=25, [2]=35, [3]=45, [0]=45},
|
|
["Blind"] = {[0]=10},
|
|
["Gouge"] = {[1]=4, [2]=4, [3]=4, [4]=4, [5]=4, [0]=4}, -- base 4s, +0.5s/talent
|
|
["Rupture"] = {[0]=8}, -- base 8s + 2s/CP (via DYN_DEBUFFS)
|
|
["Garrote"] = {[1]=18, [2]=18, [3]=18, [4]=18, [5]=18, [0]=18},
|
|
["Expose Armor"] = {[0]=30},
|
|
["Crippling Poison"] = {[0]=12},
|
|
["Crippling Poison II"] = {[0]=12},
|
|
["Deadly Poison"] = {[0]=12},
|
|
["Deadly Poison II"] = {[0]=12},
|
|
["Deadly Poison III"] = {[0]=12},
|
|
["Deadly Poison IV"] = {[0]=12},
|
|
["Deadly Poison V"] = {[0]=12},
|
|
["Mind-numbing Poison"] = {[0]=14},
|
|
["Mind-numbing Poison II"] = {[0]=14},
|
|
["Mind-numbing Poison III"] = {[0]=14},
|
|
["Wound Poison"] = {[0]=15},
|
|
["Wound Poison II"] = {[0]=15},
|
|
["Wound Poison III"] = {[0]=15},
|
|
["Wound Poison IV"] = {[0]=15},
|
|
["Instant Poison"] = {[0]=0},
|
|
["Instant Poison II"] = {[0]=0},
|
|
["Instant Poison III"] = {[0]=0},
|
|
["Instant Poison IV"] = {[0]=0},
|
|
["Instant Poison V"] = {[0]=0},
|
|
["Instant Poison VI"] = {[0]=0},
|
|
|
|
-- MAGE
|
|
["Frost Nova"] = {[1]=8, [2]=8, [3]=8, [4]=8, [0]=8},
|
|
["Polymorph"] = {[1]=20, [2]=30, [3]=40, [4]=50, [0]=50},
|
|
["Polymorph: Pig"] = {[0]=50},
|
|
["Polymorph: Turtle"] = {[0]=50},
|
|
["Polymorph: Cow"] = {[0]=50},
|
|
["Frostbolt"] = {[1]=5, [2]=6, [3]=7, [4]=8, [5]=9, [6]=9, [7]=9, [8]=9, [9]=9, [10]=9, [11]=9, [0]=9},
|
|
["Cone of Cold"] = {[0]=8},
|
|
["Frostbite"] = {[0]=5},
|
|
["Counterspell - Silenced"] = {[0]=4},
|
|
["Winter's Chill"] = {[0]=15},
|
|
["Fireball"] = {[0]=8}, -- DoT component
|
|
["Pyroblast"] = {[0]=12}, -- DoT component
|
|
["Ignite"] = {[0]=4},
|
|
["Fire Vulnerability"] = {[0]=30},
|
|
|
|
-- WARLOCK
|
|
["Corruption"] = {[1]=12, [2]=15, [3]=18, [4]=18, [5]=18, [6]=18, [7]=18, [0]=18},
|
|
["Immolate"] = {[1]=15, [2]=15, [3]=15, [4]=15, [5]=15, [6]=15, [7]=15, [8]=15, [0]=15},
|
|
["Fear"] = {[1]=10, [2]=15, [3]=20, [0]=20},
|
|
["Howl of Terror"] = {[1]=10, [2]=15, [0]=15},
|
|
["Death Coil"] = {[0]=3},
|
|
["Curse of Agony"] = {[1]=24, [2]=24, [3]=24, [4]=24, [5]=24, [6]=24, [0]=24},
|
|
["Curse of Weakness"] = {[0]=120},
|
|
["Curse of Recklessness"] = {[0]=120},
|
|
["Curse of Tongues"] = {[0]=30},
|
|
["Curse of the Elements"] = {[0]=300},
|
|
["Curse of Shadow"] = {[0]=300},
|
|
["Curse of Exhaustion"] = {[0]=12},
|
|
["Curse of Doom"] = {[0]=60},
|
|
["Siphon Life"] = {[0]=30},
|
|
["Drain Life"] = {[0]=5},
|
|
["Drain Mana"] = {[0]=5},
|
|
["Drain Soul"] = {[0]=15},
|
|
["Banish"] = {[1]=20, [2]=30, [0]=30},
|
|
["Enslave Demon"] = {[0]=300},
|
|
["Seduction"] = {[0]=15},
|
|
["Shadow Vulnerability"] = {[0]=30},
|
|
["Dark Harvest"] = {[0]=8},
|
|
|
|
-- PRIEST
|
|
["Shadow Word: Pain"] = {[1]=18, [2]=18, [3]=18, [4]=18, [5]=18, [6]=18, [7]=18, [8]=18, [0]=18},
|
|
["Psychic Scream"] = {[1]=8, [2]=8, [3]=8, [4]=8, [0]=8},
|
|
["Mind Flay"] = {[0]=3},
|
|
["Mind Control"] = {[0]=60},
|
|
["Silence"] = {[0]=5},
|
|
["Weakened Soul"] = {[0]=15},
|
|
["Devouring Plague"] = {[0]=24},
|
|
["Vampiric Embrace"] = {[0]=60},
|
|
["Blackout"] = {[0]=3},
|
|
["Mana Burn"] = {[0]=0}, -- instant
|
|
["Touch of Weakness"] = {[0]=120},
|
|
["Mind Soothe"] = {[0]=15},
|
|
|
|
-- HUNTER
|
|
["Serpent Sting"] = {[1]=15, [2]=15, [3]=15, [4]=15, [5]=15, [6]=15, [7]=15, [8]=15, [9]=15, [0]=15},
|
|
["Viper Sting"] = {[1]=8, [2]=8, [3]=8, [4]=8, [0]=8},
|
|
["Scorpid Sting"] = {[0]=20},
|
|
["Concussive Shot"] = {[0]=4},
|
|
["Scatter Shot"] = {[0]=4},
|
|
["Wing Clip"] = {[0]=10},
|
|
["Improved Concussive Shot"] = {[0]=3},
|
|
["Hunter's Mark"] = {[0]=120},
|
|
["Counterattack"] = {[0]=5},
|
|
["Wyvern Sting"] = {[0]=12}, -- sleep, then 12s DoT
|
|
["Freezing Trap Effect"] = {[1]=10,[2]=15,[3]=20,[0]=20},
|
|
["Immolation Trap Effect"] = {[0]=15},
|
|
["Explosive Trap Effect"] = {[0]=20},
|
|
["Frost Trap Aura"] = {[0]=8},
|
|
["Intimidation"] = {[0]=3},
|
|
["Entrapment"] = {[0]=5},
|
|
["Lacerate"] = {[0]=8},
|
|
["Scare Beast"] = {[1]=10,[2]=15,[3]=20,[0]=20},
|
|
|
|
-- DRUID
|
|
["Moonfire"] = {[1]=9, [2]=18, [3]=18, [4]=18, [5]=18, [6]=18, [7]=18, [8]=18, [9]=18, [10]=18, [0]=18},
|
|
["Entangling Roots"] = {[1]=12, [2]=15, [3]=18, [4]=21, [5]=24, [6]=27, [0]=27},
|
|
["Bash"] = {[1]=2, [2]=3, [3]=4, [0]=4},
|
|
["Faerie Fire"] = {[0]=40},
|
|
["Faerie Fire (Feral)"] = {[0]=40},
|
|
["Rake"] = {[1]=9, [2]=9, [3]=9, [4]=9, [0]=9},
|
|
["Rip"] = {[0]=8}, -- +2s per combo point, handled dynamically
|
|
["Pounce Bleed"] = {[0]=18},
|
|
["Pounce"] = {[0]=3}, -- stun component
|
|
["Insect Swarm"] = {[0]=12},
|
|
["Hibernate"] = {[1]=20, [2]=30, [3]=40, [0]=40},
|
|
["Feral Charge Effect"] = {[0]=4},
|
|
["Challenging Roar"] = {[0]=6},
|
|
["Mangle"] = {[0]=12},
|
|
["Growl"] = {[0]=3},
|
|
|
|
-- PALADIN
|
|
["Hammer of Justice"] = {[1]=3, [2]=4, [3]=5, [4]=6, [0]=6},
|
|
["Turn Undead"] = {[0]=20},
|
|
["Repentance"] = {[0]=6},
|
|
["Crusader Strike"] = {[0]=30},
|
|
["Judgement of the Crusader"] = {[0]=10},
|
|
["Judgement of Light"] = {[0]=10},
|
|
["Judgement of Wisdom"] = {[0]=10},
|
|
["Judgement of Justice"] = {[0]=10},
|
|
["Judgement"] = {[0]=10},
|
|
["Vindication"] = {[0]=10},
|
|
|
|
-- SHAMAN
|
|
["Frost Shock"] = {[1]=8, [2]=8, [3]=8, [4]=8, [0]=8},
|
|
["Earth Shock"] = {[0]=2}, -- interrupt
|
|
["Flame Shock"] = {[1]=12, [2]=12, [3]=12, [4]=12, [5]=12, [6]=12, [0]=12},
|
|
["Earthbind"] = {[0]=5}, -- per pulse
|
|
["Stoneclaw Stun"] = {[0]=3},
|
|
["Stormstrike"] = {[0]=12},
|
|
|
|
-- Felhunter
|
|
["Tainted Blood Effect"] = {[0]=10},
|
|
["Spell Lock"] = {[0]=6},
|
|
|
|
-- MISC / PVP / RACIAL / PROCS
|
|
["War Stomp"] = {[0]=2},
|
|
["Tidal Charm"] = {[0]=3},
|
|
["Impact"] = {[0]=2},
|
|
["Aftermath"] = {[0]=5},
|
|
["Pyroclasm"] = {[0]=3},
|
|
["Mace Stun Effect"] = {[0]=3},
|
|
["Blackout"] = {[0]=3},
|
|
["Revenge Stun"] = {[0]=3},
|
|
["Sleep"] = {[1]=20, [2]=30, [0]=30},
|
|
["Riposte"] = {[0]=6},
|
|
["Shield Bash - Silenced"] = {[0]=6},
|
|
["Kick - Silenced"] = {[0]=2},
|
|
["Stun"] = {[0]=3},
|
|
|
|
-- ENGINEERING / ITEMS
|
|
["Iron Grenade"] = {[0]=3},
|
|
["Thorium Grenade"] = {[0]=3},
|
|
["Flash Bomb"] = {[0]=10},
|
|
["Ez-Thro Dynamite"] = {[0]=2},
|
|
["Ez-Thro Dynamite II"] = {[0]=2},
|
|
["Dense Dynamite"] = {[0]=5},
|
|
["Solid Dynamite"] = {[0]=3},
|
|
["Hi-Explosive Bomb"] = {[0]=3},
|
|
["Dark Iron Bomb"] = {[0]=4},
|
|
["Goblin Sapper Charge"] = {[0]=0},
|
|
["Goblin Mortar"] = {[0]=3},
|
|
["The Big One"] = {[0]=5},
|
|
["Net-o-Matic"] = {[0]=10},
|
|
["Reckless Charge"] = {[0]=3},
|
|
["Gnomish Mind Control Cap"] = {[0]=20},
|
|
["Gnomish Net-o-Matic"] = {[0]=10},
|
|
["Gnomish Death Ray"] = {[0]=0},
|
|
["Goblin Rocket Helmet"] = {[0]=30},
|
|
["Frost Grenade"] = {[0]=5},
|
|
["Rough Copper Bomb"] = {[0]=2},
|
|
["Large Copper Bomb"] = {[0]=2},
|
|
["Small Bronze Bomb"] = {[0]=2},
|
|
["Big Bronze Bomb"] = {[0]=3},
|
|
["Big Iron Bomb"] = {[0]=3},
|
|
["Goblin Land Mine"] = {[0]=5},
|
|
["Discombobulator Ray"] = {[0]=12},
|
|
["Large Rope Net"] = {[0]=10},
|
|
["Trap"] = {[0]=10},
|
|
|
|
-- CONSUMABLES
|
|
["Free Action Potion"] = {[0]=30},
|
|
["Living Action Potion"] = {[0]=5},
|
|
["Skull of Impending Doom"] = {[0]=0},
|
|
["Tidal Charm"] = {[0]=3},
|
|
|
|
-- Other
|
|
["Thunderfury"] = {[0]=12},
|
|
["Thunderfury's Blessing"] = {[0]=12},
|
|
}
|
|
|
|
-- Dynamic debuffs that scale with combo points
|
|
NanamiPlates_SpellDB.COMBO_POINT_DEBUFFS = {
|
|
["Kidney Shot"] = true,
|
|
["Rupture"] = true,
|
|
["Rip"] = true,
|
|
}
|
|
|
|
-- Dynamic debuffs that scale with talents
|
|
NanamiPlates_SpellDB.DYN_DEBUFFS = {
|
|
["Rupture"] = "Rupture",
|
|
["Kidney Shot"] = "Kidney Shot",
|
|
["Rip"] = "Rip",
|
|
["Rend"] = "Rend",
|
|
["Shadow Word: Pain"] = "Shadow Word: Pain",
|
|
["Demoralizing Shout"] = "Demoralizing Shout",
|
|
["Frostbolt"] = "Frostbolt",
|
|
["Gouge"] = "Gouge",
|
|
}
|
|
|
|
-- Shared debuffs that can only exist once on a target (shared across all players of the same class)
|
|
NanamiPlates_SpellDB.SHARED_DEBUFFS = {
|
|
-- Rogue
|
|
["Expose Armor"] = "ROGUE",
|
|
-- Mage
|
|
["Winter's Chill"] = "MAGE",
|
|
["Fire Vulnerability"] = "MAGE",
|
|
["Frostbolt"] = "MAGE",
|
|
-- Warrior
|
|
["Thunder Clap"] = "WARRIOR",
|
|
["Demoralizing Shout"] = "WARRIOR",
|
|
["Sunder Armor"] = "WARRIOR",
|
|
["Challenging Shout"] = "WARRIOR",
|
|
["Hamstring"] = "WARRIOR",
|
|
["Mortal Strike"] = "WARRIOR",
|
|
["Piercing Howl"] = "WARRIOR",
|
|
["Disarm"] = "WARRIOR",
|
|
["Taunt"] = true,
|
|
-- Druid
|
|
["Faerie Fire"] = "DRUID",
|
|
["Faerie Fire (Feral)"] = "DRUID",
|
|
["Demoralizing Roar"] = "DRUID",
|
|
["Entangling Roots"] = "DRUID",
|
|
["Hibernate"] = "DRUID",
|
|
["Bash"] = "DRUID",
|
|
["Pounce"] = "DRUID",
|
|
["Challenging Roar"] = "DRUID",
|
|
["Feral Charge Effect"] = "DRUID",
|
|
["Mangle"] = "DRUID",
|
|
["Growl"] = true,
|
|
-- Priest
|
|
["Shadow Vulnerability"] = "PRIEST", -- Can also be Warlock, but primarily Priest
|
|
["Silence"] = "PRIEST",
|
|
["Touch of Weakness"] = "PRIEST",
|
|
["Mind Soothe"] = "PRIEST",
|
|
-- Warlock (curses are shared, but Malediction allows CoA + long curse)
|
|
["Curse of Agony"] = "WARLOCK",
|
|
["Curse of Weakness"] = "WARLOCK",
|
|
["Curse of Recklessness"] = "WARLOCK",
|
|
["Curse of Tongues"] = "WARLOCK",
|
|
["Curse of the Elements"] = "WARLOCK",
|
|
["Curse of Shadow"] = "WARLOCK",
|
|
["Curse of Exhaustion"] = "WARLOCK",
|
|
["Curse of Doom"] = "WARLOCK",
|
|
["Curse of Idiocy"] = "WARLOCK",
|
|
-- Hunter
|
|
["Hunter's Mark"] = "HUNTER",
|
|
["Scorpid Sting"] = "HUNTER",
|
|
["Scare Beast"] = "HUNTER",
|
|
["Freezing Trap Effect"] = "HUNTER",
|
|
["Immolation Trap Effect"] = "HUNTER",
|
|
["Explosive Trap Effect"] = "HUNTER",
|
|
["Frost Trap Aura"] = "HUNTER",
|
|
["Entrapment"] = "HUNTER",
|
|
|
|
-- Paladin
|
|
["Hammer of Justice"] = "PALADIN",
|
|
["Repentance"] = "PALADIN",
|
|
["Crusader Strike"] = "PALADIN",
|
|
["Vindication"] = "PALADIN",
|
|
["Judgement of the Crusader"] = "PALADIN",
|
|
["Judgement of Light"] = "PALADIN",
|
|
["Judgement of Wisdom"] = "PALADIN",
|
|
["Judgement of Justice"] = "PALADIN",
|
|
["Judgement"] = "PALADIN",
|
|
-- Other
|
|
["Thunderfury"] = true,
|
|
["Thunderfury's Blessing"] = true,
|
|
["Gift of Arthas"] = true,
|
|
["Spell Vulnerability"] = true,
|
|
["Armor Shatter"] = true,
|
|
}
|
|
|
|
-- Rogue Poisons - always show for Rogue players with "Only My Debuffs" enabled
|
|
-- Since only Rogues can apply these, they are always "mine" when present
|
|
NanamiPlates_SpellDB.ROGUE_POISONS = {
|
|
["Crippling Poison"] = true,
|
|
["Crippling Poison II"] = true,
|
|
["Deadly Poison"] = true,
|
|
["Deadly Poison II"] = true,
|
|
["Deadly Poison III"] = true,
|
|
["Deadly Poison IV"] = true,
|
|
["Deadly Poison V"] = true,
|
|
["Instant Poison"] = true,
|
|
["Instant Poison II"] = true,
|
|
["Instant Poison III"] = true,
|
|
["Instant Poison IV"] = true,
|
|
["Instant Poison V"] = true,
|
|
["Instant Poison VI"] = true,
|
|
["Mind-numbing Poison"] = true,
|
|
["Mind-numbing Poison II"] = true,
|
|
["Mind-numbing Poison III"] = true,
|
|
["Wound Poison"] = true,
|
|
["Wound Poison II"] = true,
|
|
["Wound Poison III"] = true,
|
|
["Wound Poison IV"] = true,
|
|
}
|
|
|
|
-- Rogue Poison TEXTURES - for icon-based detection when tooltip scanning fails
|
|
-- This ensures poisons are detected even without spell name
|
|
NanamiPlates_SpellDB.ROGUE_POISON_TEXTURES = {
|
|
-- Crippling Poison
|
|
["Interface\\Icons\\Ability_PoisonSting"] = "Crippling Poison",
|
|
-- Deadly Poison
|
|
["Interface\\Icons\\Ability_Rogue_DualWeild"] = "Deadly Poison",
|
|
-- Instant Poison
|
|
["Interface\\Icons\\Ability_Poisons"] = "Instant Poison",
|
|
-- Mind-numbing Poison
|
|
["Interface\\Icons\\Spell_Nature_NullifyDisease"] = "Mind-numbing Poison",
|
|
-- Wound Poison
|
|
["Interface\\Icons\\INV_Misc_Herb_16"] = "Wound Poison",
|
|
}
|
|
|
|
-- Hunter Traps - show for Hunter players when "Only My Debuffs" is enabled
|
|
-- These are placed on ground and triggered by enemies, so ownership can't be tracked reliably
|
|
NanamiPlates_SpellDB.HUNTER_TRAPS = {
|
|
["Freezing Trap Effect"] = true,
|
|
["Immolation Trap Effect"] = true,
|
|
["Explosive Trap Effect"] = true,
|
|
["Frost Trap Aura"] = true,
|
|
["Entrapment"] = true,
|
|
}
|
|
|
|
-- Hunter Trap TEXTURES - for icon-based detection when tooltip scanning fails
|
|
NanamiPlates_SpellDB.HUNTER_TRAP_TEXTURES = {
|
|
["Interface\\Icons\\Spell_Frost_ChainsOfIce"] = "Freezing Trap Effect",
|
|
["Interface\\Icons\\Spell_Fire_FlameShock"] = "Immolation Trap Effect",
|
|
["Interface\\Icons\\Spell_Fire_SelfDestruct"] = "Explosive Trap Effect",
|
|
["Interface\\Icons\\Spell_Frost_FreezingBreath"] = "Frost Trap Aura",
|
|
["Interface\\Icons\\Spell_Frost_FrostNova"] = "Frost Trap Aura",
|
|
["Interface\\Icons\\Ability_Ensnare"] = "Entrapment",
|
|
}
|
|
|
|
-- Hunter Stings - for Hunter players, treat as owned for reliable display
|
|
NanamiPlates_SpellDB.HUNTER_STINGS = {
|
|
["Serpent Sting"] = true,
|
|
["Viper Sting"] = true,
|
|
["Scorpid Sting"] = true,
|
|
["Wyvern Sting"] = true,
|
|
}
|
|
|
|
-- ============================================
|
|
-- WARLOCK MALEDICTION SUPPORT (TurtleWoW)
|
|
-- Malediction talent allows 2 curses: 1 long curse + Curse of Agony
|
|
-- ============================================
|
|
|
|
-- All Warlock curses - for detection when "Only My Debuffs" is enabled
|
|
-- Since only Warlocks can apply curses, treat any visible curse as "mine"
|
|
NanamiPlates_SpellDB.WARLOCK_CURSES = {
|
|
["Curse of Agony"] = true,
|
|
["Curse of Weakness"] = true,
|
|
["Curse of Recklessness"] = true,
|
|
["Curse of Tongues"] = true,
|
|
["Curse of the Elements"] = true,
|
|
["Curse of Shadow"] = true,
|
|
["Curse of Exhaustion"] = true,
|
|
["Curse of Doom"] = true,
|
|
["Curse of Idiocy"] = true,
|
|
}
|
|
|
|
-- Warlock Curse TEXTURES - for icon-based detection when tooltip scanning fails
|
|
NanamiPlates_SpellDB.WARLOCK_CURSE_TEXTURES = {
|
|
["Interface\\Icons\\Spell_Shadow_CurseOfSargeras"] = "Curse of Agony",
|
|
["Interface\\Icons\\Spell_Shadow_CurseOfMannoroth"] = "Curse of Weakness",
|
|
["Interface\\Icons\\Spell_Shadow_UnholyStrength"] = "Curse of Recklessness",
|
|
["Interface\\Icons\\Spell_Shadow_CurseOfTounges"] = "Curse of Tongues",
|
|
["Interface\\Icons\\Spell_Shadow_ChillTouch"] = "Curse of the Elements",
|
|
["Interface\\Icons\\Spell_Shadow_CurseOfAchimonde"] = "Curse of Shadow",
|
|
["Interface\\Icons\\Spell_Shadow_GrimWard"] = "Curse of Exhaustion",
|
|
["Interface\\Icons\\Spell_Shadow_AuraOfDarkness"] = "Curse of Doom",
|
|
["Interface\\Icons\\Spell_Shadow_MindRot"] = "Curse of Idiocy",
|
|
}
|
|
|
|
-- All curses except Agony and Doom that can coexist with Agony under Malediction
|
|
NanamiPlates_SpellDB.WARLOCK_LONG_CURSES = {
|
|
["Curse of Weakness"] = true,
|
|
["Curse of Recklessness"] = true,
|
|
["Curse of Tongues"] = true,
|
|
["Curse of the Elements"] = true,
|
|
["Curse of Shadow"] = true,
|
|
["Curse of Exhaustion"] = true,
|
|
["Curse of Idiocy"] = true,
|
|
}
|
|
|
|
-- Curse of Agony (short curse that can coexist with long curses under Malediction)
|
|
NanamiPlates_SpellDB.WARLOCK_AGONY_CURSE = "Curse of Agony"
|
|
|
|
-- Curses that auto-apply highest rank Curse of Agony when Malediction is active
|
|
NanamiPlates_SpellDB.MALEDICTION_AUTO_AGONY = {
|
|
["Curse of Recklessness"] = true,
|
|
["Curse of Shadow"] = true,
|
|
["Curse of the Elements"] = true,
|
|
}
|
|
|
|
-- Cache for Corruption haste talent check ("快速衰弱" / "Nightfall" in TurtleWoW)
|
|
-- This talent makes Corruption instant but shortens its tick interval and total
|
|
-- duration by the player's total spell haste percentage.
|
|
NanamiPlates_SpellDB.corruptionHasteCache = {
|
|
hasChecked = false,
|
|
talentRank = 0,
|
|
lastCheck = 0,
|
|
}
|
|
|
|
function NanamiPlates_SpellDB:GetCorruptionHasteRank()
|
|
local now = GetTime()
|
|
local cache = self.corruptionHasteCache
|
|
|
|
if cache.hasChecked and (now - cache.lastCheck) < 10 then
|
|
return cache.talentRank
|
|
end
|
|
|
|
local _, playerClass = UnitClass("player")
|
|
if playerClass ~= "WARLOCK" then
|
|
cache.hasChecked = true
|
|
cache.talentRank = 0
|
|
cache.lastCheck = now
|
|
return 0
|
|
end
|
|
|
|
for i = 1, 25 do
|
|
local name, _, _, _, rank, maxRank = GetTalentInfo(1, i)
|
|
if not name then break end
|
|
if maxRank == 2 and (name == "\229\191\171\233\128\159\232\161\176\229\188\177"
|
|
or name == "Nightfall" or name == "Rapid Deterioration") then
|
|
cache.hasChecked = true
|
|
cache.talentRank = rank or 0
|
|
cache.lastCheck = now
|
|
return cache.talentRank
|
|
end
|
|
end
|
|
|
|
cache.hasChecked = true
|
|
cache.talentRank = 0
|
|
cache.lastCheck = now
|
|
return 0
|
|
end
|
|
|
|
-- Cache for Malediction talent check
|
|
NanamiPlates_SpellDB.maledictionCache = {
|
|
hasChecked = false,
|
|
hasMalediction = false,
|
|
lastCheck = 0,
|
|
}
|
|
|
|
-- Check if player has Malediction talent (TurtleWoW Affliction tree)
|
|
-- Malediction is in Affliction tree (tree 1) - typically around tier 5-6
|
|
function NanamiPlates_SpellDB:HasMalediction()
|
|
local now = GetTime()
|
|
local cache = self.maledictionCache
|
|
|
|
-- Cache result for 5 seconds to avoid excessive talent queries
|
|
if cache.hasChecked and (now - cache.lastCheck) < 5 then
|
|
return cache.hasMalediction
|
|
end
|
|
|
|
-- Only check for Warlocks
|
|
local _, playerClass = UnitClass("player")
|
|
if playerClass ~= "WARLOCK" then
|
|
cache.hasChecked = true
|
|
cache.hasMalediction = false
|
|
cache.lastCheck = now
|
|
return false
|
|
end
|
|
|
|
-- Search Affliction tree (tree 1) for Malediction talent
|
|
-- TurtleWoW places it around tier 5-6, we scan all talents to be safe
|
|
for i = 1, 25 do
|
|
local name, _, _, _, rank, maxRank = GetTalentInfo(1, i)
|
|
if not name then break end
|
|
if maxRank == 1 and (name == "Malediction"
|
|
or name == "\233\130\170\229\146\146") and rank and rank > 0 then
|
|
cache.hasChecked = true
|
|
cache.hasMalediction = true
|
|
cache.lastCheck = now
|
|
return true
|
|
end
|
|
end
|
|
|
|
cache.hasChecked = true
|
|
cache.hasMalediction = false
|
|
cache.lastCheck = now
|
|
return false
|
|
end
|
|
|
|
-- Check if two curses can coexist (Malediction allows long curse + Curse of Agony)
|
|
-- Curse of Doom cannot coexist with Agony even with Malediction
|
|
function NanamiPlates_SpellDB:CanCursesCoexist(curse1, curse2)
|
|
if not self:HasMalediction() then
|
|
return false
|
|
end
|
|
|
|
if curse1 == "Curse of Doom" or curse2 == "Curse of Doom" then
|
|
return false
|
|
end
|
|
|
|
local isAgony1 = (curse1 == self.WARLOCK_AGONY_CURSE)
|
|
local isAgony2 = (curse2 == self.WARLOCK_AGONY_CURSE)
|
|
local isLong1 = self.WARLOCK_LONG_CURSES and self.WARLOCK_LONG_CURSES[curse1]
|
|
local isLong2 = self.WARLOCK_LONG_CURSES and self.WARLOCK_LONG_CURSES[curse2]
|
|
|
|
return (isAgony1 and isLong2) or (isAgony2 and isLong1)
|
|
end
|
|
|
|
-- Check if a curse is a warlock curse (for shared debuff handling)
|
|
function NanamiPlates_SpellDB:IsWarlockCurse(effect)
|
|
return effect == self.WARLOCK_AGONY_CURSE or self.WARLOCK_LONG_CURSES[effect]
|
|
end
|
|
|
|
-- Hunter Sting TEXTURES - for icon-based detection when tooltip scanning fails
|
|
NanamiPlates_SpellDB.HUNTER_STING_TEXTURES = {
|
|
["Interface\\Icons\\Ability_Hunter_Quickshot"] = "Serpent Sting",
|
|
["Interface\\Icons\\Ability_Hunter_AimedShot"] = "Viper Sting",
|
|
["Interface\\Icons\\Ability_Hunter_CriticalShot"] = "Scorpid Sting",
|
|
["Interface\\Icons\\INV_Spear_02"] = "Wyvern Sting",
|
|
}
|
|
|
|
-- Warlock DOT TEXTURES - for icon-based detection when tooltip scanning returns localized names
|
|
NanamiPlates_SpellDB.WARLOCK_DOT_TEXTURES = {
|
|
["Interface\\Icons\\Spell_Shadow_AbominationExplosion"] = "Corruption",
|
|
["Interface\\Icons\\Spell_Shadow_Requiem"] = "Siphon Life",
|
|
["Interface\\Icons\\Spell_Fire_Immolation"] = "Immolate",
|
|
["Interface\\Icons\\Spell_Shadow_SoulLeech"] = "Dark Harvest",
|
|
}
|
|
|
|
-- Debuffs that are bound to the owner (should be visible when "Only My Debuffs" is active)
|
|
NanamiPlates_SpellDB.OWNER_BOUND_DEBUFFS = {
|
|
-- Warrior
|
|
["Rend"] = true,
|
|
["Deep Wound"] = true,
|
|
|
|
-- Warlock
|
|
["Immolate"] = true,
|
|
["Corruption"] = true,
|
|
["Curse of Agony"] = true,
|
|
["Curse of Weakness"] = true,
|
|
["Curse of Recklessness"] = true,
|
|
["Curse of Tongues"] = true,
|
|
["Curse of the Elements"] = true,
|
|
["Curse of Shadow"] = true,
|
|
["Curse of Exhaustion"] = true,
|
|
["Curse of Doom"] = true,
|
|
["Curse of Idiocy"] = true,
|
|
["Siphon Life"] = true,
|
|
|
|
-- Priest
|
|
["Shadow Word: Pain"] = true,
|
|
["Devouring Plague"] = true,
|
|
|
|
-- Druid
|
|
["Moonfire"] = true,
|
|
["Insect Swarm"] = true,
|
|
["Rip"] = true,
|
|
["Rake"] = true,
|
|
["Pounce Bleed"] = true,
|
|
|
|
-- Rogue (excluding poisons - those are handled via ROGUE_POISONS visibility exception)
|
|
["Sap"] = true,
|
|
["Kidney Shot"] = true,
|
|
["Blind"] = true,
|
|
["Garrote"] = true,
|
|
["Rupture"] = true,
|
|
["Gouge"] = true,
|
|
|
|
-- Hunter
|
|
["Serpent Sting"] = true,
|
|
["Wing Clip"] = true,
|
|
["Lacerate"] = true,
|
|
|
|
-- Mage
|
|
["Ignite"] = true,
|
|
|
|
-- Paladin
|
|
["Vindication"] = true,
|
|
}
|
|
|
|
-- ============================================
|
|
-- DEBUFF TRACKING STATE
|
|
-- objects[unit][unitlevel][effect] = {effect, start, duration}
|
|
-- ============================================
|
|
NanamiPlates_SpellDB.objects = {}
|
|
NanamiPlates_SpellDB.pending = {} -- Array: [1]=unit, [2]=unitlevel, [3]=effect, [4]=duration
|
|
|
|
-- ============================================
|
|
-- OWNER_BOUND_DEBUFFS OWNERSHIP CACHE
|
|
-- Tracks player's own applications of OWNER_BOUND_DEBUFFS
|
|
-- Format: ownerBoundCache[unit][effect] = {start, duration}
|
|
-- Used to infer ownership when "Only My Debuffs" is enabled
|
|
-- ============================================
|
|
NanamiPlates_SpellDB.ownerBoundCache = {}
|
|
|
|
-- ============================================
|
|
-- DURATION LOOKUP FUNCTIONS
|
|
-- ============================================
|
|
|
|
-- Search function to avoid duplication
|
|
function NanamiPlates_SpellDB:FindEffectData(u, lvl, eff)
|
|
if not self.objects[u] then return nil end
|
|
if self.objects[u][lvl] and self.objects[u][lvl][eff] then
|
|
return self.objects[u][lvl][eff]
|
|
elseif self.objects[u][0] and self.objects[u][0][eff] then
|
|
return self.objects[u][0][eff]
|
|
else
|
|
for l, effects in pairs(self.objects[u]) do
|
|
if effects[eff] then return effects[eff] end
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- Get max rank for a spell
|
|
function NanamiPlates_SpellDB:GetMaxRank(effect)
|
|
local spellData = self.DEBUFFS[effect]
|
|
if not spellData then return 0 end
|
|
|
|
local max = 0
|
|
for id in pairs(spellData) do
|
|
if id > max then max = id end
|
|
end
|
|
return max
|
|
end
|
|
|
|
-- Get duration by spell name and rank
|
|
function NanamiPlates_SpellDB:GetDuration(effect, rank)
|
|
if not effect then return 0 end
|
|
|
|
-- Translate localized name to English if needed
|
|
if not self.DEBUFFS[effect] then
|
|
if self.localeMap and self.localeMap[effect] then
|
|
effect = self.localeMap[effect]
|
|
elseif self.learnedLocale and self.learnedLocale[effect] then
|
|
effect = self.learnedLocale[effect]
|
|
end
|
|
end
|
|
|
|
local spellData = self.DEBUFFS[effect]
|
|
if not spellData then return 0 end
|
|
|
|
-- Parse rank from string like "Rank 2" if needed
|
|
local rankNum = 0
|
|
if rank then
|
|
if type(rank) == "number" then
|
|
rankNum = rank
|
|
elseif type(rank) == "string" then
|
|
-- Extract number from "Rank X" format
|
|
for num in string_gfind(rank, "(%d+)") do
|
|
rankNum = tonumber(num) or 0
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
-- If exact rank not found, use max rank
|
|
if not spellData[rankNum] then
|
|
rankNum = self:GetMaxRank(effect)
|
|
end
|
|
|
|
local duration = spellData[rankNum] or spellData[0] or 0
|
|
|
|
-- Handle dynamic duration adjustments
|
|
-- For combo point abilities, cache the CP count at cast time
|
|
-- because combo points are consumed after cast
|
|
local cp = GetComboPoints("player", "target") or 0
|
|
if cp > 0 and self.COMBO_POINT_DEBUFFS and self.COMBO_POINT_DEBUFFS[effect] then
|
|
self.lastComboPoints = cp
|
|
elseif cp == 0 and self.COMBO_POINT_DEBUFFS and self.COMBO_POINT_DEBUFFS[effect] then
|
|
cp = self.lastComboPoints or 0
|
|
end
|
|
|
|
if effect == self.DYN_DEBUFFS["Rupture"] then
|
|
duration = duration + cp * 2
|
|
elseif effect == self.DYN_DEBUFFS["Kidney Shot"] then
|
|
duration = duration + cp * 1
|
|
elseif effect == self.DYN_DEBUFFS["Rip"] then
|
|
duration = duration + cp * 2
|
|
elseif effect == self.DYN_DEBUFFS["Demoralizing Shout"] then
|
|
-- Booming Voice: 10% per talent
|
|
local _,_,_,_,count = GetTalentInfo(2, 1)
|
|
if count and count > 0 then
|
|
duration = duration + (duration / 100 * (count * 10))
|
|
end
|
|
elseif effect == self.DYN_DEBUFFS["Shadow Word: Pain"] then
|
|
-- Improved Shadow Word: Pain: +3s per talent
|
|
local _,_,_,_,count = GetTalentInfo(3, 4)
|
|
if count and count > 0 then
|
|
duration = duration + count * 3
|
|
end
|
|
elseif effect == self.DYN_DEBUFFS["Frostbolt"] then
|
|
-- Permafrost: +1s per talent
|
|
local _,_,_,_,count = GetTalentInfo(3, 7)
|
|
if count and count > 0 then
|
|
duration = duration + count
|
|
end
|
|
elseif effect == self.DYN_DEBUFFS["Gouge"] then
|
|
-- Improved Gouge: +.5s per talent
|
|
local _,_,_,_,count = GetTalentInfo(2, 1)
|
|
if count and count > 0 then
|
|
duration = duration + (count * 0.5)
|
|
end
|
|
elseif effect == self.DYN_DEBUFFS["Rend"] then
|
|
return duration
|
|
end
|
|
|
|
if effect == "Corruption" then
|
|
local hasteRank = self:GetCorruptionHasteRank()
|
|
if hasteRank > 0 then
|
|
local hastePct = hasteRank * 0.03
|
|
duration = duration / (1 + hastePct)
|
|
end
|
|
end
|
|
|
|
return duration
|
|
end
|
|
|
|
-- ============================================
|
|
-- PENDING SPELL TRACKING
|
|
-- ============================================
|
|
|
|
-- Store recent casts by spell name for combat log fallback
|
|
NanamiPlates_SpellDB.recentCasts = {}
|
|
|
|
-- Secondary pending slot for Malediction dual-curse support
|
|
NanamiPlates_SpellDB.pendingCurse = {}
|
|
|
|
function NanamiPlates_SpellDB:AddPending(unit, unitlevel, effect, duration)
|
|
if not unit or not effect then return end
|
|
if not self.DEBUFFS[effect] then return end
|
|
if duration <= 0 then return end
|
|
|
|
-- Always store recent cast keyed by spell name (for combat log fallback)
|
|
self.recentCasts[effect] = {
|
|
duration = duration,
|
|
time = GetTime()
|
|
}
|
|
|
|
-- Try to get GUID for unique identification (SuperWoW)
|
|
local unitKey = unit
|
|
if UnitGUID and UnitExists("target") and UnitName("target") == unit then
|
|
local guid = UnitGUID and UnitGUID("target")
|
|
if guid then unitKey = guid end
|
|
end
|
|
|
|
-- Malediction dual-curse handling: use separate pending slots for curses
|
|
local isNewCurse = self:IsWarlockCurse(effect)
|
|
local isExistingCurse = self.pending[3] and self:IsWarlockCurse(self.pending[3])
|
|
|
|
if isNewCurse and isExistingCurse and self:HasMalediction() then
|
|
-- Both are curses and Malediction is active
|
|
-- Check if they can coexist (one long curse + Curse of Agony)
|
|
if self:CanCursesCoexist(effect, self.pending[3]) then
|
|
-- Store the new curse in secondary pending slot
|
|
self.pendingCurse[1] = unitKey
|
|
self.pendingCurse[2] = unitlevel or 0
|
|
self.pendingCurse[3] = effect
|
|
self.pendingCurse[4] = duration
|
|
self.pendingCurse[5] = unit
|
|
return -- Don't overwrite primary pending
|
|
end
|
|
end
|
|
|
|
-- Standard pending: overwrite previous
|
|
self.pending[1] = unitKey
|
|
self.pending[2] = unitlevel or 0
|
|
self.pending[3] = effect
|
|
self.pending[4] = duration
|
|
self.pending[5] = unit -- Store original name for fallback lookups
|
|
end
|
|
|
|
function NanamiPlates_SpellDB:RemovePending()
|
|
self.pending[1] = nil
|
|
self.pending[2] = nil
|
|
self.pending[3] = nil
|
|
self.pending[4] = nil
|
|
self.pending[5] = nil
|
|
end
|
|
|
|
-- Clear secondary curse pending slot (Malediction support)
|
|
function NanamiPlates_SpellDB:RemovePendingCurse()
|
|
self.pendingCurse[1] = nil
|
|
self.pendingCurse[2] = nil
|
|
self.pendingCurse[3] = nil
|
|
self.pendingCurse[4] = nil
|
|
self.pendingCurse[5] = nil
|
|
end
|
|
|
|
function NanamiPlates_SpellDB:PersistPending(effect)
|
|
local persisted = false
|
|
|
|
-- Check primary pending slot
|
|
if self.pending[3] then
|
|
if self.pending[3] == effect or (effect == nil and self.pending[3]) then
|
|
-- Store by GUID (pending[1]) for accurate per-mob tracking
|
|
-- Mark as isOwn = true since this is the player's own debuff
|
|
self:RefreshEffect(self.pending[1], self.pending[2], self.pending[3], self.pending[4], true)
|
|
-- Also store by name (pending[5]) as fallback for non-SuperWoW lookups
|
|
if self.pending[5] and self.pending[5] ~= self.pending[1] then
|
|
self:RefreshEffect(self.pending[5], self.pending[2], self.pending[3], self.pending[4], true)
|
|
end
|
|
persisted = true
|
|
self:RemovePending()
|
|
end
|
|
end
|
|
|
|
-- Check secondary curse pending slot (Malediction dual-curse support)
|
|
if self.pendingCurse[3] then
|
|
if self.pendingCurse[3] == effect or (effect == nil and self.pendingCurse[3]) then
|
|
-- Store by GUID for accurate per-mob tracking
|
|
self:RefreshEffect(self.pendingCurse[1], self.pendingCurse[2], self.pendingCurse[3], self.pendingCurse[4], true)
|
|
-- Also store by name as fallback
|
|
if self.pendingCurse[5] and self.pendingCurse[5] ~= self.pendingCurse[1] then
|
|
self:RefreshEffect(self.pendingCurse[5], self.pendingCurse[2], self.pendingCurse[3], self.pendingCurse[4], true)
|
|
end
|
|
persisted = true
|
|
self:RemovePendingCurse()
|
|
end
|
|
end
|
|
|
|
return persisted
|
|
end
|
|
|
|
-- ============================================
|
|
-- EFFECT TRACKING
|
|
-- ============================================
|
|
|
|
function NanamiPlates_SpellDB:AddEffect(unit, unitlevel, effect, duration, isOwn)
|
|
if not unit or not effect then return end
|
|
unitlevel = unitlevel or 0
|
|
|
|
if not self.objects[unit] then self.objects[unit] = {} end
|
|
if not self.objects[unit][unitlevel] then self.objects[unit][unitlevel] = {} end
|
|
|
|
local existing = self.objects[unit][unitlevel][effect]
|
|
if existing and existing.start then
|
|
if existing.duration and (existing.start + existing.duration) > GetTime() then
|
|
return
|
|
end
|
|
end
|
|
|
|
if not self.objects[unit][unitlevel][effect] then self.objects[unit][unitlevel][effect] = {} end
|
|
|
|
self.objects[unit][unitlevel][effect].effect = effect
|
|
self.objects[unit][unitlevel][effect].start = GetTime()
|
|
self.objects[unit][unitlevel][effect].duration = duration or self:GetDuration(effect)
|
|
self.objects[unit][unitlevel][effect].isOwn = isOwn or false
|
|
end
|
|
|
|
function NanamiPlates_SpellDB:RefreshEffect(unit, unitlevel, effect, duration, isOwn)
|
|
if not unit or not effect then return end
|
|
unitlevel = unitlevel or 0
|
|
-- Always refresh start time and duration
|
|
if not self.objects[unit] then self.objects[unit] = {} end
|
|
if not self.objects[unit][unitlevel] then self.objects[unit][unitlevel] = {} end
|
|
if not self.objects[unit][unitlevel][effect] then self.objects[unit][unitlevel][effect] = {} end
|
|
|
|
self.objects[unit][unitlevel][effect].effect = effect
|
|
self.objects[unit][unitlevel][effect].start = GetTime()
|
|
self.objects[unit][unitlevel][effect].duration = duration or self:GetDuration(effect)
|
|
self.objects[unit][unitlevel][effect].isOwn = isOwn ~= false -- default to true for backwards compatibility
|
|
end
|
|
|
|
function NanamiPlates_SpellDB:UpdateDuration(unit, unitlevel, effect, duration)
|
|
if not unit or not effect or not duration then return end
|
|
unitlevel = unitlevel or 0
|
|
|
|
if self.objects[unit] and self.objects[unit][unitlevel] and self.objects[unit][unitlevel][effect] then
|
|
self.objects[unit][unitlevel][effect].duration = duration
|
|
end
|
|
end
|
|
|
|
-- ============================================
|
|
-- OWNER_BOUND_DEBUFFS OWNERSHIP TRACKING
|
|
-- ============================================
|
|
|
|
-- Track player's application of an OWNER_BOUND_DEBUFF
|
|
function NanamiPlates_SpellDB:TrackOwnerBoundDebuff(unit, effect, duration)
|
|
if not unit or not effect then return end
|
|
if not self.OWNER_BOUND_DEBUFFS[effect] then return end
|
|
|
|
if not self.ownerBoundCache[unit] then
|
|
self.ownerBoundCache[unit] = {}
|
|
end
|
|
|
|
self.ownerBoundCache[unit][effect] = {
|
|
start = GetTime(),
|
|
duration = duration or self:GetDuration(effect, 0) or 30
|
|
}
|
|
end
|
|
|
|
-- Check if player owns an OWNER_BOUND_DEBUFF on a unit
|
|
-- Returns true if player has a valid (non-expired) cached application
|
|
function NanamiPlates_SpellDB:IsOwnerBoundDebuffMine(unit, effect)
|
|
if not unit or not effect then return false end
|
|
if not self.OWNER_BOUND_DEBUFFS[effect] then return false end
|
|
|
|
local cache = self.ownerBoundCache[unit]
|
|
if not cache or not cache[effect] then return false end
|
|
|
|
local entry = cache[effect]
|
|
if not entry.start or not entry.duration then return false end
|
|
|
|
local now = GetTime()
|
|
local elapsed = now - entry.start
|
|
|
|
-- Allow 2 second grace period beyond expected duration
|
|
-- This handles slight timing variations
|
|
if elapsed > entry.duration + 2 then
|
|
-- Entry expired, clean it up
|
|
cache[effect] = nil
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
-- Clean up expired entries from ownerBoundCache
|
|
function NanamiPlates_SpellDB:CleanupOwnerBoundCache()
|
|
local now = GetTime()
|
|
|
|
for unit, effects in pairs(self.ownerBoundCache) do
|
|
local hasAny = false
|
|
for effect, entry in pairs(effects) do
|
|
if entry.start and entry.duration then
|
|
local elapsed = now - entry.start
|
|
if elapsed > entry.duration + 5 then
|
|
-- Grace period expired, remove
|
|
effects[effect] = nil
|
|
else
|
|
hasAny = true
|
|
end
|
|
else
|
|
effects[effect] = nil
|
|
end
|
|
end
|
|
-- Clean up empty unit tables
|
|
if not hasAny then
|
|
self.ownerBoundCache[unit] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Remove a specific debuff tracking when it fades
|
|
function NanamiPlates_SpellDB:RemoveOwnerBoundDebuff(unit, effect)
|
|
if not unit or not effect then return end
|
|
if self.ownerBoundCache[unit] then
|
|
self.ownerBoundCache[unit][effect] = nil
|
|
end
|
|
end
|
|
|
|
-- ============================================
|
|
-- UNITDEBUFF WRAPPER
|
|
-- Returns: effect, rank, texture, stacks, dtype, duration, timeleft, isOwn
|
|
-- ============================================
|
|
function NanamiPlates_SpellDB:UnitDebuff(unit, id)
|
|
local unitname = UnitName(unit)
|
|
local unitlevel = UnitLevel(unit) or 0
|
|
local texture, stacks, dtype = UnitDebuff(unit, id)
|
|
local duration, timeleft = nil, -1
|
|
local rank = nil
|
|
local effect = nil
|
|
local isOwn = false
|
|
|
|
if texture then
|
|
-- Get spell name via tooltip scanning
|
|
-- Try the unit first, but if it's a GUID and that fails, try "target" if it matches
|
|
effect = self:ScanDebuff(unit, id)
|
|
|
|
-- If scanning failed and this unit is the target, try scanning "target" instead
|
|
if (not effect or effect == "") and UnitName("target") == unitname then
|
|
effect = self:ScanDebuff("target", id)
|
|
end
|
|
|
|
effect = effect or ""
|
|
end
|
|
|
|
-- Check tracked debuffs with level
|
|
if effect and effect ~= "" and (self.objects[unitname] or (UnitGUID and UnitGUID(unit) and self.objects[UnitGUID(unit)])) then
|
|
local data = nil
|
|
local unitguid = UnitGUID and UnitGUID(unit)
|
|
|
|
local dataName = self:FindEffectData(unitname, unitlevel, effect)
|
|
local dataGUID = unitguid and self:FindEffectData(unitguid, unitlevel, effect)
|
|
|
|
if dataName and dataGUID then
|
|
if (dataName.start or 0) >= (dataGUID.start or 0) then
|
|
data = dataName
|
|
else
|
|
data = dataGUID
|
|
end
|
|
else
|
|
data = dataName or dataGUID
|
|
end
|
|
|
|
if data and data.start and data.duration then
|
|
-- Clean up expired
|
|
if data.duration + data.start < GetTime() then
|
|
-- Don't remove here, let it be cleaned up elsewhere
|
|
data = nil
|
|
else
|
|
duration = data.duration
|
|
timeleft = duration + data.start - GetTime()
|
|
isOwn = data.isOwn == true
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Fallback: if we have effect name but no tracked data, get duration from DB
|
|
-- Don't set timeleft - let the caller handle untracked debuffs with their own timer cache
|
|
if effect and effect ~= "" and (not duration or duration <= 0) then
|
|
local dbDuration = self:GetDuration(effect, 0)
|
|
if dbDuration and dbDuration > 0 then
|
|
duration = dbDuration
|
|
-- timeleft stays at -1, signaling caller to use their own timer cache
|
|
end
|
|
end
|
|
|
|
return effect, rank, texture, stacks, dtype, duration, timeleft, isOwn
|
|
end
|
|
|
|
function NanamiPlates_SpellDB:InitScanner()
|
|
if self.scanner then return end
|
|
|
|
-- Create hidden tooltip for scanning
|
|
self.scanner = CreateFrame("GameTooltip", "NanamiPlatesDebuffScanner", UIParent, "GameTooltipTemplate")
|
|
self.scanner:SetOwner(UIParent, "ANCHOR_NONE")
|
|
end
|
|
|
|
-- Check if a string looks like a SuperWoW GUID
|
|
local function IsGUID(unit)
|
|
if not unit or type(unit) ~= "string" then return false end
|
|
return string_sub(unit, 1, 2) == "0x"
|
|
end
|
|
|
|
function NanamiPlates_SpellDB:ScanDebuff(unit, index)
|
|
if not self.scanner then self:InitScanner() end
|
|
|
|
local texture = UnitDebuff(unit, index)
|
|
if not texture then return nil end
|
|
|
|
-- 1. Prioritize debuff-specific mappings (manually curated, always correct)
|
|
if self.debuffPriority and self.debuffPriority[texture] then
|
|
return self.debuffPriority[texture]
|
|
end
|
|
|
|
-- 2. Try tooltip scanning FIRST (most accurate, returns actual debuff name)
|
|
local scanUnit = unit
|
|
local canScanTooltip = true
|
|
if IsGUID(unit) then
|
|
if UnitExists("target") then
|
|
local targetGUID = UnitGUID and UnitGUID("target")
|
|
if targetGUID and targetGUID == unit then
|
|
scanUnit = "target"
|
|
else
|
|
canScanTooltip = false
|
|
end
|
|
else
|
|
canScanTooltip = false
|
|
end
|
|
end
|
|
|
|
if canScanTooltip then
|
|
self.scanner:ClearLines()
|
|
self.scanner:SetUnitDebuff(scanUnit, index)
|
|
|
|
local textLeft = getglobal("NanamiPlatesDebuffScannerTextLeft1")
|
|
if textLeft then
|
|
local effect = textLeft:GetText()
|
|
if effect and effect ~= "" then
|
|
local originalTooltipName = effect
|
|
|
|
-- Try localeMap translation (static)
|
|
if self.localeMap and self.localeMap[effect] then
|
|
effect = self.localeMap[effect]
|
|
end
|
|
-- Try runtime learned locale cache
|
|
if not self.DEBUFFS[effect] and self.learnedLocale and self.learnedLocale[effect] then
|
|
effect = self.learnedLocale[effect]
|
|
end
|
|
-- Only use textureToSpell when tooltip name doesn't resolve in DEBUFFS
|
|
if not self.DEBUFFS[effect] and texture and self.textureToSpell[texture] then
|
|
local texName = self.textureToSpell[texture]
|
|
if self.DEBUFFS[texName] then
|
|
self:LearnLocale(originalTooltipName, texName)
|
|
effect = texName
|
|
end
|
|
end
|
|
-- Auto-learn: if tooltip name not in DB, but we just cast a known spell
|
|
if not self.DEBUFFS[effect] and self.lastCastSpell and self.lastCastTime then
|
|
if (GetTime() - self.lastCastTime) < 3 and self.DEBUFFS[self.lastCastSpell] then
|
|
self:LearnLocale(originalTooltipName, self.lastCastSpell)
|
|
effect = self.lastCastSpell
|
|
self.lastCastSpell = nil
|
|
end
|
|
end
|
|
-- Cache texture -> resolved name (only if we got a valid DEBUFFS match)
|
|
if texture and effect and effect ~= "" and self.DEBUFFS[effect] then
|
|
self.textureToSpell[texture] = effect
|
|
end
|
|
end
|
|
if effect and effect ~= "" then
|
|
return effect
|
|
end
|
|
end
|
|
end
|
|
|
|
-- 3. Fallback to textureToSpell (for non-target GUIDs or when tooltip produced nothing)
|
|
if self.textureToSpell[texture] then
|
|
return self.textureToSpell[texture]
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
-- Scan spellbook at login to build texture → spell mappings and locale learning
|
|
function NanamiPlates_SpellDB:ScanSpellbook()
|
|
self.learnedLocale = self.learnedLocale or {}
|
|
|
|
-- Build a reverse map: English name → texture from our existing textureToSpell
|
|
local engToTex = {}
|
|
for tex, eng in pairs(self.textureToSpell) do
|
|
if self.DEBUFFS[eng] then
|
|
engToTex[eng] = tex
|
|
end
|
|
end
|
|
|
|
local i = 1
|
|
while true do
|
|
local localName, rank = GetSpellName(i, "spell")
|
|
if not localName then break end
|
|
local spellTex = GetSpellTexture(i, "spell")
|
|
|
|
if spellTex and localName and localName ~= "" then
|
|
-- Case 1: spellbook name is already in DEBUFFS (English client or matching name)
|
|
if self.DEBUFFS[localName] then
|
|
if not self.textureToSpell[spellTex] or not self.DEBUFFS[self.textureToSpell[spellTex]] then
|
|
self.textureToSpell[spellTex] = localName
|
|
end
|
|
else
|
|
-- Case 2: spellbook name is localized (e.g. Chinese)
|
|
-- Try to find the English name via localeMap
|
|
local engName = self.localeMap and self.localeMap[localName]
|
|
if engName and self.DEBUFFS[engName] then
|
|
self.learnedLocale[localName] = engName
|
|
if not self.textureToSpell[spellTex] or not self.DEBUFFS[self.textureToSpell[spellTex]] then
|
|
self.textureToSpell[spellTex] = engName
|
|
end
|
|
else
|
|
-- Case 3: Try matching by texture path to find English name
|
|
local existing = self.textureToSpell[spellTex]
|
|
if existing and self.DEBUFFS[existing] and existing ~= localName then
|
|
self.learnedLocale[localName] = existing
|
|
end
|
|
end
|
|
end
|
|
end
|
|
i = i + 1
|
|
end
|
|
end
|
|
|
|
-- Learn locale mapping: associate a localized tooltip name with an English DEBUFFS name
|
|
-- Called when we detect a debuff was just cast by the player
|
|
function NanamiPlates_SpellDB:LearnLocale(localeName, englishName)
|
|
if not localeName or not englishName or localeName == englishName then return end
|
|
if localeName == "" or englishName == "" then return end
|
|
if not self.DEBUFFS[englishName] then return end
|
|
self.learnedLocale = self.learnedLocale or {}
|
|
if not self.learnedLocale[localeName] then
|
|
self.learnedLocale[localeName] = englishName
|
|
end
|
|
end
|
|
|
|
-- Get spell name and rank from action bar slot by matching texture to spellbook
|
|
-- Returns the HIGHEST rank spell that matches the texture (since all ranks share same texture)
|
|
function NanamiPlates_SpellDB:ScanAction(slot)
|
|
local actionTexture = GetActionTexture(slot)
|
|
if not actionTexture then return nil, nil end
|
|
|
|
-- Search through spellbook to find ALL matching textures and return highest rank
|
|
local bestName, bestRank, bestRankNum = nil, nil, -1
|
|
local i = 1
|
|
while true do
|
|
local spellName, spellRank = GetSpellName(i, "spell")
|
|
if not spellName then break end
|
|
|
|
local spellTexture = GetSpellTexture(i, "spell")
|
|
if spellTexture and spellTexture == actionTexture then
|
|
-- Parse rank number from "Rank X" string
|
|
local rankNum = 0
|
|
if spellRank then
|
|
for num in string.gfind(spellRank, "(%d+)") do
|
|
rankNum = tonumber(num) or 0
|
|
break
|
|
end
|
|
end
|
|
-- Keep track of highest rank found
|
|
if rankNum > bestRankNum then
|
|
bestName = spellName
|
|
bestRank = spellRank
|
|
bestRankNum = rankNum
|
|
end
|
|
end
|
|
i = i + 1
|
|
end
|
|
|
|
-- Return highest rank if found
|
|
if bestName then
|
|
return bestName, bestRank
|
|
end
|
|
|
|
-- If not found in spellbook, try tooltip as fallback
|
|
if not self.scanner then self:InitScanner() end
|
|
self.scanner:ClearLines()
|
|
self.scanner:SetAction(slot)
|
|
|
|
local textLeft = getglobal("NanamiPlatesDebuffScannerTextLeft1")
|
|
local textRight = getglobal("NanamiPlatesDebuffScannerTextRight1")
|
|
|
|
local effect = textLeft and textLeft:GetText() or nil
|
|
local rank = textRight and textRight:GetText() or nil
|
|
|
|
return effect, rank
|
|
end
|
|
|
|
-- Expose on the main addon table
|
|
NanamiPlates.SpellDB = NanamiPlates_SpellDB
|