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 } local function RestoreActionButton(self, index, actionButton) if GetActionInfo(index) then PickupAction(index) ClearCursor() end if not actionButton then return true, nil end local aliases = ActionBarSaverReloaded.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 ActionBarSaverReloaded.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 = ActionBarSaverReloaded.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 ActionBarSaverReloaded.sets[setName] then print(string.format("No set with the name '%s' exists", setName)) return end ActionBarSaverReloaded.sets[setName] = nil print(string.format("Deleted set '%s'", setName)) end function ListSets() local sets = {} for setName, foo in pairs(ActionBarSaverReloaded.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 = ActionBarSaverReloaded.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(ActionBarSaverReloaded.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 ActionBarSaverReloaded.spellAliases[spellID] then print(string.format("No aliases to remove for spell with ID %d", spellID)) return end ActionBarSaverReloaded.spellAliases[spellID] = nil print(string.format("Removed all aliases for spell with ID %d", spellID)) end function ListAliases() local aliases = ActionBarSaverReloaded.spellAliases if Dict.isEmpty(aliases) then print("No aliases found") return end Dict.iter(ActionBarSaverReloaded.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 function ExportSet(setName) local set = ActionBarSaverReloaded.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 for name, macro in pairs(macros) do local content = B64.Encode(macro) local cpartitions = Partition(content, 160, "|") for partitionIndex, partition in ipairs(cpartitions) do print(string.format("M|%s|%d|%s", name, partitionIndex, partition)) end end local str = table.concat(stringified, "|") local partitions = Partition(str, 200, "|") for _, partition in ipairs(partitions) do print(partition) end end local 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 ImportSet(setName, str) if not setName or setName == "" then print("Must provide a valid set name") return end local set = ActionBarSaverReloaded.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 ActionBarSaverReloaded.sets["havoc"] -- /dump ActionBarSaverReloaded.sets["havoc2"] ActionBarSaverReloaded.sets[setName] = set print(string.format("Imported set '%s'", setName)) end function PrintUsage() print("ABS Slash commands") print("/abs save - Saves your current action bar setup under the given ") print("/abs restore - Restores the saved ") print("/abs delete - Deletes the saved ") print("/abs list - Lists all saved sets") print("/abs alias - Adds an alias with to ") print("/abs unalias - Removes all aliases associated with ") print("/abs aliases - List all spell aliases") print("/abs export - Export set") print("/abs import - Import set from exported string") 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 ImportSet(args[2], args[3]) end if cmd == "" or not cmd then PrintUsage() end end SLASH_ABS1 = "/abs"