Skip to content

Commit 13ab69c

Browse files
committed
More progress on leveling build support
1 parent 38fbd82 commit 13ab69c

File tree

5 files changed

+270
-63
lines changed

5 files changed

+270
-63
lines changed

TalentTreeViewer/ImportExport.lua

Lines changed: 63 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -128,22 +128,23 @@ end
128128
--- @param treeID number
129129
--- @param levelingBuild TalentViewer_LevelingBuildEntry[]
130130
function ImportExport:WriteLevelingBuildContent(exportStream, treeID, levelingBuild)
131-
--- @type table<number, number[]>
132-
local byNodes = {};
133-
for index, entry in ipairs(levelingBuild) do
134-
byNodes[entry.nodeID] = byNodes[entry.nodeID] or {};
135-
table.insert(byNodes[entry.nodeID], index);
136-
end
137-
131+
local purchasedNodesOrder = {};
138132
local treeNodes = C_Traits.GetTreeNodes(treeID);
133+
local i = 0;
139134
for _, treeNodeID in ipairs(treeNodes) do
140-
local entries = byNodes[treeNodeID];
141-
if entries then
142-
for _, entry in ipairs(entries) do
143-
exportStream:AddValue(7, entry);
144-
end
135+
local treeNode = getNodeInfo(treeNodeID);
136+
if treeNode.ranksPurchased > 0 then
137+
i = i + 1;
138+
purchasedNodesOrder[treeNode.ID] = i;
145139
end
146140
end
141+
142+
local temp = {};
143+
for _, entry in ipairs(levelingBuild) do
144+
table.insert(temp, {order=purchasedNodesOrder[entry.nodeID], entry=entry})
145+
exportStream:AddValue(7, purchasedNodesOrder[entry.nodeID]);
146+
end
147+
DevTool:AddData({purchasedNodesOrder,temp});
147148
end
148149

149150
function ImportExport:GetLoadoutExportString()
@@ -173,6 +174,7 @@ function ImportExport:ShowImportError(errorString)
173174
StaticPopup_Show("TALENT_VIEWER_LOADOUT_IMPORT_ERROR_DIALOG", errorString);
174175
end
175176

177+
--- @param importText string
176178
function ImportExport:ImportLoadout(importText)
177179

178180
local importStream = ExportUtil.MakeImportDataStream(importText);
@@ -198,18 +200,60 @@ function ImportExport:ImportLoadout(importText)
198200
local loadoutContent = self:ReadLoadoutContent(importStream, treeId);
199201
local loadoutEntryInfo = self:ConvertToImportLoadoutEntryInfo(treeId, loadoutContent);
200202

201-
local hasLevelingBuildData, recordingIsActive = false, TalentViewer:IsRecordingLevelingBuild();
202-
if not hasLevelingBuildData then
203-
TalentViewer.recordingInfo.active = false
204-
end
203+
TalentViewer:StopRecordingLevelingBuild();
205204

206205
TalentViewer:GetTalentFrame():ImportLoadout(loadoutEntryInfo);
207206

208-
TalentViewer.recordingInfo.active = recordingIsActive;
207+
local _, _, talentBuild, levelingBuild = importText:find(LEVELING_EXPORT_STRING_PATERN:format("(.*)", "(.*)"):gsub("%-", "%%-"));
208+
if levelingBuild then
209+
local levelingImportStream = ExportUtil.MakeImportDataStream(levelingBuild);
210+
local levelingHeaderValid, levelingSerializationVersion = self:ReadLevelingExportHeader(levelingImportStream);
211+
if levelingHeaderValid and levelingSerializationVersion == LEVELING_BUILD_SERIALIZATION_VERSION then
212+
local levelingBuildEntries = self:ReadLevelingBuildContent(levelingImportStream, treeId, loadoutEntryInfo);
213+
TalentViewer:ImportLevelingBuild(levelingBuildEntries);
214+
end
215+
else
216+
TalentViewer:ClearLevelingBuild();
217+
end
209218

210219
return true;
211220
end
212221

222+
function ImportExport:ReadLevelingExportHeader(importStream)
223+
local headerBitWidth = self.levelingBitWidthVersion;
224+
local importStreamTotalBits = importStream:GetNumberOfBits();
225+
if( importStreamTotalBits < headerBitWidth) then
226+
return false, 0;
227+
end
228+
local serializationVersion = importStream:ExtractValue(self.levelingBitWidthVersion);
229+
return true, serializationVersion;
230+
end
231+
232+
--- @param loadoutEntryInfo TalentViewer_LoadoutEntryInfo[]
233+
--- @return TalentViewer_LevelingBuildEntry[]
234+
function ImportExport:ReadLevelingBuildContent(importStream, treeID, loadoutEntryInfo)
235+
local results = {};
236+
237+
local numberOfEntries = #loadoutEntryInfo;
238+
local purchasesByNodeID = {};
239+
for i = 1, numberOfEntries do
240+
local orderIndex = importStream:ExtractValue(7);
241+
if not orderIndex then break; end
242+
243+
local entry = loadoutEntryInfo[orderIndex];
244+
if entry then
245+
purchasesByNodeID[entry.nodeID] = (purchasesByNodeID[entry.nodeID] or 0) + 1;
246+
local result = {};
247+
result.nodeID = entry.nodeID;
248+
result.entryID = entry.isChoiceNode and entry.selectionEntryID;
249+
result.targetRank = purchasesByNodeID[entry.nodeID];
250+
results[i] = result;
251+
end
252+
end
253+
DevTool:AddData({results,purchasesByNodeID,loadoutEntryInfo});
254+
return results;
255+
end
256+
213257
function ImportExport:WriteLoadoutHeader(exportStream, serializationVersion, specID)
214258
exportStream:AddValue(self.bitWidthHeaderVersion, serializationVersion);
215259
exportStream:AddValue(self.bitWidthSpecID, specID);
@@ -234,7 +278,8 @@ function ImportExport:ReadLoadoutHeader(importStream)
234278
return true, serializationVersion, specID, treeHash;
235279
end
236280

237-
-- converts from compact bit-packing format to LoadoutEntryInfo format to pass to ImportLoadout API
281+
--- converts from compact bit-packing format to LoadoutEntryInfo format to pass to ImportLoadout API
282+
--- @return TalentViewer_LoadoutEntryInfo[]
238283
function ImportExport:ConvertToImportLoadoutEntryInfo(treeID, loadoutContent)
239284
local results = {};
240285
local treeNodes = C_Traits.GetTreeNodes(treeID);

TalentTreeViewer/TalentViewer.lua

Lines changed: 129 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ function TalentViewer:ApplyCurrencySpending(treeCurrency)
9999
return treeCurrency
100100
end
101101

102-
function TalentViewer:ResetTree()
102+
--- @param lockLevelingBuild ?boolean # by default, a new leveling build is created and activated when this function is called, passing true will prevent that
103+
function TalentViewer:ResetTree(lockLevelingBuild)
103104
local talentFrame = self:GetTalentFrame()
104105
wipe(self.purchasedRanks);
105106
wipe(self.selectedEntries);
@@ -111,8 +112,13 @@ function TalentViewer:ResetTree()
111112
talentFrame:UpdateClassVisuals();
112113
talentFrame:UpdateSpecBackground();
113114
talentFrame:UpdateLevelingBuildHighlights();
114-
self:ClearLevelingBuild();
115-
self:StartRecordingLevelingBuild();
115+
local isRecordingLevelingBuild = self:IsRecordingLevelingBuild();
116+
if not lockLevelingBuild then
117+
self:ClearLevelingBuild();
118+
if isRecordingLevelingBuild then
119+
self:StartRecordingLevelingBuild();
120+
end
121+
end
116122
end
117123

118124
function TalentViewer:GetActiveRank(nodeID)
@@ -273,14 +279,15 @@ function TalentViewer:ToggleTalentView()
273279
end
274280

275281
function TalentViewer:InitFrame()
276-
if self.frameInitialized then return end
277-
self.frameInitialized = true
278-
UpdateScaleForFit(TalentViewer_DF, 200, 270)
279-
table.insert(UISpecialFrames, 'TalentViewer_DF')
280-
TalentViewer_DFInset:Hide()
281-
self:InitDropDown()
282-
self:InitCheckbox()
283-
self:InitSpecSelection()
282+
if self.frameInitialized then return; end
283+
self.frameInitialized = true;
284+
UpdateScaleForFit(TalentViewer_DF, 200, 270);
285+
table.insert(UISpecialFrames, 'TalentViewer_DF');
286+
TalentViewer_DFInset:Hide();
287+
self:InitDropDown();
288+
self:InitCheckbox();
289+
self:InitSpecSelection();
290+
self:InitLevelingBuildUIs();
284291
end
285292

286293
function TalentViewer:SelectSpec(classId, specId)
@@ -434,26 +441,36 @@ end
434441
--- @param buildID number
435442
--- @return nil|TalentViewer_LevelingBuildEntry[]
436443
function TalentViewer:GetLevelingBuild(buildID)
437-
return self.levelingBuilds[buildID] or nil;
444+
return self.levelingBuilds[self.selectedSpecId] and self.levelingBuilds[self.selectedSpecId][buildID] or nil;
438445
end
439446

440-
function TalentViewer:ApplyLevelingBuild(buildID, level)
447+
--- @param lockLevelingBuild boolean # by default, a new leveling build is created and activated when this function is called, passing true will prevent that
448+
function TalentViewer:ApplyLevelingBuild(buildID, level, lockLevelingBuild)
441449
local buildEntries = self:GetLevelingBuild(buildID);
442-
if (not buildEntries or not next(buildEntries)) then
450+
if (not buildEntries) then
443451
return;
444452
end
445453

446-
self.recordingInfo.active = false; -- todo: fix
447-
454+
self.recordingInfo.buildID = buildID;
455+
self.recordingInfo.active = false;
448456
self:GetTalentFrame():SetLevelingBuildID(buildID);
449-
self:GetTalentFrame():ApplyLevelingBuild(level);
457+
self:GetTalentFrame():ApplyLevelingBuild(level, lockLevelingBuild);
450458
self.recordingInfo.active = true;
451459
end
452460

461+
--- @param buildEntries TalentViewer_LevelingBuildEntry[]
462+
function TalentViewer:ImportLevelingBuild(buildEntries)
463+
self:ClearLevelingBuild();
464+
for _, entry in ipairs(buildEntries) do
465+
self:RecordLevelingEntry(entry.nodeID, entry.targetRank, entry.entryID);
466+
end
467+
end
468+
453469
function TalentViewer:StartRecordingLevelingBuild()
454470
self.recordingInfo.active = true;
455471
self:GetTalentFrame().StartRecordingButton:Hide();
456472
self:GetTalentFrame().StopRecordingButton:Show();
473+
self:ApplyLevelingBuild(self:GetCurrentLevelingBuildID(), ns.MAX_LEVEL, true);
457474
end
458475

459476
function TalentViewer:StopRecordingLevelingBuild()
@@ -467,9 +484,11 @@ function TalentViewer:ClearLevelingBuild()
467484
button:SetOrder({});
468485
end
469486
self.recordingInfo = CopyTable(defaultRecordingInfo);
470-
self:StopRecordingLevelingBuild();
471-
table.insert(self.levelingBuilds, self.recordingInfo.entries);
472-
self.recordingInfo.buildID = #self.levelingBuilds;
487+
self.levelingBuilds[self.selectedSpecId] = self.levelingBuilds[self.selectedSpecId] or {};
488+
table.insert(self.levelingBuilds[self.selectedSpecId], self.recordingInfo.entries);
489+
self.recordingInfo.buildID = #self.levelingBuilds[self.selectedSpecId];
490+
491+
self:GetTalentFrame():SetLevelingBuildID(self.recordingInfo.buildID);
473492
end
474493

475494
function TalentViewer:IsRecordingLevelingBuild()
@@ -514,6 +533,96 @@ function TalentViewer:UpdateRecordedLevelingChoiceEntry(nodeID, entryID)
514533
end
515534
end
516535

536+
function TalentViewer:InitLevelingBuildUIs()
537+
local slider = self:GetTalentFrame().LevelingBuildLevelSlider;
538+
local minValue = 9;
539+
local maxValue = ns.MAX_LEVEL;
540+
local steps = maxValue - minValue;
541+
local formatters = {
542+
[MinimalSliderWithSteppersMixin.Label.Left] = function() return L['Level'] end,
543+
[MinimalSliderWithSteppersMixin.Label.Right] = function(value) return value end,
544+
};
545+
local currentValue = 9;
546+
slider:Init(currentValue, minValue, maxValue, steps, formatters);
547+
slider:RegisterCallback(MinimalSliderWithSteppersMixin.Event.OnValueChanged, function(_, value)
548+
if value ~= currentValue then
549+
currentValue = value;
550+
self:ApplyLevelingBuild(self:GetCurrentLevelingBuildID(), value, true);
551+
self:StopRecordingLevelingBuild();
552+
end
553+
end);
554+
slider:RegisterCallback(MinimalSliderWithSteppersMixin.Event.OnInteractStart, function(_, value)
555+
GameTooltip:SetOwner(slider, "ANCHOR_RIGHT", 0, 0);
556+
GameTooltip:SetText(L['Leveling build']);
557+
GameTooltip:AddLine(L['Select the level to apply the leveling build to']);
558+
GameTooltip:AddLine(L['This will lag out your game!']);
559+
GameTooltip:Show();
560+
end);
561+
slider:RegisterCallback(MinimalSliderWithSteppersMixin.Event.OnInteractEnd, function(_, value)
562+
GameTooltip:Hide();
563+
end);
564+
565+
local dropDownButton = self:GetTalentFrame().LevelingBuildDropDownButton;
566+
dropDownButton:HookScript('OnEnter', function(self)
567+
GameTooltip:SetOwner(self, "ANCHOR_RIGHT", 0, 0);
568+
GameTooltip:SetText(L['Leveling build']);
569+
GameTooltip:AddLine(L['Select a leveling build to apply']);
570+
GameTooltip:AddLine(L['This will reset your current talent choices!']);
571+
GameTooltip:Show();
572+
end);
573+
574+
local dropDown = LibDD:Create_UIDropDownMenu(nil, TalentViewer_DF);
575+
576+
dropDownButton = Mixin(dropDownButton, DropDownToggleButtonMixin);
577+
dropDownButton:OnLoad_Intrinsic();
578+
local function buildMenu()
579+
self.menuListLevelingBuilds = {};
580+
local menu = self.menuListLevelingBuilds;
581+
table.insert(menu, {
582+
text = 'Leveling builds can be saved and loaded with TalentLoadoutManager',
583+
notClickable = true,
584+
notCheckable = true,
585+
});
586+
table.insert(menu, {
587+
text = 'You can also export/import leveling builds, or link them in chat',
588+
notClickable = true,
589+
notCheckable = true,
590+
});
591+
if (not IsAddOnLoaded('TalentLoadoutManager')) then
592+
table.insert(menu, {
593+
text = 'Click to download TalentLoadoutManager',
594+
notCheckable = true,
595+
func = function()
596+
StaticPopup_Show("TalentViewerExportDialog", nil, nil, 'https://www.curseforge.com/wow/addons/talent-loadout-manager');
597+
end,
598+
});
599+
end
600+
for buildID, buildEntries in ipairs(self.levelingBuilds[self.selectedSpecId] or {}) do
601+
table.insert(menu, {
602+
text = string.format(
603+
'Leveling build %d (%d points spent)',
604+
buildID,
605+
#buildEntries
606+
),
607+
func = function(_, buildID)
608+
self:ApplyLevelingBuild(buildID, currentValue, true);
609+
self:StopRecordingLevelingBuild();
610+
end,
611+
checked = self:GetCurrentLevelingBuildID() == buildID,
612+
arg1 = buildID,
613+
});
614+
end
615+
end
616+
dropDownButton:SetScript('OnMouseDown', function(self)
617+
buildMenu();
618+
LibDD:ToggleDropDownMenu(1, nil, dropDown, self, 5, 0, TalentViewer.menuListLevelingBuilds or nil);
619+
end)
620+
621+
dropDown:Hide();
622+
buildMenu();
623+
LibDD:EasyMenu(self.menuListLevelingBuilds, dropDown, dropDown, 0, 0);
624+
end
625+
517626
-------------------------
518627
--- Button highlights ---
519628
-------------------------

0 commit comments

Comments
 (0)