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.
854 lines
27 KiB
854 lines
27 KiB
|
4 years ago
|
-- if not C_Covenants or GetExpansionLevel() ~= 8 then -- Skip for pre-Shadowlands
|
||
|
|
-- return
|
||
|
|
-- end
|
||
|
|
BTWLOADOUTS_SOULBINDS_ACTIVE = GetExpansionLevel() == 8
|
||
|
|
|
||
|
|
local ADDON_NAME,Internal = ...
|
||
|
|
local L = Internal.L
|
||
|
|
|
||
|
|
local GetActiveCovenantID = C_Covenants.GetActiveCovenantID
|
||
|
|
local GetCovenantIDs = C_Covenants.GetCovenantIDs
|
||
|
|
local GetCovenantData = C_Covenants.GetCovenantData
|
||
|
|
local ActivateSoulbind = C_Soulbinds.ActivateSoulbind
|
||
|
|
local GetActiveSoulbindID = C_Soulbinds.GetActiveSoulbindID
|
||
|
|
local GetSoulbindData = C_Soulbinds.GetSoulbindData
|
||
|
|
local GetSoulbindNode = C_Soulbinds.GetNode
|
||
|
|
local SelectSoulbindNode = C_Soulbinds.SelectNode
|
||
|
|
|
||
|
|
local HelpTipBox_Anchor = Internal.HelpTipBox_Anchor;
|
||
|
|
local HelpTipBox_SetText = Internal.HelpTipBox_SetText;
|
||
|
|
|
||
|
|
local function CompareSets(a, b)
|
||
|
|
if a.soulbindID ~= b.soulbindID then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
|
||
|
|
if not tCompare(a.nodes, b.nodes, 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
|
||
|
|
|
||
|
|
--[[ SOULBIND DROPDOWN ]]
|
||
|
|
|
||
|
|
local function SoulbindDropDown_OnClick(self, arg1, arg2, checked)
|
||
|
|
local tab = BtWLoadoutsFrame.Soulbinds;
|
||
|
|
|
||
|
|
CloseDropDownMenus();
|
||
|
|
local set = tab.set;
|
||
|
|
|
||
|
|
local temp = tab.temp;
|
||
|
|
local soulbindID = set.soulbindID;
|
||
|
|
if temp[soulbindID] then
|
||
|
|
wipe(temp[soulbindID]);
|
||
|
|
else
|
||
|
|
temp[soulbindID] = {};
|
||
|
|
end
|
||
|
|
for nodeID in pairs(set.nodes) do
|
||
|
|
temp[soulbindID][nodeID] = true;
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Clear the current talents and copy back the previously selected talents if they exist
|
||
|
|
soulbindID = arg1;
|
||
|
|
set.soulbindID = soulbindID;
|
||
|
|
wipe(set.nodes);
|
||
|
|
if temp[soulbindID] then
|
||
|
|
for nodeID in pairs(temp[soulbindID]) do
|
||
|
|
set.nodes[nodeID] = true;
|
||
|
|
end
|
||
|
|
end
|
||
|
|
BtWLoadoutsFrame:Update();
|
||
|
|
end
|
||
|
|
local function SoulbindDropDownInit(self, level, menuList)
|
||
|
|
local info = UIDropDownMenu_CreateInfo();
|
||
|
|
|
||
|
|
local set = self:GetParent().set;
|
||
|
|
local selected = set and set.soulbindID;
|
||
|
|
|
||
|
|
if (level or 1) == 1 then
|
||
|
|
if self.includeNone then
|
||
|
|
info.text = L["None"];
|
||
|
|
info.func = SoulbindDropDown_OnClick;
|
||
|
|
info.checked = selected == nil;
|
||
|
|
UIDropDownMenu_AddButton(info, level);
|
||
|
|
end
|
||
|
|
|
||
|
|
for _,covenantID in ipairs(GetCovenantIDs()) do
|
||
|
|
local covenantData = GetCovenantData(covenantID)
|
||
|
|
|
||
|
|
info.text = covenantData.name;
|
||
|
|
info.hasArrow, info.menuList = true, covenantData.ID;
|
||
|
|
info.keepShownOnClick = true;
|
||
|
|
info.notCheckable = true;
|
||
|
|
UIDropDownMenu_AddButton(info, level);
|
||
|
|
end
|
||
|
|
else
|
||
|
|
local covenantData = GetCovenantData(menuList)
|
||
|
|
for _,soulbindID in ipairs(covenantData.soulbindIDs) do
|
||
|
|
local soulbindData = GetSoulbindData(soulbindID)
|
||
|
|
|
||
|
|
info.text = soulbindData.name;
|
||
|
|
info.arg1 = soulbindData.ID;
|
||
|
|
info.func = SoulbindDropDown_OnClick;
|
||
|
|
info.checked = selected == soulbindData.ID;
|
||
|
|
UIDropDownMenu_AddButton(info, level);
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
BtWLoadoutsSoulbindDropDownMixin = {}
|
||
|
|
function BtWLoadoutsSoulbindDropDownMixin:OnShow()
|
||
|
|
if not self.initialized then
|
||
|
|
UIDropDownMenu_Initialize(self, SoulbindDropDownInit);
|
||
|
|
self.initialized = true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
--[[ SET HANDLING ]]
|
||
|
|
|
||
|
|
local function UpdateSetFilters(set)
|
||
|
|
set.filters = set.filters or {}
|
||
|
|
|
||
|
|
local filters = set.filters
|
||
|
|
wipe(filters)
|
||
|
|
|
||
|
|
Internal.UpdateRestrictionFilters(set)
|
||
|
|
|
||
|
|
filters.soulbind = set.soulbindID
|
||
|
|
|
||
|
|
local soulbindData = GetSoulbindData(set.soulbindID)
|
||
|
|
filters.covenant = soulbindData.covenantID
|
||
|
|
|
||
|
|
set.filters = filters
|
||
|
|
|
||
|
|
return set
|
||
|
|
end
|
||
|
|
local function RefreshSet(set)
|
||
|
|
local nodes = set.nodes or {}
|
||
|
|
wipe(nodes)
|
||
|
|
local soulbindData = GetSoulbindData(set.soulbindID)
|
||
|
|
for _,node in ipairs(soulbindData.tree.nodes) do
|
||
|
|
if node.state == Enum.SoulbindNodeState.Selected then
|
||
|
|
nodes[node.ID] = true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
set.nodes = nodes
|
||
|
|
|
||
|
|
return UpdateSetFilters(set)
|
||
|
|
end
|
||
|
|
local function AddSet()
|
||
|
|
local soulbindID = GetActiveSoulbindID()
|
||
|
|
if soulbindID == 0 then
|
||
|
|
soulbindID = 7 -- Default to pelagos because why not
|
||
|
|
end
|
||
|
|
local soulbindData = GetSoulbindData(soulbindID)
|
||
|
|
return Internal.AddSet("soulbinds", RefreshSet({
|
||
|
|
soulbindID = soulbindID,
|
||
|
|
name = format(L["New %s Set"], soulbindData.name),
|
||
|
|
useCount = 0,
|
||
|
|
nodes = {},
|
||
|
|
}))
|
||
|
|
end
|
||
|
|
local function DeleteSet(id)
|
||
|
|
Internal.DeleteSet(BtWLoadoutsSets.soulbinds, 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.soulbinds) do
|
||
|
|
if setID == id then
|
||
|
|
table.remove(set.soulbinds, index)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
local frame = BtWLoadoutsFrame.Soulbinds;
|
||
|
|
local set = frame.set;
|
||
|
|
if set.setID == id then
|
||
|
|
frame.set = nil
|
||
|
|
BtWLoadoutsFrame:Update();
|
||
|
|
end
|
||
|
|
end
|
||
|
|
local function GetSet(id)
|
||
|
|
if type(id) == "table" then
|
||
|
|
return id;
|
||
|
|
elseif id < 0 then -- Fake a soulbind set
|
||
|
|
local set = GetSoulbindData(math.abs(id))
|
||
|
|
set.soulbindID = set.ID
|
||
|
|
set.setID = -set.ID
|
||
|
|
set.ID = nil
|
||
|
|
return set
|
||
|
|
else
|
||
|
|
return BtWLoadoutsSets.soulbinds[id]
|
||
|
|
end
|
||
|
|
end
|
||
|
|
local function GetSets(id, ...)
|
||
|
|
if id ~= nil then
|
||
|
|
return GetSet(id), GetSets(...);
|
||
|
|
end
|
||
|
|
end
|
||
|
|
local function IsSetActive(set)
|
||
|
|
if set.soulbindID then
|
||
|
|
local covenantID = GetActiveCovenantID()
|
||
|
|
if covenantID then
|
||
|
|
local soulbindID = GetActiveSoulbindID()
|
||
|
|
local soulbindData = GetSoulbindData(set.soulbindID)
|
||
|
|
-- The target soulbind is unlocked, is for the players covenant, so is valid for the character
|
||
|
|
if soulbindData.unlocked and soulbindData.covenantID == covenantID then
|
||
|
|
if set.soulbindID == soulbindID then
|
||
|
|
if set.nodes then
|
||
|
|
for nodeID in pairs(set.nodes) do
|
||
|
|
local node = GetSoulbindNode(nodeID)
|
||
|
|
if node.state ~= Enum.SoulbindNodeState.Selected and node.state ~= Enum.SoulbindNodeState.Unavailable then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return true
|
||
|
|
else
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
else
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
return true;
|
||
|
|
end
|
||
|
|
local function CombineSets(result, state, ...)
|
||
|
|
result = result or {};
|
||
|
|
|
||
|
|
local covenantID = GetActiveCovenantID()
|
||
|
|
if covenantID then
|
||
|
|
for i=1,select('#', ...) do
|
||
|
|
local set = select(i, ...);
|
||
|
|
local soulbindData = GetSoulbindData(set.soulbindID)
|
||
|
|
if soulbindData.covenantID == covenantID and soulbindData.unlocked and Internal.AreRestrictionsValidForPlayer(set.restrictions) then
|
||
|
|
result.soulbindID = set.soulbindID
|
||
|
|
result.nodes = set.nodes
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
local soulbindID = GetActiveSoulbindID()
|
||
|
|
if state and not IsSetActive(result) then
|
||
|
|
state.noCombatSwap = true
|
||
|
|
state.noTaxiSwap = true -- Maybe check for rested area or tomb first?
|
||
|
|
state.blockedByJailersChains = true
|
||
|
|
state.needTome = true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
return result;
|
||
|
|
end
|
||
|
|
local function ActivateSet(set, state)
|
||
|
|
local complete = true;
|
||
|
|
|
||
|
|
if (not state or (not state.ignoreTome and not state.ignoreJailersChains)) and not IsSetActive(set) then
|
||
|
|
local soulbindData = GetSoulbindData(set.soulbindID)
|
||
|
|
Internal.LogMessage("Switching soulbind to %s", soulbindData.name)
|
||
|
|
|
||
|
|
ActivateSoulbind(set.soulbindID)
|
||
|
|
if set.nodes then
|
||
|
|
for nodeID in pairs(set.nodes) do
|
||
|
|
local node = GetSoulbindNode(nodeID)
|
||
|
|
if node.state ~= Enum.SoulbindNodeState.Selected and node.state ~= Enum.SoulbindNodeState.Unavailable then
|
||
|
|
SelectSoulbindNode(nodeID)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
complete = false
|
||
|
|
end
|
||
|
|
|
||
|
|
return complete
|
||
|
|
end
|
||
|
|
local function CheckErrors(errorState, set)
|
||
|
|
set = GetSet(set)
|
||
|
|
|
||
|
|
if not Internal.AreRestrictionsValidFor(set.restrictions, errorState.specID) then
|
||
|
|
return L["Incompatible Restrictions"]
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
local genericSoulbindSets = {}
|
||
|
|
for _,covenantID in ipairs(GetCovenantIDs()) do
|
||
|
|
local covenantData = GetCovenantData(covenantID)
|
||
|
|
for index,soulbindID in ipairs(covenantData.soulbindIDs) do
|
||
|
|
local soulbindData = GetSoulbindData(soulbindID)
|
||
|
|
|
||
|
|
genericSoulbindSets[#genericSoulbindSets+1] = UpdateSetFilters({
|
||
|
|
setID = -soulbindData.ID,
|
||
|
|
name = soulbindData.name,
|
||
|
|
soulbindID = soulbindData.ID,
|
||
|
|
covenantID = covenantID,
|
||
|
|
order = -20 + (index * 4 + covenantID),
|
||
|
|
})
|
||
|
|
end
|
||
|
|
end
|
||
|
|
-- Initializes the set dropdown menu for the Loadouts page
|
||
|
|
local function SetDropDownInit(self, set, index)
|
||
|
|
local temp = {}
|
||
|
|
for _,set in pairs(genericSoulbindSets) do
|
||
|
|
temp[set.setID] = set
|
||
|
|
end
|
||
|
|
for _,set in pairs(BtWLoadoutsSets.soulbinds) do
|
||
|
|
if type(set) == "table" then
|
||
|
|
temp[set.setID] = set
|
||
|
|
end
|
||
|
|
end
|
||
|
|
self:SetSelected(index and set.soulbinds[index] or nil)
|
||
|
|
self:SetSets(temp)
|
||
|
|
self:SetFilters(BtWLoadoutsFilters.soulbinds)
|
||
|
|
self:SetCategories(BtWLoadoutsCategories.soulbinds)
|
||
|
|
self:SetIncludeNone(index ~= nil)
|
||
|
|
self:OnItemClick(function (self, setID)
|
||
|
|
local index = index or (#set.soulbinds + 1)
|
||
|
|
|
||
|
|
if set.soulbinds[index] then
|
||
|
|
local subset = GetSet(set.soulbinds[index]);
|
||
|
|
subset.useCount = (subset.useCount or 1) - 1;
|
||
|
|
end
|
||
|
|
|
||
|
|
if setID == "none" then
|
||
|
|
table.remove(set.soulbinds, index);
|
||
|
|
elseif setID == "new" then
|
||
|
|
local subset = AddSet();
|
||
|
|
set.soulbinds[index] = subset.setID;
|
||
|
|
|
||
|
|
BtWLoadoutsFrame.Soulbinds.set = subset;
|
||
|
|
PanelTemplates_SetTab(BtWLoadoutsFrame, BtWLoadoutsFrame.Soulbinds:GetID());
|
||
|
|
|
||
|
|
BtWLoadoutsHelpTipFlags["TUTORIAL_CREATE_TALENT_SET"] = true;
|
||
|
|
else -- Change to a specific set
|
||
|
|
set.soulbinds[index] = setID;
|
||
|
|
end
|
||
|
|
|
||
|
|
if set.soulbinds[index] then
|
||
|
|
local subset = GetSet(set.soulbinds[index]);
|
||
|
|
subset.useCount = (subset.useCount or 0) + 1;
|
||
|
|
end
|
||
|
|
|
||
|
|
self:SetSelected(set.soulbinds[index])
|
||
|
|
|
||
|
|
CloseDropDownMenus()
|
||
|
|
BtWLoadoutsFrame:Update();
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
Internal.AddLoadoutSegment({
|
||
|
|
id = "soulbinds",
|
||
|
|
name = L["Soulbinds"],
|
||
|
|
events = "SOULBIND_ACTIVATED",
|
||
|
|
enabled = BTWLOADOUTS_SOULBINDS_ACTIVE,
|
||
|
|
add = AddSet,
|
||
|
|
get = GetSets,
|
||
|
|
combine = CombineSets,
|
||
|
|
isActive = IsSetActive,
|
||
|
|
activate = ActivateSet,
|
||
|
|
dropdowninit = SetDropDownInit,
|
||
|
|
checkerrors = CheckErrors,
|
||
|
|
|
||
|
|
export = function (set)
|
||
|
|
return {
|
||
|
|
version = 1,
|
||
|
|
name = set.name,
|
||
|
|
soulbindID = set.soulbindID,
|
||
|
|
nodes = set.nodes,
|
||
|
|
restrictions = set.restrictions,
|
||
|
|
}
|
||
|
|
end,
|
||
|
|
import = function (source, version, name, ...)
|
||
|
|
assert(version == 1)
|
||
|
|
|
||
|
|
local soulbindID = source.soulbindID or ...
|
||
|
|
return Internal.AddSet("soulbinds", UpdateSetFilters({
|
||
|
|
soulbindID = soulbindID,
|
||
|
|
name = name or source.name,
|
||
|
|
useCount = 0,
|
||
|
|
nodes = source.nodes,
|
||
|
|
restrictions = source.restrictions,
|
||
|
|
}))
|
||
|
|
end,
|
||
|
|
getByValue = function (set)
|
||
|
|
-- If the nodes is missing then we just want the faux set
|
||
|
|
if set.nodes == nil then
|
||
|
|
return GetSet(-set.soulbindID)
|
||
|
|
else
|
||
|
|
return Internal.GetSetByValue(BtWLoadoutsSets.soulbinds, set, CompareSets)
|
||
|
|
end
|
||
|
|
end,
|
||
|
|
verify = function (source, ...)
|
||
|
|
local soulbindID = source.soulbindID or ...
|
||
|
|
if not soulbindID or not GetSoulbindData(soulbindID) then
|
||
|
|
return false, L["Invalid soulbind"]
|
||
|
|
end
|
||
|
|
if source.nodes ~= nil and type(source.nodes) ~= "table" then
|
||
|
|
return false, L["Invalid nodes"]
|
||
|
|
end
|
||
|
|
if source.restrictions ~= nil and type(source.restrictions) ~= "table" then
|
||
|
|
return false, L["Missing restrictions"]
|
||
|
|
end
|
||
|
|
|
||
|
|
-- @TODO verify talent ids?
|
||
|
|
|
||
|
|
return true
|
||
|
|
end,
|
||
|
|
})
|
||
|
|
|
||
|
|
-- [[ TAB UI ]]
|
||
|
|
|
||
|
|
local function GetConduitEmblemAtlas(conduitType)
|
||
|
|
if conduitType == Enum.SoulbindConduitType.Potency then
|
||
|
|
return "Soulbinds_Tree_Conduit_Icon_Attack";
|
||
|
|
elseif conduitType == Enum.SoulbindConduitType.Endurance then
|
||
|
|
return "Soulbinds_Tree_Conduit_Icon_Protect";
|
||
|
|
elseif conduitType == Enum.SoulbindConduitType.Finesse then
|
||
|
|
return "Soulbinds_Tree_Conduit_Icon_Utility";
|
||
|
|
end
|
||
|
|
end
|
||
|
|
BtWLoadoutsSoulbindNodeMixin = {}
|
||
|
|
function BtWLoadoutsSoulbindNodeMixin:SetNode(node)
|
||
|
|
self.node = node
|
||
|
|
self.Icon:SetTexture(node.icon)
|
||
|
|
|
||
|
|
if node.conduitType ~= nil then
|
||
|
|
local atlas = GetConduitEmblemAtlas(node.conduitType);
|
||
|
|
|
||
|
|
self.Emblem:Show()
|
||
|
|
self.EmblemBg:Show()
|
||
|
|
self.Emblem:SetAtlas(atlas);
|
||
|
|
self.EmblemBg:SetAtlas(atlas)
|
||
|
|
self.EmblemBg:SetVertexColor(0, 0, 0);
|
||
|
|
|
||
|
|
self.Icon:Hide();
|
||
|
|
self.Ring:SetAtlas("Soulbinds_Tree_Conduit_Ring", false);
|
||
|
|
self.MouseOverlay:SetAtlas("Soulbinds_Tree_Conduit_Ring", false);
|
||
|
|
else
|
||
|
|
self.Emblem:Hide()
|
||
|
|
self.EmblemBg:Hide()
|
||
|
|
self.Icon:Show();
|
||
|
|
self.Ring:SetAtlas("Soulbinds_Tree_Ring", false);
|
||
|
|
self.MouseOverlay:SetAtlas("Soulbinds_Tree_Ring", false);
|
||
|
|
end
|
||
|
|
end
|
||
|
|
function BtWLoadoutsSoulbindNodeMixin:SetSelected(selected)
|
||
|
|
if not selected then
|
||
|
|
self.Icon:SetDesaturated(false);
|
||
|
|
self.IconOverlay:Show();
|
||
|
|
self.Ring:SetDesaturated(true);
|
||
|
|
self.MouseOverlay:SetDesaturated(true);
|
||
|
|
else
|
||
|
|
self.Icon:SetDesaturated(false);
|
||
|
|
self.IconOverlay:Hide();
|
||
|
|
self.Ring:SetDesaturated(false);
|
||
|
|
self.MouseOverlay:SetDesaturated(false);
|
||
|
|
end
|
||
|
|
end
|
||
|
|
function BtWLoadoutsSoulbindNodeMixin:OnClick()
|
||
|
|
local container = self:GetParent():GetParent():GetParent()
|
||
|
|
local node = self.node
|
||
|
|
local nodes = container.set.nodes
|
||
|
|
local selected = not nodes[node.ID]
|
||
|
|
|
||
|
|
while node.childNodeIDs and #node.childNodeIDs == 1 do
|
||
|
|
local childID = node.childNodeIDs[1]
|
||
|
|
|
||
|
|
if #container.nodesByID[childID].node.parentNodeIDs ~= 1 then
|
||
|
|
break
|
||
|
|
end
|
||
|
|
|
||
|
|
node = container.nodesByID[childID].node
|
||
|
|
end
|
||
|
|
|
||
|
|
for _,frame in pairs(container.nodesByID) do
|
||
|
|
if frame.node.row == node.row then
|
||
|
|
nodes[frame.node.ID] = nil
|
||
|
|
end
|
||
|
|
end
|
||
|
|
nodes[node.ID] = selected and true or nil
|
||
|
|
|
||
|
|
while #node.parentNodeIDs == 1 do
|
||
|
|
local parentID = node.parentNodeIDs[1]
|
||
|
|
node = container.nodesByID[parentID].node
|
||
|
|
|
||
|
|
if not node.childNodeIDs or #node.childNodeIDs ~= 1 then
|
||
|
|
break
|
||
|
|
end
|
||
|
|
|
||
|
|
for _,frame in pairs(container.nodesByID) do
|
||
|
|
if frame.node.row == node.row then
|
||
|
|
nodes[frame.node.ID] = nil
|
||
|
|
end
|
||
|
|
end
|
||
|
|
nodes[node.ID] = selected and true or nil
|
||
|
|
end
|
||
|
|
|
||
|
|
container:Update()
|
||
|
|
end
|
||
|
|
function BtWLoadoutsSoulbindNodeMixin:OnEnter()
|
||
|
|
self.MouseOverlay:Show();
|
||
|
|
end
|
||
|
|
function BtWLoadoutsSoulbindNodeMixin:OnLeave()
|
||
|
|
self.MouseOverlay:Hide();
|
||
|
|
end
|
||
|
|
|
||
|
|
local SoulbindTreeLinkDirections = {
|
||
|
|
Vertical = 1,
|
||
|
|
Converge = 2,
|
||
|
|
Diverge = 3,
|
||
|
|
};
|
||
|
|
|
||
|
|
BtWLoadoutsSoulbindTreeNodeLinkMixin = {}
|
||
|
|
function BtWLoadoutsSoulbindTreeNodeLinkMixin:Init(direction, angle)
|
||
|
|
if direction == SoulbindTreeLinkDirections.Vertical then
|
||
|
|
self.Background:SetAtlas("Soulbinds_Tree_Connector_Vertical", true);
|
||
|
|
self.FillMask:SetAtlas("Soulbinds_Tree_Connector_Vertical_Mask", true);
|
||
|
|
elseif direction == SoulbindTreeLinkDirections.Converge then
|
||
|
|
self.Background:SetAtlas("Soulbinds_Tree_Connector_Diagonal_Close", true);
|
||
|
|
self.FillMask:SetAtlas("Soulbinds_Tree_Connector_Diagonal_Close_Mask", true);
|
||
|
|
elseif direction == SoulbindTreeLinkDirections.Diverge then
|
||
|
|
self.Background:SetAtlas("Soulbinds_Tree_Connector_Diagonal_Far", true);
|
||
|
|
self.FillMask:SetAtlas("Soulbinds_Tree_Connector_Diagonal_Far_Mask", true);
|
||
|
|
end
|
||
|
|
|
||
|
|
self:RotateTextures(angle);
|
||
|
|
end
|
||
|
|
function BtWLoadoutsSoulbindTreeNodeLinkMixin:OnHide()
|
||
|
|
self.FlowAnim1:Stop();
|
||
|
|
self.FlowAnim2:Stop();
|
||
|
|
self.FlowAnim3:Stop();
|
||
|
|
self.FlowAnim4:Stop();
|
||
|
|
self.FlowAnim5:Stop();
|
||
|
|
self.FlowAnim6:Stop();
|
||
|
|
end
|
||
|
|
function BtWLoadoutsSoulbindTreeNodeLinkMixin:Reset()
|
||
|
|
self:SetState(Enum.SoulbindNodeState.Unselected);
|
||
|
|
end
|
||
|
|
function BtWLoadoutsSoulbindTreeNodeLinkMixin:SetState(state)
|
||
|
|
self.state = state;
|
||
|
|
|
||
|
|
if state == Enum.SoulbindNodeState.Unselected or state == Enum.SoulbindNodeState.Unavailable then
|
||
|
|
self:DesaturateHierarchy(1);
|
||
|
|
for _, foreground in ipairs(self.foregrounds) do
|
||
|
|
foreground:SetShown(false);
|
||
|
|
end
|
||
|
|
self.FlowAnim1:Stop();
|
||
|
|
self.FlowAnim2:Stop();
|
||
|
|
self.FlowAnim3:Stop();
|
||
|
|
self.FlowAnim4:Stop();
|
||
|
|
self.FlowAnim5:Stop();
|
||
|
|
self.FlowAnim6:Stop();
|
||
|
|
elseif state == Enum.SoulbindNodeState.Selectable then
|
||
|
|
self:DesaturateHierarchy(0);
|
||
|
|
for _, foreground in ipairs(self.foregrounds) do
|
||
|
|
foreground:SetShown(true);
|
||
|
|
foreground:SetVertexColor(.3, .3, .3);
|
||
|
|
end
|
||
|
|
self.FlowAnim1:Play();
|
||
|
|
self.FlowAnim2:Play();
|
||
|
|
self.FlowAnim3:Play();
|
||
|
|
self.FlowAnim4:Play();
|
||
|
|
self.FlowAnim5:Play();
|
||
|
|
self.FlowAnim6:Play();
|
||
|
|
elseif state == Enum.SoulbindNodeState.Selected then
|
||
|
|
self:DesaturateHierarchy(0);
|
||
|
|
for _, foreground in ipairs(self.foregrounds) do
|
||
|
|
foreground:SetShown(true);
|
||
|
|
foreground:SetVertexColor(.192, .686, .941);
|
||
|
|
end
|
||
|
|
self.FlowAnim1:Play();
|
||
|
|
self.FlowAnim2:Play();
|
||
|
|
self.FlowAnim3:Play();
|
||
|
|
self.FlowAnim4:Play();
|
||
|
|
self.FlowAnim5:Play();
|
||
|
|
self.FlowAnim6:Play();
|
||
|
|
end
|
||
|
|
end
|
||
|
|
function BtWLoadoutsSoulbindTreeNodeLinkMixin:GetState()
|
||
|
|
return self.state;
|
||
|
|
end
|
||
|
|
|
||
|
|
BtWLoadoutsSoulbindsMixin = {}
|
||
|
|
function BtWLoadoutsSoulbindsMixin:OnLoad()
|
||
|
|
self.RestrictionsDropDown:SetSupportedTypes("spec", "race")
|
||
|
|
self.RestrictionsDropDown:SetScript("OnChange", function ()
|
||
|
|
self:Update()
|
||
|
|
end)
|
||
|
|
|
||
|
|
self.temp = {}
|
||
|
|
self.nodes = CreateFramePool("BUTTON", self.Scroll:GetScrollChild(), "BtWLoadoutsSoulbindNodeTemplate");
|
||
|
|
self.links = CreateFramePool("FRAME", self.Scroll, "BtWLoadoutsSoulbindTreeNodeLinkTemplate");
|
||
|
|
self.nodesByID = {}
|
||
|
|
end
|
||
|
|
function BtWLoadoutsSoulbindsMixin:OnShow()
|
||
|
|
if not self.initialized then
|
||
|
|
UIDropDownMenu_SetWidth(self.SoulbindDropDown, 170);
|
||
|
|
UIDropDownMenu_JustifyText(self.SoulbindDropDown, "LEFT");
|
||
|
|
|
||
|
|
self.initialized = true;
|
||
|
|
end
|
||
|
|
end
|
||
|
|
function BtWLoadoutsSoulbindsMixin:ChangeSet(set)
|
||
|
|
self.set = set
|
||
|
|
self:Update()
|
||
|
|
end
|
||
|
|
function BtWLoadoutsSoulbindsMixin:UpdateSetName(value)
|
||
|
|
if self.set and self.set.name ~= not value then
|
||
|
|
self.set.name = value;
|
||
|
|
self:Update();
|
||
|
|
end
|
||
|
|
end
|
||
|
|
function BtWLoadoutsSoulbindsMixin:OnButtonClick(button)
|
||
|
|
CloseDropDownMenus()
|
||
|
|
if button.isAdd then
|
||
|
|
BtWLoadoutsHelpTipFlags["TUTORIAL_NEW_SET"] = true;
|
||
|
|
|
||
|
|
self.Name:ClearFocus()
|
||
|
|
self:ChangeSet(AddSet())
|
||
|
|
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 = DeleteSet,
|
||
|
|
})
|
||
|
|
else
|
||
|
|
StaticPopup_Show("BTWLOADOUTS_DELETESET", set.name, nil, {
|
||
|
|
set = set,
|
||
|
|
func = DeleteSet,
|
||
|
|
})
|
||
|
|
end
|
||
|
|
elseif button.isRefresh then
|
||
|
|
local set = self.set;
|
||
|
|
RefreshSet(set)
|
||
|
|
self:Update()
|
||
|
|
elseif button.isExport then
|
||
|
|
local set = self.set;
|
||
|
|
self:GetParent():SetExport(Internal.Export("soulbinds", set.setID))
|
||
|
|
elseif button.isActivate then
|
||
|
|
local set = self.set;
|
||
|
|
Internal.ActivateProfile({
|
||
|
|
soulbinds = {set.setID}
|
||
|
|
});
|
||
|
|
end
|
||
|
|
end
|
||
|
|
function BtWLoadoutsSoulbindsMixin:OnSidebarItemClick(button)
|
||
|
|
CloseDropDownMenus()
|
||
|
|
if button.isHeader then
|
||
|
|
button.collapsed[button.id] = not button.collapsed[button.id]
|
||
|
|
self:Update()
|
||
|
|
else
|
||
|
|
if IsModifiedClick("SHIFT") then
|
||
|
|
local set = GetSet(button.id);
|
||
|
|
Internal.ActivateProfile({
|
||
|
|
soulbinds = {button.id}
|
||
|
|
});
|
||
|
|
else
|
||
|
|
self.Name:ClearFocus();
|
||
|
|
self:ChangeSet(GetSet(button.id))
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
function BtWLoadoutsSoulbindsMixin:OnSidebarItemDoubleClick(button)
|
||
|
|
CloseDropDownMenus()
|
||
|
|
if button.isHeader then
|
||
|
|
return
|
||
|
|
end
|
||
|
|
|
||
|
|
local set = GetSet(button.id);
|
||
|
|
Internal.ActivateProfile({
|
||
|
|
soulbinds = {button.id}
|
||
|
|
});
|
||
|
|
end
|
||
|
|
function BtWLoadoutsSoulbindsMixin:OnSidebarItemDragStart(button)
|
||
|
|
CloseDropDownMenus()
|
||
|
|
if button.isHeader then
|
||
|
|
return
|
||
|
|
end
|
||
|
|
|
||
|
|
local icon = "INV_Misc_QuestionMark";
|
||
|
|
local set = GetSet(button.id);
|
||
|
|
local command = format("/btwloadouts activate soulbinds %d", button.id);
|
||
|
|
if set.specID then
|
||
|
|
icon = select(4, GetSpecializationInfoByID(set.specID));
|
||
|
|
end
|
||
|
|
|
||
|
|
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 BtWLoadoutsSoulbindsMixin:Update()
|
||
|
|
self:GetParent().TitleText:SetText(L["Soulbinds"]);
|
||
|
|
local sidebar = BtWLoadoutsFrame.Sidebar
|
||
|
|
|
||
|
|
sidebar:SetSupportedFilters("covenant", "spec", "class", "role", "race")
|
||
|
|
sidebar:SetSets(BtWLoadoutsSets.soulbinds)
|
||
|
|
sidebar:SetCollapsed(BtWLoadoutsCollapsed.soulbinds)
|
||
|
|
sidebar:SetCategories(BtWLoadoutsCategories.soulbinds)
|
||
|
|
sidebar:SetFilters(BtWLoadoutsFilters.soulbinds)
|
||
|
|
sidebar:SetSelected(self.set)
|
||
|
|
|
||
|
|
sidebar:Update()
|
||
|
|
self.set = sidebar:GetSelected()
|
||
|
|
local set = self.set
|
||
|
|
|
||
|
|
local showingNPE = BtWLoadoutsFrame:SetNPEShown(set == nil, L["Soulbinds"], L["Create soulbind trees for switching between soulbind paths, leave rows blank to not skip them. Conduits are not affected."])
|
||
|
|
|
||
|
|
self:GetParent().ExportButton:SetEnabled(true)
|
||
|
|
self:GetParent().DeleteButton:SetEnabled(true);
|
||
|
|
|
||
|
|
if not showingNPE then
|
||
|
|
local soulbindID = set.soulbindID;
|
||
|
|
local soulbindData = GetSoulbindData(soulbindID)
|
||
|
|
|
||
|
|
UpdateSetFilters(set)
|
||
|
|
sidebar:Update()
|
||
|
|
|
||
|
|
set.restrictions = set.restrictions or {}
|
||
|
|
self.RestrictionsDropDown:SetSelections(set.restrictions)
|
||
|
|
self.RestrictionsDropDown:SetLimitations()
|
||
|
|
self.RestrictionsButton:SetEnabled(true);
|
||
|
|
|
||
|
|
if not self.Name:HasFocus() then
|
||
|
|
self.Name:SetText(set.name or "");
|
||
|
|
end
|
||
|
|
|
||
|
|
UIDropDownMenu_SetSelectedValue(self.SoulbindDropDown, soulbindID);
|
||
|
|
UIDropDownMenu_SetText(self.SoulbindDropDown, soulbindData.name);
|
||
|
|
UIDropDownMenu_EnableDropDown(self.SoulbindDropDown);
|
||
|
|
|
||
|
|
local selected = set.nodes;
|
||
|
|
self.nodes:ReleaseAll()
|
||
|
|
self.links:ReleaseAll()
|
||
|
|
wipe(self.nodesByID)
|
||
|
|
for _,node in ipairs(soulbindData.tree.nodes) do
|
||
|
|
local nodeFrame = self.nodes:Acquire()
|
||
|
|
nodeFrame:SetFrameLevel(6)
|
||
|
|
nodeFrame:SetPoint("TOP", (node.column - 1) * (nodeFrame:GetWidth() + 30), -node.row * (nodeFrame:GetHeight() + 12) - 17)
|
||
|
|
nodeFrame:SetNode(node)
|
||
|
|
nodeFrame:SetSelected(selected[node.ID])
|
||
|
|
nodeFrame:Show()
|
||
|
|
|
||
|
|
self.nodesByID[node.ID] = nodeFrame
|
||
|
|
end
|
||
|
|
for _, linkFromFrame in pairs(self.nodesByID) do
|
||
|
|
if linkFromFrame.node.row > 0 then
|
||
|
|
for _,parentID in ipairs(linkFromFrame.node.parentNodeIDs) do
|
||
|
|
local linkToFrame = self.nodesByID[parentID]
|
||
|
|
linkToFrame.node.childNodeIDs = linkToFrame.node.childNodeIDs or {}
|
||
|
|
linkToFrame.node.childNodeIDs[#linkToFrame.node.childNodeIDs+1] = linkFromFrame.node.ID
|
||
|
|
|
||
|
|
local linkFrame = self.links:Acquire()
|
||
|
|
linkFrame:SetFrameLevel(2)
|
||
|
|
|
||
|
|
local toColumn = linkToFrame.node.column;
|
||
|
|
local fromColumn = linkFromFrame.node.column;
|
||
|
|
local offset = toColumn - fromColumn;
|
||
|
|
|
||
|
|
if offset < 0 then
|
||
|
|
linkFrame:SetPoint("BOTTOMRIGHT", linkFromFrame, "CENTER");
|
||
|
|
linkFrame:SetPoint("TOPLEFT", linkToFrame, "CENTER");
|
||
|
|
elseif offset > 0 then
|
||
|
|
linkFrame:SetPoint("BOTTOMLEFT", linkFromFrame, "CENTER");
|
||
|
|
linkFrame:SetPoint("TOPRIGHT", linkToFrame, "CENTER");
|
||
|
|
else
|
||
|
|
linkFrame:SetPoint("BOTTOM", linkFromFrame, "CENTER");
|
||
|
|
linkFrame:SetPoint("TOP", linkToFrame, "CENTER");
|
||
|
|
end
|
||
|
|
|
||
|
|
local direction;
|
||
|
|
local diagonal = toColumn ~= fromColumn;
|
||
|
|
if diagonal then
|
||
|
|
direction = SoulbindTreeLinkDirections.Converge
|
||
|
|
else
|
||
|
|
direction = SoulbindTreeLinkDirections.Vertical;
|
||
|
|
end
|
||
|
|
|
||
|
|
local quarter = (math.pi * 0.5);
|
||
|
|
local angle = RegionUtil.CalculateAngleBetween(linkToFrame, linkFromFrame) - quarter;
|
||
|
|
|
||
|
|
linkFrame:Init(direction, angle);
|
||
|
|
linkFrame:SetState((selected[linkToFrame.node.ID] and selected[linkFromFrame.node.ID]) and Enum.SoulbindNodeState.Selected or Enum.SoulbindNodeState.Unselected);
|
||
|
|
linkFrame:Show();
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
local playerCovenantID = GetActiveCovenantID()
|
||
|
|
local playerSoulbindID = GetActiveSoulbindID()
|
||
|
|
self:GetParent().RefreshButton:SetEnabled(soulbindID == playerSoulbindID)
|
||
|
|
self:GetParent().ActivateButton:SetEnabled(soulbindData.covenantID == playerCovenantID and soulbindData.unlocked);
|
||
|
|
|
||
|
|
local helpTipBox = self:GetParent().HelpTipBox;
|
||
|
|
helpTipBox:Hide();
|
||
|
|
else
|
||
|
|
local soulbindID = GetActiveSoulbindID()
|
||
|
|
if soulbindID == 0 then
|
||
|
|
soulbindID = 7 -- Default to pelagos because why not
|
||
|
|
end
|
||
|
|
local soulbindData = GetSoulbindData(soulbindID)
|
||
|
|
|
||
|
|
self.Name:SetText(format(L["New %s Set"], soulbindData.name));
|
||
|
|
|
||
|
|
self.nodes:ReleaseAll()
|
||
|
|
self.links:ReleaseAll()
|
||
|
|
for _,node in ipairs(soulbindData.tree.nodes) do
|
||
|
|
local nodeFrame = self.nodes:Acquire()
|
||
|
|
nodeFrame:SetFrameLevel(6)
|
||
|
|
nodeFrame:SetPoint("TOP", self.Inset, "TOP", (node.column - 1) * (nodeFrame:GetWidth() + 30), -node.row * (nodeFrame:GetHeight() + 12) - 17)
|
||
|
|
nodeFrame:SetNode(node)
|
||
|
|
nodeFrame:SetSelected(false)
|
||
|
|
nodeFrame:Show()
|
||
|
|
|
||
|
|
self.nodesByID[node.ID] = nodeFrame
|
||
|
|
end
|
||
|
|
|
||
|
|
local helpTipBox = self:GetParent().HelpTipBox;
|
||
|
|
helpTipBox:Hide();
|
||
|
|
end
|
||
|
|
end
|
||
|
|
function BtWLoadoutsSoulbindsMixin:SetSetByID(setID)
|
||
|
|
self.set = GetSet(setID)
|
||
|
|
end
|