550 lines
14 KiB
Lua
550 lines
14 KiB
Lua
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",
|
|
}
|
|
|
|
---@param setName string
|
|
---@return nil
|
|
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,
|
|
}
|
|
|
|
---@param index number
|
|
---@return nil
|
|
local function ClearActionButton(index)
|
|
if GetActionInfo(index) then
|
|
PickupAction(index)
|
|
ClearCursor()
|
|
end
|
|
end
|
|
|
|
---@param index number
|
|
---@param actionButton {type: string, id: number|string}
|
|
---@return boolean, number?
|
|
local function RestoreActionButton(index, actionButton)
|
|
ClearActionButton(index)
|
|
|
|
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
|
|
|
|
---@param index number
|
|
---@return boolean
|
|
local function ClearActionButton(index)
|
|
if not index then return false end
|
|
PickupAction(index)
|
|
ClearCursor()
|
|
return true
|
|
end
|
|
|
|
---@param actionButton {type: string, id: number|string}
|
|
---@return boolean
|
|
local function IsMacro(actionButton) return actionButton and actionButton.type == "macro" end
|
|
|
|
---@return table<string, number>
|
|
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
|
|
|
|
---@param warnings table<string, number>
|
|
---@param macroName string
|
|
---@param usages number
|
|
---@return nil
|
|
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
|
|
|
|
---@param setName string
|
|
---@return nil
|
|
function SaveActionbarSet(setName)
|
|
if not setName or setName == "" then
|
|
print("Set name cannot be empty")
|
|
return
|
|
end
|
|
|
|
local duplicates = GetMacroDuplicates()
|
|
---@type table<number, {type: string, id: number|string}>
|
|
local set = {}
|
|
---@type string[]
|
|
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
|
|
|
|
---@param setName string
|
|
---@return nil
|
|
function RestoreActionbarSet(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]
|
|
ClearActionButton(i)
|
|
if actionButton then
|
|
if IsMacro(actionButton) and duplicates[actionButton.id] then
|
|
---@cast actionButton {type: string, id: string}
|
|
AddWarning(messages, actionButton.id, duplicates[actionButton.id])
|
|
end
|
|
|
|
local succeeded, restoredID = RestoreActionButton(i, actionButton)
|
|
if not succeeded or not restoredID 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
|
|
else
|
|
ClearActionButton(i)
|
|
end
|
|
end
|
|
|
|
print(string.format("Restored set '%s'", setName))
|
|
for _, warning in ipairs(messages) do
|
|
print(warning)
|
|
end
|
|
end
|
|
|
|
---@param setName string
|
|
---@return nil
|
|
function DeleteActionbarSet(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
|
|
|
|
---@return nil
|
|
function ListActionbarSets()
|
|
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
|
|
|
|
---@param of string
|
|
---@param to string
|
|
---@return nil
|
|
function AliasSpell(of, to)
|
|
if not of or not to then
|
|
print("Must provide args in the format 'spellID aliasID'")
|
|
return
|
|
end
|
|
local ofId = tonumber(of)
|
|
local toId = tonumber(to)
|
|
|
|
if not (ofId and toId) then
|
|
print(string.format("Could not parse %s and/or %s to numbers", of, to))
|
|
return
|
|
end
|
|
|
|
local aliases = ActionBarSaverDaved.spellAliases[ofId] or {}
|
|
|
|
for _, id in ipairs(aliases) do
|
|
if id == toId then
|
|
print(string.format("Spell %d is already aliased by %d", ofId, toId))
|
|
return
|
|
end
|
|
end
|
|
|
|
table.insert(ActionBarSaverDaved.spellAliases[ofId], toId)
|
|
print(string.format("Added %d as an alias for %d", toId, ofId))
|
|
end
|
|
|
|
---@param of string
|
|
---@return nil
|
|
function DeleteSpellAliases(of)
|
|
if not of then
|
|
print("Must provide a valid spellID")
|
|
return
|
|
end
|
|
|
|
local ofId = tonumber(of)
|
|
|
|
if not ofId then
|
|
print(string.format("Could not parse spellID from '%s'", of))
|
|
return
|
|
end
|
|
|
|
if not ActionBarSaverDaved.spellAliases[ofId] then
|
|
print(string.format("No aliases to remove for spell with ID %d", ofId))
|
|
return
|
|
end
|
|
|
|
ActionBarSaverDaved.spellAliases[ofId] = nil
|
|
print(string.format("Removed all aliases for spell with ID %d", ofId))
|
|
end
|
|
|
|
---@return nil
|
|
function ListAliases()
|
|
for spellID, spellAliases in pairs(ActionBarSaverDaved.spellAliases) do
|
|
print(string.format("Spell %d is aliased by: %s", spellID, table.concat(spellAliases, ", ")))
|
|
end
|
|
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 ImportActionbarSet(importingSet, line) end
|
|
end
|
|
importingSet = nil
|
|
end
|
|
end)
|
|
|
|
---@param setName string
|
|
---@return nil
|
|
function ExportActionbarSet(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
|
|
|
|
---@param action {slot: number, id: number|string, type: string}
|
|
---@return string
|
|
function FormatAction(action) return string.format("slot: %d, id: %s, type: %s", action.slot, action.id, action.type) end
|
|
|
|
---@param action string
|
|
---@return {slot: number, id: number|string, type: string}
|
|
function ParseAction(action)
|
|
local ret = {
|
|
slot = 0,
|
|
id = 0,
|
|
type = "",
|
|
}
|
|
|
|
if not action or action == "" then return ret end
|
|
action = strtrim(action)
|
|
---@type string, string, string
|
|
local slot, id, typeChar = string.match(action, "([^\\]+)\\([^\\]+)\\([^\\]+)")
|
|
if not typeChar then
|
|
print(string.format("Unknown action type '%s' for action '%s'", tostring(typeChar), FormatAction(ret)))
|
|
return ret
|
|
end
|
|
local type = inverseTypeMap[typeChar]
|
|
if not type then
|
|
print(string.format("Unknown action type '%s' for action '%s'", tostring(typeChar), FormatAction(ret)))
|
|
return ret
|
|
end
|
|
|
|
local slotNum = tonumber(slot)
|
|
if not slotNum then
|
|
print(string.format("Unknown slot '%s' for action '%s'", tostring(slot), FormatAction(ret)))
|
|
return ret
|
|
end
|
|
|
|
if not id then
|
|
print(string.format("Unknown id '%s' for action '%s'", tostring(id), FormatAction(ret)))
|
|
return ret
|
|
end
|
|
|
|
return {
|
|
slot = slotNum,
|
|
id = id,
|
|
type = type,
|
|
}
|
|
end
|
|
|
|
---@param importString string
|
|
---@return nil
|
|
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
|
|
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))
|
|
end
|
|
|
|
---@param setName string
|
|
---@param str string
|
|
---@return nil
|
|
function ImportActionbarSet(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
|
|
|
|
ActionBarSaverDaved.sets[setName] = set
|
|
print(string.format("Imported set '%s'", setName))
|
|
end
|
|
|
|
---@param setName string
|
|
---@return nil
|
|
function ImportActionbarSetDialogue(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
|
|
|
|
---@return nil
|
|
function PrintActionbarUsage()
|
|
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 SaveActionbarSet(args[2]) end
|
|
if cmd == "restore" then RestoreActionbarSet(args[2]) end
|
|
if cmd == "delete" then DeleteActionbarSet(args[2]) end
|
|
if cmd == "list" then ListActionbarSets() 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 ExportActionbarSet(args[2]) end
|
|
if cmd == "import" then ImportActionbarSetDialogue(args[2]) end
|
|
|
|
if cmd == "" or not cmd then PrintActionbarUsage() end
|
|
end
|
|
SLASH_ABS1 = "/abs"
|