--Section Generators function ZO_Tooltip:AddAbilityProgressBar(currentXP, lastRankXP, nextRankXP) local bar = self:AcquireStatusBar(self:GetStyle("abilityProgressBar")) local maxValue = 1 local currentValue = 1 if nextRankXP == lastRankXP or currentXP >= nextRankXP then bar:SetMinMax(0, maxValue) bar:SetValue(currentValue) else maxValue = nextRankXP - lastRankXP currentValue = currentXP - lastRankXP bar:SetMinMax(0, maxValue) bar:SetValue(currentValue) end local narrationText = function() local range = maxValue local percentage = currentValue / range percentage = string.format("%.2f", percentage * 100) return zo_strformat(SI_SCREEN_NARRATION_PROGRESS_BAR_PERCENT_FORMATTER, percentage) end self:AddStatusBar(bar, narrationText) end do local TANK_ROLE_ICON = zo_iconFormat("EsoUI/Art/LFG/LFG_tank_down_no_glow_64.dds", 40, 40) local HEALER_ROLE_ICON = zo_iconFormat("EsoUI/Art/LFG/LFG_healer_down_no_glow_64.dds", 40, 40) local DAMAGE_ROLE_ICON = zo_iconFormat("EsoUI/Art/LFG/LFG_dps_down_no_glow_64.dds", 40, 40) local g_roleIconTable = {} local UNKNOWN_STAT_VALUE_TEXT = GetString(SI_ABILITY_TOOLTIP_UNKNOWN_VALUE) local UNKNOWN_STAT_VALUE_TYPE_TEXT = GetString(SI_ABILITY_TOOLTIP_UNKNOWN_VALUE_TYPE) local UNKNOWN_SECONDS_STAT_VALUE_TEXT = GetString(SI_ABILITY_TOOLTIP_UNKNOWN_SECONDS) local function GetNextAbilityMechanicFlagIter(abilityId) return function(_, lastFlag) return GetNextAbilityMechanicFlag(abilityId, lastFlag) end end function ZO_Tooltip:AddAbilityStats(abilityId, overrideActiveRank, overrideCasterUnitTag) overrideCasterUnitTag = overrideCasterUnitTag or "player" local statsSection = self:AcquireSection(self:GetStyle("abilityStatsSection")) --Channel/Cast Time local channeled, durationValue = GetAbilityCastInfo(abilityId, overrideActiveRank, overrideCasterUnitTag) if channeled ~= nil then -- Only include a line if we can determine if it's going to be definitely either channeled or cast local castTimePair = statsSection:AcquireStatValuePair(self:GetStyle("statValuePair")) if channeled then castTimePair:SetStat(GetString(SI_ABILITY_TOOLTIP_CHANNEL_TIME_LABEL), self:GetStyle("statValuePairStat")) if durationValue then castTimePair:SetValue(ZO_FormatTimeMilliseconds(durationValue, TIME_FORMAT_STYLE_CHANNEL_TIME, TIME_FORMAT_PRECISION_TENTHS_RELEVANT, TIME_FORMAT_DIRECTION_NONE), self:GetStyle("abilityStatValuePairValue")) else castTimePair:SetValue(UNKNOWN_SECONDS_STAT_VALUE_TEXT) end else castTimePair:SetStat(GetString(SI_ABILITY_TOOLTIP_CAST_TIME_LABEL), self:GetStyle("statValuePairStat")) if durationValue then castTimePair:SetValue(ZO_FormatTimeMilliseconds(durationValue, TIME_FORMAT_STYLE_CAST_TIME, TIME_FORMAT_PRECISION_TENTHS_RELEVANT, TIME_FORMAT_DIRECTION_NONE), self:GetStyle("abilityStatValuePairValue")) else castTimePair:SetValue(UNKNOWN_SECONDS_STAT_VALUE_TEXT) end end statsSection:AddStatValuePair(castTimePair) end --Target local targetDescription = GetAbilityTargetDescription(abilityId, overrideActiveRank, overrideCasterUnitTag) if targetDescription and targetDescription ~= "" then local targetPair = statsSection:AcquireStatValuePair(self:GetStyle("statValuePair")) targetPair:SetStat(GetString(SI_ABILITY_TOOLTIP_TARGET_TYPE_LABEL), self:GetStyle("statValuePairStat")) targetPair:SetValue(targetDescription, self:GetStyle("abilityStatValuePairValue")) statsSection:AddStatValuePair(targetPair) end --Range local minRangeCM, maxRangeCM = GetAbilityRange(abilityId, overrideActiveRank, overrideCasterUnitTag) local maxRangeM = maxRangeCM and FormatFloatRelevantFraction(maxRangeCM / 100) or UNKNOWN_STAT_VALUE_TEXT if maxRangeM == UNKNOWN_STAT_VALUE_TEXT or maxRangeCM > 0 then local minRangeM = minRangeCM and FormatFloatRelevantFraction(minRangeCM / 100) or UNKNOWN_STAT_VALUE_TEXT local rangePair = statsSection:AcquireStatValuePair(self:GetStyle("statValuePair")) rangePair:SetStat(GetString(SI_ABILITY_TOOLTIP_RANGE_LABEL), self:GetStyle("statValuePairStat")) if minRangeM == UNKNOWN_STAT_VALUE_TEXT or minRangeCM > 0 then rangePair:SetValue(zo_strformat(SI_ABILITY_TOOLTIP_MIN_TO_MAX_RANGE, minRangeM, maxRangeM), self:GetStyle("abilityStatValuePairValue")) elseif maxRangeM == UNKNOWN_STAT_VALUE_TEXT then rangePair:SetValue(GetString(SI_ABILITY_TOOLTIP_UNKNOWN_METERS), self:GetStyle("abilityStatValuePairValue")) else rangePair:SetValue(zo_strformat(SI_ABILITY_TOOLTIP_RANGE, maxRangeM), self:GetStyle("abilityStatValuePairValue")) end statsSection:AddStatValuePair(rangePair) end --Radius/Distance local radiusCM = GetAbilityRadius(abilityId, overrideActiveRank, overrideCasterUnitTag) local radiusM = radiusCM and FormatFloatRelevantFraction(radiusCM / 100) or UNKNOWN_STAT_VALUE_TEXT if radiusM == UNKNOWN_STAT_VALUE_TEXT or radiusCM > 0 then local angleDistanceCM = GetAbilityAngleDistance(abilityId) -- Angle distance is the distance to the left and right of the caster, not total distance. So we're multiplying by 2 to accurately reflect that in the UI. local areaWidthM = angleDistanceCM and FormatFloatRelevantFraction(angleDistanceCM * 2 / 100) or UNKNOWN_STAT_VALUE_TEXT local showArea = areaWidthM == UNKNOWN_STAT_VALUE_TEXT or angleDistanceCM > 0 local radiusDistancePair = statsSection:AcquireStatValuePair(self:GetStyle("statValuePair")) local statLabel = showArea and GetString(SI_ABILITY_TOOLTIP_AREA_LABEL) or GetString(SI_ABILITY_TOOLTIP_RADIUS_LABEL) radiusDistancePair:SetStat(statLabel, self:GetStyle("statValuePairStat")) if showArea then radiusDistancePair:SetValue(zo_strformat(SI_ABILITY_TOOLTIP_AOE_DIMENSIONS, radiusM, areaWidthM), self:GetStyle("abilityStatValuePairValue")) elseif radiusM == UNKNOWN_STAT_VALUE_TEXT then radiusDistancePair:SetValue(GetString(SI_ABILITY_TOOLTIP_UNKNOWN_METERS), self:GetStyle("abilityStatValuePairValue")) else radiusDistancePair:SetValue(zo_strformat(SI_ABILITY_TOOLTIP_RADIUS, radiusM), self:GetStyle("abilityStatValuePairValue")) end statsSection:AddStatValuePair(radiusDistancePair) end --Duration local durationValue = nil local isAbilityDurationToggled = IsAbilityDurationToggled(abilityId, overrideCasterUnitTag) if isAbilityDurationToggled == nil then durationValue = UNKNOWN_STAT_VALUE_TYPE_TEXT elseif isAbilityDurationToggled then durationValue = GetString(SI_ABILITY_TOOLTIP_TOGGLE_DURATION) else local durationMS = GetAbilityDuration(abilityId, overrideActiveRank, overrideCasterUnitTag) if durationMS == nil then durationValue = UNKNOWN_SECONDS_STAT_VALUE_TEXT elseif durationMS > 0 then durationValue = ZO_FormatTimeMilliseconds(durationMS, TIME_FORMAT_STYLE_DURATION, TIME_FORMAT_PRECISION_TENTHS_RELEVANT, TIME_FORMAT_DIRECTION_NONE) end end if durationValue then local durationPair = statsSection:AcquireStatValuePair(self:GetStyle("statValuePair")) durationPair:SetStat(GetString(SI_ABILITY_TOOLTIP_DURATION_LABEL), self:GetStyle("statValuePairStat")) durationPair:SetValue(durationValue, self:GetStyle("abilityStatValuePairValue")) statsSection:AddStatValuePair(durationPair) end --Cooldown local cooldownMS = GetAbilityCooldown(abilityId, overrideCasterUnitTag) local unknownCooldown = cooldownMS == nil if unknownCooldown or cooldownMS > 0 then local cooldownPair = statsSection:AcquireStatValuePair(self:GetStyle("statValuePair")) cooldownPair:SetStat(GetString(SI_ABILITY_TOOLTIP_COOLDOWN), self:GetStyle("statValuePairStat")) local cooldownValue = unknownCooldown and UNKNOWN_SECONDS_STAT_VALUE_TEXT or ZO_FormatTimeMilliseconds(cooldownMS, TIME_FORMAT_STYLE_DURATION, TIME_FORMAT_PRECISION_TENTHS_RELEVANT, TIME_FORMAT_DIRECTION_NONE) cooldownPair:SetValue(cooldownValue, self:GetStyle("abilityStatValuePairValue")) statsSection:AddStatValuePair(cooldownPair) end -- Costs self:AddAbilityCosts(abilityId, overrideActiveRank, statsSection) self:AddAbilityCostsOverTime(abilityId, overrideActiveRank, statsSection) --Roles local isTankRole, isHealerRole, isDamageRole = GetAbilityRoles(abilityId) local roleNarrationText = {} if isTankRole then table.insert(g_roleIconTable, TANK_ROLE_ICON) table.insert(roleNarrationText, GetString("SI_LFGROLE", LFG_ROLE_TANK)) end if isHealerRole then table.insert(g_roleIconTable, HEALER_ROLE_ICON) table.insert(roleNarrationText, GetString("SI_LFGROLE", LFG_ROLE_HEAL)) end if isDamageRole then table.insert(g_roleIconTable, DAMAGE_ROLE_ICON) table.insert(roleNarrationText, GetString("SI_LFGROLE", LFG_ROLE_DPS)) end if #g_roleIconTable > 0 then local rolesPair = statsSection:AcquireStatValuePair(self:GetStyle("statValuePair")) rolesPair:SetStat(GetString(SI_ABILITY_TOOLTIP_ROLE_LABEL), self:GetStyle("statValuePairStat")) local finalIconText = table.concat(g_roleIconTable, "") rolesPair:SetValueWithCustomNarration(finalIconText, roleNarrationText, self:GetStyle("abilityStatValuePairValue")) statsSection:AddStatValuePair(rolesPair) ZO_ClearNumericallyIndexedTable(g_roleIconTable) end self:AddSection(statsSection) end function ZO_Tooltip:GetMechanicFlagStyle(mechanicFlag) if mechanicFlag == COMBAT_MECHANIC_FLAGS_MAGICKA then return self:GetStyle("abilityStatValuePairMagickaValue") elseif mechanicFlag == COMBAT_MECHANIC_FLAGS_STAMINA then return self:GetStyle("abilityStatValuePairStaminaValue") elseif mechanicFlag == COMBAT_MECHANIC_FLAGS_HEALTH then return self:GetStyle("abilityStatValuePairHealthValue") end return self:GetStyle("abilityStatValuePairValue") end function ZO_Tooltip:AddAbilityCosts(abilityId, overrideActiveRank, statsSection) local costAbility = GetCurrentChainedAbility(abilityId) local baseCost, mechanicFlags, isCostChargedPerTick = GetAbilityBaseCostInfo(costAbility, overrideActiveRank, "player") if baseCost == 0 or isCostChargedPerTick == true then -- If cost is definitely 0 or isCostChargedPerTick is true, we don't want to show -- anything for cost no matter what return end local isUnknownCost = baseCost == nil local isUnknownMechanics = mechanicFlags == nil local isUnknownIfCostChargedPerTick = isCostChargedPerTick == nil if isUnknownIfCostChargedPerTick or (isUnknownCost and isUnknownMechanics) then -- Super generic "we know nothing about the cost" variant local costPair = statsSection:AcquireStatValuePair(self:GetStyle("statValuePair")) costPair:SetStat(GetString(SI_ABILITY_TOOLTIP_RESOURCE_COST_LABEL), self:GetStyle("statValuePairStat")) costPair:SetValue(UNKNOWN_STAT_VALUE_TYPE_TEXT, self:GetStyle("abilityStatValuePairValue")) statsSection:AddStatValuePair(costPair) return end if mechanicFlags then -- Mechanics known, add a specific line for each for flag in ZO_FlagHelpers.MaskHasFlagsIterator(mechanicFlags) do local costPair = statsSection:AcquireStatValuePair(self:GetStyle("statValuePair")) costPair:SetStat(GetString(SI_ABILITY_TOOLTIP_RESOURCE_COST_LABEL), self:GetStyle("statValuePairStat")) -- Cost may or may not be known local cost = isUnknownCost and UNKNOWN_STAT_VALUE_TEXT or GetAbilityCost(costAbility, flag, overrideActiveRank, "player") local mechanicName = GetString("SI_COMBATMECHANICFLAGS", flag) local costString = zo_strformat(SI_ABILITY_TOOLTIP_RESOURCE_COST, cost, mechanicName) local style = self:GetMechanicFlagStyle(flag) costPair:SetValue(costString, style) statsSection:AddStatValuePair(costPair) end else -- Don't know the mechanics but must know the cost (see super generic case above), just add a single generic cost line local costPair = statsSection:AcquireStatValuePair(self:GetStyle("statValuePair")) costPair:SetStat(GetString(SI_ABILITY_TOOLTIP_RESOURCE_COST_LABEL), self:GetStyle("statValuePairStat")) local costString = zo_strformat(SI_ABILITY_TOOLTIP_RESOURCE_COST, baseCost, UNKNOWN_STAT_VALUE_TYPE_TEXT) costPair:SetValue(costString, self:GetStyle("abilityStatValuePairValue")) statsSection:AddStatValuePair(costPair) end end function ZO_Tooltip:AddAbilityCostsOverTime(abilityId, overrideActiveRank, statsSection) local costAbility = GetCurrentChainedAbility(abilityId) local baseCost, mechanicFlags, isCostChargedPerTick = GetAbilityBaseCostInfo(costAbility, overrideActiveRank, "player") if isCostChargedPerTick == nil then -- Do nothing, AddAbilityCosts would have already added a line for this state return end if baseCost == 0 or not isCostChargedPerTick then -- If cost is definitely 0 or isCostChargedPerTick is false, we don't want to show -- anything for cost over time no matter what return end -- If we got this far, we definitely know we're cost over time local frequencyMS = GetAbilityFrequencyMS(costAbility, "player") if frequencyMS == 0 then -- If frequencyMS is definitely 0 we don't want to show anything no matter what return end local isUnknownCost = baseCost == nil local isUnknownMechanics = mechanicFlags == nil local isUnknownFrequency = frequencyMS == nil local formattedFrequency = frequencyMS and ZO_FormatTimeMilliseconds(frequencyMS, TIME_FORMAT_STYLE_SHOW_LARGEST_UNIT, TIME_FORMAT_PRECISION_TENTHS_RELEVANT, TIME_FORMAT_DIRECTION_NONE) or GetString(SI_ABILITY_TOOLTIP_UNKNOWN_SECONDS_SHORT) if mechanicFlags then -- Mechanics known, add a specific line for each for flag in ZO_FlagHelpers.MaskHasFlagsIterator(mechanicFlags) do local costPair = statsSection:AcquireStatValuePair(self:GetStyle("statValuePair")) costPair:SetStat(GetString(SI_ABILITY_TOOLTIP_RESOURCE_COST_LABEL), self:GetStyle("statValuePairStat")) -- Cost may or may not be known local cost = isUnknownCost and UNKNOWN_STAT_VALUE_TEXT or GetAbilityCostPerTick(costAbility, flag, overrideActiveRank) local mechanicName = GetString("SI_COMBATMECHANICFLAGS", flag) local costOverTimeString = zo_strformat(SI_ABILITY_TOOLTIP_RESOURCE_COST_OVER_TIME, cost, mechanicName, formattedFrequency) local style = self:GetMechanicFlagStyle(flag) costPair:SetValue(costOverTimeString, style) statsSection:AddStatValuePair(costPair) end else -- Don't know the mechanics, just add a single generic cost line local costPair = statsSection:AcquireStatValuePair(self:GetStyle("statValuePair")) costPair:SetStat(GetString(SI_ABILITY_TOOLTIP_RESOURCE_COST_LABEL), self:GetStyle("statValuePairStat")) if isUnknownCost then if isUnknownFrequency then costPair:SetValue(GetString(SI_ABILITY_TOOLTIP_UNKNOWN_COST_OVER_UNKNOWN_TIME), self:GetStyle("abilityStatValuePairValue")) else local costOverTimeString = zo_strformat(SI_ABILITY_TOOLTIP_UNKNOWN_COST_OVER_TIME, formattedFrequency) costPair:SetValue(costOverTimeString, self:GetStyle("abilityStatValuePairValue")) end else local costOverTimeString = zo_strformat(SI_ABILITY_TOOLTIP_RESOURCE_COST_OVER_TIME, baseCost, UNKNOWN_STAT_VALUE_TYPE_TEXT, formattedFrequency) costPair:SetValue(costOverTimeString, self:GetStyle("abilityStatValuePairValue")) end statsSection:AddStatValuePair(costPair) end end end function ZO_Tooltip:AddAbilityDescription(abilityId, overrideDescription, overrideCasterUnitTag) local descriptionHeader = GetAbilityDescriptionHeader(abilityId, overrideCasterUnitTag) local NO_OVERRIDE_RANK = nil local description = overrideDescription or GetAbilityDescription(abilityId, NO_OVERRIDE_RANK, overrideCasterUnitTag) local bodyDescriptionStyle = self:GetStyle("bodyDescription") if descriptionHeader ~= "" or description ~= "" then local descriptionSection = self:AcquireSection(self:GetStyle("bodySection")) if descriptionHeader ~= "" then --descriptionHeader has already been run through grammar descriptionSection:AddLine(descriptionHeader, self:GetStyle("bodyHeader")) end if description ~= "" then --description has already been run through grammar descriptionSection:AddLine(description, bodyDescriptionStyle) end self:AddSection(descriptionSection) end local craftedAbilityId = GetAbilityCraftedAbilityId(abilityId) if craftedAbilityId ~= 0 then for slotType = SCRIBING_SLOT_ITERATION_BEGIN, SCRIBING_SLOT_ITERATION_END do local scriptDescription = GenerateCraftedAbilityScriptSlotDescriptionForAbilityDescription(abilityId, slotType, overrideCasterUnitTag) if scriptDescription ~= "" then local descriptionSection = self:AcquireSection(self:GetStyle("bodySection")) descriptionSection:AddLine(scriptDescription, bodyDescriptionStyle) self:AddSection(descriptionSection) end end end end function ZO_Tooltip:AddAbilityNewEffects(...) local numNewEffectReturns = select("#", ...) if(numNewEffectReturns > 0) then for i = 1, numNewEffectReturns do local newEffect = select(i, ...) local newEffectSection = self:AcquireSection(self:GetStyle("bodySection")) newEffectSection:AddLine(GetString(SI_ABILITY_TOOLTIP_NEW_EFFECT), self:GetStyle("newEffectTitle"), self:GetStyle("bodyHeader")) newEffectSection:AddLine(newEffect, self:GetStyle("newEffectBody"), self:GetStyle("bodyDescription")) self:AddSection(newEffectSection) end end end --Layout Functions function ZO_Tooltip:LayoutSimpleAbility(abilityId, options) local omitHeader = options and options.omitHeader if not omitHeader then local headerSection = self:AcquireSection(self:GetStyle("abilityHeaderSection")) self:AddSectionEvenIfEmpty(headerSection) end local formattedAbilityName = ZO_CachedStrFormat(SI_ABILITY_NAME, GetAbilityName(abilityId)) self:AddLine(formattedAbilityName, self:GetStyle("title")) if not IsAbilityPassive(abilityId) then self:AddAbilityStats(abilityId) end self:AddAbilityDescription(abilityId) end function ZO_Tooltip:LayoutEndlessDungeonBuffAbility(buffAbilityId, includeLifetimeStacks, stackCount) local buffType, isAvatarVision = GetAbilityEndlessDungeonBuffType(buffAbilityId) local buffBucketType = GetAbilityEndlessDungeonBuffBucketType(buffAbilityId) local showStacks = includeLifetimeStacks or ShouldAbilityShowStacks(buffAbilityId) if not stackCount then stackCount = showStacks and GetNumStacksForEndlessDungeonBuff(buffAbilityId, includeLifetimeStacks) end local headerSection = self:AcquireSection(self:GetStyle("abilityHeaderSection")) local buffBucketTypeText = ZO_CachedStrFormat(SI_ENDLESS_DUNGEON_BUFF_TYPE_FORMATTER, GetString("SI_ENDLESSDUNGEONBUFFBUCKETTYPE", buffBucketType)) headerSection:AddLine(buffBucketTypeText, self:GetStyle("abilityHeader")) local buffTypeText = isAvatarVision and GetString("SI_ENDLESSDUNGEONBUFFTYPE_AVATAR", buffType) or GetString("SI_ENDLESSDUNGEONBUFFTYPE", buffType) buffTypeText = ZO_CachedStrFormat(SI_ENDLESS_DUNGEON_BUFF_TYPE_FORMATTER, buffTypeText) headerSection:AddLine(buffTypeText, self:GetStyle("abilityHeader")) if showStacks then headerSection:AddLine(zo_iconTextFormat("EsoUI/Art/Tooltips/icon_moras_gift.dds", 20, 20, stackCount), self:GetStyle("abilityStack")) end self:AddSection(headerSection) local abilityName = GetAbilityName(buffAbilityId) local formattedAbilityName if showStacks then formattedAbilityName = zo_strformat(SI_ABILITY_NAME_WITH_QUANTITY, abilityName, stackCount) else formattedAbilityName = ZO_CachedStrFormat(SI_ABILITY_NAME, abilityName) end self:AddLine(formattedAbilityName, self:GetStyle("title")) self:AddAbilityDescription(buffAbilityId) end function ZO_Tooltip:LayoutAbilityLink(link, options) local abilityId = GetAbilityIdFromLink(link) self:LayoutSimpleAbility(abilityId, options) end