Initial commit
This commit is contained in:
12
ActionBarSaverReloaded.toc
Normal file
12
ActionBarSaverReloaded.toc
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
## Interface: 40400
|
||||||
|
## Title: ActionBarSaver: Reloaded
|
||||||
|
## Version: 1.0.7
|
||||||
|
## Notes: Manage, save, and restore action bar profiles
|
||||||
|
## Author: Voodoomoose
|
||||||
|
## SavedVariables: ActionBarSaverReloaded
|
||||||
|
|
||||||
|
Constants.lua
|
||||||
|
StringHelpers.lua
|
||||||
|
TableHelpers.lua
|
||||||
|
Main.lua
|
||||||
|
Actions.lua
|
277
Actions.lua
Normal file
277
Actions.lua
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
local ADDON_LOADED, shared = ...
|
||||||
|
|
||||||
|
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)
|
||||||
|
-- Clear the slot
|
||||||
|
if GetActionInfo(index) then
|
||||||
|
PickupAction(index)
|
||||||
|
ClearCursor()
|
||||||
|
end
|
||||||
|
|
||||||
|
if not actionButton then return true, nil end
|
||||||
|
|
||||||
|
local aliases = self.db.class.spellAliases[actionButton.id] or {}
|
||||||
|
local ids = Array.insert(aliases, actionButton.id, 1)
|
||||||
|
|
||||||
|
for _, id in ipairs(ids) 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 Str.nullOrEmpty(setName) then
|
||||||
|
self: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
|
||||||
|
|
||||||
|
self.db.class.sets[setName] = set
|
||||||
|
self:Print(string.format("Saved set '%s'!", setName))
|
||||||
|
Array.iter(warnings, function(warning) self:Print(warning) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function RestoreSet(setName)
|
||||||
|
if Str.nullOrEmpty(setName) then
|
||||||
|
self:Print("Set name cannot be empty")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local set = self.db.class.sets[setName]
|
||||||
|
|
||||||
|
if not set then
|
||||||
|
self:Print(string.format("No set with the name '%s' exists", setName))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if InCombatLockdown() then
|
||||||
|
self: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
|
||||||
|
|
||||||
|
self:Print(string.format("Restored set '%s'", setName))
|
||||||
|
Array.iter(messages, function(warning) self:Print(warning) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function DeleteSet(setName)
|
||||||
|
if Str.nullOrEmpty(setName) then
|
||||||
|
self:Print("Set name cannot be empty")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not self.db.class.sets[setName] then
|
||||||
|
self:Print(string.format("No set with the name '%s' exists", setName))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
self.db.class.sets[setName] = nil
|
||||||
|
|
||||||
|
self:Print(string.format("Deleted set '%s'", setName))
|
||||||
|
end
|
||||||
|
|
||||||
|
function ListSets()
|
||||||
|
local sets = Dict.keysAsArray(self.db.class.sets)
|
||||||
|
table.sort(sets)
|
||||||
|
local setsStr = table.concat(sets, ", ")
|
||||||
|
|
||||||
|
self:Print(not Str.nullOrEmpty(setsStr) and setsStr or "No sets found")
|
||||||
|
end
|
||||||
|
|
||||||
|
function AliasSpell(args)
|
||||||
|
if Str.nullOrEmpty(args) then
|
||||||
|
self: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
|
||||||
|
self:Print(string.format("Could not parse spellID and aliasID from '%s'", args))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local aliases = self.db.class.spellAliases[spellID] or {}
|
||||||
|
|
||||||
|
if Array.contains(aliases, aliasID) then
|
||||||
|
self:Print(string.format("Spell %d is already aliased by %d", spellID, aliasID))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(aliases, aliasID)
|
||||||
|
self.db.class.spellAliases[spellID] = aliases
|
||||||
|
|
||||||
|
self:Print(string.format("Added %d as an alias for %d", aliasID, spellID))
|
||||||
|
end
|
||||||
|
|
||||||
|
function DeleteSpellAliases(spellID)
|
||||||
|
if Str.nullOrEmpty(spellID) then
|
||||||
|
self:Print("Must provide a valid spellID")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
spellID = tonumber(spellID)
|
||||||
|
|
||||||
|
if not self.db.class.spellAliases[spellID] then
|
||||||
|
self:Print(string.format("No aliases to remove for spell with ID %d", spellID))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
self.db.class.spellAliases[spellID] = nil
|
||||||
|
|
||||||
|
self:Print(string.format("Removed all aliases for spell with ID %d", spellID))
|
||||||
|
end
|
||||||
|
|
||||||
|
function ListAliases()
|
||||||
|
local aliases = self.db.class.spellAliases
|
||||||
|
|
||||||
|
if Dict.isEmpty(aliases) then
|
||||||
|
self:Print("No aliases found")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Dict.iter(self.db.class.spellAliases, function(spellID, aliases)
|
||||||
|
self:Print(string.format("Spell %d is aliased by: %s", spellID, table.concat(aliases, ", ")))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function PrintUsage()
|
||||||
|
self:Print("ABS Slash commands")
|
||||||
|
self:Print("/abs save <set> - Saves your current action bar setup under the given <set>")
|
||||||
|
self:Print("/abs restore <set> - Restores the saved <set>")
|
||||||
|
self:Print("/abs delete <set> - Deletes the saved <set>")
|
||||||
|
self:Print("/abs list - Lists all saved sets")
|
||||||
|
self:Print("/abs alias <spellID> <aliasID> - Adds an alias with <aliasID> to <spellID>")
|
||||||
|
self:Print("/abs unalias <spellID> - Removes all aliases associated with <spellID>")
|
||||||
|
self:Print("/abs aliases - List all spell aliases")
|
||||||
|
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 == "" then
|
||||||
|
PrintUsage()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
SLASH_ABS1 = "/abs"
|
30
Main.lua
Normal file
30
Main.lua
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
local ADDON_NAME, shared = ...
|
||||||
|
|
||||||
|
local frame = CreateFrame("Frame")
|
||||||
|
frame:RegisterEvent("ADDON_LOADED")
|
||||||
|
frame:SetScript("OnEvent", function(self, event, addon)
|
||||||
|
if addon ~= ADDON_NAME then return end
|
||||||
|
|
||||||
|
ActionBarSaverReloaded = ActionBarSaverReloaded or {}
|
||||||
|
ActionBarSaverReloaded.spellAliases = ActionBarSaverReloaded.spellAliases or {}
|
||||||
|
ActionBarSaverReloaded.sets = ActionBarSaverReloaded.sets or {}
|
||||||
|
end)
|
||||||
|
|
||||||
|
--function ABS:OnInitialize()
|
||||||
|
-- self.commands = {
|
||||||
|
-- save = self.actions.SaveSet,
|
||||||
|
-- restore = self.actions.RestoreSet,
|
||||||
|
-- delete = self.actions.DeleteSet,
|
||||||
|
-- list = self.actions.ListSets,
|
||||||
|
-- alias = self.actions.AliasSpell,
|
||||||
|
-- unalias = self.actions.DeleteSpellAliases,
|
||||||
|
-- aliases = self.actions.ListAliases,
|
||||||
|
-- }
|
||||||
|
--end
|
||||||
|
--
|
||||||
|
--function ABS:HandleCommands(input)
|
||||||
|
-- local cmd, args = Str.split(input, " ", 2)
|
||||||
|
-- local fn = self.commands[Str.toLower(cmd)]
|
||||||
|
--
|
||||||
|
-- if fn then fn(self, args) else self.actions.PrintUsage(self) end
|
||||||
|
--end
|
25
README.md
Normal file
25
README.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# ActionBarSaver:Reloaded
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
ActionBarSaver:Reloaded is an addon for saving and restoring action bar profiles. It is based on the original ActionBarSaver addon but is a full re-write.
|
||||||
|
|
||||||
|
All sets are saved by class rather than by character. Additionally, when you list profiles, you will only see profiles that pertain to your class.
|
||||||
|
|
||||||
|
Features such as rename have been deleted for simplicity. To perform a rename, simply save the set with a new name and delete the old set. Additionally, restoring a set will no longer try to re-create macros that do not exist. It will simply notify you of the missing macro and restore nothing for that slot. ABS:R will not work properly if you have multiple macros with the same name, and will warn you of potential issues if you restore a set that has a macro with a shared name.
|
||||||
|
|
||||||
|
A new feature has been added for setting up aliases for spells. A common use case for this would be restoring a single set for two characters that share a class but have a different race. For example, you could create a set on a troll shaman that contains `Berserking` and then add an alias for `War Stomp` using `/abs alias 20554 20549`. If you do this, when you restore a set it will first try to restore the proper spell but will also try each alias that you set up. A spell can have as many aliases as you want.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
`/abs save <set>` - Saves your current action bar setup under the given <set>\
|
||||||
|
`/abs restore <set>` - Restores the saved <set>\
|
||||||
|
`/abs delete <set>` - Deletes the saved <set>\
|
||||||
|
`/abs list` - Lists all saved sets\
|
||||||
|
`/abs alias <spellID> <aliasID>` - Adds an alias with <aliasID> to <spellID>\
|
||||||
|
`/abs unalias <spellID>` - Removes all aliases associated with <spellID>\
|
||||||
|
`/abs aliases` - List all spell aliases
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
* Aliases do not work both ways. If you alias `Berserking` as `War Stomp` and then save a set that contains `Berserking`, it will work properly if you restore that set on a tauren. However, if you save a set that contains `War Stomp` and try to restore it on a troll, it will fail. This will be addressed in a future version.
|
31
RELEASE_NOTES.md
Normal file
31
RELEASE_NOTES.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
## 1.0.7 (2024-06-30)
|
||||||
|
|
||||||
|
- Updated for Cata classic
|
||||||
|
|
||||||
|
## 1.0.6 (2023-08-29)
|
||||||
|
|
||||||
|
- TOC bump for ICC patch
|
||||||
|
|
||||||
|
## 1.0.5 (2023-06-20)
|
||||||
|
|
||||||
|
- TOC bump for ToGC patch
|
||||||
|
|
||||||
|
## 1.0.4 (2023-01-18)
|
||||||
|
|
||||||
|
- TOC bump for WOTLK patch
|
||||||
|
|
||||||
|
## 1.0.3 (2022-11-09)
|
||||||
|
|
||||||
|
- Fixed bug with equipment sets not being restored properly
|
||||||
|
|
||||||
|
## 1.0.2 (2022-10-27)
|
||||||
|
|
||||||
|
- Fixed bug where totem sets were not being saved or restored
|
||||||
|
|
||||||
|
## 1.0.1 (2022-09-26)
|
||||||
|
|
||||||
|
- Fixed bug related to restoring sets containing companions and mounts
|
||||||
|
|
||||||
|
## 1.0.0 (2022-07-15)
|
||||||
|
|
||||||
|
- Initial re-write of the addon
|
9
StringHelpers.lua
Normal file
9
StringHelpers.lua
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
Str = {}
|
||||||
|
|
||||||
|
function Str.toLower(s) return string.lower(s or "") end
|
||||||
|
function Str.nullOrEmpty(s) return not s or s == "" end
|
||||||
|
|
||||||
|
function Str.split(s, delimiter, max)
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
return string.split(delimiter, s, max)
|
||||||
|
end
|
84
TableHelpers.lua
Normal file
84
TableHelpers.lua
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
Array = {}
|
||||||
|
Dict = {}
|
||||||
|
|
||||||
|
function Array.iter(arr, fn)
|
||||||
|
for _,v in ipairs(arr) do
|
||||||
|
fn(v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Array.map(arr, fn)
|
||||||
|
local t = {}
|
||||||
|
|
||||||
|
for _,v in ipairs(arr) do
|
||||||
|
table.insert(t, fn(v))
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
function Array.mapRange(start, stop, fn)
|
||||||
|
local arr = {}
|
||||||
|
|
||||||
|
for i = start, stop do
|
||||||
|
arr[i] = fn(i)
|
||||||
|
end
|
||||||
|
|
||||||
|
return arr
|
||||||
|
end
|
||||||
|
|
||||||
|
function Array.iterRange(start, stop, fn)
|
||||||
|
for i = start, stop do
|
||||||
|
fn(i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Array.contains(arr, value)
|
||||||
|
for _,v in ipairs(arr) do
|
||||||
|
if v == value then return true end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function Array.insert(arr, value, index)
|
||||||
|
local t = {}
|
||||||
|
|
||||||
|
for _, v in ipairs(arr) do
|
||||||
|
table.insert(t, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(t, index, value)
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
function Dict.iter(t, fn)
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
fn(k,v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Dict.map(t, fn)
|
||||||
|
local nt = {}
|
||||||
|
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
nt[k] = fn(k,v)
|
||||||
|
end
|
||||||
|
|
||||||
|
return nt
|
||||||
|
end
|
||||||
|
|
||||||
|
function Dict.keysAsArray(t)
|
||||||
|
local nt = {}
|
||||||
|
|
||||||
|
for k,_ in pairs(t) do
|
||||||
|
table.insert(nt, k)
|
||||||
|
end
|
||||||
|
|
||||||
|
return nt
|
||||||
|
end
|
||||||
|
|
||||||
|
function Dict.isEmpty(t)
|
||||||
|
return next(t) == nil
|
||||||
|
end
|
3
constants.lua
Normal file
3
constants.lua
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ADDON_NAME = "ActionBarSaverReloaded"
|
||||||
|
MAX_MACROS = 138
|
||||||
|
MAX_ACTION_BUTTONS = 132
|
Reference in New Issue
Block a user