Quantcast
--[[--------------------------------------------------------------------
    Copyright (C) 2012 Sidoine De Wispelaere.
    Copyright (C) 2012, 2013, 2014 Johnny C. Lam.
    See the file LICENSE.txt for copying permission.
--]]--------------------------------------------------------------------

-- Keep data about the player action bars (key bindings mostly)
local OVALE, Ovale = ...
local OvaleActionBar = Ovale:NewModule("OvaleActionBar", "AceEvent-3.0")
Ovale.OvaleActionBar = OvaleActionBar

--<private-static-properties>
local L = Ovale.L
local OvaleDebug = Ovale.OvaleDebug
local OvaleProfiler = Ovale.OvaleProfiler

local gsub = string.gsub
local strlen = string.len
local strmatch = string.match
local strupper = string.upper
local tonumber = tonumber
local wipe = wipe
local API_GetActionInfo = GetActionInfo
local API_GetActionText = GetActionText
local API_GetBindingKey = GetBindingKey
local API_GetBonusBarIndex = GetBonusBarIndex
local API_GetMacroItem = GetMacroItem
local API_GetMacroSpell = GetMacroSpell

-- Register for debugging messages.
OvaleDebug:RegisterDebugging(OvaleActionBar)
-- Register for profiling.
OvaleProfiler:RegisterProfiling(OvaleActionBar)
--</private-static-properties>

--<public-static-properties>
-- Maps each action slot (1..120) to the current action: action[slot] = action
OvaleActionBar.action = {}
-- Maps each action slot (1..120) to its current keybind: keybind[slot] = keybind
OvaleActionBar.keybind = {}

-- Maps each spell/macro/item ID to its current action slot.
-- spell[spellId] = slot
OvaleActionBar.spell = {}
-- macro[macroName] = slot
OvaleActionBar.macro = {}
-- item[itemId] = slot
OvaleActionBar.item = {}
--</public-static-properties>

--<private-static-methods>
local function GetKeyBinding(slot)
	--[[
		ACTIONBUTTON1..12			=> primary (1..12, 13..24), bonus (73..120)
		MULTIACTIONBAR1BUTTON1..12	=> bottom left (61..72)
		MULTIACTIONBAR2BUTTON1..12	=> bottom right (49..60)
		MULTIACTIONBAR3BUTTON1..12	=> top right (25..36)
		MULTIACTIONBAR4BUTTON1..12	=> top left (37..48)
	--]]
	local name
	if slot <= 24 or slot > 72 then
		name = "ACTIONBUTTON" .. (((slot - 1)%12) + 1)
	elseif slot <= 36 then
		name = "MULTIACTIONBAR3BUTTON" .. (slot - 24)
	elseif slot <= 48 then
		name = "MULTIACTIONBAR4BUTTON" .. (slot - 36)
	elseif slot <= 60 then
		name = "MULTIACTIONBAR2BUTTON" .. (slot - 48)
	else
		name = "MULTIACTIONBAR1BUTTON" .. (slot - 60)
	end
	local key = name and API_GetBindingKey(name)
	-- Shorten the keybinding names.
	if key and strlen(key) > 4 then
		key = strupper(key)
		-- Strip whitespace.
		key = gsub(key, "%s+", "")
		-- Convert modifiers to a single character.
		key = gsub(key, "ALT-", "A")
		key = gsub(key, "CTRL-", "C")
		key = gsub(key, "SHIFT-", "S")
		-- Shorten numberpad keybinding names.
		key = gsub(key, "NUMPAD", "N")
		key = gsub(key, "PLUS", "+")
		key = gsub(key, "MINUS", "-")
		key = gsub(key, "MULTIPLY", "*")
		key = gsub(key, "DIVIDE", "/")
	end
	return key
end

local function ParseHyperlink(hyperlink)
	local color, linkType, linkData, text = strmatch(hyperlink, "|?c?f?f?(%x*)|?H?([^:]*):?(%d+)|?h?%[?([^%[%]]*)%]?|?h?|?r?")
	return color, linkType, linkData, text
end
--</private-static-methods>

--<public-static-methods>
function OvaleActionBar:OnEnable()
	self:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
	self:RegisterEvent("PLAYER_ENTERING_WORLD", "UpdateActionSlots")
	self:RegisterEvent("UPDATE_BINDINGS")
	self:RegisterEvent("UPDATE_BONUS_ACTIONBAR", "UpdateActionSlots")
	self:RegisterMessage("Ovale_StanceChanged", "UpdateActionSlots")
	self:RegisterMessage("Ovale_TalentsChanged", "UpdateActionSlots")
end

function OvaleActionBar:OnDisable()
	self:UnregisterEvent("ACTIONBAR_SLOT_CHANGED")
	self:UnregisterEvent("PLAYER_ENTERING_WORLD")
	self:UnregisterEvent("UPDATE_BINDINGS")
	self:UnregisterEvent("UPDATE_BONUS_ACTIONBAR")
	self:UnregisterMessage("Ovale_StanceChanged")
	self:UnregisterMessage("Ovale_TalentsChanged")
end

function OvaleActionBar:ACTIONBAR_SLOT_CHANGED(event, slot)
	slot = tonumber(slot)
	if slot == 0 then
		self:UpdateActionSlots(event)
	elseif slot then
		self:UpdateActionSlot(slot)
	end
end

function OvaleActionBar:UPDATE_BINDINGS(event)
	self:Debug("%s: Updating key bindings.", event)
	self:UpdateKeyBindings()
end

function OvaleActionBar:UpdateActionSlots(event)
	self:StartProfiling("OvaleActionBar_UpdateActionSlots")
	self:Debug("%s: Updating all action slot mappings.", event)
	wipe(self.action)
	wipe(self.item)
	wipe(self.macro)
	wipe(self.spell)

	local start = 1
	local bonus = tonumber(API_GetBonusBarIndex()) * 12
	if bonus > 0 then
		start = 13
		for slot = bonus - 11, bonus do
			self:UpdateActionSlot(slot)
		end
	end
	for slot = start, 72 do
		self:UpdateActionSlot(slot)
	end
	self:StopProfiling("OvaleActionBar_UpdateActionSlots")
end

function OvaleActionBar:UpdateActionSlot(slot)
	self:StartProfiling("OvaleActionBar_UpdateActionSlot")
	-- Clear old slot and associated actions.
	local action = self.action[slot]
	if self.spell[action] == slot then
		self.spell[action] = nil
	elseif self.item[action] == slot then
		self.item[action] = nil
	elseif self.macro[action] == slot then
		self.macro[action] = nil
	end
	self.action[slot] = nil

	-- Map the current action in the slot.
	local actionType, id, subType = API_GetActionInfo(slot)
	if actionType == "spell" then
		id = tonumber(id)
		if id then
			if not self.spell[id] or slot < self.spell[id] then
				self.spell[id] = slot
			end
			self.action[slot] = id
		end
	elseif actionType == "item" then
		id = tonumber(id)
		if id then
			if not self.item[id] or slot < self.item[id] then
				self.item[id] = slot
			end
			self.action[slot] = id
		end
	elseif actionType == "macro" then
		id = tonumber(id)
		if id then
			local actionText = API_GetActionText(slot)
			if actionText then
				if not self.macro[actionText] or slot < self.macro[actionText] then
					self.macro[actionText] = slot
				end
				local _, _, spellId = API_GetMacroSpell(id)
				if spellId then
					if not self.spell[spellId] or slot < self.spell[spellId] then
						self.spell[spellId] = slot
					end
					self.action[slot] = spellId
				else
					local _, hyperlink = API_GetMacroItem(id)
					if hyperlink then
						local _, _, linkData = ParseHyperlink(hyperlink)
						local itemId = gsub(linkData, ":.*", "")
						itemId = tonumber(itemId)
						if itemId then
							if not self.item[itemId] or slot < self.item[itemId] then
								self.item[itemId] = slot
							end
							self.action[slot] = itemId
						end
					end
				end
				if not self.action[slot] then
					self.action[slot] = actionText
				end
			end
		end
	end
	if self.action[slot] then
		self:Debug("Mapping button %s to %s.", slot, self.action[slot])
	else
		self:Debug("Clearing mapping for button %s.", slot)
	end

	-- Update the keybind for the slot.
	self.keybind[slot] = GetKeyBinding(slot)
	self:StopProfiling("OvaleActionBar_UpdateActionSlot")
end

function OvaleActionBar:UpdateKeyBindings()
	self:StartProfiling("OvaleActionBar_UpdateKeyBindings")
	for slot = 1, 120 do
		self.keybind[slot] = GetKeyBinding(slot)
	end
	self:StopProfiling("OvaleActionBar_UpdateKeyBindings")
end

-- Get the action slot that matches a spell ID.
function OvaleActionBar:GetForSpell(spellId)
	return self.spell[spellId]
end

-- Get the action slot that matches a macro name.
function OvaleActionBar:GetForMacro(macroName)
	return self.macro[macroName]
end

-- Get the action slot that matches an item ID.
function OvaleActionBar:GetForItem(itemId)
	return self.item[itemId]
end

-- Get the keybinding for an action slot.
function OvaleActionBar:GetBinding(slot)
	return self.keybind[slot]
end
--</public-static-methods>