521 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			521 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
local ADDON_LOADED, shared = ...
 | 
						|
 | 
						|
local typeMap = {
 | 
						|
	spell = "S",
 | 
						|
	item = "I",
 | 
						|
	macro = "M",
 | 
						|
	companion = "C",
 | 
						|
	equipmentset = "E",
 | 
						|
	summonmount = "U",
 | 
						|
}
 | 
						|
local inverseTypeMap = {
 | 
						|
	S = "spell",
 | 
						|
	I = "item",
 | 
						|
	M = "macro",
 | 
						|
	C = "companion",
 | 
						|
	E = "equipmentset",
 | 
						|
	U = "summonmount",
 | 
						|
}
 | 
						|
 | 
						|
local function PickupEquipmentSet(setName)
 | 
						|
	local setIndex = 0
 | 
						|
 | 
						|
	for i = 1, C_EquipmentSet.GetNumEquipmentSets() do
 | 
						|
		local sn = C_EquipmentSet.GetEquipmentSetInfo(i)
 | 
						|
 | 
						|
		if sn == setName then setIndex = i end
 | 
						|
	end
 | 
						|
 | 
						|
	C_EquipmentSet.PickupEquipmentSet(setIndex)
 | 
						|
end
 | 
						|
 | 
						|
local pickupActionButton = {
 | 
						|
	item = PickupItem,
 | 
						|
	spell = PickupSpell,
 | 
						|
	macro = PickupMacro,
 | 
						|
	companion = PickupSpell,
 | 
						|
	equipmentset = PickupEquipmentSet,
 | 
						|
	summonmount = C_MountJournal.Pickup,
 | 
						|
}
 | 
						|
 | 
						|
local function RestoreActionButton(self, index, actionButton)
 | 
						|
	if GetActionInfo(index) then
 | 
						|
		PickupAction(index)
 | 
						|
		ClearCursor()
 | 
						|
	end
 | 
						|
 | 
						|
	if not actionButton then return true, nil end
 | 
						|
 | 
						|
	local aliases = ActionBarSaverDaved.spellAliases[actionButton.id] or {}
 | 
						|
	table.insert(aliases, actionButton.id)
 | 
						|
 | 
						|
	for _, id in ipairs(aliases) do
 | 
						|
		pickupActionButton[actionButton.type](id)
 | 
						|
 | 
						|
		if GetCursorInfo() == actionButton.type then
 | 
						|
			PlaceAction(index)
 | 
						|
			return true, id
 | 
						|
		end
 | 
						|
 | 
						|
		ClearCursor()
 | 
						|
	end
 | 
						|
 | 
						|
	return false
 | 
						|
end
 | 
						|
 | 
						|
local function IsMacro(actionButton) return actionButton and actionButton.type == "macro" end
 | 
						|
 | 
						|
local function GetMacroDuplicates()
 | 
						|
	local t = {}
 | 
						|
	local duplicates = {}
 | 
						|
 | 
						|
	for i = 1, MAX_MACROS do
 | 
						|
		local macroName = GetMacroInfo(i)
 | 
						|
 | 
						|
		if macroName then
 | 
						|
			if not t[macroName] then
 | 
						|
				t[macroName] = 1
 | 
						|
			else
 | 
						|
				t[macroName] = t[macroName] + 1
 | 
						|
				duplicates[macroName] = t[macroName]
 | 
						|
			end
 | 
						|
		end
 | 
						|
	end
 | 
						|
 | 
						|
	return duplicates
 | 
						|
end
 | 
						|
 | 
						|
local function AddWarning(warnings, macroName, usages)
 | 
						|
	table.insert(
 | 
						|
		warnings,
 | 
						|
		string.format("Warning: Found %d macros named '%s'. Consider renaming them to avoid issues", usages, macroName)
 | 
						|
	)
 | 
						|
end
 | 
						|
 | 
						|
function SaveSet(setName)
 | 
						|
	if not setName or setName == "" then
 | 
						|
		print("Set name cannot be empty")
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	local duplicates = GetMacroDuplicates()
 | 
						|
	local set = {}
 | 
						|
	local warnings = {}
 | 
						|
 | 
						|
	for i = 1, MAX_ACTION_BUTTONS do
 | 
						|
		local type, id = GetActionInfo(i)
 | 
						|
 | 
						|
		if type == "macro" then
 | 
						|
			-- use macro name as the ID
 | 
						|
			id = GetMacroInfo(id)
 | 
						|
			if duplicates[id] then AddWarning(warnings, id, duplicates[id]) end
 | 
						|
		end
 | 
						|
 | 
						|
		if type and id then set[i] = type and {
 | 
						|
			type = type,
 | 
						|
			id = id,
 | 
						|
		} end
 | 
						|
	end
 | 
						|
 | 
						|
	ActionBarSaverDaved.sets[setName] = set
 | 
						|
	print(string.format("Saved set '%s'!", setName))
 | 
						|
	for _, warning in ipairs(warnings) do
 | 
						|
		print(warning)
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
function RestoreSet(setName)
 | 
						|
	if not setName or setName == "" then
 | 
						|
		print("Set name cannot be empty")
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	local set = ActionBarSaverDaved.sets[setName]
 | 
						|
 | 
						|
	if not set then
 | 
						|
		print(string.format("No set with the name '%s' exists", setName))
 | 
						|
		return
 | 
						|
	end
 | 
						|
	if InCombatLockdown() then
 | 
						|
		print("Cannot restore sets while in combat")
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	local duplicates = GetMacroDuplicates()
 | 
						|
	local messages = {}
 | 
						|
 | 
						|
	-- Start with an empty cursor
 | 
						|
	ClearCursor()
 | 
						|
 | 
						|
	for i = 1, MAX_ACTION_BUTTONS do
 | 
						|
		local actionButton = set[i]
 | 
						|
 | 
						|
		if IsMacro(actionButton) and duplicates[actionButton.id] then
 | 
						|
			AddWarning(messages, actionButton.id, duplicates[actionButton.id])
 | 
						|
		end
 | 
						|
 | 
						|
		local succeeded, restoredID = RestoreActionButton(self, i, actionButton)
 | 
						|
		if not succeeded then
 | 
						|
			table.insert(
 | 
						|
				messages,
 | 
						|
				string.format(
 | 
						|
					"Error: Unable to restore %s with id [%s] to slot %d",
 | 
						|
					actionButton.type,
 | 
						|
					actionButton.id or "",
 | 
						|
					i
 | 
						|
				)
 | 
						|
			)
 | 
						|
		elseif actionButton and restoredID ~= actionButton.id then
 | 
						|
			table.insert(
 | 
						|
				messages,
 | 
						|
				string.format(
 | 
						|
					"Info: Restored spell %d (%s) in place of spell %d",
 | 
						|
					restoredID,
 | 
						|
					GetSpellInfo(restoredID),
 | 
						|
					actionButton.id
 | 
						|
				)
 | 
						|
			)
 | 
						|
		end
 | 
						|
	end
 | 
						|
 | 
						|
	print(string.format("Restored set '%s'", setName))
 | 
						|
	for _, warning in ipairs(messages) do
 | 
						|
		print(warning)
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
function DeleteSet(setName)
 | 
						|
	if not setName or setName == "" then
 | 
						|
		print("Set name cannot be empty")
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	if not ActionBarSaverDaved.sets[setName] then
 | 
						|
		print(string.format("No set with the name '%s' exists", setName))
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	ActionBarSaverDaved.sets[setName] = nil
 | 
						|
 | 
						|
	print(string.format("Deleted set '%s'", setName))
 | 
						|
end
 | 
						|
 | 
						|
function ListSets()
 | 
						|
	local sets = {}
 | 
						|
	for setName, foo in pairs(ActionBarSaverDaved.sets) do
 | 
						|
		sets[#sets + 1] = setName
 | 
						|
	end
 | 
						|
	table.sort(sets)
 | 
						|
	local setsStr = table.concat(sets, ", ")
 | 
						|
 | 
						|
	print(not (not setsStr or setsStr == "") and setsStr or "No sets found")
 | 
						|
end
 | 
						|
 | 
						|
function AliasSpell(args)
 | 
						|
	if not args or args == "" then
 | 
						|
		print("Must provide args in the format 'spellID aliasID'")
 | 
						|
		return
 | 
						|
	end
 | 
						|
	local spellID, aliasID = string.match(args, "(%d+)%s+(%d+)")
 | 
						|
 | 
						|
	spellID = tonumber(spellID)
 | 
						|
	aliasID = tonumber(aliasID)
 | 
						|
 | 
						|
	if not (spellID and aliasID) then
 | 
						|
		print(string.format("Could not parse spellID and aliasID from '%s'", args))
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	local aliases = ActionBarSaverDaved.spellAliases[spellID] or {}
 | 
						|
 | 
						|
	for _, id in ipairs(aliases) do
 | 
						|
		if id == aliasID then
 | 
						|
			print(string.format("Spell %d is already aliased by %d", spellID, aliasID))
 | 
						|
			return
 | 
						|
		end
 | 
						|
	end
 | 
						|
 | 
						|
	table.insert(ActionBarSaverDaved.spellAliases[spellID], aliasID)
 | 
						|
	print(string.format("Added %d as an alias for %d", aliasID, spellID))
 | 
						|
end
 | 
						|
 | 
						|
function DeleteSpellAliases(spellID)
 | 
						|
	if not spellID or spellID == "" then
 | 
						|
		print("Must provide a valid spellID")
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	spellID = tonumber(spellID)
 | 
						|
 | 
						|
	if not ActionBarSaverDaved.spellAliases[spellID] then
 | 
						|
		print(string.format("No aliases to remove for spell with ID %d", spellID))
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	ActionBarSaverDaved.spellAliases[spellID] = nil
 | 
						|
 | 
						|
	print(string.format("Removed all aliases for spell with ID %d", spellID))
 | 
						|
end
 | 
						|
 | 
						|
function ListAliases()
 | 
						|
	local aliases = ActionBarSaverDaved.spellAliases
 | 
						|
 | 
						|
	if Dict.isEmpty(aliases) then
 | 
						|
		print("No aliases found")
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	Dict.iter(
 | 
						|
		ActionBarSaverDaved.spellAliases,
 | 
						|
		function(spellID, aliases)
 | 
						|
			print(string.format("Spell %d is aliased by: %s", spellID, table.concat(aliases, ", ")))
 | 
						|
		end
 | 
						|
	)
 | 
						|
end
 | 
						|
 | 
						|
---@param text string
 | 
						|
---@param size number
 | 
						|
---@param deliminer string
 | 
						|
---@return string[]
 | 
						|
local function Partition(text, size, deliminer)
 | 
						|
	local words = {}
 | 
						|
	for word in text:gmatch("[^" .. deliminer .. "]+") do
 | 
						|
		words[#words + 1] = word
 | 
						|
	end
 | 
						|
 | 
						|
	local ret = {}
 | 
						|
	local currentChunk = ""
 | 
						|
 | 
						|
	for _, word in ipairs(words) do
 | 
						|
		if #currentChunk + #word + 1 <= size then
 | 
						|
			currentChunk = currentChunk .. deliminer .. word
 | 
						|
		else
 | 
						|
			if #currentChunk > 0 then ret[#ret + 1] = currentChunk end
 | 
						|
			currentChunk = word
 | 
						|
		end
 | 
						|
	end
 | 
						|
 | 
						|
	if #currentChunk > 0 then ret[#ret + 1] = currentChunk end
 | 
						|
 | 
						|
	return ret
 | 
						|
end
 | 
						|
 | 
						|
local importingSet = nil
 | 
						|
local importExportFrame = CreateFrame("Frame", "ABSImportExportFrame", UIParent)
 | 
						|
importExportFrame:SetSize(512, 512)
 | 
						|
importExportFrame:SetPoint("CENTER")
 | 
						|
importExportFrame:SetFrameStrata("HIGH")
 | 
						|
importExportFrame:EnableMouse(true)
 | 
						|
importExportFrame:SetMovable(true)
 | 
						|
importExportFrame:SetResizable(false)
 | 
						|
importExportFrame:SetBackdrop({
 | 
						|
	bgFile = "Interface/Tooltips/UI-Tooltip-Background",
 | 
						|
	edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
 | 
						|
	tile = true,
 | 
						|
	tileSize = 4,
 | 
						|
	edgeSize = 4,
 | 
						|
	insets = {
 | 
						|
		left = 4,
 | 
						|
		right = 4,
 | 
						|
		top = 4,
 | 
						|
		bottom = 4,
 | 
						|
	},
 | 
						|
})
 | 
						|
importExportFrame:SetBackdropColor(0, 0, 0, 0.8)
 | 
						|
importExportFrame:SetBackdropBorderColor(0.5, 0.5, 0.5, 1)
 | 
						|
 | 
						|
importExportFrame:SetMovable(true)
 | 
						|
importExportFrame:EnableMouse(true)
 | 
						|
importExportFrame:RegisterForDrag("LeftButton")
 | 
						|
importExportFrame:SetScript("OnDragStart", function(self) self:StartMoving() end)
 | 
						|
importExportFrame:SetScript("OnDragStop", function(self) self:StopMovingOrSizing() end)
 | 
						|
importExportFrame:SetScript("OnShow", function(self) self:SetScale(1) end)
 | 
						|
importExportFrame:Hide()
 | 
						|
 | 
						|
local importExportFrameTextBox = CreateFrame("EditBox", "ABSImportExportFrameTextBox", importExportFrame)
 | 
						|
importExportFrameTextBox:SetSize(512, 512)
 | 
						|
importExportFrameTextBox:SetPoint("TOPLEFT", importExportFrame, "TOPLEFT", 0, 0)
 | 
						|
importExportFrameTextBox:SetFont("Fonts\\FRIZQT__.ttf", 12)
 | 
						|
importExportFrameTextBox:SetTextColor(1, 1, 1, 1)
 | 
						|
importExportFrameTextBox:SetTextInsets(20, 20, 20, 20)
 | 
						|
importExportFrameTextBox:SetMultiLine(true)
 | 
						|
importExportFrameTextBox:SetAutoFocus(true)
 | 
						|
importExportFrameTextBox:SetMaxLetters(1000000)
 | 
						|
importExportFrameTextBox:SetScript("OnEscapePressed", function(self)
 | 
						|
	importExportFrame:Hide()
 | 
						|
	if importingSet then
 | 
						|
		local lines = { strsplit("\n", self:GetText()) }
 | 
						|
		for _, line in ipairs(lines) do
 | 
						|
			line = strtrim(line)
 | 
						|
			if line ~= "" then ImportSet(importingSet, line) end
 | 
						|
		end
 | 
						|
		importingSet = nil
 | 
						|
	end
 | 
						|
end)
 | 
						|
 | 
						|
function ExportSet(setName)
 | 
						|
	local set = ActionBarSaverDaved.sets[setName]
 | 
						|
	if not set then
 | 
						|
		print(string.format("No set with the name '%s' exists", setName))
 | 
						|
		return
 | 
						|
	end
 | 
						|
	local macros = {}
 | 
						|
	local stringified = {}
 | 
						|
	for slot, action in pairs(set) do
 | 
						|
		local typeChar = typeMap[action.type]
 | 
						|
		if not typeChar then
 | 
						|
			print(string.format("Unknown action type '%s' in set '%s'", action.type, setName))
 | 
						|
			return
 | 
						|
		end
 | 
						|
		stringified[#stringified + 1] =
 | 
						|
			string.format("%s\\%s\\%s", tostring(slot), tostring(action.id), tostring(typeChar))
 | 
						|
		if typeChar == "M" then
 | 
						|
			local _, _, macro = GetMacroInfo(action.id)
 | 
						|
			if macro then macros[action.id] = macro end
 | 
						|
		end
 | 
						|
	end
 | 
						|
 | 
						|
	local export = {}
 | 
						|
	for name, macro in pairs(macros) do
 | 
						|
		local content = B64.Encode(macro)
 | 
						|
		export[#export + 1] = string.format("Mž%sž%s", name, content)
 | 
						|
	end
 | 
						|
	export[#export + 1] = table.concat(stringified, "ž")
 | 
						|
	local str = table.concat(export, "\n")
 | 
						|
	importExportFrame:Show()
 | 
						|
	importExportFrameTextBox:SetText(str)
 | 
						|
	importExportFrameTextBox:SetFocus()
 | 
						|
end
 | 
						|
 | 
						|
function ParseAction(action)
 | 
						|
	if not action or action == "" then return nil end
 | 
						|
	action = strtrim(action)
 | 
						|
	local slot, id, typeChar = string.match(action, "([^\\]+)\\([^\\]+)\\([^\\]+)")
 | 
						|
	if not typeChar then
 | 
						|
		print(string.format("Unknown action type '%s' in set '%s'", tostring(typeChar), tostring(setName)))
 | 
						|
		return
 | 
						|
	end
 | 
						|
	local type = inverseTypeMap[typeChar]
 | 
						|
	if not type then
 | 
						|
		print(string.format("Unknown action type '%s' in set '%s'", tostring(typeChar), tostring(setName)))
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	slot = tonumber(slot)
 | 
						|
 | 
						|
	if not slot then
 | 
						|
		print(string.format("Unknown slot '%s' in set '%s'", tostring(slot), tostring(setName)))
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	if not id then
 | 
						|
		print(string.format("Unknown id '%s' in set '%s'", tostring(id), tostring(setName)))
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	return {
 | 
						|
		slot = slot,
 | 
						|
		id = id,
 | 
						|
		type = type,
 | 
						|
	}
 | 
						|
end
 | 
						|
 | 
						|
function ImportMacro(importString)
 | 
						|
	if not importString or importString == "" then
 | 
						|
		print("Must provide a valid macro string")
 | 
						|
		return
 | 
						|
	end
 | 
						|
	importString = strtrim(importString)
 | 
						|
	local name, content = string.match(importString, "^Mž([^ž]+)ž([^ž]+)")
 | 
						|
	if not name or not content then
 | 
						|
		print("Error: Invalid macro part format")
 | 
						|
		return
 | 
						|
	end
 | 
						|
	content = strtrim(content)
 | 
						|
	name = strtrim(name)
 | 
						|
 | 
						|
	local reconstructed = B64.Decode(content)
 | 
						|
	local macroIdx = GetMacroIndexByName(name)
 | 
						|
	if macroIdx == 0 then
 | 
						|
		macroIdx = CreateMacro(name, "Inv_misc_questionmark", "")
 | 
						|
		macroIdx = GetMacroIndexByName(name)
 | 
						|
	end
 | 
						|
	EditMacro(macroIdx, name, nil, reconstructed)
 | 
						|
	print(string.format("Imported macro '%s' with index %d and content '%s'", name, macroIdx, reconstructed))
 | 
						|
	return
 | 
						|
end
 | 
						|
 | 
						|
function ImportSet(setName, str)
 | 
						|
	if not setName or setName == "" then
 | 
						|
		print("Must provide a valid set name")
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	if string.find(str, "^Mž") then
 | 
						|
		ImportMacro(str)
 | 
						|
		return
 | 
						|
	end
 | 
						|
	local set = ActionBarSaverDaved.sets[setName] or {}
 | 
						|
	--    if set then
 | 
						|
	--        print(string.format("Set '%s' already exists", setName))
 | 
						|
	--        return
 | 
						|
	--    end
 | 
						|
 | 
						|
	str = strtrim(str)
 | 
						|
	local data = { strsplit("ž", str) }
 | 
						|
	for _, action in ipairs(data) do
 | 
						|
		local paction = ParseAction(action)
 | 
						|
		if paction then set[paction.slot] = {
 | 
						|
			type = paction.type,
 | 
						|
			id = paction.id,
 | 
						|
		} end
 | 
						|
	end
 | 
						|
 | 
						|
	-- /dump ActionBarSaverDaved.sets["havoc"]
 | 
						|
	-- /dump ActionBarSaverDaved.sets["havoc2"]
 | 
						|
	ActionBarSaverDaved.sets[setName] = set
 | 
						|
	print(string.format("Imported set '%s'", setName))
 | 
						|
end
 | 
						|
 | 
						|
function ImportSetDialogue(setName)
 | 
						|
	if not setName or setName == "" then
 | 
						|
		print("Must provide a valid set name")
 | 
						|
		return
 | 
						|
	end
 | 
						|
	importingSet = setName
 | 
						|
	importExportFrameTextBox:SetText("")
 | 
						|
	importExportFrame:Show()
 | 
						|
	importExportFrameTextBox:SetFocus()
 | 
						|
end
 | 
						|
 | 
						|
function PrintUsage()
 | 
						|
	print("ABS Slash commands")
 | 
						|
	print("/abs save <set> - Saves your current action bar setup under the given <set>")
 | 
						|
	print("/abs restore <set> - Restores the saved <set>")
 | 
						|
	print("/abs delete <set> - Deletes the saved <set>")
 | 
						|
	print("/abs list - Lists all saved sets")
 | 
						|
	print("/abs alias <spellID> <aliasID> - Adds an alias with <aliasID> to <spellID>")
 | 
						|
	print("/abs unalias <spellID> - Removes all aliases associated with <spellID>")
 | 
						|
	print("/abs aliases - List all spell aliases")
 | 
						|
	print("/abs export <set> - Brings up a dialog to export the given <set>")
 | 
						|
	print("/abs import <set> - Brings up a dialog to import the given <set>")
 | 
						|
end
 | 
						|
 | 
						|
SlashCmdList["ABS"] = function(argv)
 | 
						|
	local args = { strsplit(" ", argv) }
 | 
						|
	local cmd = args[1]
 | 
						|
 | 
						|
	if cmd == "save" then SaveSet(args[2]) end
 | 
						|
	if cmd == "restore" then RestoreSet(args[2]) end
 | 
						|
	if cmd == "delete" then DeleteSet(args[2]) end
 | 
						|
	if cmd == "list" then ListSets() end
 | 
						|
	if cmd == "alias" then AliasSpell(args[2], args[3]) end
 | 
						|
	if cmd == "unalias" then DeleteSpellAliases(args[2]) end
 | 
						|
	if cmd == "aliases" then ListAliases() end
 | 
						|
	if cmd == "export" then ExportSet(args[2]) end
 | 
						|
	if cmd == "import" then ImportSetDialogue(args[2]) end
 | 
						|
 | 
						|
	if cmd == "" or not cmd then PrintUsage() end
 | 
						|
end
 | 
						|
SLASH_ABS1 = "/abs"
 |