You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1324 lines
43 KiB

--[[
]]
local ADDON_NAME,Internal = ...
local L = Internal.L
local UnitClass = UnitClass
local GetActionInfo = GetActionInfo
local GetMacroBody = GetMacroBody
local trim = strtrim
local HelpTipBox_Anchor = Internal.HelpTipBox_Anchor;
local HelpTipBox_SetText = Internal.HelpTipBox_SetText;
local function push(tbl, ...)
local n = select('#', ...)
for i=1,n do
tbl[i] = select(i, ...)
end
tbl.n = n
end
local function compare(a, b)
if a.n ~= b.n then
return false
end
local n = a.n
for i=1,n do
if a[i] ~= b[i] then
return false
end
end
return true
end
local function CompareSets(a, b)
if not tCompare(a.actions, b.actions, 10) then
return false
end
if not tCompare(a.ignored, b.ignored, 10) then
return false
end
if type(a.restrictions) ~= type(b.restrictions) and not tCompare(a.restrictions, b.restrictions, 10) then
return false
end
return true
end
-- Unlike others, imported sets lack ignored values so need a slightly different comparison function
local function CompareImportSets(set, import)
for slot = 1,120 do
if set.ignored[slot] ~= import.ignored[slot] then
return false
end
if not set.ignored[slot] and (type(set.actions[slot]) ~= type(import.actions[slot]) or (set.actions[slot] ~= import.actions[slot] and not tCompare(set.actions[slot], import.actions[slot], 10))) then
return false
end
end
return true
end
-- Track changes to macros
do
local mapCreated = false
local macros = setmetatable({}, {
__index = function (self, key)
local result = {}
self[key] = result
return result
end
})
local macroNameMap = setmetatable({}, {
__index = function (self, name)
for index,macro in ipairs(macros) do
if macro.name == name then
self[macro.name] = index
return index
end
end
end
}) -- Maps names to macro index
local macroBodyMap = setmetatable({}, {
__index = function (self, body)
for index,macro in ipairs(macros) do
if macro.body == body then
self[macro.body] = index
return index
end
end
end
}) -- Maps body to macro index
local function BuildMacroMap()
mapCreated = true
if MacroFrame and MacroFrame:IsShown() then
MacroFrame:Update()
end
local global, character = GetNumMacros()
local macro
wipe(macroNameMap)
wipe(macroBodyMap)
for i=1,global do
macro = macros[i]
macro.index, macro.name, macro.icon, macro.body = i, GetMacroInfo(i)
macro.body = macro.body and trim(macro.body) -- Remove empty lines from start and end
macroNameMap[macro.name] = i
macroBodyMap[macro.body] = i
end
for i=global+1,MAX_ACCOUNT_MACROS do
wipe(macros[i])
end
for i=MAX_ACCOUNT_MACROS+1,MAX_ACCOUNT_MACROS+character do
macro = macros[i]
macro.index, macro.name, macro.icon, macro.body = i, GetMacroInfo(i)
macro.body = macro.body and trim(macro.body) -- Remove empty lines from start and end
macroNameMap[macro.name] = i
macroBodyMap[macro.body] = i
end
for i=MAX_ACCOUNT_MACROS+character+1,MAX_ACCOUNT_MACROS+MAX_CHARACTER_MACROS do
wipe(macros[i])
end
end
local frame = CreateFrame("Frame")
frame:SetScript("OnEvent", BuildMacroMap)
frame:RegisterEvent("PLAYER_LOGIN")
frame:Hide()
hooksecurefunc("CreateMacro", BuildMacroMap)
hooksecurefunc("DeleteMacro", BuildMacroMap)
local function EditMacroHook(id, name, icon, body)
if type(id) == "string" then
id = macroNameMap[id]
end
-- Probably means someone is using EditMacro to make a new macro or
-- editing a macro very early in the load
if id == nil or id == 0 or not mapCreated then
return BuildMacroMap()
end
body = body and trim(body) -- Remove empty lines from start and end
local macro = macros[id]
local changed = (name ~= nil and name ~= macro.name) or (body ~= nil and body ~= macro.body)
if changed then -- Update action bar sets with the new macro
for _,set in pairs(BtWLoadoutsSets.actionbars) do
if type(set) == "table" then
for _,action in pairs(set.actions) do
if action.type == "macro" then
if trim(action.macroText) == macro.body and action.name == macro.name then
action.macroText = body or macro.body
action.name = name or macro.name
end
end
end
end
end
end
-- Update our macro index
if name == nil then
macro.icon = icon or macro.icon
if body ~= nil then -- Only the body has changed so macros wont have reordered
if macro.body then
macroBodyMap[macro.body] = nil -- Setting to nil will cause the index metamethod to run if needed later
end
macro.body = body
macroBodyMap[body] = id
end
else
local min, max = id, GetMacroIndexByName(name)
if min > max then
min, max = max, min
end
-- Rebuild our macro index
for i=min,max do
macro = macros[i]
if GetMacroInfo(i) then
macro.index, macro.name, macro.icon, macro.body = i, GetMacroInfo(i)
macro.body = macro.body and trim(macro.body)
if macro.name then
macroNameMap[macro.name] = i
end
if macro.body then
macroBodyMap[macro.body] = i
end
else
wipe(macro)
end
end
end
end
hooksecurefunc("EditMacro", EditMacroHook)
end
local function UpdateSetFilters(set)
set.filters = set.filters or {}
Internal.UpdateRestrictionFilters(set)
return set
end
local covenantClassAbilities = {
[313347] = false, -- Zone ability base spell
-- Death Knight
[312202] = true, -- shackle-the-unworthy
[311648] = true, -- swarming-mist
[315443] = true, -- abomination-limb
[315442] = true, -- deaths-due
-- Demon Hunter
[306830] = true, -- elysian-decree
[317009] = true, -- sinful-brand
[329554] = true, -- fodder-to-the-flame
[323639] = true, -- the-hunt
-- Druid
[326434] = true, -- kindred-spirits
[323546] = true, -- ravenous-frenzy
[325727] = true, -- adaptive-swarm
[323764] = true, -- convoke-the-spirits
-- Hunter
[308491] = true, -- resonating-arrow
[324149] = true, -- flayed-shot
[325028] = true, -- death-chakram
[328231] = true, -- wild-spirits
-- Mage
[307443] = true, -- radiant-spark
[314793] = true, -- mirrors-of-torment
[324220] = true, -- deathborne
[314791] = true, -- shifting-power
-- Monk
[310454] = true, -- weapons-of-order
[326860] = true, -- fallen-order
[325216] = true, -- bonedust-brew
[327104] = true, -- faeline-stomp
-- Paladin
[304971] = true, -- divine-toll
[316958] = true, -- ashen-hallow
[328204] = true, -- vanquishers-hammer
[328278] = true, -- blessing-of-the-seasons
-- Priest
[325013] = true, -- boon-of-the-ascended
[323673] = true, -- mindgames
[324724] = true, -- unholy-nova
[327661] = true, -- fae-blessings
-- Rogue
[323547] = true, -- echoing-reprimand
[323654] = true, -- slaughter
[328547] = true, -- Serrated-Bone-Spike
[328305] = true, -- sepsis
-- Shaman
[324386] = true, -- vesper-totem
[320674] = true, -- chain-harvest
[326059] = true, -- primordial-wave
[328923] = true, -- fae-transfusion
-- Warlock
[312321] = true, -- scouring-tithe
[321792] = true, -- impending-catastrophe
[325289] = true, -- decimating-bolt
[325640] = true, -- soul-rot
-- Warrior
[307865] = true, -- spear-of-bastion
[317349] = true, -- condemn
[324143] = true, -- conquerors-banner
[325886] = true, -- ancient-aftershock
}
local function IsCovenantClassAbility(id)
return covenantClassAbilities[id] ~= nil
end
local function GetCovenantClassAbility()
for id,valid in pairs(covenantClassAbilities) do
if valid and IsSpellKnown(id, false) then
return id
end
end
return IsSpellKnown(313347, false) and 313347 -- Zone ability base spell
end
local covenantSignatureAbilities = {
[326526] = false, -- Zone ability base spell, not valid to test with IsSpellKnown as its known at level 60
[324739] = true, -- Summon Steward
[300728] = true, -- Door of Shadows
[324631] = true, -- Fleshcraft
[310143] = true, -- Soulshape
}
local function IsCovenantSignatureAbility(id)
return covenantSignatureAbilities[id] ~= nil
end
local function GetCovenantSignatureAbility()
for id,valid in pairs(covenantSignatureAbilities) do
if valid and IsSpellKnown(id, false) then
return id
end
end
return IsSpellKnown(326526, false) and 326526 -- Zone ability base spell
end
local function GetActionInfoTable(slot, tbl)
local actionType, id, subType = GetActionInfo(slot)
if not actionType and not tbl then -- If no action and tbl is missing just return
return
end
tbl = tbl or {}
-- If we use the base version of the spell it should always work
if actionType == "spell" then
id = FindBaseSpellByID(id) or id
if IsCovenantSignatureAbility(id) then
id = GetCovenantSignatureAbility() or id
elseif IsCovenantClassAbility(id) then
id = GetCovenantClassAbility() or id
end
end
-- There are some situations where actions can be "empty" but showing as macros with id 0
if actionType == "macro" and id == 0 then
actionType = nil
id = nil
end
tbl.type, tbl.id, tbl.subType, tbl.macroText = actionType, id, subType, nil
tbl.icon = GetActionTexture(slot)
tbl.name = GetActionText(slot)
if actionType == "macro" then
tbl.macroText = trim(GetMacroBody(id))
end
return tbl
end
local function GetMacroByText(text)
if text == nil then
return
end
text = trim(text)
local global, character = GetNumMacros()
for i=1,global do
if trim(GetMacroBody(i)) == text then
return i
end
end
for i=MAX_ACCOUNT_MACROS+1,MAX_ACCOUNT_MACROS+character do
if trim(GetMacroBody(i)) == text then
return i
end
end
end
local function CompareSlot(slot, tbl, settings)
local actionType, id, subType = GetActionInfo(slot)
if actionType == "spell" then
id = FindBaseSpellByID(id) or id
if settings and settings.adjustCovenant then
if IsCovenantSignatureAbility(id) then
id = GetCovenantSignatureAbility() or id
elseif IsCovenantClassAbility(id) then
id = GetCovenantClassAbility() or id
end
end
end
if tbl == nil then
return actionType == nil
elseif actionType == "macro" and tbl.type == "macro" then
local macroText = trim(GetMacroBody(id) or "")
-- Macro in the action slot has the same text as the macro we want
if macroText == trim(tbl.macroText) then
return true
end
-- There is a macro with the text we want and its not the one in the action slot
if GetMacroByText(tbl.macroText) ~= nil then
return false
end
return id == GetMacroIndexByName(tbl.name)
elseif actionType == "companion" and subType == "MOUNT" and tbl.type == "summonmount" then
return id == select(2, C_MountJournal.GetDisplayedMountInfo(tbl.id))
elseif tbl.type == "companion" and tbl.subType == "MOUNT" and actionType == "summonmount" then
return tbl.id == select(2, C_MountJournal.GetDisplayedMountInfo(id))
else
return tbl.type == actionType and tbl.id == id and tbl.subType == subType
end
end
Internal.GetMacroByText = GetMacroByText
local function PickupMacroByText(text)
local index = GetMacroByText(text)
if index then
PickupMacro(index)
return true
end
return false
end
-- Pickup an action, when test is true the action wont actually be picked up
local function PickupActionTable(tbl, test, settings, activating)
if tbl == nil or tbl.type == nil then
return true, "Success"
end
local success, msg = true, "Success"
local noError, err = pcall(function ()
if tbl.type == "macro" then
local index = GetMacroByText(tbl.macroText)
if not index or index == 0 then
if settings and settings.createMissingMacros then
msg = L["Could not find macro by text, creating as account macro"]
local numMacros = GetNumMacros()
if activating and numMacros < MAX_ACCOUNT_MACROS then
index = CreateMacro(tbl.name or "BtWLoadouts Missing Macro", "INV_Misc_QuestionMark", tbl.macroText)
else
index = -1
end
elseif settings and settings.createMissingMacrosCharacter then
msg = L["Could not find macro by text, creating as character macro"]
local _, numMacros = GetNumMacros()
if activating and numMacros < MAX_CHARACTER_MACROS then
index = CreateMacro(tbl.name or "BtWLoadouts Missing Macro", "INV_Misc_QuestionMark", tbl.macroText, true)
else
index = -1
end
elseif tbl.name then
msg = L["Could not find macro by text"]
index = GetMacroIndexByName(tbl.name)
end
end
if not index or index == 0 then
msg = L["Could not find macro by text or name"]
success = false
elseif not test then
PickupMacro(index)
end
elseif tbl.type == "spell" then
-- If we use the base version of the spell it should always work
tbl.id = FindBaseSpellByID(tbl.id) or tbl.id
if settings and settings.adjustCovenant then
if IsCovenantSignatureAbility(tbl.id) then
tbl.id = GetCovenantSignatureAbility() or tbl.id
elseif IsCovenantClassAbility(tbl.id) then
tbl.id = GetCovenantClassAbility() or tbl.id
end
end
local index
success = false
if tbl.subType == "spell" then
for tabIndex = 1,min(2,GetNumSpellTabs()) do
local offset, numEntries = select(3, GetSpellTabInfo(tabIndex))
for spellIndex = offset,offset+numEntries do
local skillType, id = GetSpellBookItemInfo(spellIndex, "spell")
if skillType == "SPELL" and id == tbl.id then
index = spellIndex
break
end
end
end
else
local spellIndex = 1
local skillType, id = GetSpellBookItemInfo(spellIndex, tbl.subType)
while skillType do
if (skillType == "SPELL" or (skillType == "PETACTION" and tbl.subType == "pet")) and id == tbl.id then
index = spellIndex
break
end
spellIndex = spellIndex + 1
skillType, id = GetSpellBookItemInfo(spellIndex, tbl.subType)
end
end
if index then
success = true
if not test then
PickupSpellBookItem(index, tbl.subType)
end
end
if not success then
-- In cases where we need a pvp talent but they arent active
-- we have to pickup the talent, not the spell
local pvptalents = C_SpecializationInfo.GetAllSelectedPvpTalentIDs()
for _,talentId in ipairs(pvptalents) do
if select(6, GetPvpTalentInfoByID(talentId)) == tbl.id then
success = true
if not test then
PickupPvpTalent(talentId)
end
end
end
end
if not success then
if tbl.subType == "pet" then
if IsSpellKnown(tbl.id, true) then
success = true
if not test then
PickupPetSpell(tbl.id)
end
end
elseif tbl.subType == "spell" then
if IsSpellKnown(tbl.id, false) or IsPlayerSpell(tbl.id) then
success = true
if not test then
PickupSpell(tbl.id)
end
end
end
end
if not success then
msg = L["Spell not found"]
end
elseif tbl.type == "item" then
if not test then
PickupItem(tbl.id)
end
elseif tbl.type == "summonmount" then
if tbl.id == 0xFFFFFFF then -- Random Favourite
if not test then
C_MountJournal.Pickup(0)
end
else
if not select(11, C_MountJournal.GetMountInfoByID(tbl.id)) then
success = false
msg = L["Mount is not available"]
elseif not test then
-- We will attempt to pickup the mount using the latest way, if that
-- fails because of filtering we will pickup the spell instead
local index = nil
for i=1,C_MountJournal.GetNumDisplayedMounts() do
if select(12,C_MountJournal.GetDisplayedMountInfo(i)) == tbl.id then
index = i
break
end
end
if index then
C_MountJournal.Pickup(index)
else
PickupSpell((select(2, C_MountJournal.GetMountInfoByID(tbl.id))))
end
end
end
elseif tbl.type == "summonpet" then
if not C_PetJournal.GetPetInfoByPetID(tbl.id) then
success = false
msg = L["Pet is not available"]
elseif not test then
C_PetJournal.PickupPet(tbl.id)
end
elseif tbl.type == "companion" then -- This is the old way of handling mounts and pets
if not test and tbl.subType == "MOUNT" then
PickupSpell(tbl.id)
end
elseif tbl.type == "equipmentset" then
local id = C_EquipmentSet.GetEquipmentSetID(tbl.id)
if not id then
success = false
msg = L["Equipment set is not available"]
elseif not test then
C_EquipmentSet.PickupEquipmentSet(id)
end
elseif tbl.type == "flyout" then
if not GetFlyoutInfo(tbl.id) then
success = false
msg = L["Flyout is not available"]
else
-- Find the spell book index for the flyout
local index
for tabIndex = 1,min(2,GetNumSpellTabs()) do
local offset, numEntries = select(3, GetSpellTabInfo(tabIndex))
for spellIndex = offset,offset+numEntries do
local skillType, id = GetSpellBookItemInfo(spellIndex, "spell")
if skillType == "FLYOUT" and id == tbl.id then
index = spellIndex
break
end
end
end
if not index then -- Couldn't find the flyout in the spell book
success = false
msg = L["Flyout is not is spell book"]
elseif not test then
PickupSpellBookItem(index, "spell")
end
end
end
end)
if not noError then
success = false
msg = L["Error: "] .. err
end
return success, msg
end
local ActionCacheA, ActionCacheB = {}, {}
local function SetActon(slot, tbl)
local success, done, msg = true, true, "Success"
ClearCursor()
success, msg = PickupActionTable(tbl)
if success then
if tbl == nil or tbl.type == nil then
PickupAction(slot)
ClearCursor()
else
if GetCursorInfo() then
push(ActionCacheA, GetCursorInfo())
PlaceAction(slot)
push(ActionCacheB, GetCursorInfo())
if compare(ActionCacheA, ActionCacheB) then -- Compare the cursor now to before we placed the action, if they are the same it failed
msg = "Failed to place action"
success, done = false, false
end
else
msg = "Failed to pickup action"
success, done = false, false
end
end
end
if tbl == nil or tbl.type == nil then
Internal.LogMessage("Emptying action bar slot %d (%s, %s)", slot, success and "true" or "false", msg)
elseif tbl.type == "macro" then
Internal.LogMessage("Switching action bar slot %d to %s:%s:%s (%s, %s)", slot, tbl.type, tbl.id, tbl.macroText:gsub("\n", "\\ "), success and "true" or "false", msg)
else
Internal.LogMessage("Switching action bar slot %d to %s:%s (%s, %s)", slot, tbl.type, tbl.id, success and "true" or "false", msg)
end
ClearCursor()
return success, done
end
local function IsActionBarSetActive(set)
for slot=1,120 do
if not set.ignored[slot] then
local action = set.actions[slot]
local available = PickupActionTable(action, true, set.settings)
if available and not CompareSlot(slot, action, set.settings) then
return false
end
end
end
return true;
end
local function ActivateActionBarSet(set, state)
local complete = true
for slot=1,120 do
if not set.ignored[slot] then
local action = set.actions[slot]
if not CompareSlot(slot, action, set.settings) then
local success, done = SetActon(slot, action)
if not done then
complete = false
end
end
end
end
return complete, not complete
end
local function RefreshActionBarSet(set)
local actions = set.actions or {}
for slot = 1,120 do
actions[slot] = GetActionInfoTable(slot)
end
set.actions = actions
return UpdateSetFilters(set)
end
local function AddActionBarSet()
local classFile = select(2, UnitClass("player"))
local name = format(L["New Set"]);
local actions, ignored = {}, {}
for slot = 1,120 do
actions[slot] = GetActionInfoTable(slot)
end
local ignoredStart = 73
if classFile == "ROGUE" then
ignoredStart = 85 -- After Stealth Bar
elseif classFile == "DRUID" then
ignoredStart = 121 -- After Form Bars
end
for slot = ignoredStart,120 do
ignored[slot] = true
end
return Internal.AddSet(BtWLoadoutsSets.actionbars, UpdateSetFilters({
name = name,
ignored = ignored,
actions = actions,
}))
end
local function GetActionBarSet(id)
return Internal.GetSet(BtWLoadoutsSets.actionbars, id)
end
local function GetActionBarSetByName(id)
return Internal.GetSetByName(BtWLoadoutsSets.actionbars, id)
end
local function GetActionBarSets(id, ...)
if id ~= nil then
return GetActionBarSet(id), GetActionBarSets(...);
end
end
-- Do not change the results action tables, that'll mess with the original sets
local function CombineActionBarSets(result, state, ...)
result = result or {};
result.actions = result.actions or {}
result.ignored = result.ignored or {}
wipe(result.actions)
for slot=1,120 do
result.ignored[slot] = true
end
for i=1,select('#', ...) do
local set = select(i, ...);
if Internal.AreRestrictionsValidForPlayer(set.restrictions) then
for slot=1,120 do
if not set.ignored[slot] then
result.ignored[slot] = false
if PickupActionTable(set.actions[slot], true, set.settings, state ~= nil) or result.actions[slot] == nil then
result.actions[slot] = set.actions[slot]
end
end
end
end
end
if state and state.blockers and not IsActionBarSetActive(result) then
state.blockers[Internal.GetCombatBlocker()] = true
end
return result;
end
local function DeleteActionBarSet(id)
Internal.DeleteSet(BtWLoadoutsSets.actionbars, id);
if type(id) == "table" then
id = id.setID;
end
for _,set in pairs(BtWLoadoutsSets.profiles) do
if type(set) == "table" then
for index,setID in ipairs(set.actionbars) do
if setID == id then
table.remove(set.actionbars, index)
end
end
end
end
local frame = BtWLoadoutsFrame.ActionBars;
local set = frame.set;
if set.setID == id then
frame.set = nil
BtWLoadoutsFrame:Update()
end
end
local function CheckErrors(errorState, set)
set = GetActionBarSet(set)
if not Internal.AreRestrictionsValidFor(set.restrictions, errorState.specID) then
return L["Incompatible Restrictions"]
end
end
Internal.PickupActionTable = PickupActionTable
Internal.IsActionBarSetActive = IsActionBarSetActive
Internal.ActivateActionBarSet = ActivateActionBarSet
Internal.AddActionBarSet = AddActionBarSet
Internal.RefreshActionBarSet = RefreshActionBarSet
Internal.GetActionBarSet = GetActionBarSet
Internal.GetActionBarSetByName = GetActionBarSetByName
Internal.GetActionBarSets = GetActionBarSets
Internal.CombineActionBarSets = CombineActionBarSets
Internal.DeleteActionBarSet = DeleteActionBarSet
-- Initializes the set dropdown menu for the Loadouts page
local function SetDropDownInit(self, set, index)
Internal.SetDropDownInit(self, set, index, "actionbars", BtWLoadoutsFrame.ActionBars)
end
Internal.AddLoadoutSegment({
id = "actionbars",
name = L["Action Bars"],
after = "talents,pvptalents,essences,soulbinds,equipment",
events = "ACTIONBAR_SLOT_CHANGED",
add = AddActionBarSet,
get = GetActionBarSets,
getByName = GetActionBarSetByName,
combine = CombineActionBarSets,
isActive = IsActionBarSetActive,
activate = ActivateActionBarSet,
dropdowninit = SetDropDownInit,
checkerrors = CheckErrors,
export = function (set)
local result = {
version = 1,
name = set.name,
actions = CopyTable(set.actions),
ignored = set.ignored,
restrictions = set.restrictions,
}
for index,ignored in pairs(result.ignored) do
if ignored then
result.actions[index] = nil
end
end
return result
end,
import = function (source, version, name, ...)
assert(version == 1)
return Internal.AddSet("actionbars", UpdateSetFilters({
name = name or source.name,
useCount = 0,
actions = source.actions,
ignored = source.ignored,
restrictions = source.restrictions,
}))
end,
getByValue = function (set)
return Internal.GetSetByValue(BtWLoadoutsSets.actionbars, set, CompareImportSets)
end,
verify = function (source, ...)
if type(source.actions) ~= "table" then
return false, L["Missing actions"]
end
if type(source.ignored) ~= "table" then
return false, L["Missing ignored"]
end
if source.restrictions ~= nil and type(source.restrictions) ~= "table" then
return false, L["Missing restrictions"]
end
-- @TODO verify table values?
return true
end,
})
BtWLoadoutsActionButtonMixin = {}
function BtWLoadoutsActionButtonMixin:OnClick(...)
local cursorType = GetCursorInfo()
if cursorType then
self:SetActionToCursor(GetCursorInfo())
elseif IsModifiedClick("CTRL") then
local set = self:GetParent().set;
local slot = self:GetID();
local tbl = set.actions[slot];
if tbl.type == "macro" then
local index = Internal.GetMacroByText(tbl.macroText)
if not index then
CreateMacro(tbl.name, "INV_Misc_QuestionMark", tbl.macroText, IsModifiedClick("SHIFT"));
end
end
BtWLoadoutsFrame:Update()
elseif IsModifiedClick("SHIFT") then
local set = self:GetParent().set;
self:SetIgnored(not set.ignored[self:GetID()]);
else
self:SetAction(nil);
end
end
function BtWLoadoutsActionButtonMixin:OnReceiveDrag()
local cursorType = GetCursorInfo()
if self:GetParent().set and cursorType then
self:SetActionToCursor(GetCursorInfo())
end
end
function BtWLoadoutsActionButtonMixin:SetActionToCursor(...)
local cursorType = ...
if cursorType then
if cursorType == "battlepet" then
local id = select(2, ...)
self:SetAction("summonpet", id)
elseif cursorType == "mount" then
local id = select(2, ...)
self:SetAction("summonmount", id)
elseif cursorType == "petaction" then
local id = select(2, ...)
self:SetAction("spell", id, "pet")
elseif cursorType == "spell" then
local subType, id = select(3, ...)
id = FindBaseSpellByID(id) or id
if IsCovenantSignatureAbility(id) then
id = GetCovenantSignatureAbility() or id
elseif IsCovenantClassAbility(id) then
id = GetCovenantClassAbility() or id
end
self:SetAction("spell", id, subType)
elseif cursorType == "equipmentset" then
local id = select(2, ...)
local icon, name
do
local id = C_EquipmentSet.GetEquipmentSetID(id)
name, icon = C_EquipmentSet.GetEquipmentSetInfo(id)
end
self:SetAction("equipmentset", id, nil, icon, name)
elseif cursorType == "macro" then
local id = select(2, ...)
local macroText = trim(GetMacroBody(id))
local name, icon = GetMacroInfo(id)
self:SetAction("macro", id, nil, icon, name, macroText)
elseif cursorType == "flyout" then
local id, icon = select(2, ...)
self:SetAction("flyout", id, nil, icon)
elseif cursorType == "item" then
self:SetAction(cursorType, (select(2, ...)))
-- else -- Anything else isnt supported
end
ClearCursor()
end
end
function BtWLoadoutsActionButtonMixin:SetAction(actionType, ...)
local set = self:GetParent().set;
if actionType == nil then -- Clearing slot
set.actions[self:GetID()] = nil;
self:Update();
return true;
else
local tbl = set.actions[self:GetID()] or {}
tbl.type, tbl.id, tbl.subType, tbl.icon, tbl.name, tbl.macroText = actionType, ...
set.actions[self:GetID()] = tbl;
self:Update()
end
end
function BtWLoadoutsActionButtonMixin:SetIgnored(ignored)
local set = self:GetParent().set;
set.ignored[self:GetID()] = ignored and true or nil;
self:Update();
end
function BtWLoadoutsActionButtonMixin:Update()
local set = self:GetParent().set;
local slot = self:GetID();
local errors = nil
local ignored = set and set.ignored[slot];
local tbl = set and set.actions[slot];
if tbl and tbl.type ~= nil then
if not ignored then
local success, msg = Internal.PickupActionTable(tbl, true, set.settings)
if not success then
errors = msg
end
end
local icon, name = tbl.icon, tbl.name
if tbl.type == "item" then
icon = select(5, GetItemInfoInstant(tbl.id))
elseif tbl.type == "spell" then
icon = select(3, GetSpellInfo(tbl.id))
elseif tbl.type == "summonmount" then
if tbl.id == 0xFFFFFFF then
icon = 413588
else
icon = select(3, C_MountJournal.GetMountInfoByID(tbl.id))
end
elseif tbl.type == "summonpet" then
icon = select(9, C_PetJournal.GetPetInfoByPetID(tbl.id))
elseif tbl.type == "macro" then
local index = Internal.GetMacroByText(tbl.macroText)
if index then
name, icon = GetMacroInfo(index)
elseif not ignored then
errors = L["Macro missing\n|rCtrl+Left Click to create macro\nCtrl+Shift+Left Click to create character macro"]
end
elseif tbl.type == "equipmentset" then
local id = C_EquipmentSet.GetEquipmentSetID(tbl.id)
if id then
name, icon = C_EquipmentSet.GetEquipmentSetInfo(id)
elseif not ignored then
errors = L["Equipment set missing"]
end
else
-- print(tbl.type, tbl.id)
end
if not icon then
icon = 134400
end
self.Name:SetText(name)
self.Icon:SetTexture(icon)
self.Icon:SetDesaturated(ignored)
self.Icon:SetAlpha(ignored and 0.8 or 1)
else
self.Name:SetText(nil)
self.Icon:SetTexture(nil)
end
self.errors = errors -- For tooltip display
self.ErrorBorder:SetShown(errors ~= nil)
self.ErrorOverlay:SetShown(errors ~= nil)
self.ignoreTexture:SetShown(ignored);
end
function BtWLoadoutsActionButtonMixin:OnEnter()
if self.errors then
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(string.format(L["Slot %d"], self:GetID()), 1, 1, 1)
GameTooltip:AddLine(format("\n|cffff0000%s|r", self.errors))
GameTooltip:Show()
end
end
function BtWLoadoutsActionButtonMixin:OnLeave()
GameTooltip:Hide();
end
BtWLoadoutsIgnoreActionBarMixin = {}
function BtWLoadoutsIgnoreActionBarMixin:OnClick()
local set = self:GetParent().set;
local setIgnored = true
for id=self.startID,self.endID do
if set.ignored[id] then
setIgnored = false
break
end
end
for id=self.startID,self.endID do
set.ignored[id] = setIgnored
self:GetParent().Slots[id]:Update()
end
end
local function DropDown_Initialize(self, level, menuList)
local set = BtWLoadoutsFrame.ActionBars.set
if set then
if (level or 1) == 1 then
local info = UIDropDownMenu_CreateInfo()
info.func = function (self, arg1, arg2, checked)
set.settings = set.settings or {}
set.settings.adjustCovenant = not checked
BtWLoadoutsFrame:Update()
end
info.checked = set.settings and set.settings.adjustCovenant
info.text = L["Adjust Covenant Abilities"]
UIDropDownMenu_AddButton(info, level)
info.func = function (self, arg1, arg2, checked)
set.settings = set.settings or {}
set.settings.createMissingMacros = not checked
set.settings.createMissingMacrosCharacter = false
BtWLoadoutsFrame:Update()
end
info.checked = set.settings and set.settings.createMissingMacros
info.text = L["Create Missing Macros"]
UIDropDownMenu_AddButton(info, level)
info.func = function (self, arg1, arg2, checked)
set.settings = set.settings or {}
set.settings.createMissingMacrosCharacter = not checked
set.settings.createMissingMacros = false
BtWLoadoutsFrame:Update()
end
info.checked = set.settings and set.settings.createMissingMacrosCharacter
info.text = L["Create Missing Macros (Character Only)"]
UIDropDownMenu_AddButton(info, level)
UIDropDownMenu_AddSeparator()
info.isTitle, info.notCheckable, info.checked, info.func = true, true, false, nil
info.text = L["Restrictions"]
UIDropDownMenu_AddButton(info, level)
end
self.OrigInitFunc(self, level, menuList)
end
end
BtWLoadoutsActionBarsMixin = {}
function BtWLoadoutsActionBarsMixin:OnLoad()
self.SettingsDropDown:SetSupportedTypes("covenant", "spec", "race")
self.SettingsDropDown:SetScript("OnChange", function ()
self:Update()
end)
end
function BtWLoadoutsActionBarsMixin:OnShow()
if not self.initialized then
self.SettingsDropDown.OrigInitFunc = self.SettingsDropDown.initialize
UIDropDownMenu_Initialize(self.SettingsDropDown, DropDown_Initialize, "MENU");
self.initialized = true
end
end
function BtWLoadoutsActionBarsMixin:UpdateSetName(value)
if self.set and self.set.name ~= not value then
self.set.name = value;
self:Update();
end
end
function BtWLoadoutsActionBarsMixin:ChangeSet(set)
self.set = set
self:Update()
end
function BtWLoadoutsActionBarsMixin:OnButtonClick(button)
CloseDropDownMenus()
if button.isAdd then
self.Name:ClearFocus();
self:ChangeSet(AddActionBarSet())
C_Timer.After(0, function ()
self.Name:HighlightText();
self.Name:SetFocus();
end);
elseif button.isDelete then
local set = self.set;
if set.useCount > 0 then
StaticPopup_Show("BTWLOADOUTS_DELETEINUSESET", set.name, nil, {
set = set,
func = DeleteActionBarSet,
});
else
StaticPopup_Show("BTWLOADOUTS_DELETESET", set.name, nil, {
set = set,
func = DeleteActionBarSet,
});
end
elseif button.isRefresh then
local set = self.set;
RefreshActionBarSet(set)
self:Update()
elseif button.isExport then
local set = self.set;
self:GetParent():SetExport(Internal.Export("actionbars", set.setID))
elseif button.isActivate then
Internal.ActivateProfile({
actionbars = {self.set.setID}
});
end
end
function BtWLoadoutsActionBarsMixin:OnSidebarItemClick(button)
CloseDropDownMenus()
if button.isHeader then
button.collapsed[button.id] = not button.collapsed[button.id]
self:Update()
else
if IsModifiedClick("SHIFT") then
Internal.ActivateProfile({
actionbars = {button.id}
});
else
self.Name:ClearFocus();
self:ChangeSet(GetActionBarSet(button.id))
end
end
end
function BtWLoadoutsActionBarsMixin:OnSidebarItemDoubleClick(button)
CloseDropDownMenus()
if button.isHeader then
return
end
Internal.ActivateProfile({
actionbars = {button.id}
});
end
function BtWLoadoutsActionBarsMixin:OnSidebarItemDragStart(button)
CloseDropDownMenus()
if button.isHeader then
return
end
local icon = "INV_Misc_QuestionMark";
local set = GetActionBarSet(button.id);
local command = format("/btwloadouts activate actionbars %d", button.id);
if command then
local macroId;
local numMacros = GetNumMacros();
for i=1,numMacros do
if GetMacroBody(i):trim() == command then
macroId = i;
break;
end
end
if not macroId then
if numMacros == MAX_ACCOUNT_MACROS then
print(L["Cannot create any more macros"]);
return;
end
if InCombatLockdown() then
print(L["Cannot create macros while in combat"]);
return;
end
macroId = CreateMacro(set.name, icon, command, false);
else
-- Rename the macro while not in combat
if not InCombatLockdown() then
icon = select(2,GetMacroInfo(macroId))
EditMacro(macroId, set.name, icon, command)
end
end
if macroId then
PickupMacro(macroId);
end
end
end
function BtWLoadoutsActionBarsMixin:Update()
self:GetParent():SetTitle(L["Action Bars"]);
local sidebar = BtWLoadoutsFrame.Sidebar
sidebar:SetSupportedFilters("covenant", "spec", "class", "role", "race")
sidebar:SetSets(BtWLoadoutsSets.actionbars)
sidebar:SetCollapsed(BtWLoadoutsCollapsed.actionbars)
sidebar:SetCategories(BtWLoadoutsCategories.actionbars)
sidebar:SetFilters(BtWLoadoutsFilters.actionbars)
sidebar:SetSelected(self.set)
sidebar:Update()
self.set = sidebar:GetSelected()
local set = self.set
local showingNPE = BtWLoadoutsFrame:SetNPEShown(set == nil, L["Action Bars"], L["Create different action bar layouts, including stealth, form, and stance bars. You can ignore specific action buttons or entire bars."])
self:GetParent().ExportButton:SetEnabled(true)
self:GetParent().RefreshButton:SetEnabled(true)
self:GetParent().ActivateButton:SetEnabled(true);
self:GetParent().DeleteButton:SetEnabled(true);
if not showingNPE then
local slots = set.actions;
UpdateSetFilters(set)
sidebar:Update()
set.restrictions = set.restrictions or {}
self.SettingsDropDown:SetSelections(set.restrictions)
self.SettingsDropDown:SetLimitations()
if not self.Name:HasFocus() then
self.Name:SetText(set.name or "");
end
for slot,item in pairs(self.Slots) do
item:SetID(slot)
item:Update();
local icon = item.Icon:GetTexture()
if icon ~= nil and icon ~= 134400 then
slots[slot].icon = icon
end
end
local helpTipBox = self:GetParent().HelpTipBox;
if not BtWLoadoutsHelpTipFlags["ACTIONBAR_IGNORE"] then
helpTipBox.closeFlag = "ACTIONBAR_IGNORE";
HelpTipBox_Anchor(helpTipBox, "RIGHT", self.Bar1Slot1);
helpTipBox:Show();
HelpTipBox_SetText(helpTipBox, L["Shift+Left Mouse Button to ignore a slot."]);
else
helpTipBox.closeFlag = nil;
helpTipBox:Hide();
end
else
self.Name:SetText(L["New Set"]);
for slot,item in pairs(self.Slots) do
item:SetID(slot)
item:Update();
end
local helpTipBox = self:GetParent().HelpTipBox;
helpTipBox:Hide();
end
end
function BtWLoadoutsActionBarsMixin:SetSetByID(setID)
self.set = GetActionBarSet(setID)
end