Quantcast
-- 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, ...)
	if DEBUG then
		local msg = "|cffffff78DressToKill:|r "
		if select("#", ...) > 0 then
			msg = msg .. fmt:format(...)
		else
			msg = msg .. fmt
		end
		ChatFrame1:AddMessage(msg)
	end
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"],
	MainHandSlot = L["Main Hand"],
	NeckSlot = L["Neck Slot"],
	RangedSlot = L["Ranged Slot"],
	--SecondaryHandSlot = L["Secondary Hand"],
	ShoulderSlot = L["Shoulders"],
	Trinket0Slot = L["Trinket 0"],
	Trinket1Slot = L["Trinket 1"],
	WaistSlot = L["Waist"],
	WristSlot = L["Wrist"],
}

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
		local avail = GetInventoryItemsForSlot(slotId)
		if next(avail) then
			slotAvail[slotId] = avail
		end
	end

	local stash = {}
	-- Loop through all of the inventory slots
	for slotId,avail in pairs(slotAvail) do
		local maxScore, winner = 0, 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

		-- 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 equipped = DressToKill:EquipItem(slotId, mask, stash)
				if equipped then
					local link = GetInventoryItemLink("player", slotId)
					local score = weightFunction(link, slotId)
					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
		DressToKill:EquipItem(slotId, winner, stash)
		local link = GetInventoryItemLink("player", slotId)
		print("Choosing %s", link)
		blacklist[winner] = true
	end
	UIErrorsFrame:AddMessage("Scan complete!", 0.2, 1, 0.2)
	print("Evaluation complete!")
end

function DressToKill:EquipItem(slotId, mask, stash)
	local bag,slot = DressToKill:ItemInBag(mask)
	local eqslot = DressToKill:ItemEquipped(mask)
	if bag then
		-- Equip the item
		PickupContainerItem(bag, slot)
		EquipCursorItem(slotId)
		local equipped = coroutine.yield()
		return equipped
	elseif eqslot 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 bag,slot = DressToKill:ItemInBag(mask)
	local eqslot = DressToKill:ItemEquipped(mask)
	if bag then
		-- Just put the item back
		PickupInventoryItem(slotId)
		PickupContainerItem(bag, slot)
		coroutine.yield()
	elseif eqslot 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")

local function OnEvent(self, event)
	self.equipped = true
	if event == "EQUIP_BIND_CONFIRM" and CursorHasItem() then
		self.equipped = false
		debug(L["Clearing non-soulbound item"])
		ClearCursor()
	end

	local thread = DressToKill.currentThread
	if thread and coroutine.status(thread) ~= "dead" then
		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_OpenToFrame(DressToKillOptionsFrame)
		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: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
--]]