local addonName, ns = ... local Amibis = {} ns.Amibis = Amibis Amibis.version = "1.0.0" local DEFAULT_AMIBIS_DB = { selectedPhase = "T5", } local DEFAULT_AMIBIS_CHAR_DB = { frameX = nil, frameY = nil, framePoint = nil, frameRelPoint = nil, overrideSpec = nil, } local TOP_UPGRADES_COUNT = 3 local UPGRADE_STAT_KEYS = { ["PRIEST"] = { ["Discipline"] = { "healing", "spellDamage", "spellPower", "intellect", "spirit", "mp5", "haste", "crit" }, ["Holy"] = { "healing", "spellPower", "intellect", "spirit", "mp5", "haste", "crit" }, ["Shadow"] = { "spellDamage", "spellPower", "intellect", "spirit", "mp5", "haste", "crit" }, }, ["DRUID"] = { ["Balance"] = { "spellDamage", "spellPower", "intellect", "spirit", "mp5", "haste", "crit" }, ["Restoration"] = { "healing", "spellPower", "intellect", "spirit", "mp5", "haste", "crit" }, }, ["PALADIN"] = { ["Holy"] = { "healing", "spellPower", "intellect", "spirit", "mp5", "haste", "crit" }, }, ["SHAMAN"] = { ["Elemental"] = { "spellDamage", "spellPower", "intellect", "spirit", "mp5", "haste", "crit" }, ["Restoration"] = { "healing", "spellPower", "intellect", "spirit", "mp5", "haste", "crit" }, }, ["MAGE"] = { ["Arcane"] = { "spellDamage", "spellPower", "intellect", "spirit", "mp5", "haste", "crit" }, ["Fire"] = { "spellDamage", "spellPower", "intellect", "spirit", "mp5", "haste", "crit" }, ["Frost"] = { "spellDamage", "spellPower", "intellect", "spirit", "mp5", "haste", "crit" }, }, ["WARLOCK"] = { ["Affliction"] = { "spellDamage", "spellPower", "intellect", "spirit", "mp5", "haste", "crit" }, ["Demonology"] = { "spellDamage", "spellPower", "intellect", "spirit", "mp5", "haste", "crit" }, ["Destruction"] = { "spellDamage", "spellPower", "intellect", "spirit", "mp5", "haste", "crit" }, }, } local DEFAULT_UPGRADE_STAT_KEYS = { "spellPower", "intellect", "spirit", "mp5", "haste", "crit" } local function GetUpgradeStatKeys(class, spec) if class and UPGRADE_STAT_KEYS[class] and UPGRADE_STAT_KEYS[class][spec] then return UPGRADE_STAT_KEYS[class][spec] end return DEFAULT_UPGRADE_STAT_KEYS end function Amibis:GetStatKeysForSpec(class, spec) return GetUpgradeStatKeys(class, spec) end local function InitializeDB() if not AmibisDB then AmibisDB = CopyTable(DEFAULT_AMIBIS_DB) else for k, v in pairs(DEFAULT_AMIBIS_DB) do if AmibisDB[k] == nil then AmibisDB[k] = v end end end if not AmibisCharDB then AmibisCharDB = CopyTable(DEFAULT_AMIBIS_CHAR_DB) else for k, v in pairs(DEFAULT_AMIBIS_CHAR_DB) do if AmibisCharDB[k] == nil then AmibisCharDB[k] = v end end end end function Amibis:GetPlayerClass() return select(2, UnitClass("player")) end function Amibis:GetPlayerSpec() local class = self:GetPlayerClass() local specNames = { ["PRIEST"] = { "Discipline", "Holy", "Shadow" }, ["MAGE"] = { "Arcane", "Fire", "Frost" }, ["WARLOCK"] = { "Affliction", "Demonology", "Destruction" }, ["DRUID"] = { "Balance", "Feral Combat", "Restoration" }, ["PALADIN"] = { "Holy", "Protection", "Retribution" }, ["SHAMAN"] = { "Elemental", "Enhancement", "Restoration" }, ["WARRIOR"] = { "Arms", "Fury", "Protection" }, ["ROGUE"] = { "Assassination", "Combat", "Subtlety" }, ["HUNTER"] = { "Beast Mastery", "Marksmanship", "Survival" }, } if not specNames[class] then return nil end local maxPoints = 0 local specIndex = 1 for i = 1, 3 do local r1, r2, r3, r4 = GetTalentTabInfo(i) local points = tonumber(r1) or tonumber(r2) or tonumber(r3) or tonumber(r4) or 0 if points > maxPoints then maxPoints = points specIndex = i end end return specNames[class][specIndex] end function Amibis:GetEquippedItems() local items = {} local slotMap = { ["Head"] = 1, ["Neck"] = 2, ["Shoulder"] = 3, ["Back"] = 15, ["Chest"] = 5, ["Wrist"] = 9, ["Hands"] = 10, ["Waist"] = 6, ["Legs"] = 7, ["Feet"] = 8, ["Finger0"] = 11, ["Finger1"] = 12, ["Trinket0"] = 13, ["Trinket1"] = 14, ["MainHand"] = 16, ["SecondaryHand"] = 17, ["Ranged"] = 18, } for slotName, slotID in pairs(slotMap) do local link = GetInventoryItemLink("player", slotID) if link then local itemID = tonumber(link:match("item:(%d+)")) local name = GetItemInfo(itemID) or "Unknown" items[slotName] = { itemID = itemID, name = name, link = link, slotID = slotID, } else items[slotName] = nil end end return items end local STAT_MAP = { ["ITEM_MOD_HEALING_DONE_SHORT"] = "healing", ["ITEM_MOD_SPELL_HEALING_DONE_SHORT"] = "healing", ["ITEM_MOD_HEALING_DONE"] = "healing", ["ITEM_MOD_SPELL_HEALING_DONE"] = "healing", ["ITEM_MOD_DAMAGE_DONE_SHORT"] = "spellDamage", ["ITEM_MOD_SPELL_DAMAGE_DONE_SHORT"] = "spellDamage", ["ITEM_MOD_DAMAGE_DONE"] = "spellDamage", ["ITEM_MOD_SPELL_DAMAGE_DONE"] = "spellDamage", ["ITEM_MOD_INTELLECT_SHORT"] = "intellect", ["ITEM_MOD_SPIRIT_SHORT"] = "spirit", ["ITEM_MOD_STAMINA_SHORT"] = "stamina", ["ITEM_MOD_MANA_REGENERATION_SHORT"] = "mp5", ["ITEM_MOD_POWER_REGEN0_SHORT"] = "mp5", ["ITEM_MOD_HASTE_RATING_SHORT"] = "haste", ["ITEM_MOD_SPELL_HASTE_RATING_SHORT"] = "haste", ["ITEM_MOD_SPELL_CRIT_RATING_SHORT"] = "crit", ["ITEM_MOD_CRIT_RATING_SHORT"] = "crit", ["ITEM_MOD_SPELL_POWER_SHORT"] = "spellPower", ["ITEM_MOD_SPELL_DMG_SHORT"] = "spellDamage", ["ITEM_MOD_HEALING_SHORT"] = "healing", } local MAX_AGGREGATE_STATS = { healing = true, spellDamage = true, } function Amibis:ScanItemStats(itemID) if not itemID then return nil end local _, link = GetItemInfo(itemID) if not link then link = "item:" .. itemID .. ":0:0:0:0:0:0:0:0" end local stats = GetItemStats(link) if not stats then return nil end local result = {} for wowKey, ourKey in pairs(STAT_MAP) do local value = stats[wowKey] if value then if MAX_AGGREGATE_STATS[ourKey] then local current = result[ourKey] if not current or value > current then result[ourKey] = value end else result[ourKey] = (result[ourKey] or 0) + value end end end return result end function Amibis:AggregateStats(items) local totals = { healing = 0, spellDamage = 0, intellect = 0, spirit = 0, stamina = 0, mp5 = 0, haste = 0, crit = 0, spellPower = 0, } for slotName, item in pairs(items) do if item and item.itemID then local stats = self:ScanItemStats(item.itemID) if stats then for k, v in pairs(stats) do totals[k] = (totals[k] or 0) + v end end end end return totals end function Amibis:CompareGear() local class = self:GetPlayerClass() local spec = AmibisCharDB.overrideSpec or self:GetPlayerSpec() local phase = AmibisDB.selectedPhase or "T5" local bisList = ns.Data.BISLists:GetBISList(class, spec, phase) if not bisList then return { class = class, spec = spec, phase = phase, hasBISList = false, slots = {}, bisCount = 0, totalSlots = 0, topUpgrades = {}, } end local equipped = self:GetEquippedItems() local slots = {} local bisCount = 0 local totalSlots = 0 local allUpgrades = {} for slotName, bisItem in pairs(bisList) do totalSlots = totalSlots + 1 local equippedItem = equipped[slotName] local isBIS = false if equippedItem and equippedItem.itemID == bisItem.itemID then isBIS = true bisCount = bisCount + 1 end table.insert(slots, { slotName = slotName, equipped = equippedItem, bis = bisItem, isBIS = isBIS, }) if not isBIS then local statDiffs = {} local score = 0 local equippedStats = (equippedItem and self:ScanItemStats(equippedItem.itemID)) or {} local bisStats = self:ScanItemStats(bisItem.itemID) or {} local upgradeStatKeys = GetUpgradeStatKeys(class, spec) for _, statKey in ipairs(upgradeStatKeys) do local diff = (bisStats[statKey] or 0) - (equippedStats[statKey] or 0) if diff > 0 then statDiffs[statKey] = diff score = score + diff end end table.insert(allUpgrades, { slot = slotName, equipped = equippedItem, bis = bisItem, statDiffs = statDiffs, score = score, }) end end table.sort(allUpgrades, function(a, b) if a.score ~= b.score then return a.score > b.score end return a.slot < b.slot end) local topUpgrades = {} for i = 1, math.min(TOP_UPGRADES_COUNT, #allUpgrades) do table.insert(topUpgrades, allUpgrades[i]) end table.sort(slots, function(a, b) if a.isBIS ~= b.isBIS then return b.isBIS end return a.slotName < b.slotName end) local equippedStats = self:AggregateStats(equipped) for slotName, bisItem in pairs(bisList) do GetItemInfo(bisItem.itemID) end local bisItems = {} for slotName, bisItem in pairs(bisList) do bisItems[slotName] = { itemID = bisItem.itemID } end local bisStats = self:AggregateStats(bisItems) local statsComplete = true for slotName, bisItem in pairs(bisList) do local _, link = GetItemInfo(bisItem.itemID) if link then local s = GetItemStats(link) if not s or not next(s) then statsComplete = false break end else statsComplete = false break end end return { class = class, spec = spec, phase = phase, hasBISList = true, slots = slots, bisCount = bisCount, totalSlots = totalSlots, topUpgrades = topUpgrades, equippedStats = equippedStats, bisStats = bisStats, statsComplete = statsComplete, } end function Amibis:SaveFramePosition(frame) local point, _, relPoint, x, y = frame:GetPoint() AmibisCharDB.framePoint = point AmibisCharDB.frameRelPoint = relPoint AmibisCharDB.frameX = x AmibisCharDB.frameY = y end function Amibis:LoadFramePosition(frame) local point = AmibisCharDB.framePoint local relPoint = AmibisCharDB.frameRelPoint local x = AmibisCharDB.frameX local y = AmibisCharDB.frameY if point and x and y then frame:ClearAllPoints() frame:SetPoint(point, UIParent, relPoint or point, x, y) else frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0) end end function Amibis:InitializeUI() if ns.UI and ns.UI.Initialize then ns.UI.Initialize() end end function Amibis:ToggleFrame() if ns.UI and ns.UI.ToggleFrame then ns.UI.ToggleFrame() end end local eventFrame = CreateFrame("Frame") eventFrame:RegisterEvent("ADDON_LOADED") eventFrame:RegisterEvent("PLAYER_LOGIN") eventFrame:RegisterEvent("PLAYER_TALENT_UPDATE") eventFrame:SetScript("OnEvent", function(self, event, ...) if event == "ADDON_LOADED" then local addon = ... if addon == addonName then InitializeDB() end elseif event == "PLAYER_LOGIN" then Amibis:InitializeUI() elseif event == "PLAYER_TALENT_UPDATE" then if ns.UI and ns.UI.RefreshUI then ns.UI.RefreshUI() end end end) SLASH_AMIBIS1 = "/amibis" SlashCmdList["AMIBIS"] = function(msg) local cmd = msg:trim():lower() if cmd == "" then Amibis:ToggleFrame() elseif cmd:find("^spec%s+") then local spec = cmd:match("^spec%s+(.+)") if spec and spec ~= "" then spec = spec:gsub("^%l", string.upper) AmibisCharDB.overrideSpec = spec print("Amibis: Spec override set to " .. spec .. ". Type /amibis spec clear to reset.") end elseif cmd == "spec clear" then AmibisCharDB.overrideSpec = nil print("Amibis: Spec override cleared.") else Amibis:ToggleFrame() end end