Guest User

PassiveTreeView.lua

a guest
Feb 3rd, 2020
629
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 26.25 KB | None | 0 0
  1. -- Path of Building
  2. --
  3. -- Class: Passive Tree View
  4. -- Passive skill tree viewer.
  5. -- Draws the passive skill tree, and also maintains the current view settings (zoom level, position, etc)
  6. --
  7. local pairs = pairs
  8. local ipairs = ipairs
  9. local t_insert = table.insert
  10. local t_remove = table.remove
  11. local m_min = math.min
  12. local m_max = math.max
  13. local m_floor = math.floor
  14.  
  15. local PassiveTreeViewClass = newClass("PassiveTreeView", function(self)
  16. self.ring = NewImageHandle()
  17. self.ring:Load("Assets/ring.png", "CLAMP")
  18. self.highlightRing = NewImageHandle()
  19. self.highlightRing:Load("Assets/small_ring.png", "CLAMP")
  20.  
  21. self.tooltip = new("Tooltip")
  22.  
  23. self.zoomLevel = 3
  24. self.zoom = 1.2 ^ self.zoomLevel
  25. self.zoomX = 0
  26. self.zoomY = 0
  27.  
  28. self.searchStr = ""
  29. self.searchStrCached = ""
  30. self.searchStrResults = {}
  31. self.showStatDifferences = true
  32. end)
  33.  
  34. function PassiveTreeViewClass:Load(xml, fileName)
  35. if xml.attrib.zoomLevel then
  36. self.zoomLevel = tonumber(xml.attrib.zoomLevel)
  37. self.zoom = 1.2 ^ self.zoomLevel
  38. end
  39. if xml.attrib.zoomX and xml.attrib.zoomY then
  40. self.zoomX = tonumber(xml.attrib.zoomX)
  41. self.zoomY = tonumber(xml.attrib.zoomY)
  42. end
  43. if xml.attrib.searchStr then
  44. self.searchStr = xml.attrib.searchStr
  45. end
  46. if xml.attrib.showHeatMap then
  47. self.showHeatMap = xml.attrib.showHeatMap == "true"
  48. end
  49. if xml.attrib.showStatDifferences then
  50. self.showStatDifferences = xml.attrib.showStatDifferences == "true"
  51. end
  52. end
  53.  
  54. function PassiveTreeViewClass:Save(xml)
  55. xml.attrib = {
  56. zoomLevel = tostring(self.zoomLevel),
  57. zoomX = tostring(self.zoomX),
  58. zoomY = tostring(self.zoomY),
  59. searchStr = self.searchStr,
  60. showHeatMap = tostring(self.showHeatMap),
  61. showStatDifferences = tostring(self.showStatDifferences),
  62. }
  63. end
  64.  
  65. function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
  66. local spec = build.spec
  67. local tree = spec.tree
  68.  
  69. local cursorX, cursorY = GetCursorPos()
  70. local mOver = cursorX >= viewPort.x and cursorX < viewPort.x + viewPort.width and cursorY >= viewPort.y and cursorY < viewPort.y + viewPort.height
  71.  
  72. -- Process input events
  73. local treeClick
  74. for id, event in ipairs(inputEvents) do
  75. if event.type == "KeyDown" then
  76. if event.key == "LEFTBUTTON" then
  77. if mOver then
  78. -- Record starting coords of mouse drag
  79. -- Dragging won't actually commence unless the cursor moves far enough
  80. self.dragX, self.dragY = cursorX, cursorY
  81. end
  82. elseif event.key == "p" then
  83. self.showHeatMap = not self.showHeatMap
  84. elseif event.key == "d" and IsKeyDown("CTRL") then
  85. self.showStatDifferences = not self.showStatDifferences
  86. elseif event.key == "PAGEUP" then
  87. self:Zoom(IsKeyDown("SHIFT") and 3 or 1, viewPort)
  88. elseif event.key == "PAGEDOWN" then
  89. self:Zoom(IsKeyDown("SHIFT") and -3 or -1, viewPort)
  90. end
  91. elseif event.type == "KeyUp" then
  92. if event.key == "LEFTBUTTON" then
  93. if self.dragX and not self.dragging then
  94. -- Mouse button went down, but didn't move far enough to trigger drag, so register a normal click
  95. treeClick = "LEFT"
  96. end
  97. elseif mOver then
  98. if event.key == "RIGHTBUTTON" then
  99. treeClick = "RIGHT"
  100. elseif event.key == "WHEELUP" then
  101. self:Zoom(IsKeyDown("SHIFT") and 3 or 1, viewPort)
  102. elseif event.key == "WHEELDOWN" then
  103. self:Zoom(IsKeyDown("SHIFT") and -3 or -1, viewPort)
  104. end
  105. end
  106. end
  107. end
  108.  
  109. if not IsKeyDown("LEFTBUTTON") then
  110. -- Left mouse button isn't down, stop dragging if dragging was in progress
  111. self.dragging = false
  112. self.dragX, self.dragY = nil, nil
  113. end
  114. if self.dragX then
  115. -- Left mouse is down
  116. if not self.dragging then
  117. -- Check if mouse has moved more than a few pixels, and if so, initiate dragging
  118. if math.abs(cursorX - self.dragX) > 5 or math.abs(cursorY - self.dragY) > 5 then
  119. self.dragging = true
  120. end
  121. end
  122. if self.dragging then
  123. self.zoomX = self.zoomX + cursorX - self.dragX
  124. self.zoomY = self.zoomY + cursorY - self.dragY
  125. self.dragX, self.dragY = cursorX, cursorY
  126. end
  127. end
  128.  
  129. -- Ctrl-click to zoom
  130. if treeClick and IsKeyDown("CTRL") then
  131. self:Zoom(treeClick == "RIGHT" and -2 or 2, viewPort)
  132. treeClick = nil
  133. end
  134.  
  135. -- Clamp zoom offset
  136. local clampFactor = self.zoom * 2 / 3
  137. self.zoomX = m_min(m_max(self.zoomX, -viewPort.width * clampFactor), viewPort.width * clampFactor)
  138. self.zoomY = m_min(m_max(self.zoomY, -viewPort.height * clampFactor), viewPort.height * clampFactor)
  139.  
  140. -- Create functions that will convert coordinates between the screen and tree coordinate spaces
  141. local scale = m_min(viewPort.width, viewPort.height) / tree.size * self.zoom
  142. local offsetX = self.zoomX + viewPort.x + viewPort.width/2
  143. local offsetY = self.zoomY + viewPort.y + viewPort.height/2
  144. local function treeToScreen(x, y)
  145. return x * scale + offsetX,
  146. y * scale + offsetY
  147. end
  148. local function screenToTree(x, y)
  149. return (x - offsetX) / scale,
  150. (y - offsetY) / scale
  151. end
  152.  
  153. if IsKeyDown("SHIFT") then
  154. -- Enable path tracing mode
  155. self.traceMode = true
  156. self.tracePath = self.tracePath or { }
  157. else
  158. self.traceMode = false
  159. self.tracePath = nil
  160. end
  161.  
  162. local hoverNode
  163. if mOver then
  164. -- Cursor is over the tree, check if it is over a node
  165. local curTreeX, curTreeY = screenToTree(cursorX, cursorY)
  166. for nodeId, node in pairs(spec.nodes) do
  167. if node.rsq then
  168. -- Node has a defined size (i.e has artwork)
  169. local vX = curTreeX - node.x
  170. local vY = curTreeY - node.y
  171. if vX * vX + vY * vY <= node.rsq then
  172. hoverNode = node
  173. break
  174. end
  175. end
  176. end
  177. end
  178.  
  179. -- If hovering over a node, find the path to it (if unallocated) or the list of dependant nodes (if allocated)
  180. local hoverPath, hoverDep
  181. if self.traceMode then
  182. -- Path tracing mode is enabled
  183. if hoverNode then
  184. if not hoverNode.path then
  185. -- Don't highlight the node if it can't be pathed to
  186. hoverNode = nil
  187. elseif not self.tracePath[1] then
  188. -- Initialise the trace path using this node's path
  189. for _, pathNode in ipairs(hoverNode.path) do
  190. t_insert(self.tracePath, 1, pathNode)
  191. end
  192. else
  193. local lastPathNode = self.tracePath[#self.tracePath]
  194. if hoverNode ~= lastPathNode then
  195. -- If node is directly linked to the last node in the path, add it
  196. if isValueInArray(hoverNode.linked, lastPathNode) then
  197. local index = isValueInArray(self.tracePath, hoverNode)
  198. if index then
  199. -- Node is already in the trace path, remove it first
  200. t_remove(self.tracePath, index)
  201. end
  202. t_insert(self.tracePath, hoverNode)
  203. else
  204. hoverNode = nil
  205. end
  206. end
  207. end
  208. end
  209. -- Use the trace path as the path
  210. hoverPath = { }
  211. for _, pathNode in pairs(self.tracePath) do
  212. hoverPath[pathNode] = true
  213. end
  214. elseif hoverNode and hoverNode.path then
  215. -- Use the node's own path and dependance list
  216. hoverPath = { }
  217. if not hoverNode.dependsOnIntuitiveLeap then
  218. for _, pathNode in pairs(hoverNode.path) do
  219. hoverPath[pathNode] = true
  220. end
  221. end
  222. hoverDep = { }
  223. for _, depNode in pairs(hoverNode.depends) do
  224. hoverDep[depNode] = true
  225. end
  226. end
  227.  
  228. if treeClick == "LEFT" then
  229. if hoverNode then
  230. -- User left-clicked on a node
  231. if hoverNode.alloc then
  232. -- Node is allocated, so deallocate it
  233. spec:DeallocNode(hoverNode)
  234. spec:AddUndoState()
  235. build.buildFlag = true
  236. elseif hoverNode.path then
  237. -- Node is unallocated and can be allocated, so allocate it
  238. spec:AllocNode(hoverNode, self.tracePath and hoverNode == self.tracePath[#self.tracePath] and self.tracePath)
  239. spec:AddUndoState()
  240. build.buildFlag = true
  241. end
  242. end
  243. elseif treeClick == "RIGHT" then
  244. if hoverNode and hoverNode.alloc and hoverNode.type == "Socket" then
  245. local slot = build.itemsTab.sockets[hoverNode.id]
  246. if slot:IsEnabled() then
  247. -- User right-clicked a jewel socket, jump to the item page and focus the corresponding item slot control
  248. slot.dropped = true
  249. build.itemsTab:SelectControl(slot)
  250. build.viewMode = "ITEMS"
  251. end
  252. elseif hoverNode then
  253. -- alocate the single node without pathing to it
  254. spec:AllocSingleNode(hoverNode)
  255. spec:AddUndoState()
  256. build.buildFlag = true
  257. end
  258. end
  259.  
  260. -- Draw the background artwork
  261. local bg = tree.assets.Background1
  262. if bg.width == 0 then
  263. bg.width, bg.height = bg.handle:ImageSize()
  264. end
  265. if bg.width > 0 then
  266. local bgSize = bg.width * scale * 1.33 * 2.5
  267. SetDrawColor(1, 1, 1)
  268. DrawImage(bg.handle, viewPort.x, viewPort.y, viewPort.width, viewPort.height, (self.zoomX + viewPort.width/2) / -bgSize, (self.zoomY + viewPort.height/2) / -bgSize, (viewPort.width/2 - self.zoomX) / bgSize, (viewPort.height/2 - self.zoomY) / bgSize)
  269. end
  270.  
  271. -- Hack to draw class background art, the position data doesn't seem to be in the tree JSON yet
  272. if build.spec.curClassId == 1 then
  273. local scrX, scrY = treeToScreen(-2750, 1600)
  274. self:DrawAsset(tree.assets.BackgroundStr, scrX, scrY, scale)
  275. elseif build.spec.curClassId == 2 then
  276. local scrX, scrY = treeToScreen(2550, 1600)
  277. self:DrawAsset(tree.assets.BackgroundDex, scrX, scrY, scale)
  278. elseif build.spec.curClassId == 3 then
  279. local scrX, scrY = treeToScreen(-250, -2200)
  280. self:DrawAsset(tree.assets.BackgroundInt, scrX, scrY, scale)
  281. elseif build.spec.curClassId == 4 then
  282. local scrX, scrY = treeToScreen(-150, 2350)
  283. self:DrawAsset(tree.assets.BackgroundStrDex, scrX, scrY, scale)
  284. elseif build.spec.curClassId == 5 then
  285. local scrX, scrY = treeToScreen(-2100, -1500)
  286. self:DrawAsset(tree.assets.BackgroundStrInt, scrX, scrY, scale)
  287. elseif build.spec.curClassId == 6 then
  288. local scrX, scrY = treeToScreen(2350, -1950)
  289. self:DrawAsset(tree.assets.BackgroundDexInt, scrX, scrY, scale)
  290. end
  291.  
  292. -- Draw the group backgrounds
  293. for _, group in pairs(tree.groups) do
  294. local scrX, scrY = treeToScreen(group.x, group.y)
  295. if group.ascendancyName then
  296. if group.isAscendancyStart then
  297. if group.ascendancyName ~= spec.curAscendClassName then
  298. SetDrawColor(1, 1, 1, 0.25)
  299. end
  300. self:DrawAsset(tree.assets["Classes"..group.ascendancyName], scrX, scrY, scale)
  301. SetDrawColor(1, 1, 1)
  302. end
  303. elseif group.oo[3] then
  304. self:DrawAsset(tree.assets.PSGroupBackground3, scrX, scrY, scale, true)
  305. elseif group.oo[2] then
  306. self:DrawAsset(tree.assets.PSGroupBackground2, scrX, scrY, scale)
  307. elseif group.oo[1] then
  308. self:DrawAsset(tree.assets.PSGroupBackground1, scrX, scrY, scale)
  309. end
  310. end
  311.  
  312. -- Draw the connecting lines between nodes
  313. SetDrawLayer(nil, 20)
  314. for _, connector in pairs(tree.connectors) do
  315. local node1, node2 = spec.nodes[connector.nodeId1], spec.nodes[connector.nodeId2]
  316.  
  317. -- Determine the connector state
  318. local state = "Normal"
  319. if node1.alloc and node2.alloc then
  320. state = "Active"
  321. elseif hoverPath then
  322. if (node1.alloc or node1 == hoverNode or hoverPath[node1]) and (node2.alloc or node2 == hoverNode or hoverPath[node2]) then
  323. state = "Intermediate"
  324. end
  325. end
  326.  
  327. -- Convert vertex coordinates to screen-space and add them to the coordinate array
  328. local vert = connector.vert[state]
  329. connector.c[1], connector.c[2] = treeToScreen(vert[1], vert[2])
  330. connector.c[3], connector.c[4] = treeToScreen(vert[3], vert[4])
  331. connector.c[5], connector.c[6] = treeToScreen(vert[5], vert[6])
  332. connector.c[7], connector.c[8] = treeToScreen(vert[7], vert[8])
  333.  
  334. if hoverDep and hoverDep[node1] and hoverDep[node2] then
  335. -- Both nodes depend on the node currently being hovered over, so color the line red
  336. SetDrawColor(1, 0, 0)
  337. elseif connector.ascendancyName and connector.ascendancyName ~= spec.curAscendClassName then
  338. -- Fade out lines in ascendancy classes other than the current one
  339. SetDrawColor(0.75, 0.75, 0.75)
  340. end
  341. DrawImageQuad(tree.assets[connector.type..state].handle, unpack(connector.c))
  342. SetDrawColor(1, 1, 1)
  343. end
  344.  
  345. if self.showHeatMap then
  346. -- Build the power numbers if needed
  347. self.heatMapStat = build.calcsTab.powerStat
  348. build.calcsTab:BuildPower()
  349. end
  350.  
  351. -- Update cached node data
  352. if self.searchStrCached ~= self.searchStr then
  353. self.searchStrCached = self.searchStr
  354. for nodeId, node in pairs(spec.nodes) do
  355. self.searchStrResults[nodeId] = #self.searchStr > 0 and self:DoesNodeMatchSearchStr(node)
  356. end
  357. end
  358.  
  359. -- Draw the nodes
  360. for nodeId, node in pairs(spec.nodes) do
  361. -- Determine the base and overlay images for this node based on type and state
  362. local base, overlay
  363. local isAlloc = node.alloc or build.calcsTab.mainEnv.grantedPassives[nodeId]
  364. SetDrawLayer(nil, 25)
  365. if node.type == "ClassStart" then
  366. overlay = isAlloc and node.startArt or "PSStartNodeBackgroundInactive"
  367. elseif node.type == "AscendClassStart" then
  368. overlay = "PassiveSkillScreenAscendancyMiddle"
  369. elseif node.type == "Mastery" then
  370. -- This is the icon that appears in the center of many groups
  371. SetDrawLayer(nil, 15)
  372. base = node.sprites.mastery
  373. else
  374. local state
  375. if self.showHeatMap or isAlloc or node == hoverNode or (self.traceMode and node == self.tracePath[#self.tracePath])then
  376. -- Show node as allocated if it is being hovered over
  377. -- Also if the heat map is turned on (makes the nodes more visible)
  378. state = "alloc"
  379. elseif hoverPath and hoverPath[node] then
  380. state = "path"
  381. else
  382. state = "unalloc"
  383. end
  384. if node.type == "Socket" then
  385. -- Node is a jewel socket, retrieve the socketed jewel (if present) so we can display the correct art
  386. base = tree.assets[node.overlay[state]]
  387. local socket, jewel = build.itemsTab:GetSocketAndJewelForNodeID(nodeId)
  388. if isAlloc and jewel then
  389. if jewel.baseName == "Crimson Jewel" then
  390. overlay = "JewelSocketActiveRed"
  391. elseif jewel.baseName == "Viridian Jewel" then
  392. overlay = "JewelSocketActiveGreen"
  393. elseif jewel.baseName == "Cobalt Jewel" then
  394. overlay = "JewelSocketActiveBlue"
  395. elseif jewel.baseName == "Prismatic Jewel" then
  396. overlay = "JewelSocketActivePrismatic"
  397. elseif jewel.baseName:match("Eye Jewel$") then
  398. overlay = "JewelSocketActiveAbyss"
  399. end
  400. end
  401. else
  402. -- Normal node (includes keystones and notables)
  403. base = node.sprites[node.type:lower()..(isAlloc and "Active" or "Inactive")]
  404. overlay = node.overlay[state .. (node.ascendancyName and "Ascend" or "")]
  405. end
  406. end
  407.  
  408. -- Convert node position to screen-space
  409. local scrX, scrY = treeToScreen(node.x, node.y)
  410.  
  411. -- Determine color for the base artwork
  412. if node.ascendancyName and node.ascendancyName ~= spec.curAscendClassName then
  413. -- By default, fade out nodes from ascendancy classes other than the current one
  414. SetDrawColor(0.5, 0.5, 0.5)
  415. end
  416. if self.showHeatMap then
  417. if not isAlloc and node.type ~= "ClassStart" and node.type ~= "AscendClassStart" then
  418. if self.heatMapStat and self.heatMapStat.stat then
  419. -- Calculate color based on a single stat
  420. local stat = m_max(node.power.singleStat or 0, 0)
  421. local statCol = (stat / build.calcsTab.powerMax.singleStat * 1.5) ^ 0.5
  422. if main.nodePowerTheme == "RED/BLUE" then
  423. SetDrawColor(statCol, 0, 0)
  424. elseif main.nodePowerTheme == "RED/GREEN" then
  425. SetDrawColor(0, statCol, 0)
  426. elseif main.nodePowerTheme == "GREEN/BLUE" then
  427. SetDrawColor(0, 0, statCol)
  428. end
  429. else
  430. -- Calculate color based on DPS and defensive powers
  431. local offence = m_max(node.power.offence or 0, 0)
  432. local defence = m_max(node.power.defence or 0, 0)
  433. local dpsCol = (offence / build.calcsTab.powerMax.offence * 1.5) ^ 0.5
  434. local defCol = (defence / build.calcsTab.powerMax.defence * 1.5) ^ 0.5
  435. local mixCol = (m_max(dpsCol - 0.5, 0) + m_max(defCol - 0.5, 0)) / 2
  436. if main.nodePowerTheme == "RED/BLUE" then
  437. SetDrawColor(dpsCol, mixCol, defCol)
  438. elseif main.nodePowerTheme == "RED/GREEN" then
  439. SetDrawColor(dpsCol, defCol, mixCol)
  440. elseif main.nodePowerTheme == "GREEN/BLUE" then
  441. SetDrawColor(mixCol, dpsCol, defCol)
  442. end
  443. end
  444. else
  445. SetDrawColor(1, 1, 1)
  446. end
  447. elseif launch.devModeAlt then
  448. -- Debug display
  449. if node.extra then
  450. SetDrawColor(1, 0, 0)
  451. elseif node.unknown then
  452. SetDrawColor(0, 1, 1)
  453. else
  454. SetDrawColor(0, 0, 0)
  455. end
  456. else
  457. SetDrawColor(1, 1, 1)
  458. end
  459.  
  460. -- Draw base artwork
  461. if base then
  462. self:DrawAsset(base, scrX, scrY, scale)
  463. end
  464.  
  465. if overlay then
  466. -- Draw overlay
  467. if node.type ~= "ClassStart" and node.type ~= "AscendClassStart" then
  468. if hoverNode and hoverNode ~= node then
  469. -- Mouse is hovering over a different node
  470. if hoverDep and hoverDep[node] then
  471. -- This node depends on the hover node, turn it red
  472. SetDrawColor(1, 0, 0)
  473. elseif hoverNode.type == "Socket" then
  474. -- Hover node is a socket, check if this node falls within its radius and color it accordingly
  475. for index, data in ipairs(build.data.jewelRadius) do
  476. if hoverNode.nodesInRadius[index][node.id] then
  477. SetDrawColor(data.col)
  478. break
  479. end
  480. end
  481. end
  482. end
  483. end
  484. self:DrawAsset(tree.assets[overlay], scrX, scrY, scale)
  485. SetDrawColor(1, 1, 1)
  486. end
  487. if self.searchStrResults[nodeId] then
  488. -- Node matches the search string, show the highlight circle
  489. SetDrawLayer(nil, 30)
  490. SetDrawColor(1, 0, 0)
  491. local size = 175 * scale / self.zoom ^ 0.4
  492. DrawImage(self.highlightRing, scrX - size, scrY - size, size * 2, size * 2)
  493. end
  494. if node == hoverNode and (node.type ~= "Socket" or not IsKeyDown("SHIFT")) and not main.popups[1] then
  495. -- Draw tooltip
  496. SetDrawLayer(nil, 100)
  497. local size = m_floor(node.size * scale)
  498. if self.tooltip:CheckForUpdate(node, self.showStatDifferences, self.tracePath, launch.devModeAlt, build.outputRevision) then
  499. self:AddNodeTooltip(self.tooltip, node, build)
  500. end
  501. self.tooltip:Draw(m_floor(scrX - size), m_floor(scrY - size), size * 2, size * 2, viewPort)
  502. end
  503. end
  504.  
  505. -- Draw ring overlays for jewel sockets
  506. SetDrawLayer(nil, 25)
  507. for nodeId, slot in pairs(build.itemsTab.sockets) do
  508. local node = spec.nodes[nodeId]
  509. if node == hoverNode then
  510. -- Mouse is over this socket, show all radius rings
  511. local scrX, scrY = treeToScreen(node.x, node.y)
  512. for _, radData in ipairs(build.data.jewelRadius) do
  513. local size = radData.rad * scale
  514. SetDrawColor(radData.col)
  515. DrawImage(self.ring, scrX - size, scrY - size, size * 2, size * 2)
  516. end
  517. elseif node.alloc then
  518. local socket, jewel = build.itemsTab:GetSocketAndJewelForNodeID(nodeId)
  519. if jewel and jewel.jewelRadiusIndex then
  520. -- Socket is allocated and there's a jewel socketed into it which has a radius, so show it
  521. local scrX, scrY = treeToScreen(node.x, node.y)
  522. local radData = build.data.jewelRadius[jewel.jewelRadiusIndex]
  523. local size = radData.rad * scale
  524. SetDrawColor(radData.col)
  525. DrawImage(self.ring, scrX - size, scrY - size, size * 2, size * 2)
  526. end
  527. end
  528. end
  529. end
  530.  
  531. -- Draws the given asset at the given position
  532. function PassiveTreeViewClass:DrawAsset(data, x, y, scale, isHalf)
  533. if not data then
  534. return
  535. end
  536. if data.width == 0 then
  537. data.width, data.height = data.handle:ImageSize()
  538. if data.width == 0 then
  539. return
  540. end
  541. end
  542. local width = data.width * scale * 1.33
  543. local height = data.height * scale * 1.33
  544. if isHalf then
  545. DrawImage(data.handle, x - width, y - height * 2, width * 2, height * 2)
  546. DrawImage(data.handle, x - width, y, width * 2, height * 2, 0, 1, 1, 0)
  547. else
  548. DrawImage(data.handle, x - width, y - height, width * 2, height * 2, unpack(data))
  549. end
  550. end
  551.  
  552. -- Zoom the tree in or out
  553. function PassiveTreeViewClass:Zoom(level, viewPort)
  554. -- Calculate new zoom level and zoom factor
  555. self.zoomLevel = m_max(0, m_min(12, self.zoomLevel + level))
  556. local oldZoom = self.zoom
  557. self.zoom = 1.2 ^ self.zoomLevel
  558.  
  559. -- Adjust zoom center position so that the point on the tree that is currently under the mouse will remain under it
  560. local factor = self.zoom / oldZoom
  561. local cursorX, cursorY = GetCursorPos()
  562. local relX = cursorX - viewPort.x - viewPort.width/2
  563. local relY = cursorY - viewPort.y - viewPort.height/2
  564. self.zoomX = relX + (self.zoomX - relX) * factor
  565. self.zoomY = relY + (self.zoomY - relY) * factor
  566. end
  567.  
  568. function PassiveTreeViewClass:DoesNodeMatchSearchStr(node)
  569. if node.type == "ClassStart" or node.type == "Mastery" then
  570. return
  571. end
  572.  
  573. -- Check node name
  574. local errMsg, match = PCall(string.match, node.dn:lower(), self.searchStr:lower())
  575. if match then
  576. return true
  577. end
  578.  
  579. -- Check node description
  580. for index, line in ipairs(node.sd) do
  581. -- Check display text first
  582. errMsg, match = PCall(string.match, line:lower(), self.searchStr:lower())
  583. if match then
  584. return true
  585. end
  586. if not match and node.mods[index].list then
  587. -- Then check modifiers
  588. for _, mod in ipairs(node.mods[index].list) do
  589. errMsg, match = PCall(string.match, mod.name, self.searchStr)
  590. if match then
  591. return true
  592. end
  593. end
  594. end
  595. end
  596.  
  597. -- Check node type
  598. local errMsg, match = PCall(string.match, node.type:lower(), self.searchStr:lower())
  599. if match then
  600. return true
  601. end
  602. end
  603.  
  604. function PassiveTreeViewClass:AddNodeName(tooltip, node, build)
  605. tooltip:AddLine(24, "^7"..node.dn..(launch.devModeAlt and " ["..node.id.."]" or ""))
  606. if node.type == "Socket" then
  607. local attribTotals = { }
  608. for nodeId in pairs(node.nodesInRadius[2]) do
  609. local specNode = build.spec.nodes[nodeId]
  610. for _, attrib in ipairs{"Str","Dex","Int"} do
  611. attribTotals[attrib] = (attribTotals[attrib] or 0) + specNode.finalModList:Sum("BASE", nil, attrib)
  612. end
  613. end
  614. if attribTotals["Str"] >= 40 then
  615. tooltip:AddLine(16, "^7Can support "..colorCodes.STRENGTH.."Strength ^7threshold jewels")
  616. end
  617. if attribTotals["Dex"] >= 40 then
  618. tooltip:AddLine(16, "^7Can support "..colorCodes.DEXTERITY.."Dexterity ^7threshold jewels")
  619. end
  620. if attribTotals["Int"] >= 40 then
  621. tooltip:AddLine(16, "^7Can support "..colorCodes.INTELLIGENCE.."Intelligence ^7threshold jewels")
  622. end
  623. end
  624. end
  625.  
  626. function PassiveTreeViewClass:AddNodeTooltip(tooltip, node, build)
  627. -- Special case for sockets
  628. if node.type == "Socket" and node.alloc then
  629. local socket, jewel = build.itemsTab:GetSocketAndJewelForNodeID(node.id)
  630. if jewel then
  631. build.itemsTab:AddItemTooltip(tooltip, jewel, { nodeId = node.id })
  632. else
  633. self:AddNodeName(tooltip, node, build)
  634. end
  635. tooltip:AddSeparator(14)
  636. if socket:IsEnabled() then
  637. tooltip:AddLine(14, colorCodes.TIP.."Tip: Right click this socket to go to the items page and choose the jewel for this socket.")
  638. end
  639. tooltip:AddLine(14, colorCodes.TIP.."Tip: Hold Shift to hide this tooltip.")
  640. return
  641. end
  642.  
  643. -- Node name
  644. self:AddNodeName(tooltip, node, build)
  645. if launch.devModeAlt then
  646. if node.power and node.power.offence then
  647. -- Power debugging info
  648. tooltip:AddLine(16, string.format("DPS power: %g Defence power: %g", node.power.offence, node.power.defence))
  649. end
  650. end
  651.  
  652. -- Node description
  653. if node.sd[1] then
  654. tooltip:AddLine(16, "")
  655. for i, line in ipairs(node.sd) do
  656. if node.mods[i].list then
  657. if launch.devModeAlt then
  658. -- Modifier debugging info
  659. local modStr
  660. for _, mod in pairs(node.mods[i].list) do
  661. modStr = (modStr and modStr..", " or "^2") .. modLib.formatMod(mod)
  662. end
  663. if node.mods[i].extra then
  664. modStr = (modStr and modStr.." " or "") .. "^1" .. node.mods[i].extra
  665. end
  666. if modStr then
  667. line = line .. " " .. modStr
  668. end
  669. end
  670. end
  671. tooltip:AddLine(16, ((node.mods[i].extra or not node.mods[i].list) and colorCodes.UNSUPPORTED or colorCodes.MAGIC)..line)
  672. end
  673. end
  674.  
  675. -- Reminder text
  676. if node.reminderText then
  677. tooltip:AddSeparator(14)
  678. for _, line in ipairs(node.reminderText) do
  679. tooltip:AddLine(14, "^xA0A080"..line)
  680. end
  681. end
  682.  
  683. -- Mod differences
  684. if self.showStatDifferences then
  685. local calcFunc, calcBase = build.calcsTab:GetMiscCalculator(build)
  686. tooltip:AddSeparator(14)
  687. local path = (node.alloc and node.depends) or self.tracePath or node.path or { }
  688. local pathLength = #path
  689. local pathNodes = { }
  690. for _, node in pairs(path) do
  691. pathNodes[node] = true
  692. end
  693. local nodeOutput, pathOutput
  694. if node.alloc then
  695. -- Calculate the differences caused by deallocating this node and its dependants
  696. nodeOutput = calcFunc({ removeNodes = { [node] = true } })
  697. if pathLength > 1 then
  698. pathOutput = calcFunc({ removeNodes = pathNodes })
  699. end
  700. else
  701. -- Calculated the differences caused by allocating this node and all nodes along the path to it
  702. nodeOutput = calcFunc({ addNodes = { [node] = true } })
  703. if pathLength > 1 then
  704. pathOutput = calcFunc({ addNodes = pathNodes })
  705. end
  706. end
  707. local count = build:AddStatComparesToTooltip(tooltip, calcBase, nodeOutput, node.alloc and "^7Unallocating this node will give you:" or "^7Allocating this node will give you:")
  708. if pathLength > 1 then
  709. count = count + build:AddStatComparesToTooltip(tooltip, calcBase, pathOutput, node.alloc and "^7Unallocating this node and all nodes depending on it will give you:" or "^7Allocating this node and all nodes leading to it will give you:", pathLength)
  710. end
  711. if count == 0 then
  712. tooltip:AddLine(14, string.format("^7No changes from %s this node%s.", node.alloc and "unallocating" or "allocating", pathLength > 1 and " or the nodes leading to it" or ""))
  713. end
  714. tooltip:AddLine(14, colorCodes.TIP.."Tip: Press Ctrl+D to disable the display of stat differences.")
  715. else
  716. tooltip:AddSeparator(14)
  717. tooltip:AddLine(14, colorCodes.TIP.."Tip: Press Ctrl+D to enable the display of stat differences.")
  718. end
  719.  
  720. -- Pathing distance
  721. tooltip:AddSeparator(14)
  722. if node.path and #node.path > 0 then
  723. if self.traceMode and isValueInArray(self.tracePath, node) then
  724. tooltip:AddLine(14, "^7"..#self.tracePath .. " nodes in trace path")
  725. else
  726. tooltip:AddLine(14, "^7"..#node.path .. " points to node")
  727. if #node.path > 1 then
  728. -- Handy hint!
  729. tooltip:AddLine(14, colorCodes.TIP)
  730. tooltip:AddLine(14, "Tip: To reach this node by a different path, hold Shift, then trace the path and click this node")
  731. end
  732. end
  733. end
  734. if node.type == "Socket" then
  735. tooltip:AddLine(14, colorCodes.TIP.."Tip: Hold Shift to hide this tooltip.")
  736. end
  737. if node.depends and #node.depends > 1 then
  738. tooltip:AddSeparator(14)
  739. tooltip:AddLine(14, "^7"..#node.depends .. " points gained from unallocating these nodes")
  740. end
  741. end
Advertisement
Add Comment
Please, Sign In to add comment