diff ui/ReforgesTab.lua @ 0:ec731d2fe6ba

Version 1.2.12.0
author Adam tegen <adam.tegen@gmail.com>
date Tue, 20 May 2014 21:43:23 -0500
parents
children ece9167c0d1c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ReforgesTab.lua	Tue May 20 21:43:23 2014 -0500
@@ -0,0 +1,504 @@
+local _, AskMrRobot = ...
+
+--------------------------------------------------------------------
+-- Local Reforge Utility Code
+--------------------------------------------------------------------
+
+StaticPopupDialogs["REFORGE_TAB_PLEASE_OPEN"] = {
+	text = "You need to open the reforge window for this to work",
+	button1 = "Ok",
+	timeout = 0,
+	whileDead = true,
+	hideOnEscape = true,
+	preferredIndex = 3,  -- avoid some UI taint, see http://www.wowace.com/announcements/how-to-avoid-some-ui-taint/
+}
+
+--from LibReforge
+local SPI = 1
+local DODGE = 2
+local PARRY = 3
+local HIT = 4
+local CRIT = 5
+local HASTE = 6
+local EXP = 7
+local MASTERY = 8
+
+--from LibReforge
+local StatNames = {
+	ITEM_MOD_SPIRIT_SHORT,
+	ITEM_MOD_DODGE_RATING_SHORT,
+	ITEM_MOD_PARRY_RATING_SHORT,
+	ITEM_MOD_HIT_RATING_SHORT,
+	ITEM_MOD_CRIT_RATING_SHORT,
+	ITEM_MOD_HASTE_RATING_SHORT,
+	ITEM_MOD_EXPERTISE_RATING_SHORT,
+	ITEM_MOD_MASTERY_RATING_SHORT
+}
+StatNames[0] = NONE
+local StatToString = {
+	"ITEM_MOD_SPIRIT_SHORT",
+	"ITEM_MOD_DODGE_RATING_SHORT",
+	"ITEM_MOD_PARRY_RATING_SHORT",
+	"ITEM_MOD_HIT_RATING_SHORT",
+	"ITEM_MOD_CRIT_RATING_SHORT",
+	"ITEM_MOD_HASTE_RATING_SHORT",
+	"ITEM_MOD_EXPERTISE_RATING_SHORT",
+	"ITEM_MOD_MASTERY_RATING_SHORT"
+}
+
+
+local REFORGE_TABLE_BASE = 112
+local REFORGE_TABLE = {
+  {1, 2}, {1, 3}, {1, 4}, {1, 5}, {1, 6}, {1, 7}, {1, 8},
+  {2, 1}, {2, 3}, {2, 4}, {2, 5}, {2, 6}, {2, 7}, {2, 8},
+  {3, 1}, {3, 2}, {3, 4}, {3, 5}, {3, 6}, {3, 7}, {3, 8},
+  {4, 1}, {4, 2}, {4, 3}, {4, 5}, {4, 6}, {4, 7}, {4, 8},
+  {5, 1}, {5, 2}, {5, 3}, {5, 4}, {5, 6}, {5, 7}, {5, 8},
+  {6, 1}, {6, 2}, {6, 3}, {6, 4}, {6, 5}, {6, 7}, {6, 8},
+  {7, 1}, {7, 2}, {7, 3}, {7, 4}, {7, 5}, {7, 6}, {7, 8},
+  {8, 1}, {8, 2}, {8, 3}, {8, 4}, {8, 5}, {8, 6}, {8, 7},
+}
+
+--------------- returns the index into the REFORGE_TABLE or nil
+-- returns the reforge id or 0
+local function GetReforgeIdForItem(item)
+  local id = tonumber(item:match("item:%d+:%d+:%d+:%d+:%d+:%d+:%-?%d+:%-?%d+:%d+:(%d+)"))
+  return (id and id ~= 0 and id or 0)
+end
+
+local function GetReforgeIdFromStats(fromStat, toStat)
+	if (toStat > fromStat) then
+		return REFORGE_TABLE_BASE + 7 * (fromStat - 1) + toStat - 1;
+	else
+		return REFORGE_TABLE_BASE + 7 * (fromStat - 1) + toStat;
+	end
+end
+
+
+--------------------------------------------------------------------
+-- Initialization
+--------------------------------------------------------------------
+AskMrRobot.ReforgesTab = AskMrRobot.inheritsFrom(AskMrRobot.Frame)
+
+function AskMrRobot.ReforgesTab:new(parent)
+
+	local tab = AskMrRobot.Frame:new(nil, parent)
+	setmetatable(tab, { __index = AskMrRobot.ReforgesTab })
+	tab:SetPoint("TOPLEFT")
+	tab:SetPoint("BOTTOMRIGHT")
+	tab:Hide()
+
+	local text = tab:CreateFontString("AmrReforgesHeader", "ARTWORK", "GameFontNormalLarge")
+	text:SetPoint("TOPLEFT", 0, -5)
+	text:SetText("Reforges")
+
+	tab.stamp = AskMrRobot.RobotStamp:new(nil, tab)
+	tab.stamp:Hide()
+	tab.stamp.smallText:SetText("Your reforges are 100% optimal!")
+	tab.stamp:SetPoint("TOPLEFT", text, "BOTTOMLEFT", 2, -15)
+	tab.stamp:SetPoint("RIGHT", tab, "RIGHT", -20, 0)
+
+	tab.reforgeDetails = tab:CreateFontString("AmrReforgeDetails", "ARTWORK", "GameFontWhite")
+	tab.reforgeDetails:SetPoint("TOPLEFT", text, "BOTTOMLEFT", 0, -15)
+	tab.reforgeDetails:SetPoint("RIGHT", -30, 0)
+	tab.reforgeDetails:SetWordWrap(true)
+	tab.reforgeDetails:SetJustifyH("LEFT")
+	tab.reforgeDetails:SetText('Open a reforge window, then click the "Reforge!" button to do it automatically.')
+	tab.reforgeDetails:SetHeight(50)
+
+	tab.reforgeButton = CreateFrame("Button", "AmrReforgeButton", tab, "UIPanelButtonTemplate")
+	tab.reforgeButton:SetText("Reforge!")
+	tab.reforgeButton:SetPoint("TOPLEFT", 0, -80)
+	tab.reforgeButton:SetWidth(140)
+	tab.reforgeButton:SetHeight(20)
+	tab.reforgeButton:SetScript("OnClick", function()
+		tab:OnReforge()
+	end)
+
+	tab.reforgeCost = tab:CreateFontString(nil, "ARTWORK", "GameFontNormal")
+	tab.reforgeCost:SetPoint("TOPLEFT", tab.reforgeButton, "TOPRIGHT", 25, 0)
+	tab.reforgeCost:SetPoint("BOTTOM", tab.reforgeButton, "BOTTOM", 0, 0)
+	tab.reforgeCost:SetPoint("RIGHT", tab, "RIGHT", -30, 0)
+	tab.reforgeCost:SetText('')
+
+	tab.slotHeader = tab:CreateFontString(nil, "ARTWORK", "GameFontNormal")
+	tab.slotHeader:SetText("Slot")
+	tab.slotHeader:SetPoint("TOPLEFT", tab.reforgeButton, "BOTTOMLEFT", 0, -30)
+
+	tab.reforgeHeader = tab:CreateFontString(nil, "ARTWORK", "GameFontNormal")
+	tab.reforgeHeader:SetText("Optimal Reforge")
+	tab.reforgeHeader:SetPoint("TOPLEFT", tab.slotHeader, "TOPLEFT", 100, 0)
+
+	-- pre-allocate a visual element for all possible slots; showBadReforges will set text and show the number that are needed, and hide the rest
+	tab.slots = {}
+	tab.optimized = {}
+
+	for i = 1, #AskMrRobot.slotNames do
+		tab.slots[i] = tab:CreateFontString(nil, "ARTWORK", "GameFontWhite")
+		tab.slots[i]:SetPoint("TOPLEFT", tab.slotHeader, "TOPLEFT", 0, -20 * i)
+		tab.slots[i]:Hide()
+
+		tab.optimized[i] = tab:CreateFontString(nil, "ARTWORK", "GameFontWhite")
+		tab.optimized[i]:SetPoint("TOPLEFT", tab.reforgeHeader, "TOPLEFT", 0, -20 * i)
+		tab.optimized[i]:Hide()
+	end
+
+	tab:RegisterEvent("FORGE_MASTER_ITEM_CHANGED")
+	tab:RegisterEvent("FORGE_MASTER_SET_ITEM")
+	tab:RegisterEvent("FORGE_MASTER_OPENED")
+	tab:RegisterEvent("FORGE_MASTER_CLOSED")
+
+	tab:SetScript("OnEvent", function(...)
+		tab:OnEvent(...)
+	end)
+
+
+	-- initialize stat required for performing the reforges
+	tab.state = {}
+	tab:ResetState()
+
+
+	return tab
+end
+
+
+--------------------------------------------------------------------
+-- Rendering
+--------------------------------------------------------------------
+
+local function GetReforgeString(fromId, toId)
+	if toId == 0 then
+		return "Restore"
+	end
+	local pair = REFORGE_TABLE[toId - REFORGE_TABLE_BASE]
+
+	local text = _G[StatToString[pair[1]]] .. ' -> ' .. _G[StatToString[pair[2]]]
+	--print('from ' .. fromId)
+	if fromId == 0 then
+		return text
+	end
+	return 'Restore, then ' .. text
+end
+
+-- draw all of the reforges that still need to be performed
+function AskMrRobot.ReforgesTab:Render()
+
+	local reforges = AskMrRobot.itemDiffs.reforges
+	local i = 1
+	local cost = 0
+
+	-- for all the bad items
+	for slotNum, badReforge in AskMrRobot.sortSlots(reforges) do
+
+		self.optimized[i]:SetText(GetReforgeString(badReforge.current, badReforge.optimized))
+		self.optimized[i]:Show()
+
+		self.slots[i]:SetText(_G[strupper(AskMrRobot.slotNames[slotNum])])
+		self.slots[i]:Show()
+
+		-- Restore is free, so only add cost for non-restore reforges
+		if badReforge.optimized > 0 then
+			local slotId = AskMrRobot.slotIds[slotNum]
+			local itemLink = GetInventoryItemLink("player", slotId)
+			cost = cost + (itemLink and select (11, GetItemInfo(itemLink)) or 0)
+		end
+
+		i = i + 1
+	end
+
+	self.reforgeCost:SetText("Total reforge cost: ~" .. math.ceil(cost / 10000) .. " Gold")
+
+	-- hide / show the headers
+	if i == 1 then
+		self.reforgeHeader:Hide()
+		self.slotHeader:Hide()
+		self.reforgeButton:Hide()
+		self.reforgeCost:Hide()
+		self.reforgeDetails:Hide()
+		self.stamp:Show()
+	else
+		self.stamp:Hide()
+		self.reforgeButton:Show()
+		self.reforgeCost:Show()
+		self.reforgeHeader:Show()
+		self.reforgeDetails:Show()
+		self.slotHeader:Show()
+	end
+
+	-- hide the remaining slots
+	while i <= #self.slots do
+		self.optimized[i]:Hide()
+		self.slots[i]:Hide()
+		i = i + 1
+	end
+end
+
+--------------------------------------------------------------------
+-- Reforge Logic
+--------------------------------------------------------------------
+
+-- reset state for a fresh pass at automatic reforging
+function AskMrRobot.ReforgesTab:ResetState()
+
+	self.state.queue = {}           -- list of all reforges actions that still need to be performed
+	self.state.currentItem = nil    -- the current item we are trying to reforge
+	self.state.pendingSlot = nil    -- the slot that we have requested to place into the reforger
+	self.state.currentSlot = nil    -- the current slot in the reforger
+	self.state.pendingReforge = -1  -- the reforge that we have requested to perform on the current item
+end
+
+-- refresh the queue of reforges that still need to be performed
+function AskMrRobot.ReforgesTab:RefreshQueue()
+
+	-- clear the queue
+	self.state.queue = {}
+
+	local reforges = AskMrRobot.itemDiffs.reforges
+
+	-- add all reforges that need updating to the reforge queue
+	for slotNum, badReforge in AskMrRobot.sortSlots(reforges) do
+		self:AddToReforgeQueue(slotNum, badReforge.optimized);
+	end
+end
+
+function AskMrRobot.ReforgesTab:AddToReforgeQueue(itemSlot, reforgeId)
+
+	-- the game's slot id, not the same as the ids that we use on our servers
+	local gameSlot = AskMrRobot.slotIds[itemSlot]
+	
+	local item = GetInventoryItemLink("player", gameSlot)
+	if item == nil then 
+		--print ('no item')
+		return 
+	end
+
+	local current = GetReforgeIdForItem(item)
+
+	if current ~= reforgeId then
+		-- restore first
+		if current ~= 0 and reforgeId ~= 0 then
+			tinsert(self.state.queue, { ["slot"] = gameSlot, ["reforge"] = 0 })
+		end
+
+		-- then reforge to the specified reforge
+		tinsert(self.state.queue, { ["slot"] = gameSlot, ["reforge"] = reforgeId })
+	end
+end
+
+function AskMrRobot.ReforgesTab:IsQueueEmpty()
+	return self.state.queue == nil or #self.state.queue == 0 or self.state.queue == {};
+end
+
+-- returns true if we are waiting on the game to finish a pending async reforge operation
+function AskMrRobot.ReforgesTab:HasPendingOperation()
+
+	-- waiting for an item to be placed into the reforger
+	if self.state.pendingSlot then
+		return true
+	end
+
+	-- waiting for a reforge to be completed
+	if self.state.pendingReforge ~= -1 then
+		return true
+	end
+
+	return false
+end
+
+-- put the next item in the reforge queue into the game's reforge UI
+function AskMrRobot.ReforgesTab:PutNextItemInForge()
+
+	if self:IsQueueEmpty() or self:HasPendingOperation() then
+		return
+	end
+	
+	-- get the first action in the queue
+	local currentAction = self.state.queue[1]
+	local itemSlot = currentAction.slot
+
+	local item = GetInventoryItemLink("player", itemSlot)
+		
+	-- set current item that we are trying to reforge
+	self.state.currentItem = item
+
+	-- if this item isn't already in the reforger, put it in
+	if self.state.currentSlot ~= itemSlot then
+		ClearCursor()                 -- make sure no item is selected
+		SetReforgeFromCursorItem()    -- pick up the old item (calling this with an item already in the reforger will put it back on the mouse cursor)
+		ClearCursor()                 -- clear the cursor to finish removing any current item from the game reforge UI
+		PickupInventoryItem(itemSlot) -- pick up the right equipped item
+
+		-- pending, listen for an event from the game to complete setting this item into the reforger
+		self.state.pendingSlot = itemSlot
+
+		SetReforgeFromCursorItem()    -- put the item into the reforger, and wait for the FORGE_MASTER_SET_ITEM event, which calls DoReforge
+ 	end
+
+end
+
+-- an item is in the reforger, ready to be reforged, so do it
+function AskMrRobot.ReforgesTab:DoReforge()
+
+	if self:IsQueueEmpty() or self:HasPendingOperation() then
+		return
+	end
+
+	local currentAction = self.state.queue[1]
+	local desiredReforge = currentAction.reforge
+
+	-- the index that needs to be provided to WoW's ReforgeItem method, corresponds to one of the options in the game reforge UI
+	local reforgeIndex = -1
+
+	if desiredReforge ~= 0 then
+		local targetFrom = REFORGE_TABLE[desiredReforge - REFORGE_TABLE_BASE][1];
+		local targetTo = REFORGE_TABLE[desiredReforge - REFORGE_TABLE_BASE][2];
+
+		for i=1, GetNumReforgeOptions() do
+			local from, _, _, to, _, _ = GetReforgeOptionInfo(i)
+	 		--print(i .. ': ' .. from .. " -> " .. to)
+			if StatNames[targetFrom] == from and StatNames[targetTo] == to then
+				reforgeIndex = i
+				break
+			end
+		end
+	else
+		reforgeIndex = 0
+	end
+
+	if reforgeIndex == -1 then
+		-- we couldn't do this reforge... we either had a bad reforge (wrong stats on an item), or the game had no options in the UI for some reason
+
+		-- remove the item from the reforge window
+		ClearCursor()
+		SetReforgeFromCursorItem()
+		ClearCursor()
+
+		-- reset state and quit reforging (clears the queue)
+		self:ResetState()
+
+	else
+
+		local currentReforge = GetReforgeIdForItem(self.state.currentItem);
+		if currentReforge == desiredReforge then
+			-- we already have this reforge on the item... probably shouldn't ever happen, but if it does, recalculate and start over
+			tremove(self.state.queue, 1)
+
+			-- remove the item from the reforge window
+			ClearCursor()
+			SetReforgeFromCursorItem()
+			ClearCursor()
+
+			-- update the state of the entire UI, and start again with the next required reforge
+			AskMrRobot_ReforgeFrame:OnUpdate()
+			self:OnReforge()
+
+		else
+			-- we have a reforge (or restore) to do, kick it off and wait for CheckReforge to respond to completion
+			self:TryReforge(reforgeIndex)
+		end
+
+	end
+
+end
+
+-- wraps WoW API call to ReforgeItem, fires a manual timeout in case the UI does not raise an event
+function AskMrRobot.ReforgesTab:TryReforge(reforgeIndex)
+
+	-- we have a reforge (or restore) to do, kick it off and wait for FORGE_MASTER_ITEM_CHANGED, which calls CheckReforge
+	self.state.pendingReforge = reforgeIndex
+	ReforgeItem(reforgeIndex)
+
+	-- sometimes the game doesn't send the FORGE_MASTER_ITEM_CHANGED event, so also check after a delay also
+	AskMrRobot.wait(0.250, AskMrRobot.ReforgesTab.CheckReforge, self)
+
+end
+
+-- check that a requested reforge has been completed
+function AskMrRobot.ReforgesTab:CheckReforge()
+
+	if self:IsQueueEmpty() or self.state.pendingReforge == -1 then
+
+		-- responding to a reforge that the user has manually performed, update the UI and terminate any automatic process that is going on
+		AskMrRobot_ReforgeFrame:OnUpdate()
+		self:ResetState()
+
+	else
+		-- responding to a reforge that we have initiated
+
+		local currentReforge, icon, name, quality, bound, cost = GetReforgeItemInfo();
+		if currentReforge == self.state.pendingReforge then
+			tremove(self.state.queue, 1)
+
+			-- remove the item from the reforge window
+			ClearCursor()
+			SetReforgeFromCursorItem()
+			ClearCursor()
+
+			-- update the state of the entire UI, and start again with the next required reforge
+			AskMrRobot_ReforgeFrame:OnUpdate()
+			self:OnReforge()
+		else
+			-- try again
+			self:TryReforge(self.state.pendingReforge)
+		end
+	end
+
+end
+
+
+--------------------------------------------------------------------
+-- Event Handling
+--------------------------------------------------------------------
+
+-- event called when the Mr. Robot Reforge button is clicked, kicks off automatic reforge
+function AskMrRobot.ReforgesTab:OnReforge()
+
+	-- need to be at a reforger for this to work
+	if not self.isReforgeOpen then
+		StaticPopup_Show("REFORGE_TAB_PLEASE_OPEN")
+		return
+	end
+
+	-- reset state and refresh the queue of reforges that still need to be done
+	self:ResetState()
+	self:RefreshQueue()
+
+	-- get goin, put the first item in the reforger
+	self:PutNextItemInForge()
+end
+
+function AskMrRobot.ReforgesTab:On_FORGE_MASTER_SET_ITEM()
+
+	if self.state.pendingSlot then
+		
+		-- we have successfully finished placing an item into the reforger
+		self.state.currentSlot = self.state.pendingSlot
+
+		-- indicate that we are no longer waiting for an item
+		self.state.pendingSlot = nil
+
+		-- now reforge it
+		self:DoReforge()
+	end 
+
+end
+
+function AskMrRobot.ReforgesTab:On_FORGE_MASTER_ITEM_CHANGED()
+	self:CheckReforge()
+end
+
+function AskMrRobot.ReforgesTab:On_FORGE_MASTER_OPENED()
+	self.isReforgeOpen = true
+end
+
+function AskMrRobot.ReforgesTab:On_FORGE_MASTER_CLOSED()
+	self.isReforgeOpen = false
+end
+
+function AskMrRobot.ReforgesTab:OnEvent(frame, event, ...)
+	--print("EVENT " .. event)
+	local handler = self["On_" .. event]
+	if handler then
+		handler(self, ...)
+	end
+end
\ No newline at end of file