WoWInterface SVN DressToKill

[/] [trunk/] [DressToKill.lua] - Rev 29

Compare with Previous | Blame | View Log

-- Copyright 2008 James Whitehead II
-- Credits for name 'DressToKill' go to Aska on StormReaver(EU)

DressToKill = {}
local L = DressToKillLocals

local DEBUG = false
local function debug(fmt, ...)
        local msg = "|cffffff78DressToKill:|r " 
        if select("#", ...) > 0 then
                local succ,err = pcall(string.format, fmt, ...)
                if not succ then
                        error(debugstack())
                else
                        msg = msg .. err
                end
        else
                msg = msg .. fmt
        end
        if DEBUG then
                ChatFrame1:AddMessage(msg)
        end
        DressToKillDebugScrollFrame:AddMessage(msg)
end

function DressToKill:SetDebug(value)
        DEBUG = value
end

local function print(fmt, ...)
        local msg = "|cffffff78DressToKill:|r " 
        if select("#", ...) > 0 then
                msg = msg .. fmt:format(...)
        else
                msg = msg .. fmt
        end
        ChatFrame1:AddMessage(msg)
end

local slotNames = {
        BackSlot = L["Back"],
        ChestSlot = L["Chest"],
        FeetSlot = L["Feet"],
        Finger0Slot = L["Left Finger"],
        Finger1Slot = L["Right Finger"],
        HeadSlot = L["Head"],
        HandsSlot = L["Hands"],
        LegsSlot = L["Legs"],
        NeckSlot = L["Neck Slot"],
        RangedSlot = L["Ranged Slot"],
        ShoulderSlot = L["Shoulders"],
        Trinket0Slot = L["Trinket 0"],
        Trinket1Slot = L["Trinket 1"],
        WaistSlot = L["Waist"],
        WristSlot = L["Wrist"],
        --MainHandSlot = L["Main Hand"],
        --SecondaryHandSlot = L["Secondary Hand"],
}

local slotIds = {}

function DressToKill:Initialize()
        -- Only run this code once
        if self.initDone then return end
        self.initDone = true

        for slot in pairs(slotNames) do
                local id = GetInventorySlotInfo(slot)
                if id then
                        slotIds[slot] = id
                else
                        local err = string.format(L["Could not get inventory slot information for %s"], tostring(slot))
                        error(err)
                        return
                end
        end

        DressToKillDB = DressToKillDB or {}
        self.profile = DressToKillDB
        self.profile.weightFuncs = self.profile.weightFuncs or {
                ["Healing/Mana"] = {handler = [[local healing = GetSpellBonusHealing()
local mana = UnitManaMax("player")
return (healing * 1.0) + (mana * 0.7)]]},
                ["Health/Armor"] = {handler = [[local health = UnitHealthMax("player")
local base, pos, neg = UnitArmor("player")
local armor = base + pos - neg
return (health * 1.0) + (armor * 0.8)]]},
                ["Attack Power"] = {handler = [[local base, pos, neg = UnitAttackPower("player")
local apower = base + pos - neg
return apower]]},
        }
end

function DressToKill:FindEmptySlot()
        for bag=0,4 do 
                for slot=1,GetContainerNumSlots(bag) do
                        if not GetContainerItemInfo(bag, slot) then
                                return bag, slot
                        end
                end
        end
end

local function scanFunction(weightFunction)
        debug(L["********** Beginning an inventory scan **********"])
        UIErrorsFrame:AddMessage("Please do not change buffs or forms during scan...", 1, 0.2, 0.2)

        local blacklist = {}
        local slotAvail = {}
        for slotName,slotId in pairs(slotIds) do
                debug(L["Collecting available items for %s"], slotName)
                local avail = GetInventoryItemsForSlot(slotId)
                if next(avail) then
                        local c = 0
                        for k,v in pairs(avail) do c = c + 1 end
                        debug(L["Found %d available items"], c)
                        slotAvail[slotId] = avail
                else
                        debug(L["Found no available items"])
                end
        end

        -- Let's handle the weapons, since they're somewhat complicated
        local mainslot = GetInventorySlotInfo("MainHandSlot")
        local offslot = GetInventorySlotInfo("SecondaryHandSlot")

        -- Unequip both weapon slots
        local mh_link = GetInventoryItemLink("player", mainslot)
        local oh_link = GetInventoryItemLink("player", offslot)

        debug(L["Mainhand link: %s"], tostring(mh_link))
        debug(L["Offhand link: %s"], tostring(oh_link))

        if mh_link then
                debug(L["Found a mainhand weapon: %s"], mh_link)
                local mh_bag, mh_slot = DressToKill:FindEmptySlot()
                if not mh_bag then
                        print(L["Out of inventory space, cannot proceed"])
                        return
                end
                debug(L["Unequipping the mainhand weapon"])
                PickupInventoryItem(mainslot)
                PickupContainerItem(mh_bag, mh_slot)
                coroutine.yield()
        end

        if oh_link then
                debug(L["Found an offhand weapon: %s"], oh_link)
                local oh_bag, oh_slot = DressToKill:FindEmptySlot()
                if not oh_bag then
                        print(L["Out of inventory space, cannot proceed"])
                        return
                end
                debug(L["Unequipping the offhand weapon"])
                PickupInventoryItem(offslot)
                PickupContainerItem(oh_bag, oh_slot)
                coroutine.yield()
        end

        local mh_avail = GetInventoryItemsForSlot(mainslot)
        local oh_avail = GetInventoryItemsForSlot(offslot)

        -- Calculate weapon base score with nothing equipped
        local linen_shirt = "\124cffffffff\124Hitem:2576:0:0:0:0:0:0:0\124h[White Linen Shirt]\124h\124r"
        local weapon_max, weapon_win = (- math.huge), {}

        -- Try on each mainhand weapon
        local score
        for mh_mask,item in pairs(mh_avail) do
                local name, link = GetItemInfo(item)
                debug(L["Equipping mainhand weapon: %s"], link)

                local mh_equipped = DressToKill:EquipItem(mainslot, mh_mask)
                blacklist[mh_mask] = true

                if mh_equipped then 
                        debug(L["Successfully equipped %s"], link)
                        local mh_link = GetInventoryItemLink("player", mainslot)
                        local mh_score = weightFunction(mh_link, mainslot)
                        debug(L["Mainhand score: %d"], mh_score)

                        if not IsEquippedItemType("Two-Hand") then
                                debug(L["This is a one hand weapon, try offhand weapons"])
                                -- Try to equip each off-hand weapon to compliment this one
                                for oh_mask,item in pairs(oh_avail) do
                                        if not blacklist[oh_mask] then
                                                score = mh_score
                                                local name, link = GetItemInfo(item)

                                                debug(L["Equipping offhand weapon: %s"], link)
                                                local oh_equipped = DressToKill:EquipItem(offslot, oh_mask)

                                                -- If we equipped offhand, then score it too
                                                if oh_equipped then
                                                        local oh_link = GetInventoryItemLink("player", offslot) or linen_shirt
                                                        local oh_score = weightFunction(oh_link, offslot)
                                                        score = oh_score
                                                        debug(L["Got score of %s for %s/%s"], score, mh_link, oh_link) 
                                                else
                                                        debug(L["Failed to equip %s"], link)
                                                end

                                                if score >= weapon_max then
                                                        weapon_max = score
                                                        weapon_win.mh = mh_mask
                                                        weapon_win.oh = oh_equipped and oh_mask or nil
                                                end

                                                -- Unequip the offhand item
                                                if oh_equipped then
                                                        debug(L["Unequipping %s"], link)
                                                        DressToKill:UnequipItem(offslot, oh_mask, oh_stash)
                                                end
                                        end
                                end
                        else
                                score = mh_score
                                debug("Got score of %s for %s", score, mh_link)
                                if score >= weapon_max then
                                        weapon_max = score
                                        weapon_win.mh = mh_mask
                                        weapon_win.oh = nil
                                end
                        end

                        -- Unequip this mainhand item
                        DressToKill:UnequipItem(mainslot, mh_mask, mh_stash)
                        blacklist[mh_mask] = false
                end
        end

        -- Equip the weapon winners (MAINHAND)
        DressToKill:EquipItem(mainslot, weapon_win.mh, mh_stash)
        local link = GetInventoryItemLink("player", mainslot)
        print("Choosing %s", link)
        debug("Choosing %s", link)
        blacklist[weapon_win.mh] = true

        -- Equip the offhand winner, if there was one
        if weapon_win.oh then
                DressToKill:EquipItem(offslot, weapon_win.oh, oh_stash)
                local link = GetInventoryItemLink("player", offslot)
                print("Choosing %s", link)
                blacklist[weapon_win.oh] = true
        end

        local stash = {}
        -- Loop through all of the inventory slots
        for slotId,avail in pairs(slotAvail) do
                local maxScore, winner = (- math.huge), nil
                local link = GetInventoryItemLink("player", slotId)
                if link then
                        -- We current have an item equipped, so stash that item elsewhere
                        local bag,slot = DressToKill:FindEmptySlot()
                        if not bag or not slot then
                                print(L["Out of inventory space, cannot proceed"])
                                return
                        end

                        stash.bag = bag
                        stash.slot = slot
                        PickupInventoryItem(slotId)
                        PickupContainerItem(bag, slot)
                        coroutine.yield()
                end

                -- Get the base score for this slot, so we can normalize our scores
                local linen_shirt = "\124cffffffff\124Hitem:2576:0:0:0:0:0:0:0\124h[White Linen Shirt]\124h\124r"
                local base = weightFunction(linen_shirt, slotId) or 0

                -- Loop through all of the available items, and try equipping each of them
                for mask,item in pairs(avail) do
                        if not blacklist[mask] then
                                local name,link = GetItemInfo(item)
                                debug(L["Trying on %s for slot %s"], link, slotId)
                                local equipped = DressToKill:EquipItem(slotId, mask, stash)
                                if equipped then
                                        local link = GetInventoryItemLink("player", slotId)
                                        local score = weightFunction(link, slotId) - base
                                        debug("Got score of %s for %s", score, link)
                                        if score >= maxScore then
                                                maxScore = score
                                                winner = mask
                                        end

                                        -- Unequip the item
                                        DressToKill:UnequipItem(slotId, mask, stash)
                                else
                                        debug(L["Item not equipped... skipping..."])
                                end
                        else
                                debug(L["Item blacklisted, skipping..."])
                        end
                end
                -- Now equip the item that won this round and blacklist assuming there was a winner
                if winner then
                        DressToKill:EquipItem(slotId, winner, stash)
                        local link = GetInventoryItemLink("player", slotId)
                        debug(L["Choosing %s for this slot"], link)
                        print(L["Choosing %s"], link)
                        blacklist[winner] = true
                else
                        debug(L["There was no winner from this round, not enough trinkets or rings?"])
                end
        end
        UIErrorsFrame:AddMessage("Scan complete!", 0.2, 1, 0.2)
        print("Evaluation complete!")
        debug(L["Evaluation complete!"])
end

function DressToKill:EquipItem(slotId, mask, stash)
    local player, bank, bags, slot, bag = EquipmentManager_UnpackLocation(mask)
        if bag then
                -- Equip the item
                PickupContainerItem(bag, slot)
                EquipCursorItem(slotId)
                local equipped = coroutine.yield()
                return equipped
        elseif player then
                -- The item was equipped, so find the current slot in the stash
                PickupContainerItem(stash.bag, stash.slot)
                EquipCursorItem(slotId)
                local equipped = coroutine.yield()
                return equipped
        end
end

function DressToKill:UnequipItem(slotId, mask, stash)
    local player, bank, bags, slot, bag = EquipmentManager_UnpackLocation(mask)
        if bag then
                -- Just put the item back
                PickupInventoryItem(slotId)
                PickupContainerItem(bag, slot)
                coroutine.yield()
        elseif player then
                -- Put it back into the stash slot
                PickupInventoryItem(slotId)
                PickupContainerItem(stash.bag, stash.slot)
                coroutine.yield()
        end
end

local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("UNIT_INVENTORY_CHANGED")
eventFrame:RegisterEvent("EQUIP_BIND_CONFIRM")
eventFrame:RegisterEvent("UI_ERROR_MESSAGE")

--local LEVEL_LOW = LEVEL_TOO_LOW:gsub("%%d", "%%d%+"):gsub("%.", "%%.")

local function OnEvent(self, event, arg1)
        self.equipped = true
        local thread = DressToKill.currentThread
        if thread and coroutine.status(thread) ~= "dead" then
                if event == "EQUIP_BIND_CONFIRM" and CursorHasItem() then
                        self.equipped = false
                        debug(L["Clearing non-soulbound item"])
                        ClearCursor()
                elseif event == "UI_ERROR_MESSAGE" and arg1 == ERR_PROFICIENCY_NEEDED then
                        self.equipped = false
                        debug(L["Clearing item we don't have the proficiency for"])
                        ClearCursor()
                elseif event == "UI_ERROR_MESSAGE" and arg1 == ERR_2HSKILLNOTFOUND then
                        self.equipped = false
                        debug(L["Clearing item since we lack dual wielding skill"])
                        ClearCursor()
                elseif event == "UI_ERROR_MESSAGE" and arg1 == ERR_CANT_EQUIP_SKILL then
                        self.equipped = false
                        debug(L["Clearing item since we lack the skill to equip it"])
                        ClearCursor()
                elseif event == "UI_ERROR_MESSAGE" then
                        self.equipped = false
                        debug(L["Clearing item due to API saying it is valid for an invalid slot"])
                        ClearCursor()
                elseif event == "UI_ERROR_MESSAGE" then
                        debug(L["Got an UI error message: %s"], arg1)
                end
                
                self:Show()
        end
end

local function OnUpdate(self, elapsed)
        self:Hide()
        local succ,err = coroutine.resume(DressToKill.currentThread, self.equipped)     
        assert(succ, err)
end

eventFrame:SetScript("OnEvent", OnEvent)
eventFrame:SetScript("OnUpdate", OnUpdate)
eventFrame:Hide()

SLASH_DRESSTOKILL1 = "/dtk"
SLASH_DRESSTOKILL2 = "/dress"
SLASH_DRESSTOKILL3 = "/dresstokill"

SlashCmdList["DRESSTOKILL"] = function(msg, editbox)
        DressToKill:Initialize()
        if msg and msg:lower():match("%s*config%s*") then
                InterfaceOptionsFrame_OpenToCategory(DressToKillOptionsFrame)
                return
        end

        if msg and msg:lower():match("%s*debug%s*") then
                InterfaceOptionsFrame_OpenToCategory(DressToKillDebugFrame)
                return
        end

        -- Check to see if a function was specified on the commandline
        local weightName = msg:match("^%s*(.+)$")
        if not weightName then
                if DressToKill.profile.selected then
                        weightName = DressToKill.profile.selected
                else
                        print(L["You don't have a default weight function selected"])
                        return
                end
        end
        
        -- Make sure the function actually exist
        if not DressToKill.profile.weightFuncs[weightName] then
                print(L["The weight function '%s' doesn't exist"], weightName)
                return
        end

        -- Compile the function
        local source = DressToKill.profile.weightFuncs[weightName].handler
        local newsrc = string.format("return function (link, slot) %s end", source)
        local weightFunction,err = loadstring(newsrc)

        if not weightFunction then
                print("Failed to compile weight function: " .. tostring(err))
                return
        else
                local succ,err = pcall(weightFunction)
                if not succ then
                        print("Failed when running weight function: " .. tostring(err))
                        return
                else
                        weightFunction = err
                end
        end

        print(L["Dressing to kill with %s..."], weightName)
        DressToKill.currentThread = coroutine.create(scanFunction)
        coroutine.resume(DressToKill.currentThread, weightFunction)
end

local ITEM_INVENTORY_PLAYER = 0x00100000
local ITEM_INVENTORY_BACKPACK = 0x00200000
local ITEM_INVENTORY_BAGS = 0x00400000
local ITEM_INVENTORY_BANK = 0x00800000
local MASK_BAG = 0xf00
local MASK_SLOT = 0x3f
local bagMap = {
        [0x100] = 1,
        [0x200] = 2,
        [0x400] = 3,
        [0x800] = 4,
}

function DressToKill:ItemInBank(mask)
        if bit.band(mask, ITEM_INVENTORY_BANK) > 0 then
        end
end

function DressToKill:ItemInBag(mask)
        if bit.band(mask, ITEM_INVENTORY_BAGS) > 0 then
                local bag = bagMap[bit.band(mask, MASK_BAG)]
                local slot = bit.band(mask, MASK_SLOT)
                return bag, slot
        elseif bit.band(mask, ITEM_INVENTORY_BACKPACK) > 0 then
                local slot = bit.band(mask, MASK_SLOT)
                return 0, slot
        end
end

function DressToKill:ItemEquipped(mask)
        if bit.band(mask, ITEM_INVENTORY_PLAYER) > 0 then
                local slot = bit.band(mask, MASK_SLOT)
                return slot
        end
end

--[[
ITEM_INVENTORY_LOCATION_PLAYER          = 0x00100000
ITEM_INVENTORY_LOCATION_BACKPACK        = 0x00200000
ITEM_INVENTORY_LOCATION_BAGS            = 0x00400000
ITEM_INVENTORY_LOCATION_BANK            = 0x00800000

--
-- With four netherweave bags:
-- In backpack, slots 1 -> 16
--
-- 001100000000000000000001
-- 001100000000000000000010
-- ...
-- 001100000000000000010000
--
-- First bag, slots 1 -> 16
--
-- 010000000000000100000001
-- ...
-- 010000000000000100010000
--
-- Second bag, slots 1 -> 16
--
-- 010000000000001000000001
-- ...
-- 010000000000001000010000
--
-- Third bag, slots 1 -> 16
--
-- 010000000000010000000001
-- ...
-- 010000000000010000010000
--
-- Fourth bag, slots 1 -> 16
--
-- 010000000000100000000001
-- ...
-- 010000000000100000010000
--
-- Hands(10)
-- 000100000000000000001010
--
-- Waist(6)
-- 000100000000000000000110
--
-- Finger0(11)
-- 000100000000000000001011
--
-- Finger1(12)
-- 000100000000000000001011
--]]

Compare with Previous | Blame