Skip to content

Commit 43a1a98

Browse files
committed
Implemented importing icy-veins builds, as leveling builds; and made performance improvements to the level slider
1 parent 318851d commit 43a1a98

File tree

5 files changed

+216
-17
lines changed

5 files changed

+216
-17
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
local name, ns = ...
2+
3+
--- @class TalentViewerIcyVeinsImport
4+
local IcyVeinsImport = ns.IcyVeinsImport
5+
6+
--- @type TalentViewer
7+
local TalentViewer = ns.TalentViewer
8+
9+
local skillMappings = tInvert{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'Ă', 'ă', 'Â', 'â', 'Î', 'î', 'Ș', 'ș', 'Ț', 'ț', 'ë', 'é', 'ê', 'ï', 'ô', 'β', 'Γ', 'γ', 'Δ', 'δ', 'ε', 'ζ'};
10+
11+
--- @type LibTalentTree-1.0
12+
local libTT = LibStub('LibTalentTree-1.0');
13+
14+
--- @param text string
15+
--- @return boolean
16+
function IcyVeinsImport:IsTalentUrl(text)
17+
-- example URL https://www.icy-veins.com/wow/dragonflight-talent-calculator#6--250$foo+bar*
18+
return not not text:match('^https?://www%.icy%-veins%.com/wow/dragonflight%-talent%-calculator%#%d+%-%-%d+%$[^+]-%+[^*]-%*');
19+
end
20+
21+
function IcyVeinsImport:ShowImportError(errorString)
22+
StaticPopup_Show("TALENT_VIEWER_LOADOUT_IMPORT_ERROR_DIALOG", errorString);
23+
end
24+
25+
--- @param fullUrl string
26+
function IcyVeinsImport:ImportUrl(fullUrl)
27+
if not self:IsTalentUrl(fullUrl) then
28+
self:ShowImportError("Invalid URL");
29+
30+
return nil;
31+
end
32+
33+
local classID, specID, levelingOrder = self:ParseUrl(fullUrl);
34+
if not levelingOrder or not classID or not specID then
35+
self:ShowImportError("Invalid URL");
36+
37+
return nil;
38+
end
39+
40+
TalentViewer:SelectSpec(classID, specID); -- also clears the viewer's state
41+
TalentViewer:StartRecordingLevelingBuild(); -- just to be sure ;)
42+
43+
if true then
44+
TalentViewer:ImportLevelingBuild(levelingOrder);
45+
TalentViewer:ApplyLevelingBuild(TalentViewer:GetCurrentLevelingBuildID(), ns.MAX_LEVEL, true);
46+
return
47+
end
48+
49+
local backup = TalentViewer.db.ignoreRestrictions;
50+
TalentViewer.db.ignoreRestrictions = true;
51+
52+
local talentFrame = TalentViewer:GetTalentFrame();
53+
for level = 10, 20 or ns.MAX_LEVEL do
54+
local entry = levelingOrder[level];
55+
if entry then
56+
if entry.entryID then
57+
talentFrame:SetSelection(entry.nodeID, entry.entryID);
58+
else
59+
talentFrame:PurchaseRank(entry.nodeID);
60+
end
61+
end
62+
end
63+
64+
TalentViewer.db.ignoreRestrictions = backup;
65+
end
66+
67+
--- @param url string
68+
--- @return nil|number # classID
69+
--- @return nil|number # specID
70+
--- @return nil|table<number, TalentViewer_LevelingBuildEntry> # [level] = entry
71+
function IcyVeinsImport:ParseUrl(url)
72+
local dataSection = url:match('#(.*)');
73+
74+
local classID, specID, classData, specData = dataSection:match('^(%d+)%-%-(%d+)%$([^+]-)%+([^*]-)%*');
75+
classID = tonumber(classID);
76+
specID = tonumber(specID);
77+
78+
local treeID = classID and libTT:GetClassTreeId(classID);
79+
80+
if not classID or not specID or not classData or not specData then
81+
return nil;
82+
end
83+
84+
local classNodes, specNodes = self:GetClassAndSpecNodeIDs(specID, treeID);
85+
86+
local levelingOrder = {};
87+
self:ParseDataSegment(8, classData, levelingOrder, classNodes);
88+
self:ParseDataSegment(9, specData, levelingOrder, specNodes);
89+
90+
return classID, specID, levelingOrder;
91+
end
92+
93+
function IcyVeinsImport:ParseDataSegment(startingLevel, dataSegment, levelingOrder, nodes)
94+
local splitDataSegment = {};
95+
for char in string.gmatch(dataSegment, '.') do
96+
table.insert(splitDataSegment, char);
97+
end
98+
local level = startingLevel;
99+
local rankByNodeID = {};
100+
for index, char in ipairs(splitDataSegment) do
101+
if char ~= '0' and char ~= '1' then
102+
level = level + 2;
103+
local nextChar = splitDataSegment[index + 1];
104+
local mappingIndex = skillMappings[char];
105+
106+
local nodeID = nodes[mappingIndex];
107+
local entryIndex = nextChar == '1' and 2 or 1;
108+
local nodeInfo = libTT:GetNodeInfo(nodeID);
109+
local entry = nodeInfo.type == Enum.TraitNodeType.Selection and nodeInfo.entryIDs and nodeInfo.entryIDs[entryIndex] or nil;
110+
rankByNodeID[nodeID] = (rankByNodeID[nodeID] or 0) + 1;
111+
112+
levelingOrder[level] = {
113+
nodeID = nodeID,
114+
entryID = entry,
115+
targetRank = rankByNodeID[nodeID],
116+
};
117+
end
118+
end
119+
end
120+
121+
IcyVeinsImport.classAndSpecNodeCache = {};
122+
function IcyVeinsImport:GetClassAndSpecNodeIDs(specID, treeID)
123+
if self.classAndSpecNodeCache[specID] then
124+
return unpack(self.classAndSpecNodeCache[specID]);
125+
end
126+
127+
local nodes = C_Traits.GetTreeNodes(treeID);
128+
129+
local classNodes = {};
130+
local specNodes = {};
131+
132+
for _, nodeID in ipairs(nodes or {}) do
133+
local nodeInfo = libTT:GetNodeInfo(nodeID);
134+
if nodeInfo.isClassNode then
135+
table.insert(classNodes, nodeID);
136+
elseif libTT:IsNodeVisibleForSpec(specID, nodeID) and nodeInfo.maxRanks > 0 then
137+
table.insert(specNodes, nodeID);
138+
end
139+
end
140+
141+
table.sort(classNodes);
142+
table.sort(specNodes);
143+
144+
self.classAndSpecNodeCache[specID] = {classNodes, specNodes};
145+
146+
return classNodes, specNodes;
147+
end
148+

TalentTreeViewer/TalentTreeViewer.toc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ locale\locale.xml
1515
TalentViewer.lua
1616
TalentViewerUIMixin.lua
1717
ImportExport.lua
18+
IcyVeinsImport.lua
1819
TalentViewerUI.xml

TalentTreeViewer/TalentViewer.lua

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ local TalentViewer = {
1414
purchasedRanks = {},
1515
selectedEntries = {},
1616
currencySpending = {},
17+
_ns = ns,
1718
}
1819
_G.TalentViewer = TalentViewer
1920

2021
ns.ImportExport = {}
22+
ns.IcyVeinsImport = {}
2123
ns.TalentViewer = TalentViewer
2224

2325
--- @class TalentViewer_Cache
@@ -224,14 +226,20 @@ end
224226

225227
function TalentViewer:ImportLoadout(importString)
226228
--- @type TalentViewerImportExport
227-
local ImportExport = ns.ImportExport
229+
local ImportExport = ns.ImportExport;
230+
--- @type TalentViewerIcyVeinsImport
231+
local IcyVeinsImport = ns.IcyVeinsImport;
228232

229233
if TalentViewer_DF:IsShown() then
230-
TalentViewer_DF:Raise()
234+
TalentViewer_DF:Raise();
231235
else
232-
TalentViewer:ToggleTalentView()
236+
TalentViewer:ToggleTalentView();
233237
end
234-
ImportExport:ImportLoadout(importString)
238+
if IcyVeinsImport:IsTalentUrl(importString) then
239+
IcyVeinsImport:ImportUrl(importString);
240+
else
241+
ImportExport:ImportLoadout(importString);
242+
end
235243
end
236244

237245
function TalentViewer:ExportLoadout()
@@ -290,6 +298,9 @@ function TalentViewer:InitFrame()
290298
self:InitLevelingBuildUIs();
291299
end
292300

301+
--- Reset the talent tree, and select the specified spec
302+
--- @param classId number
303+
--- @param specId number
293304
function TalentViewer:SelectSpec(classId, specId)
294305
assert(type(classId) == 'number', 'classId must be a number')
295306
assert(type(specId) == 'number', 'specId must be a number')
@@ -489,6 +500,8 @@ function TalentViewer:ApplyLevelingBuild(buildID, level, lockLevelingBuild)
489500
self:GetTalentFrame():SetLevelingBuildID(buildID);
490501
self:GetTalentFrame():ApplyLevelingBuild(level, lockLevelingBuild);
491502
self.recordingInfo.active = true;
503+
504+
self:GetTalentFrame().LevelingBuildLevelSlider:SetValue(level);
492505
end
493506

494507
--- @return table<number, TalentViewer_LevelingBuildEntry> # [level] = entry
@@ -557,7 +570,8 @@ function TalentViewer:ClearLevelingBuild()
557570
end
558571
self.levelingBuilds[self.selectedSpecId] = self.levelingBuilds[self.selectedSpecId] or {};
559572
if
560-
self.levelingBuilds[self.selectedSpecId][self.recordingInfo.buildID] == self.recordingInfo.entries
573+
self.levelingBuilds[self.selectedSpecId][self.recordingInfo.buildID]
574+
and self.levelingBuilds[self.selectedSpecId][self.recordingInfo.buildID].entries == self.recordingInfo.entries
561575
and not next(self.recordingInfo.entries[1])
562576
and not next(self.recordingInfo.entries[2])
563577
then -- the build is already empty, no point resetting it
@@ -646,24 +660,32 @@ function TalentViewer:InitLevelingBuildUIs()
646660
};
647661
local currentValue = 9;
648662
slider:Init(currentValue, minValue, maxValue, steps, formatters);
649-
slider:RegisterCallback(MinimalSliderWithSteppersMixin.Event.OnValueChanged, function(_, value)
650-
if value ~= currentValue then
651-
currentValue = value;
652-
self:ApplyLevelingBuild(self:GetCurrentLevelingBuildID(), value, true);
653-
self:StopRecordingLevelingBuild();
654-
end
655-
end);
656-
slider:RegisterCallback(MinimalSliderWithSteppersMixin.Event.OnInteractStart, function()
663+
664+
local callingFromSlider = false;
665+
local function onValueChange()
666+
local value = slider:GetValue();
667+
if callingFromSlider or value == currentValue then return; end
668+
currentValue = value;
669+
callingFromSlider = true;
670+
self:ApplyLevelingBuild(self:GetCurrentLevelingBuildID(), value, true);
671+
callingFromSlider = false;
672+
self:StopRecordingLevelingBuild();
673+
end
674+
675+
slider:RegisterCallback(TalentViewer_LevelingSliderMixin.Event.OnDragStop, onValueChange);
676+
slider:RegisterCallback(TalentViewer_LevelingSliderMixin.Event.OnStepperClicked, onValueChange);
677+
slider:RegisterCallback(TalentViewer_LevelingSliderMixin.Event.OnEnter, function()
657678
GameTooltip:SetOwner(slider, 'ANCHOR_RIGHT', 0, 0);
658679
GameTooltip:SetText(L['Leveling build']);
659680
GameTooltip:AddLine(L['Select the level to apply the leveling build to']);
660681
GameTooltip:AddLine(L['This will lag out your game!']);
661682
GameTooltip:Show();
662683
end);
663-
slider:RegisterCallback(MinimalSliderWithSteppersMixin.Event.OnInteractEnd, function()
684+
slider:RegisterCallback(TalentViewer_LevelingSliderMixin.Event.OnLeave, function()
664685
GameTooltip:Hide();
665686
end);
666687

688+
667689
local dropDownButton = self:GetTalentFrame().LevelingBuildDropDownButton;
668690
dropDownButton:HookScript('OnEnter', function()
669691
GameTooltip:SetOwner(dropDownButton, 'ANCHOR_RIGHT', 0, 0);

TalentTreeViewer/TalentViewerUI.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@
399399
<Anchor point="RIGHT" relativeKey="$parent.BottomBar" relativePoint="RIGHT" x="-30" y="3"/>
400400
</Anchors>
401401
</Button>
402-
<Slider parentKey="LevelingBuildLevelSlider" inherits="MinimalSliderWithSteppersTemplate" hidden="false">
402+
<Slider parentKey="LevelingBuildLevelSlider" inherits="MinimalSliderWithSteppersTemplate" mixin="TalentViewer_LevelingSliderMixin" hidden="false">
403403
<Size x="150" y="25"/>
404404
<Anchors>
405405
<Anchor point="TOPRIGHT" relativeKey="$parent.LevelingBuildDropDownButton" relativePoint="BOTTOMRIGHT" x="0" y="-3"/>

TalentTreeViewer/TalentViewerUIMixin.lua

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,34 @@ do
6868
end
6969
end
7070

71+
TalentViewer_LevelingSliderMixin = CreateFromMixins(MinimalSliderWithSteppersMixin);
72+
local LevelingSliderMixin = TalentViewer_LevelingSliderMixin;
73+
LevelingSliderMixin:GenerateCallbackEvents(
74+
{
75+
'OnDragStop',
76+
'OnStepperClicked',
77+
'OnEnter',
78+
'OnLeave',
79+
}
80+
);
81+
82+
function LevelingSliderMixin:GetValue()
83+
return self.Slider:GetValue();
84+
end
85+
86+
function LevelingSliderMixin:OnStepperClicked(...)
87+
MinimalSliderWithSteppersMixin.OnStepperClicked(self, ...);
88+
self:TriggerEvent(self.Event.OnStepperClicked, ...);
89+
end
90+
91+
function LevelingSliderMixin:OnLoad()
92+
MinimalSliderWithSteppersMixin.OnLoad(self);
93+
94+
self.Slider:HookScript('OnEnter', function() self:TriggerEvent(self.Event.OnEnter); end);
95+
self.Slider:HookScript('OnLeave', function() self:TriggerEvent(self.Event.OnLeave); end);
96+
self.Slider:HookScript('OnMouseUp', function() self:TriggerEvent(self.Event.OnDragStop); end);
97+
end
98+
7199
--- @class TalentViewer_LevelingOrderFrame
72100
local LevelingOrderMixin = {};
73101
--- @param order number[]
@@ -857,11 +885,11 @@ do
857885
preferredIndex = 3,
858886
};
859887
StaticPopupDialogs['TalentViewerImportDialog'] = {
860-
text = HUD_CLASS_TALENTS_IMPORT_DIALOG_TITLE,
888+
text = HUD_CLASS_TALENTS_IMPORT_DIALOG_TITLE .. '\n' .. L['Icy-veins calculator links are also supported!'],
861889
button1 = OKAY,
862890
button2 = CLOSE,
863891
OnAccept = function(dialog)
864-
ImportExport:ImportLoadout(dialog.editBox:GetText());
892+
TalentViewer:ImportLoadout(dialog.editBox:GetText());
865893
dialog:Hide();
866894
end,
867895
OnShow = function(dialog)

0 commit comments

Comments
 (0)