From d3c7764fad253785a52fdc6299d6ea7281c77550 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:44:41 +0300 Subject: [PATCH 1/3] Tree and item skill tooltips --- src/Classes/GemSelectControl.lua | 174 +--------------------- src/Classes/GemTooltip.lua | 248 +++++++++++++++++++++++++++++++ src/Classes/ItemSlotControl.lua | 2 +- src/Classes/ItemsTab.lua | 48 ++++++ src/Classes/PassiveTreeView.lua | 94 +++++++++++- src/Classes/Tooltip.lua | 27 ++++ src/Data/Skills/other.lua | 1 + src/Data/Skills/sup_dex.lua | 12 ++ src/Data/Skills/sup_int.lua | 23 +++ src/Data/Skills/sup_str.lua | 16 ++ src/Export/Scripts/skills.lua | 23 +++ 11 files changed, 493 insertions(+), 175 deletions(-) create mode 100644 src/Classes/GemTooltip.lua diff --git a/src/Classes/GemSelectControl.lua b/src/Classes/GemSelectControl.lua index 89e546f0a48..dddbde3a884 100644 --- a/src/Classes/GemSelectControl.lua +++ b/src/Classes/GemSelectControl.lua @@ -11,6 +11,7 @@ local m_min = math.min local m_max = math.max local m_floor = math.floor +local gemTooltip = LoadModule("Classes/GemTooltip") local toolTipText = "Prefix tag searches with a colon and exclude tags with a dash. e.g. :fire:lightning:-cold:area" local imbuedTooltipText = "\"Socketed in\" item must be set in order to add an imbued support.\nOnly one imbued support is allowed per item." @@ -595,178 +596,7 @@ function GemSelectClass:CheckSupporting(gemA, gemB) end function GemSelectClass:AddGemTooltip(gemInstance) - local fontSizeBig = main.showFlavourText and 18 or 16 - local fontSizeTitle = main.showFlavourText and 24 or 20 - self.tooltip.center = true - self.tooltip.color = colorCodes.GEM - self.tooltip.tooltipHeader = "GEM" - local primary = gemInstance.gemData.grantedEffect - local secondary = gemInstance.gemData.secondaryGrantedEffect - if secondary and (not secondary.support or gemInstance.gemData.secondaryEffectName) then - local grantedEffect = gemInstance.gemData.VaalGem and secondary or primary - local grantedEffectSecondary = gemInstance.gemData.VaalGem and primary or secondary - self.tooltip:AddLine(fontSizeTitle, colorCodes.GEM..grantedEffect.name, "FONTIN SC") - self.tooltip:AddSeparator(10) - self.tooltip:AddLine(fontSizeBig, "^x7F7F7F" .. gemInstance.gemData.tagString, "FONTIN SC") - self:AddCommonGemInfo(gemInstance, grantedEffect, true) - self.tooltip:AddSeparator(10) - self.tooltip:AddLine(fontSizeTitle, colorCodes.GEM .. (gemInstance.gemData.secondaryEffectName or grantedEffectSecondary.name), "FONTIN SC") - self.tooltip:AddSeparator(10) - self:AddCommonGemInfo(gemInstance, grantedEffectSecondary) - else - local grantedEffect = gemInstance.gemData.grantedEffect - self.tooltip:AddLine(fontSizeTitle, colorCodes.GEM..grantedEffect.name, "FONTIN SC") - self.tooltip:AddSeparator(10) - if grantedEffect.legacy then - self.tooltip:AddLine(fontSizeTitle, colorCodes.WARNING .. "Legacy Gem", "FONTIN SC") - self.tooltip:AddLine(fontSizeBig, colorCodes.WARNING .. "Gem only exists in Standard League", "FONTIN SC") - self.tooltip:AddSeparator(10) - end - self.tooltip:AddLine(fontSizeBig, "^x7F7F7F" .. gemInstance.gemData.tagString, "FONTIN SC") - self:AddCommonGemInfo(gemInstance, grantedEffect, true, secondary and secondary.support and secondary) - end -end - -function GemSelectClass:AddCommonGemInfo(gemInstance, grantedEffect, addReq, mergeStatsFrom) - local fontSizeBig = main.showFlavourText and 18 or 16 - local displayInstance = gemInstance.displayEffect or gemInstance - local grantedEffectLevel = grantedEffect.levels[displayInstance.level] or { } - if addReq then - self.tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FLevel: ^7%d%s%s", - gemInstance.level, - ((displayInstance.level > gemInstance.level) and " (" .. colorCodes.MAGIC .. "+" .. (displayInstance.level - gemInstance.level) .. "^7)") or ((displayInstance.level < gemInstance.level) and " (" .. colorCodes.WARNING .. "-" .. (gemInstance.level - displayInstance.level) .. "^7)") or "", - (gemInstance.level >= gemInstance.gemData.naturalMaxLevel) and " (Max)" or "" - ), "FONTIN SC") - end - if grantedEffect.support then - if grantedEffectLevel.manaMultiplier then - self.tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FCost & Reservation Multiplier: ^7%d%%", grantedEffectLevel.manaMultiplier + 100), "FONTIN SC") - end - local reservation - for name, res in pairs(self.reservationMap) do - if grantedEffectLevel[name] then - reservation = (reservation and (reservation .. ", ") or "") .. self.costs[isValueInArrayPred(self.costs, function(v) return v.Resource == res end)].ResourceString:gsub("{0}", string.format("%d", grantedEffectLevel[name])) - end - end - if reservation then - self.tooltip:AddLine(fontSizeBig, "^x7F7F7FReservation Override: ^7"..reservation, "FONTIN SC") - end - if grantedEffectLevel.cooldown then - local string = string.format("^x7F7F7FCooldown Time: ^7%.2f sec", grantedEffectLevel.cooldown) - if grantedEffectLevel.storedUses and grantedEffectLevel.storedUses > 1 then - string = string .. string.format(" (%d uses)", grantedEffectLevel.storedUses) - end - self.tooltip:AddLine(fontSizeBig, string, "FONTIN SC") - end - else - local reservation - for name, res in pairs(self.reservationMap) do - if grantedEffectLevel[name] then - reservation = (reservation and (reservation..", ") or "") .. self.costs[isValueInArrayPred(self.costs, function(v) return v.Resource == res end)].ResourceString:gsub("{0}", string.format("%d", grantedEffectLevel[name])) - end - end - if reservation then - self.tooltip:AddLine(fontSizeBig, "^x7F7F7FReservation: ^7" .. reservation, "FONTIN SC") - end - local cost - for _, res in ipairs(self.costs) do - if grantedEffectLevel.cost and grantedEffectLevel.cost[res.Resource] then - cost = (cost and (cost..", ") or "") .. res.ResourceString:gsub("{0}", string.format("%g", round(grantedEffectLevel.cost[res.Resource] / res.Divisor, 2))) - end - end - if cost then - self.tooltip:AddLine(fontSizeBig, "^x7F7F7FCost: ^7"..cost, "FONTIN SC") - end - if grantedEffectLevel.cooldown then - local string = string.format("^x7F7F7FCooldown Time: ^7%.2f sec", grantedEffectLevel.cooldown, "FONTIN SC") - if grantedEffectLevel.storedUses and grantedEffectLevel.storedUses > 1 then - string = string .. string.format(" (%d uses)", grantedEffectLevel.storedUses) - end - self.tooltip:AddLine(fontSizeBig, string, "FONTIN SC") - end - if grantedEffectLevel.vaalStoredUses then - self.tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FCan Store ^7%d ^x7F7F7FUse (%d Souls)", grantedEffectLevel.vaalStoredUses, grantedEffectLevel.vaalStoredUses * grantedEffectLevel.cost.Soul), "FONTIN SC") - end - if grantedEffectLevel.soulPreventionDuration then - self.tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FSoul Gain Prevention: ^7%d sec", grantedEffectLevel.soulPreventionDuration), "FONTIN SC") - end - if gemInstance.gemData.tags.attack then - if grantedEffectLevel.attackSpeedMultiplier then - self.tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FAttack Speed: ^7%d%% of base", grantedEffectLevel.attackSpeedMultiplier + 100), "FONTIN SC") - end - if grantedEffectLevel.attackTime then - self.tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FAttack Time: ^7%.2f sec", grantedEffectLevel.attackTime / 1000), "FONTIN SC") - end - if grantedEffectLevel.baseMultiplier then - self.tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FAttack Damage: ^7%g%% of base", grantedEffectLevel.baseMultiplier * 100), "FONTIN SC") - end - else - if grantedEffect.castTime > 0 then - self.tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FCast Time: ^7%.2f sec", grantedEffect.castTime), "FONTIN SC") - else - self.tooltip:AddLine(fontSizeBig, "^x7F7F7FCast Time: ^7Instant", "FONTIN SC") - end - end - if grantedEffectLevel.critChance then - self.tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FCritical Strike Chance: ^7%.2f%%", grantedEffectLevel.critChance), "FONTIN SC") - end - if grantedEffectLevel.damageEffectiveness then - self.tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FEffectiveness of Added Damage: ^7%d%%", grantedEffectLevel.damageEffectiveness * 100), "FONTIN SC") - end - end - if addReq and displayInstance.quality > 0 then - self.tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FQuality: "..colorCodes.MAGIC.."+%d%%^7%s", - gemInstance.quality, - (displayInstance.quality > gemInstance.quality) and " ("..colorCodes.MAGIC.."+"..(displayInstance.quality - gemInstance.quality).."^7)" or "" - ), "FONTIN SC") - end - self.tooltip:AddSeparator(10) - if addReq then - local reqLevel = grantedEffect.levels[gemInstance.level] and grantedEffect.levels[gemInstance.level].levelRequirement or 1 - local reqStr = calcLib.getGemStatRequirement(reqLevel, grantedEffect.support, gemInstance.gemData.reqStr) - local reqDex = calcLib.getGemStatRequirement(reqLevel, grantedEffect.support, gemInstance.gemData.reqDex) - local reqInt = calcLib.getGemStatRequirement(reqLevel, grantedEffect.support, gemInstance.gemData.reqInt) - self.skillsTab.build:AddRequirementsToTooltip(self.tooltip, reqLevel, reqStr, reqDex, reqInt) - end - if grantedEffect.description then - local wrap = main:WrapString(grantedEffect.description, 16, m_max(DrawStringWidth(16, "VAR", gemInstance.gemData.tagString), 400)) - for _, line in ipairs(wrap) do - self.tooltip:AddLine(fontSizeBig, colorCodes.GEM..line, "FONTIN SC") - end - end - if self.skillsTab.build.data.describeStats then - self.tooltip:AddSeparator(10) - local stats = calcLib.buildSkillInstanceStats(displayInstance, grantedEffect) - if mergeStatsFrom then - for stat, val in pairs(calcLib.buildSkillInstanceStats(displayInstance, mergeStatsFrom)) do - stats[stat] = (stats[stat] or 0) + val - end - end - local descriptions, lineMap = self.skillsTab.build.data.describeStats(stats, grantedEffect.statDescriptionScope) - for _, line in ipairs(descriptions) do - local source = grantedEffect.statMap[lineMap[line]] or self.skillsTab.build.data.skillStatMap[lineMap[line]] - if source then - if launch.devModeAlt then - local devText = lineMap[line] - if source[1] then - if not source[1].value then - source[1].value = lineMap[line] - end - devText = modLib.formatMod(source[1]) - end - line = line .. " ^2" .. devText - end - self.tooltip:AddLine(fontSizeBig, colorCodes.MAGIC .. line, "FONTIN SC") - else - if launch.devModeAlt then - line = line .. " ^1" .. lineMap[line] - end - local line = colorCodes.UNSUPPORTED .. line - line = main.notSupportedModTooltips and (line .. main.notSupportedTooltipText) or line - self.tooltip:AddLine(fontSizeBig, line, "FONTIN SC") - end - end - end + gemTooltip.AddGemTooltip(self.tooltip, self.skillsTab.build, gemInstance) end function GemSelectClass:OnFocusGained() diff --git a/src/Classes/GemTooltip.lua b/src/Classes/GemTooltip.lua new file mode 100644 index 00000000000..a01d179875c --- /dev/null +++ b/src/Classes/GemTooltip.lua @@ -0,0 +1,248 @@ +-- Path of Building +-- +-- Module: Gem Tooltip +-- Shared renderer for gem-style tooltips. + +local m_max = math.max + +---@class GemTooltip +local GemTooltip = { } + +local function getFontSizes() + return main.showFlavourText and 18 or 16, main.showFlavourText and 24 or 20 +end + + +local reservationMap = { + manaReservationFlat = "Mana", + manaReservationPercent = "ManaPercent", + lifeReservationFlat = "Life", + lifeReservationPercent = "LifePercent", + } + +---@param tooltip Tooltip +---@param build Build +---@param gemInstance any +---@param grantedEffect any +---@param addLevel boolean +---@param addReq boolean +---@param mergeStatsFrom any +local function addCommonGemInfo(tooltip, build, gemInstance, grantedEffect, addLevel, addReq, mergeStatsFrom) + local fontSizeBig = main.showFlavourText and 18 or 16 + local displayInstance = gemInstance.displayEffect or gemInstance + local grantedEffectLevel = grantedEffect.levels[displayInstance.level] or {} + if addLevel then + tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FLevel: ^7%d%s%s", + gemInstance.level, + ((displayInstance.level > gemInstance.level) and " (" .. colorCodes.MAGIC .. "+" .. (displayInstance.level - gemInstance.level) .. "^7)") or + ((displayInstance.level < gemInstance.level) and " (" .. colorCodes.WARNING .. "-" .. (gemInstance.level - displayInstance.level) .. "^7)") or + "", + (gemInstance.level >= (gemInstance.gemData.naturalMaxLevel or math.huge)) and " (Max)" or "" + ), "FONTIN SC") + end + if grantedEffect.support then + if grantedEffectLevel.manaMultiplier then + tooltip:AddLine(fontSizeBig, + string.format("^x7F7F7FCost & Reservation Multiplier: ^7%d%%", grantedEffectLevel.manaMultiplier + 100), + "FONTIN SC") + end + local reservation + for name, res in pairs(reservationMap) do + if grantedEffectLevel[name] then + reservation = (reservation and (reservation .. ", ") or "") .. + data.costs[isValueInArrayPred(data.costs, function(v) return v.Resource == res end)].ResourceString:gsub( + "{0}", string.format("%d", grantedEffectLevel[name])) + end + end + if reservation then + tooltip:AddLine(fontSizeBig, "^x7F7F7FReservation Override: ^7" .. reservation, "FONTIN SC") + end + if grantedEffectLevel.cooldown then + local string = string.format("^x7F7F7FCooldown Time: ^7%.2f sec", grantedEffectLevel.cooldown) + if grantedEffectLevel.storedUses and grantedEffectLevel.storedUses > 1 then + string = string .. string.format(" (%d uses)", grantedEffectLevel.storedUses) + end + tooltip:AddLine(fontSizeBig, string, "FONTIN SC") + end + else + local reservation + for name, res in pairs(reservationMap) do + if grantedEffectLevel[name] then + reservation = (reservation and (reservation .. ", ") or "") .. + data.costs[isValueInArrayPred(data.costs, function(v) return v.Resource == res end)].ResourceString:gsub( + "{0}", string.format("%d", grantedEffectLevel[name])) + end + end + if reservation then + tooltip:AddLine(fontSizeBig, "^x7F7F7FReservation: ^7" .. reservation, "FONTIN SC") + end + local cost + for _, res in ipairs(data.costs) do + if grantedEffectLevel.cost and grantedEffectLevel.cost[res.Resource] then + cost = (cost and (cost .. ", ") or "") .. + res.ResourceString:gsub("{0}", + string.format("%g", round(grantedEffectLevel.cost[res.Resource] / res.Divisor, 2))) + end + end + if cost then + tooltip:AddLine(fontSizeBig, "^x7F7F7FCost: ^7" .. cost, "FONTIN SC") + end + if grantedEffectLevel.cooldown then + local string = string.format("^x7F7F7FCooldown Time: ^7%.2f sec", grantedEffectLevel.cooldown, "FONTIN SC") + if grantedEffectLevel.storedUses and grantedEffectLevel.storedUses > 1 then + string = string .. string.format(" (%d uses)", grantedEffectLevel.storedUses) + end + tooltip:AddLine(fontSizeBig, string, "FONTIN SC") + end + if grantedEffectLevel.vaalStoredUses then + tooltip:AddLine(fontSizeBig, + string.format("^x7F7F7FCan Store ^7%d ^x7F7F7FUse (%d Souls)", grantedEffectLevel.vaalStoredUses, + grantedEffectLevel.vaalStoredUses * grantedEffectLevel.cost.Soul), "FONTIN SC") + end + if grantedEffectLevel.soulPreventionDuration then + tooltip:AddLine(fontSizeBig, + string.format("^x7F7F7FSoul Gain Prevention: ^7%d sec", grantedEffectLevel.soulPreventionDuration), + "FONTIN SC") + end + if gemInstance.gemData.tags.attack then + if grantedEffectLevel.attackSpeedMultiplier then + tooltip:AddLine(fontSizeBig, + string.format("^x7F7F7FAttack Speed: ^7%d%% of base", grantedEffectLevel.attackSpeedMultiplier + 100), + "FONTIN SC") + end + if grantedEffectLevel.attackTime then + tooltip:AddLine(fontSizeBig, + string.format("^x7F7F7FAttack Time: ^7%.2f sec", grantedEffectLevel.attackTime / 1000), "FONTIN SC") + end + if grantedEffectLevel.baseMultiplier then + tooltip:AddLine(fontSizeBig, + string.format("^x7F7F7FAttack Damage: ^7%g%% of base", grantedEffectLevel.baseMultiplier * 100), + "FONTIN SC") + end + else + if grantedEffect.castTime > 0 then + tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FCast Time: ^7%.2f sec", grantedEffect.castTime), + "FONTIN SC") + else + tooltip:AddLine(fontSizeBig, "^x7F7F7FCast Time: ^7Instant", "FONTIN SC") + end + end + if grantedEffectLevel.critChance then + tooltip:AddLine(fontSizeBig, + string.format("^x7F7F7FCritical Strike Chance: ^7%.2f%%", grantedEffectLevel.critChance), "FONTIN SC") + end + if grantedEffectLevel.damageEffectiveness then + tooltip:AddLine(fontSizeBig, + string.format("^x7F7F7FEffectiveness of Added Damage: ^7%d%%", + grantedEffectLevel.damageEffectiveness * 100), "FONTIN SC") + end + end + if addReq and displayInstance.quality > 0 then + tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FQuality: " .. colorCodes.MAGIC .. "+%d%%^7%s", + gemInstance.quality, + (displayInstance.quality > gemInstance.quality) and + " (" .. colorCodes.MAGIC .. "+" .. (displayInstance.quality - gemInstance.quality) .. "^7)" or "" + ), "FONTIN SC") + end + tooltip:AddSeparator(10) + if addReq then + local reqLevel = grantedEffect.levels[gemInstance.level] and + grantedEffect.levels[gemInstance.level].levelRequirement or 1 + local reqStr = calcLib.getGemStatRequirement(reqLevel, grantedEffect.support, gemInstance.gemData.reqStr) + local reqDex = calcLib.getGemStatRequirement(reqLevel, grantedEffect.support, gemInstance.gemData.reqDex) + local reqInt = calcLib.getGemStatRequirement(reqLevel, grantedEffect.support, gemInstance.gemData.reqInt) + build:AddRequirementsToTooltip(tooltip, reqLevel, reqStr, reqDex, reqInt) + end + if grantedEffect.description then + tooltip:AddLine(fontSizeBig, colorCodes.GEM .. grantedEffect.description, "FONTIN SC") + end + if build.data.describeStats then + tooltip:AddSeparator(10) + local stats = calcLib.buildSkillInstanceStats(displayInstance, grantedEffect) + if mergeStatsFrom then + for stat, val in pairs(calcLib.buildSkillInstanceStats(displayInstance, mergeStatsFrom)) do + stats[stat] = (stats[stat] or 0) + val + end + end + local descriptions, lineMap = build.data.describeStats(stats, grantedEffect.statDescriptionScope) + for _, line in ipairs(descriptions) do + local source = grantedEffect.statMap[lineMap[line]] or build.data.skillStatMap[lineMap[line]] + if source then + if launch.devModeAlt then + local devText = lineMap[line] + if source[1] then + if not source[1].value then + source[1].value = lineMap[line] + end + devText = modLib.formatMod(source[1]) + end + line = line .. " ^2" .. devText + end + tooltip:AddLine(fontSizeBig, colorCodes.MAGIC .. line, "FONTIN SC") + else + if launch.devModeAlt then + line = line .. " ^1" .. lineMap[line] + end + local line = colorCodes.UNSUPPORTED .. line + line = main.notSupportedModTooltips and (line .. main.notSupportedTooltipText) or line + tooltip:AddLine(fontSizeBig, line, "FONTIN SC") + end + end + end +end + +---@class GemToolTipOptions +---@field skipRequirements? boolean +---@param tooltip Tooltip +---@param build Build +---@param gemInstance any +---@param options? GemToolTipOptions +function GemTooltip.AddGemTooltip(tooltip, build, gemInstance, options) + options = options or { } + local fontSizeBig, fontSizeTitle = getFontSizes() + local skipRequirements = options.skipRequirements + tooltip.center = true + tooltip.color = colorCodes.GEM + tooltip.maxWidth = 600 + tooltip.tooltipHeader = "GEM" + tooltip.gemIcon = gemInstance.gemData.grantedEffect.icon + + local primary = gemInstance.gemData.grantedEffect + local secondary = gemInstance.gemData.secondaryGrantedEffect + + if secondary and (not secondary.support or gemInstance.gemData.secondaryEffectName) then + local grantedEffect = gemInstance.gemData.VaalGem and secondary or primary + local grantedEffectSecondary = gemInstance.gemData.VaalGem and primary or secondary + tooltip:AddLine(fontSizeTitle, colorCodes.GEM .. grantedEffect.name, "FONTIN SC") + tooltip:AddSeparator(10) + tooltip:AddLine(fontSizeBig, "^x7F7F7F" .. gemInstance.gemData.tagString, "FONTIN SC") + addCommonGemInfo(tooltip, build, gemInstance, grantedEffect, true, not skipRequirements) + tooltip:AddSeparator(10) + tooltip:AddLine(fontSizeTitle, + colorCodes.GEM .. (gemInstance.gemData.secondaryEffectName or grantedEffectSecondary.name), "FONTIN SC") + tooltip:AddSeparator(10) + addCommonGemInfo(tooltip, build, gemInstance, grantedEffectSecondary, true, not skipRequirements) + else + local grantedEffect = gemInstance.gemData.grantedEffect + tooltip:AddLine(fontSizeTitle, colorCodes.GEM .. grantedEffect.name, "FONTIN SC") + tooltip:AddSeparator(10) + if grantedEffect.legacy then + tooltip:AddLine(fontSizeTitle, colorCodes.WARNING .. "Legacy Gem", "FONTIN SC") + tooltip:AddLine(fontSizeBig, colorCodes.WARNING .. "Gem only exists in Standard League", "FONTIN SC") + tooltip:AddSeparator(10) + end + if gemInstance.gemData.tagString then + tooltip:AddLine(fontSizeBig, "^x7F7F7F" .. gemInstance.gemData.tagString, "FONTIN SC") + end + addCommonGemInfo(tooltip, build, gemInstance, grantedEffect, true, not skipRequirements, + secondary and secondary.suport and secondary) + end + if primary.flavourText and main.showFlavourText then + tooltip:AddSeparator(10) + for _, line in ipairs(primary.flavourText) do + tooltip:AddLine(fontSizeBig, colorCodes.UNIQUE .. line, "FONTIN SC ITALIC") + end + end +end + +return GemTooltip diff --git a/src/Classes/ItemSlotControl.lua b/src/Classes/ItemSlotControl.lua index d7b549f3a26..c3d59db4b54 100644 --- a/src/Classes/ItemSlotControl.lua +++ b/src/Classes/ItemSlotControl.lua @@ -49,7 +49,7 @@ local ItemSlotClass = newClass("ItemSlotControl", "DropDownControl", function(se -- not selControl.ListControl allows hover when All Items or Unique/Rare DB Sections are in focus if main.popups[1] or mode == "OUT" or not item or (not self.dropped and itemsTab.selControl and itemsTab.selControl ~= self.controls.activate and not itemsTab.selControl.ListControl) then tooltip:Clear(true) - elseif tooltip:CheckForUpdate(item, launch.devModeAlt, itemsTab.build.outputRevision) then + elseif tooltip:CheckForUpdate(item, launch.devModeAlt, itemsTab.build.outputRevision, IsKeyDown("SHIFT")) then itemsTab:AddItemTooltip(tooltip, item, self) end end diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index e6083ea7839..427b5503bb3 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -15,6 +15,7 @@ local m_ceil = math.ceil local m_floor = math.floor local m_modf = math.modf +local gemTooltip = LoadModule("Classes/GemTooltip") local rarityDropList = { { label = colorCodes.NORMAL.."Normal", rarity = "NORMAL" }, { label = colorCodes.MAGIC.."Magic", rarity = "MAGIC" }, @@ -4254,6 +4255,53 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode, maxWidth) end end + -- Skill tooltip. We add child tooltips, which will be rendered to the right of the main + -- tooltip, growing downwards + if not tooltip.childTooltips then + tooltip.childTooltips = {} + end + for _, tt in ipairs(tooltip.childTooltips) do + tt:Clear() + end + local itemSkills = copyTable(item.grantedSkills or {}) + -- append "Supported by #" to active skills + for _, mod in ipairs(modList) do + if mod.name == "ExtraSupport" then + t_insert(itemSkills, mod.value) + end + end + if #itemSkills > 0 then + tooltip:AddSeparator(14) + tooltip:AddLine(14, + colorCodes.TIP .. + "Tip: Hold Shift to display a tooltip for the granted skill" .. + (#itemSkills > 1 and "s" or "") .. ".") + for i, itemSkill in ipairs(itemSkills) do + if not tooltip.childTooltips[i] then + tooltip.childTooltips[i] = new("Tooltip") + end + -- find gem since the item data only contains the skill id + local skill = data.skills[itemSkill.skillId] + if skill and skill.id and IsKeyDown("SHIFT") then + local gemId = data.gemForSkill[skill] or "" + local gem = data.gems[gemId] + -- if the skill has no matching gem, make up one. it will lack some information, but should still display somewhat correctly + ---@type GemToolTipOptions + local options = {} + if not gem then + gem = { grantedEffect = skill, tags = {} } + options.skipRequirements = true + end + local gemInst = { + gemData = gem, + level = itemSkill.level or 1, + quality = 0, + grantedEffect = skill + } + gemTooltip.AddGemTooltip(tooltip.childTooltips[i], self.build, gemInst, options) + end + end + end -- Stat differences if not self.showStatDifferences then tooltip:AddSeparator(14) diff --git a/src/Classes/PassiveTreeView.lua b/src/Classes/PassiveTreeView.lua index f774737424e..c33c9f52c7a 100644 --- a/src/Classes/PassiveTreeView.lua +++ b/src/Classes/PassiveTreeView.lua @@ -14,6 +14,7 @@ local m_floor = math.floor local band = bit.band local b_rshift = bit.rshift +local gemTooltip = LoadModule("Classes/GemTooltip") local PassiveTreeViewClass = newClass("PassiveTreeView", function(self) self.ring = NewImageHandle() self.ring:Load("Assets/ring.png", "CLAMP") @@ -54,6 +55,7 @@ local PassiveTreeViewClass = newClass("PassiveTreeView", function(self) self.kalguur2:Load("TreeData/PassiveSkillScreenKalguuranJewelCircle2.png", "CLAMP") self.tooltip = new("Tooltip") + self.skillTooltip = new("Tooltip") self.zoomLevel = 3 self.zoom = 1.2 ^ self.zoomLevel @@ -984,11 +986,64 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) -- Draw tooltip SetDrawLayer(nil, 100) local size = m_floor(node.size * scale) - if self.tooltip:CheckForUpdate(node, self.showStatDifferences, self.tracePath, launch.devModeAlt, build.outputRevision, self.compareSpec) then + if self.tooltip:CheckForUpdate(node, self.showStatDifferences, self.tracePath, launch.devModeAlt, build.outputRevision, build.spec.allocMode) then self:AddNodeTooltip(self.tooltip, node, build) end self.tooltip.center = true - self.tooltip:Draw(m_floor(scrX - size), m_floor(scrY - size), size * 2, size * 2, viewPort) + local ttWidth, ttHeight = self.tooltip:GetDynamicSize(viewPort) + local skillWidth, skillHeight = self.skillTooltip:GetDynamicSize(viewPort) + + local fatSkill = skillWidth > skillHeight * 1.5 + + local totalWidth, totalHeight + if fatSkill then + totalWidth = m_max(ttWidth, (#self.skillTooltip.lines > 0 and skillWidth or 0)) + totalHeight = ttHeight + (#self.skillTooltip.lines > 0 and skillHeight or 0) + else + totalWidth = ttWidth + (#self.skillTooltip.lines > 0 and skillWidth or 0) + totalHeight = m_max(ttHeight, (#self.skillTooltip.lines > 0 and skillHeight or 0)) + end + + -- main tooltip is anchored from top left to the node + local nodeX = m_floor(scrX + size) + local ttX = m_floor(scrX + size) + local nodeY = m_floor(scrY - size) + local ttY = m_floor(scrY - size) + + + -- if the right side goes outside the viewport, we adjust by moving to the left + local rEdgeX = ttX + totalWidth - viewPort.x + local rOverBy = rEdgeX - viewPort.width + if rOverBy > 0 then + ttX = ttX - rOverBy + end + + -- same for bottom edge + local btmEdgeY = ttY + totalHeight - viewPort.y + local btmOverBy = btmEdgeY - viewPort.height + if btmOverBy > 0 then + ttY = ttY - btmOverBy + end + + SetDrawLayer(nil, 100) + -- main tooltip is attached to node, unless it is pushed + if fatSkill then + self.tooltip:Draw(m_min(nodeX, viewPort.width - ttWidth + viewPort.x), m_max(ttY, viewPort.y), nil, nil, + viewPort) + else + self.tooltip:Draw(m_max(ttX, viewPort.x), m_min(nodeY, viewPort.height - ttHeight + viewPort.y), nil, nil, + viewPort) + end + SetDrawLayer(nil, 99) + -- draw below main tooltip + if fatSkill then + self.skillTooltip:Draw(ttX, ttY + ttHeight, nil, nil, + viewPort) + -- draw to the right of main tooltip + else + self.skillTooltip:Draw(ttX + ttWidth + 5, ttY, nil, nil, + viewPort) + end end end @@ -1478,6 +1533,41 @@ function PassiveTreeViewClass:AddNodeTooltip(tooltip, node, build) for i, line in ipairs(mNode.sd) do addModInfoToTooltip(mNode, i, masteryColor..line) end + -- add child tooltip for skills + self.skillTooltip:Clear() + for _, mod in ipairs(mNode.finalModList or mNode.modList or {}) do + if mod.name == "ExtraSkill" or mod.name == "ExtraSupport" then + local found = false + for grantedEffect, gemId in pairs(data.gemForSkill) do + if grantedEffect.id == mod.value.skillId then + local gem = data.gems[gemId] + found = true + local gemInst = { + gemData = gem, + level = mod.value.level or 1, + quality = 0, + grantedEffect = + grantedEffect + } + gemTooltip.AddGemTooltip(self.skillTooltip, build, gemInst) + break + end + end + -- if we didn't find a matching gem, look up based on the skill id + if not found then + local skill = data.skills[mod.value.skillId] + -- poe1 item skills don't seem to have matching gems, so make up one + local gem = { grantedEffect = skill, tags = {} } + local gemInst = { + gemData = gem, + level = mod.value.level or 1, + quality = 0, + grantedEffect = skill + } + gemTooltip.AddGemTooltip(self.skillTooltip, build, gemInst, { skipRequirements = true }) + end + end + end end -- Reminder text diff --git a/src/Classes/Tooltip.lua b/src/Classes/Tooltip.lua index 26f47c87e4f..e6e1474f9c8 100644 --- a/src/Classes/Tooltip.lua +++ b/src/Classes/Tooltip.lua @@ -43,6 +43,7 @@ end local TooltipClass = newClass("Tooltip", function(self) self.lines = { } self.blocks = { } + self.childTooltips = nil self:Clear() end) @@ -52,11 +53,13 @@ function TooltipClass:Clear(clearUpdateParams) if self.updateParams and clearUpdateParams then wipeTable(self.updateParams) end + ---@type string|boolean self.tooltipHeader = false self.titleYOffset = 0 self.recipe = nil self.center = false self.maxWidth = nil + ---@type string|[number, number, number] self.color = { 0.5, 0.3, 0 } t_insert(self.blocks, { height = 0 }) end @@ -612,5 +615,29 @@ function TooltipClass:Draw(x, y, w, h, viewPort) DrawImage(nil, ttX, ttY, totalDrawWidth, BORDER_WIDTH) -- top DrawImage(nil, ttX, ttY + maxColumnHeight - BORDER_WIDTH, totalDrawWidth, BORDER_WIDTH) -- bottom + -- draw child tooltips for item skills. these are placed directly to the right of the main + -- tooltip, growing downwards, unless they would go outside the viewport, in which case they + -- will draw over the main tooltip + if self.childTooltips then + local totalH = 0 + -- we will move the tooltips up as a group, so get the total height + for _, tt in ipairs(self.childTooltips) do + local _, childH = tt:GetDynamicSize(viewPort) + totalH = totalH + childH + end + -- if the whole group would go over the bottom edge, we apply a negative offset to keep them + -- in + local yOffset = math.min(0, viewPort.height - totalH - ttY) + -- movement to the left happens individually. i.e. the right edges are aligned + local yPos = math.max(ttY + yOffset, viewPort.y) + for _, tt in ipairs(self.childTooltips) do + local childW, childH = tt:GetSize() + local furthestAllowedX = viewPort.width + viewPort.x - childW + tt:Draw(math.min(ttX + ttW + 4, furthestAllowedX), yPos, nil, nil, + viewPort) + -- next tooltip goes below this one + yPos = yPos + childH + 6 + end + end return ttW, ttH end diff --git a/src/Data/Skills/other.lua b/src/Data/Skills/other.lua index a8fc196fb84..fb75d03be5c 100644 --- a/src/Data/Skills/other.lua +++ b/src/Data/Skills/other.lua @@ -1470,6 +1470,7 @@ skills["SupportEarthbreaker"] = { skills["SupportEclipse"] = { name = "Eclipse", description = "Supports any skill gem.\nCannot support skills that don't come from gems.", + flavourText = {"\"Although we are two bodies, we are of one mind, one heart, drifting", "through the cosmos, in search of purpose. The moment we cross paths,", "both fleeting and eternal, our eyes are opened to our potential.\"", }, color = 4, support = true, requireSkillTypes = { }, diff --git a/src/Data/Skills/sup_dex.lua b/src/Data/Skills/sup_dex.lua index caf15b809fe..17dd9623b80 100644 --- a/src/Data/Skills/sup_dex.lua +++ b/src/Data/Skills/sup_dex.lua @@ -829,6 +829,7 @@ skills["SupportCastOnDeath"] = { skills["SupportCastOnWardBreak"] = { name = "Cast on Ward Break", description = "Each supported spell skill will be triggered when your Ward breaks. Cannot support skills used by totems, traps, or mines. Vaal skills, channelling skills, and skills with a reservation cannot be triggered.", + flavourText = {"\"Prove yourself! Are you a knave, or a knight?", "I shall know by the strength of your blows!\"", }, color = 2, support = true, requireSkillTypes = { SkillType.Spell, SkillType.Triggerable, SkillType.AND, }, @@ -990,6 +991,7 @@ skills["SupportAwakenedChain"] = { skills["SupportGreaterChain"] = { name = "Greater Chain", description = "Supports projectile skills, and any other skills that chain.", + flavourText = {"\"We are all connected. Each of us burns with the same fire.", "Queen Hyrri knew that... she chooses to battle still, somewhere", "beyond Hinekora's sight. There must be a very good reason.\"", }, color = 2, support = true, requireSkillTypes = { SkillType.Chains, SkillType.Projectile, SkillType.ThresholdJewelProjectile, SkillType.ThresholdJewelRangedAttack, SkillType.ThresholdJewelChaining, }, @@ -1411,6 +1413,7 @@ skills["SupportAwakenedColdPenetration"] = { skills["SupportCompanionship"] = { name = "Companionship", description = "Supports skills that create minions which can be damaged. Cannot support triggered skills.", + flavourText = {"The Black Sekhema was a formidable fighter", "alongside her dekhara, but she was unstoppable", "when she fought atop her trusted mount, Shiyo.", }, color = 2, support = true, requireSkillTypes = { SkillType.CreatesMinion, }, @@ -1513,6 +1516,7 @@ skills["SupportCriticalStrikeAffliction"] = { skills["SupportCullTheWeak"] = { name = "Cull the Weak", description = "Supports any skill that hits enemies. If normal or magic enemies are left below a percentage of maximum life after being hit by these skills, they will be killed.", + flavourText = {"\"Beidat is the lesser of three evils. The other two desire only", "to consume us, but under him, there is a chance to survive...", "a chance to serve... if you can prove yourself useful.\"", }, color = 2, support = true, requireSkillTypes = { SkillType.Damage, SkillType.Attack, }, @@ -2105,6 +2109,7 @@ skills["SupportAwakenedFork"] = { skills["SupportGreaterFork"] = { name = "Greater Fork", description = "Supports projectile skills, making their projectiles fork into two projectiles the first two times they hit an enemy and don't pierce it.", + flavourText = {"Lioneye earned his moniker by killing", "three men with a single arrow.", }, color = 2, support = true, requireSkillTypes = { SkillType.Projectile, SkillType.ThresholdJewelProjectile, SkillType.ThresholdJewelRangedAttack, }, @@ -2538,6 +2543,7 @@ skills["SupportImpale"] = { skills["SupportInvention"] = { name = "Invention", description = "Supports skills which throw Traps or Mines.", + flavourText = {"\"The direct approach failed. So be it. In my crusade", "to protect Wraeclast, I must take... a longer view.\"", }, color = 2, support = true, requireSkillTypes = { SkillType.Trapped, SkillType.RemoteMined, }, @@ -2581,6 +2587,7 @@ skills["SupportInvention"] = { skills["SupportLethalDose"] = { name = "Lethal Dose", description = "Supports any skill that hits enemies.", + flavourText = {"\"They cast me into a pit of vipers, and", "that is where I found my true self.\"", }, color = 2, support = true, requireSkillTypes = { SkillType.Damage, SkillType.Attack, }, @@ -2711,6 +2718,7 @@ skills["SupportLocusMine"] = { skills["SupportMachinations"] = { name = "Machinations", description = "Supports skills that hit enemies. Cannot modify the skills of minions.", + flavourText = {"\"My master enjoys the strange, and the unexpected.", "I'm sure you can think of a few ways to entertain them.\"", }, color = 2, support = true, requireSkillTypes = { SkillType.Damage, SkillType.Attack, }, @@ -3401,6 +3409,7 @@ skills["SupportNightblade"] = { skills["SupportPacifism"] = { name = "Pacifism", description = "Supports any skill that hits enemies.", + flavourText = {"\"My people refused to take part in the ills of Wraeclast.", "They were called primitives by some, and cowards by", "others... but they are gone, and we still remain.\"", }, color = 2, support = true, requireSkillTypes = { }, @@ -4286,6 +4295,7 @@ skills["SupportTrapAndMineDamage"] = { skills["SupportUnholyTrinity"] = { name = "Unholy Trinity", description = "Supports skills that hit enemies, causing them to gain bonuses while you have unholy resonance of lightning, physical and chaos, and to grant unholy resonance of damage types other than the highest one in a hit. There is a maximum of 50 unholy resonance for each damage type. Cannot support triggered skills, instant skills, orb skills, brands, skills with a reservation, or skills used by totems, traps, or mines. Cannot modify the skills of minions.", + flavourText = {"It was a delicate balancing act for Malachai to ensure the loyalty", "of his three students - each as ambitious as they were depraved.", }, color = 2, support = true, requireSkillTypes = { SkillType.Damage, SkillType.Attack, }, @@ -4661,6 +4671,7 @@ skills["SupportAwakenedVoidManipulation"] = { skills["SupportVoidstorm"] = { name = "Voidstorm", description = "Supports ranged attack skills that fire projectiles upwards. Cannot support triggered attacks, attacks used by things other than you, or skills which create minions.", + flavourText = {"\"Not even stopping to look behind, I dove through the shimmering window...", "With not a minute to waste, I took a nearby tool and jammed it into the infernal", "device, where it hummed, ominously entrenched in my laboratory floor.\"", }, color = 2, support = true, requireSkillTypes = { SkillType.Rain, }, @@ -4703,6 +4714,7 @@ skills["SupportVoidstorm"] = { skills["TriggeredSupportVoidstorm"] = { name = "Voidstorm", baseTypeName = "Voidstorm", + flavourText = {"\"Not even stopping to look behind, I dove through the shimmering window...", "With not a minute to waste, I took a nearby tool and jammed it into the infernal", "device, where it hummed, ominously entrenched in my laboratory floor.\"", }, color = 2, description = "Creates a voidstorm that blinds enemies within it. Entering the storm will cause it to explode, dealing attack damage in an area.", skillTypes = { [SkillType.Attack] = true, [SkillType.Area] = true, [SkillType.Damage] = true, [SkillType.RangedAttack] = true, [SkillType.Cooldown] = true, [SkillType.Duration] = true, [SkillType.Triggerable] = true, [SkillType.Triggered] = true, [SkillType.SkillGrantedBySupport] = true, [SkillType.ProjectilesNumberModifiersNotApplied] = true, }, diff --git a/src/Data/Skills/sup_int.lua b/src/Data/Skills/sup_int.lua index b6111ab4499..75b09202212 100644 --- a/src/Data/Skills/sup_int.lua +++ b/src/Data/Skills/sup_int.lua @@ -490,6 +490,7 @@ skills["SupportAwakenedBlasphemy"] = { skills["SupportEldritchBlasphemy"] = { name = "Eldritch Blasphemy", description = "Supports hex curse skills, turning them into auras that will apply their effect to all enemies in an area around you.", + flavourText = {"Even trapped in stone, the Elder's shadow lingered", "in their hearts and coiled around their dreams.", }, color = 3, support = true, requireSkillTypes = { SkillType.AppliesCurse, SkillType.Hex, SkillType.AND, }, @@ -1050,6 +1051,7 @@ skills["SupportChargedMines"] = { skills["SupportCongregation"] = { name = "Congregation", description = "Supports skills which create Minions.", + flavourText = {"\"I have a recurring nightmare... I run...", "I scream... but only the dead hear me...\"", }, color = 3, support = true, requireSkillTypes = { SkillType.CreatesMinion, }, @@ -1279,6 +1281,7 @@ skills["SupportAwakenedControlledDestruction"] = { skills["SupportCooldownRecovery"] = { name = "Cooldown Recovery", description = "Supports skills that have a cooldown. Cannot modify the skills of minions.", + flavourText = {"\"The science is... incomprehensible at best. Nearest I can tell, the Arcana exhibits", "a blast of wrath once it has been fully charged, and when directed at the Elder", "will force it to take on the form it held before it entered our dimension.\"", }, color = 3, support = true, requireSkillTypes = { SkillType.Cooldown, }, @@ -1884,6 +1887,7 @@ skills["SupportDevour"] = { skills["SupportGreaterDevour"] = { name = "Greater Devour", description = "Supports any skill that hits enemies.", + flavourText = {"A High Templar's fear consumed her entire life,", "so she cast aside her own, to do what must be done.", }, color = 3, support = true, requireSkillTypes = { SkillType.Damage, SkillType.Attack, }, @@ -2571,6 +2575,7 @@ skills["SupportFocusedChannelling"] = { skills["SupportFoulgrasp"] = { name = "Foulgrasp", description = "Supports brand skills that can be recalled.", + flavourText = {"\"Words without thought drive us", "to claw and scrabble in the dirt,", "seeking respite found only in pain.\"", }, color = 3, support = true, requireSkillTypes = { SkillType.Brand, }, @@ -2610,6 +2615,7 @@ skills["SupportFoulgrasp"] = { skills["TriggeredSupportFoulgrasp"] = { name = "Foulgrasp", baseTypeName = "Foulgrasp", + flavourText = {"\"Words without thought drive us", "to claw and scrabble in the dirt,", "seeking respite found only in pain.\"", }, color = 3, baseEffectiveness = 0.85000002384186, incrementalEffectiveness = 0.046999998390675, @@ -2812,6 +2818,7 @@ skills["SupportFrigidBond"] = { skills["SupportFrostmage"] = { name = "Frostmage", description = "Supports spell skills that deal damage with hits and have no reservation. Cannot support arcane skills, Brand skills, Orb skills, Vaal skills, or skills used by totems, traps or mines. Cannot modify the skills of minions.", + flavourText = {"Her understanding of the Atlas was not innate. The", "more committed she became to understanding it,", "the more the knowledge weighed on her.", }, color = 3, support = true, requireSkillTypes = { SkillType.Damage, SkillType.Spell, SkillType.AND, }, @@ -2862,6 +2869,7 @@ skills["SupportFrostmage"] = { skills["SupportGluttony"] = { name = "Gluttony", description = "Supports Projectile Spells you cast yourself. Cannot support orb skills, triggered skills, brand skills, Vaal skills, channelling skills, skills with a reservation, or skills used by things other than you.", + flavourText = {"Champions of the Tangle are driven to seek", "sustenance for their master; should they fall", "behind, they would soon be consumed as well.", }, color = 3, support = true, requireSkillTypes = { SkillType.Projectile, SkillType.ThresholdJewelProjectile, SkillType.OR, SkillType.Spell, SkillType.AND, }, @@ -2970,6 +2978,7 @@ skills["SupportHexBloom"] = { skills["SupportHexpass"] = { name = "Hexpass", description = "Supports hex curse skills.", + flavourText = {"\"Your wards and your charms are meaningless", "to one who knows your true name.\"", }, color = 3, support = true, requireSkillTypes = { SkillType.AppliesCurse, SkillType.Hex, SkillType.AND, }, @@ -3016,6 +3025,7 @@ skills["SupportHexpass"] = { skills["SupportHextoad"] = { name = "Hextoad", description = "Supports non-aura hex curse skills.", + flavourText = {"\"A warlock like myself knows that even the most abominable", "plagues are part of the cycle of life and death. From that ruination,", "new life will emerge, and that life will kill, and so on...\"", }, color = 3, support = true, requireSkillTypes = { SkillType.Hex, SkillType.AppliesCurse, SkillType.AND, }, @@ -3062,6 +3072,7 @@ skills["SupportHextoad"] = { skills["TriggeredSupportHextoad"] = { name = "Bursting Toad", baseTypeName = "Bursting Toad", + flavourText = {"\"A warlock like myself knows that even the most abominable", "plagues are part of the cycle of life and death. From that ruination,", "new life will emerge, and that life will kill, and so on...\"", }, color = 3, baseEffectiveness = 3.5999999046326, incrementalEffectiveness = 0.045499999076128, @@ -3189,6 +3200,7 @@ skills["SupportHighImpactMine"] = { skills["SupportHiveborn"] = { name = "Hiveborn", description = "Supports offering skills you use yourself.", + flavourText = {"\"We spill forth, cleaving our flesh", "to theirs, until we become one.\"", }, color = 3, support = true, requireSkillTypes = { SkillType.Offering, }, @@ -3221,6 +3233,7 @@ skills["SupportHiveborn"] = { skills["TriggeredSupportHiveborn"] = { name = "Summon Hiveborn", baseTypeName = "Summon Hiveborn", + flavourText = {"\"We spill forth, cleaving our flesh", "to theirs, until we become one.\"", }, color = 3, description = "Summons a horde of Hiveborn Breach monsters to do your bidding. These summoned monsters have all their physical damage converted to cold.", skillTypes = { [SkillType.Cooldown] = true, [SkillType.Minion] = true, [SkillType.Spell] = true, [SkillType.Triggerable] = true, [SkillType.Triggered] = true, [SkillType.InbuiltTrigger] = true, [SkillType.SkillGrantedBySupport] = true, [SkillType.CreatesMinion] = true, }, @@ -4054,6 +4067,7 @@ skills["SupportIntensify"] = { skills["SupportInvertTheRules"] = { name = "Invert the Rules", description = "Supports any skill that hits enemies.", + flavourText = {"\"She fled from the prison of her birth, drawn by the breaking", "of that great Silence. At that moment, the universe was changed,", "and walls that were once impenetrable became brittle.", "A fortress became a shell, and she alone escaped.\"", }, color = 3, support = true, requireSkillTypes = { SkillType.Damage, SkillType.Attack, }, @@ -4283,6 +4297,7 @@ skills["TriggeredSupportKineticInstability"] = { skills["SupportGreaterKineticInstability"] = { name = "Greater Kinetic Instability", description = "Supports Wand Attacks, granting them a chance to trigger Kinetic Flux on critical strike. Cannot support triggered skills or skills used by things other than you. Cannot modify the skills of minions.", + flavourText = {"\"Accusations fly. Sinner! Heretic! I am but an initiate, and my patron urges", "silence. We will keep our heads down while one High Templar supplants", "another. It's all about power, my patron whispers, and we have none...\"", }, color = 3, support = true, requireSkillTypes = { SkillType.RangedAttack, }, @@ -4323,6 +4338,7 @@ skills["SupportGreaterKineticInstability"] = { skills["TriggeredSupportGreaterKineticInstability"] = { name = "Kinetic Flux", baseTypeName = "Kinetic Flux", + flavourText = {"\"Accusations fly. Sinner! Heretic! I am but an initiate, and my patron urges", "silence. We will keep our heads down while one High Templar supplants", "another. It's all about power, my patron whispers, and we have none...\"", }, color = 3, description = "Gathers kinetic energy into an anomaly at the targeted location, which will explode when enemies are near it, dealing your wand's damage in an area. The anomaly will dissipate after a duration.", skillTypes = { [SkillType.Area] = true, [SkillType.Triggered] = true, [SkillType.Triggerable] = true, [SkillType.SkillGrantedBySupport] = true, [SkillType.InbuiltTrigger] = true, [SkillType.RangedAttack] = true, [SkillType.Attack] = true, [SkillType.Duration] = true, [SkillType.WandAttack] = true, [SkillType.Cooldown] = true, }, @@ -4929,6 +4945,7 @@ skills["SupportMinionLife"] = { skills["SupportMinionPact"] = { name = "Minion Pact", description = "Supports spell skills that hit enemies and have no reservation. Cannot support brand skills, orb skills, channelling skills, arcane skills, Vaal skills or skills used by traps or mines. Cannot modify the skills of minions.", + flavourText = {"Immortality could grant Catarina the time necessary to shape", "a better world... but building requires sacrifice, and the useless", "would be crushed into the foundation of her new utopia.", }, color = 3, support = true, requireSkillTypes = { SkillType.Damage, SkillType.Spell, SkillType.AND, }, @@ -5174,6 +5191,7 @@ skills["SupportOvercharge"] = { skills["SupportOverloadedIntensity"] = { name = "Overloaded Intensity", description = "Supports spells skills that gain Intensity.", + flavourText = {"\"I didn't design it to overload... let's call it a happy little accident!\"", }, color = 3, support = true, requireSkillTypes = { SkillType.GainsIntensity, }, @@ -5903,6 +5921,7 @@ skills["SupportSacrifice"] = { skills["SupportScornfulHerald"] = { name = "Scornful Herald", description = "Supports Herald skills causing them to have higher critical strike chance but no buff effect.", + flavourText = {"Her bitterness blinded her to the true price of immortality.", "Let Wraeclast burn and be devoured, she told herself...", "and only herself, for centuries without end.", }, color = 3, support = true, requireSkillTypes = { SkillType.Herald, }, @@ -6142,6 +6161,7 @@ skills["SupportAwakenedSpellCascade"] = { skills["SupportGreaterSpellCascade"] = { name = "Greater Spell Cascade", description = "Supports spell skills that apply an effect to an area around a targeted location. Cannot support Vaal skills or skills used by totems, traps, or mines. Cannot modify the skills of minions.", + flavourText = {"The echoes of lost loved ones surrounded her,", "maddeningly close, but forever out of reach.", }, color = 3, support = true, requireSkillTypes = { SkillType.Cascadable, }, @@ -6264,6 +6284,7 @@ skills["SupportSpellEcho"] = { skills["SupportGreaterSpellEcho"] = { name = "Greater Spell Echo", description = "Supports spell skills, making them repeat when cast. Cannot support Vaal skills, totem skills, brand skills, channelling skills, triggered skills, instant skills, retaliation skills, blink skills, or skills with a reservation.", + flavourText = {"She gazed into the mirror...", "and the mirror gazed back.", }, color = 3, support = true, requireSkillTypes = { SkillType.Multicastable, }, @@ -6848,6 +6869,7 @@ skills["SupportAwakenedUnleash"] = { skills["SupportGreaterUnleash"] = { name = "Greater Unleash", description = "Supports spell skills, making their effect reoccur when cast. Cannot support skills with cooldowns, triggered skills, Brand skills, Vaal skills, channelling skills, skills with a reservation, or skills used by totems, traps, or mines. Cannot modify the skills of minions.", + flavourText = {"\"True power grows with patience. You must nurture it.", "You learn what it needs. You provide. You wait. Only", "then, when it is ripe, does it yield its fruits.\"", }, color = 3, support = true, requireSkillTypes = { SkillType.CanRapidFire, }, @@ -6904,6 +6926,7 @@ skills["SupportGreaterUnleash"] = { skills["SupportVaalSacrifice"] = { name = "Vaal Sacrifice", description = "Supports Vaal spell skills, making them repeat when cast at the cost of your life and energy shield. Cannot support totem skills, channelling skills, triggered skills, instant skills, retaliation skills, blink skills, or skills with a reservation.", + flavourText = {"Under Atziri, sacrifice was a ruthless and efficient science.", "Her people were willing to give everything... and they did.", }, color = 3, support = true, requireSkillTypes = { SkillType.Multicastable, SkillType.Vaal, SkillType.AND, }, diff --git a/src/Data/Skills/sup_str.lua b/src/Data/Skills/sup_str.lua index a8de5200517..5bdd88dfd1b 100644 --- a/src/Data/Skills/sup_str.lua +++ b/src/Data/Skills/sup_str.lua @@ -176,6 +176,7 @@ skills["SupportAncestralCall"] = { skills["SupportGreaterAncestralCall"] = { name = "Greater Ancestral Call", description = "Supports melee strike skills, causing them to also strike at extra targets simultaneously. The extra targets must be a minimum distance from the user. If supporting a minion attack skill, the minion's skills will not also strike extra targets. Cannot support Vaal skills or triggered skills.", + flavourText = {"\"I believe the Halls of the Dead are secretly a", "training ground. One day, we will be called", "forth to stand and fight against a great tide.\"", }, color = 1, support = true, requireSkillTypes = { SkillType.MeleeSingleTarget, }, @@ -266,6 +267,7 @@ skills["SupportAwakenedAncestralCall"] = { skills["SupportAnnhilation"] = { name = "Annihilation", description = "Supports any skill that hits enemies.", + flavourText = {"Those citizens of Oriath touched by the", "roving storms died most horrible deaths.", }, color = 1, support = true, requireSkillTypes = { SkillType.Damage, SkillType.Attack, }, @@ -557,6 +559,7 @@ skills["SupportBloodlust"] = { skills["SupportBloodsoakedBanner"] = { name = "Bloodsoaked Banner", description = "Supports banner skills. Cannot modify the skills of minions.", + flavourText = {"\"The history of Wraeclast is marked by bloody conflicts. The rise", "and fall of empires. Man slaughtered man, never suspecting the", "madness lurking under the surface might not have been innate.\"", }, color = 1, baseEffectiveness = 0.079999998211861, incrementalEffectiveness = 0.050000000745058, @@ -690,6 +693,7 @@ skills["SupportBloodthirst"] = { skills["SupportBonespire"] = { name = "Bonespire", description = "Supports warcry skills you use yourself.", + flavourText = {"In its madness, Xesht desperately seeks communion.", }, color = 1, support = true, requireSkillTypes = { SkillType.Warcry, }, @@ -727,6 +731,7 @@ skills["SupportBonespire"] = { skills["TriggeredSupportBonespire"] = { name = "Seize the Flesh", baseTypeName = "Seize the Flesh", + flavourText = {"In its madness, Xesht desperately seeks communion.", }, color = 1, description = "Cause spires of bone to erupt from the earth around you. When you use a slam on them, these spires will explode to deal damage in an area and inflicting bleeding.", skillTypes = { [SkillType.Cooldown] = true, [SkillType.Attack] = true, [SkillType.Area] = true, [SkillType.Physical] = true, [SkillType.Triggerable] = true, [SkillType.Triggered] = true, [SkillType.InbuiltTrigger] = true, [SkillType.SkillGrantedBySupport] = true, [SkillType.Warcry] = true, }, @@ -1655,6 +1660,7 @@ skills["SupportDamageOnFullLife"] = { skills["SupportDivineSentinel"] = { name = "Divine Sentinel", description = "Supports skills that create sentinel minions, causing those minions to be created with a beneficial templar aura.", + flavourText = {"\"The world will never know our sacrifice, but", "we will stand and fight for as long as it takes!\"", }, color = 1, support = true, requireSkillTypes = { SkillType.CreatesMinion, SkillType.CreatesSentinelMinion, }, @@ -2185,6 +2191,7 @@ skills["SupportAwakenedFirePenetration"] = { skills["SupportFissure"] = { name = "Fissure", description = "Supports slam skills you use yourself, causing them to create fissures when impacting the ground.", + flavourText = {"\"Oh yes, K'Tash understands you. It has a keen swarm-mind", "somewhere within its multitudes. It hears your pleas, but", "ignores them... because it hates you with a burning fury.\"", }, color = 1, support = true, requireSkillTypes = { SkillType.Slam, }, @@ -2223,6 +2230,7 @@ skills["SupportFissure"] = { skills["TriggeredSupportFissure"] = { name = "Fissure", baseTypeName = "Fissure", + flavourText = {"\"Oh yes, K'Tash understands you. It has a keen swarm-mind", "somewhere within its multitudes. It hears your pleas, but", "ignores them... because it hates you with a burning fury.\"", }, color = 1, description = "Creates multiple fissures that deal area damage while travelling outwards. Requires an Axe, Mace, Sceptre, Staff or Unarmed.", skillTypes = { [SkillType.Attack] = true, [SkillType.Area] = true, [SkillType.Melee] = true, [SkillType.Slam] = true, [SkillType.Fire] = true, [SkillType.Triggerable] = true, [SkillType.Triggered] = true, [SkillType.InbuiltTrigger] = true, [SkillType.SkillGrantedBySupport] = true, [SkillType.Cooldown] = true, }, @@ -3984,6 +3992,7 @@ skills["SupportAwakenedMultistrike"] = { skills["SupportGreaterMultistrike"] = { name = "Greater Multistrike", description = "Supports melee attack skills, making them repeat three times when used, targeting a random enemy each time. Cannot support Vaal skills, channelling skills, travel skills, retaliation skills or triggered skills.", + flavourText = {"Zana watched in awe as her father spoke. With each word,", "he seemed to grow larger and more frightening, drawing", "increasingly fervorous responses from his rapt audience.", }, color = 1, support = true, requireSkillTypes = { SkillType.Multistrikeable, }, @@ -4118,6 +4127,7 @@ skills["SupportOverexertion"] = { skills["SupportOverheat"] = { name = "Overheat", description = "Supports melee attack skills you use yourself. Cannot modify the skills of minions.", + flavourText = {"No walls can stand against its gaze. No fortress,", "no cave, no mountain. Better to die fighting.", }, color = 1, support = true, requireSkillTypes = { SkillType.Melee, }, @@ -4230,6 +4240,7 @@ skills["SupportPulverise"] = { skills["SupportPyre"] = { name = "Pyre", description = "Supports melee attack skills. Cannot support triggered attacks, attacks used by things other than you, or skills which create minions.", + flavourText = {"A vast pillar of hands embraces a charred sky.", "Within burns a beam of purest heat.", }, color = 1, support = true, requireSkillTypes = { SkillType.Melee, }, @@ -4268,6 +4279,7 @@ skills["SupportPyre"] = { skills["TriggeredSupportPyre"] = { name = "Call the Pyre", baseTypeName = "Call the Pyre", + flavourText = {"A vast pillar of hands embraces a charred sky.", "Within burns a beam of purest heat.", }, color = 1, description = "Calls forth pillars of ashen flame. These pillars deal Fire damage in an area, and cover Enemies they hit in Ash. Requires a Melee Weapon or Unarmed.", skillTypes = { [SkillType.Fire] = true, [SkillType.Area] = true, [SkillType.Attack] = true, [SkillType.Cooldown] = true, [SkillType.Triggerable] = true, [SkillType.Triggered] = true, [SkillType.InbuiltTrigger] = true, [SkillType.SkillGrantedBySupport] = true, }, @@ -4790,6 +4802,7 @@ skills["SupportBluntWeaponShockwave"] = { skills["SupportVoidShockwave"] = { name = "Void Shockwave", description = "Supports melee attack skills. Cannot support triggered attacks, attacks used by things other than you, or skills which create minions.", + flavourText = {"The Elder's influence yet lingers", "in the darkest corners of Wraeclast.", }, color = 1, support = true, requireSkillTypes = { SkillType.Melee, }, @@ -4842,6 +4855,7 @@ skills["SupportVoidShockwave"] = { skills["TriggeredSupportVoidShockwave"] = { name = "Void Shockwave", baseTypeName = "Void Shockwave", + flavourText = {"The Elder's influence yet lingers", "in the darkest corners of Wraeclast.", }, color = 1, description = "Deals attack damage in an area. A number of enemies hit by the wave will release an additional wave.", skillTypes = { [SkillType.Attack] = true, [SkillType.Area] = true, [SkillType.Damage] = true, [SkillType.Triggerable] = true, [SkillType.Triggered] = true, [SkillType.Melee] = true, [SkillType.SkillGrantedBySupport] = true, [SkillType.Cooldown] = true, }, @@ -5051,6 +5065,7 @@ skills["SupportStun"] = { skills["SupportTransfusion"] = { name = "Transfusion", description = "Supports non-blessing spells.", + flavourText = {"Let the eternal scream within", "flow from your weeping blood.", }, color = 1, support = true, requireSkillTypes = { SkillType.Spell, }, @@ -5254,6 +5269,7 @@ skills["SupportUrgentOrders"] = { skills["SupportVaalTemptation"] = { name = "Vaal Temptation", description = "Supports Vaal skills.", + flavourText = {"\"If Chaos offers you a deal, challenger,", "think deeply before you accept.\"", }, color = 1, support = true, requireSkillTypes = { SkillType.Vaal, }, diff --git a/src/Export/Scripts/skills.lua b/src/Export/Scripts/skills.lua index 2cb6819cef6..fecad488ee3 100644 --- a/src/Export/Scripts/skills.lua +++ b/src/Export/Scripts/skills.lua @@ -41,6 +41,22 @@ do end end +local function cleanAndSplit(str) -- Same as in Flavour Text exporter. + -- Normalize newlines + str = str:gsub("\r\n", "\n") + + local lines = {} + for line in str:gmatch("[^\n]+") do + line = line:match("^%s*(.-)%s*$") -- trim each line + if line ~= "" then + -- Escape quotes + line = line:gsub('"', '\\"') + table.insert(lines, line) + end + end + + return lines +end local gems = { } local trueGemNames = { } @@ -120,6 +136,13 @@ directiveTable.skill = function(state, args, out) out:write('\tname = "', displayName, '",\n') out:write('\thidden = true,\n') end + if skillGem and skillGem.BaseItemType and skillGem.BaseItemType.FlavourTextKey then + out:write('\tflavourText = {') + for _, line in ipairs(cleanAndSplit(skillGem.BaseItemType.FlavourTextKey.Text)) do + out:write('"', line, '", ') + end + out:write('},\n') + end state.noGem = false skill.baseFlags = { } local modMap = { } From ed8a60613a76af1038ae0854d3bdc31a0f9ca23f Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Mon, 20 Jul 2026 17:09:25 +0300 Subject: [PATCH 2/3] Fix typo --- src/Classes/GemTooltip.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Classes/GemTooltip.lua b/src/Classes/GemTooltip.lua index a01d179875c..cd38ff7be2c 100644 --- a/src/Classes/GemTooltip.lua +++ b/src/Classes/GemTooltip.lua @@ -235,7 +235,7 @@ function GemTooltip.AddGemTooltip(tooltip, build, gemInstance, options) tooltip:AddLine(fontSizeBig, "^x7F7F7F" .. gemInstance.gemData.tagString, "FONTIN SC") end addCommonGemInfo(tooltip, build, gemInstance, grantedEffect, true, not skipRequirements, - secondary and secondary.suport and secondary) + secondary and secondary.support and secondary) end if primary.flavourText and main.showFlavourText then tooltip:AddSeparator(10) From 746b5849f4fcaaaafee681f2ebeda27384f16150 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Tue, 21 Jul 2026 02:09:01 +1000 Subject: [PATCH 3/3] Fix mastery + some other issues When hovering over a skill node then a mastery on the tree it would retain the skill tooltip Vaal skills no longer duplicate the level / quality / requirements lines Fixed crash when using Runegraft of Blasphemy as it has an empty ExtraSkill entry Simplify the gem lookup --- src/Classes/GemTooltip.lua | 2 +- src/Classes/PassiveTreeView.lua | 31 +++++++++---------------------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/src/Classes/GemTooltip.lua b/src/Classes/GemTooltip.lua index cd38ff7be2c..5100615c21f 100644 --- a/src/Classes/GemTooltip.lua +++ b/src/Classes/GemTooltip.lua @@ -221,7 +221,7 @@ function GemTooltip.AddGemTooltip(tooltip, build, gemInstance, options) tooltip:AddLine(fontSizeTitle, colorCodes.GEM .. (gemInstance.gemData.secondaryEffectName or grantedEffectSecondary.name), "FONTIN SC") tooltip:AddSeparator(10) - addCommonGemInfo(tooltip, build, gemInstance, grantedEffectSecondary, true, not skipRequirements) + addCommonGemInfo(tooltip, build, gemInstance, grantedEffectSecondary) else local grantedEffect = gemInstance.gemData.grantedEffect tooltip:AddLine(fontSizeTitle, colorCodes.GEM .. grantedEffect.name, "FONTIN SC") diff --git a/src/Classes/PassiveTreeView.lua b/src/Classes/PassiveTreeView.lua index c33c9f52c7a..880f43d65e5 100644 --- a/src/Classes/PassiveTreeView.lua +++ b/src/Classes/PassiveTreeView.lua @@ -1392,6 +1392,7 @@ end function PassiveTreeViewClass:AddNodeTooltip(tooltip, node, build) local fontSizeBig = main.showFlavourText and 18 or 16 + self.skillTooltip:Clear() tooltip.center = true tooltip.maxWidth = 800 -- Special case for sockets @@ -1534,37 +1535,23 @@ function PassiveTreeViewClass:AddNodeTooltip(tooltip, node, build) addModInfoToTooltip(mNode, i, masteryColor..line) end -- add child tooltip for skills - self.skillTooltip:Clear() for _, mod in ipairs(mNode.finalModList or mNode.modList or {}) do if mod.name == "ExtraSkill" or mod.name == "ExtraSupport" then - local found = false - for grantedEffect, gemId in pairs(data.gemForSkill) do - if grantedEffect.id == mod.value.skillId then - local gem = data.gems[gemId] - found = true - local gemInst = { - gemData = gem, - level = mod.value.level or 1, - quality = 0, - grantedEffect = - grantedEffect - } - gemTooltip.AddGemTooltip(self.skillTooltip, build, gemInst) - break + local skill = data.skills[mod.value.skillId] + if skill then + local gem = data.gems[data.gemForSkill[skill]] + local options = { } + if not gem then + gem = { grantedEffect = skill, tags = { } } + options.skipRequirements = true end - end - -- if we didn't find a matching gem, look up based on the skill id - if not found then - local skill = data.skills[mod.value.skillId] - -- poe1 item skills don't seem to have matching gems, so make up one - local gem = { grantedEffect = skill, tags = {} } local gemInst = { gemData = gem, level = mod.value.level or 1, quality = 0, grantedEffect = skill } - gemTooltip.AddGemTooltip(self.skillTooltip, build, gemInst, { skipRequirements = true }) + gemTooltip.AddGemTooltip(self.skillTooltip, build, gemInst, options) end end end