|
| 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 | + |
0 commit comments