Editing Module:Abilities/card

Jump to navigation Jump to search
Warning: You are not logged in. Once you make an edit, a temporary account will be created for you. Learn more. Log in or create an account to continue receiving notifications after this account expires, and to access other features.
The edit can be undone. Please check the comparison below to verify that this is what you want to do, and then publish the changes below to finish undoing the edit.
Latest revision Your text
Line 2: Line 2:
local commonutils = require "Module:Utilities"
local commonutils = require "Module:Utilities"
local utils = require "Module:Abilities/utils"
local utils = require "Module:Abilities/utils"
local Icon = require"Module:Abilities/icon"
local textfit = require "Module:TextFit"


local p = {}
local p = {}
local data = mw.loadJsonData("Data:AbilityCards.json")
local data = mw.loadJsonData("Data:AbilityCards.json")


-- Maps attr type to an appropriate image and page link
-- Optional icon size, will be defaulted on the template
local ATTR_TYPE_ICON_MAP = {
bullet_armor_up = {img='Bullet_Armor.png', link='Damage_Resistance', color = 'NoColor'},
bullet_armor_down = {img='Bullet_Armor.png', link='Damage_Resistance', color = 'NoColor'},
cast = {img='AttributeIconMaxChargesIncrease.png', link=''},
charges = {img='AttributeIconMaxChargesIncrease.png', link='', color = 'Purple'},
damage = {img='Damage_heart.png', link='', color = 'NoColor'}, -- 'NoColor' will apply no icon color and use the original image
bullet_damage = {img='Bullet_damage.png', link='Bullet_Damage', color = 'NoColor'},
fire_rate = {img='Fire Rate.png', link='Fire_Rate', color = 'Brown'},
healing = {img='Healing.png', link='Healing', color = 'Green'},
health = {img='Health.png', link='Health', color = 'Green'},
move_speed = {img='Move speed.png', link='Move Speed', color = 'Green'},
range = {img='CastRange.png', link='Ability_Range', color = 'Purple'},
tech_armor_up = {img='Spirit_Armor.png', link='Damage_Resistance', color = 'NoColor'},
tech_damage = {img='AttributeIconTechShieldHealth.png', link='Spirit_Damage', color = 'NoColor',size = '12px'},
distance = {img='AttributeIconTechRange.png', link='Ability_Range', color = 'Purple'},
duration = {img='AttributeIconTechDuration.png', link='Ability Duration', color = 'Purple'},
slow = {img='MoveSlow.png', link='', color = 'Purple'},
melee_damage = {img='Melee damage.png', link='Melee Damage'},
cooldown = {img='Cooldown Icon.png', link='Ability Cooldown', color = 'Purple'},
combat_barrier = {img='Barrier.png', link='Barrier', color = 'Green'},
time = {img='AttributeIconTechDuration.png', link='Ability Duration', color = 'Purple'},
}


function get_hero_key(hero_name)
function get_hero_key(hero_name)
if hero_name == nil then return nil end
for i, hero in pairs(data) do
if hero["Name"] == hero_name then
-- if name starts with "hero_", use it directly as the key
return i
if string.sub(hero_name, 1, 5) == "hero_" then return hero_name end
end
end
local resource_lookup = mw.loadJsonData("Data:ResourceLookup.json")
return nil
return resource_lookup[hero_name:lower()].key
end
end


Line 63: Line 84:
local info1_desc = get_info_desc(hero_key, ability_num, 1)
local info1_desc = get_info_desc(hero_key, ability_num, 1)
local info1_main_boxes = get_main_boxes(hero_key, ability_num, 1)
local info1_main_boxes = get_main_boxes(hero_key, ability_num, 1)
-- Inject Hex Duration only for Rabbit Hex
if ability.Key == "ability_magician_animalhexarea"
  and ability.Other
  and ability.Other.HexDuration
  and ability.Other.HexDuration.Value then
    local icon = get_icon("duration")
    table.insert(info1_main_boxes, 1,
        frame:expandTemplate{
            title = "Ability_card_v2/Card/MainBox",
            args = {
                key = "HexDuration",
                value = ability.Other.HexDuration.Value,
                icon = icon.img,
                icon_link = icon.link,
                icon_color = icon.color,
            }
        })
end


local info1_alt_boxes = get_alt_boxes(hero_key, ability_num, 1)
local info1_alt_boxes = get_alt_boxes(hero_key, ability_num, 1)
if #info1_alt_boxes > 7 then
if #info1_alt_boxes > 6 then
error(#info1_alt_boxes .. ' alt boxes found, but only 7 are supported. Please update Module:Abilities/card and Template:Ability_card_v2')
error(#info1_alt_boxes .. ' alt boxes found, but only 6 are supported. Please update Module:Abilities/card and Template:Ability_card_v2')
end
end
Line 74: Line 114:
local info2_alt_boxes = get_alt_boxes(hero_key, ability_num, 2)
local info2_alt_boxes = get_alt_boxes(hero_key, ability_num, 2)
if #info2_alt_boxes > 7 then
if #info2_alt_boxes > 6 then
error(#info2_alt_boxes .. ' alt boxes found, but only 7 are supported. Please update Module:Abilities/card and Template:Ability_card_v2')
error(#info2_alt_boxes .. ' alt boxes found, but only 6 are supported. Please update Module:Abilities/card and Template:Ability_card_v2')
end
end
Line 82: Line 122:
local info3_alt_boxes = get_alt_boxes(hero_key, ability_num, 3)
local info3_alt_boxes = get_alt_boxes(hero_key, ability_num, 3)
if #info3_alt_boxes > 7 then
if #info3_alt_boxes > 6 then
error(#info3_alt_boxes .. ' alt boxes found, but only 7 are supported. Please update Module:Abilities/card and Template:Ability_card_v2')
error(#info3_alt_boxes .. ' alt boxes found, but only 6 are supported. Please update Module:Abilities/card and Template:Ability_card_v2')
end
end
Line 91: Line 131:
title = "Ability_card_v2/Card",
title = "Ability_card_v2/Card",
args = {
args = {
id = hero_key .. ':' .. ability.Key,
hero_key = hero_key,
hero_key = hero_key,
ability_num = ability_num,
ability_num = ability_num,
Line 100: Line 139:
icon = lang.get_string(ability.Key, 'en') .. '.png',
icon = lang.get_string(ability.Key, 'en') .. '.png',
channel_time = ability.AbilityChannelTime and ability.AbilityChannelTime.Value ~= 9999 and ability.AbilityChannelTime.Value,
channel_time = ability.Cast and ability.Cast.AbilityChannelTime and ability.Cast.AbilityChannelTime.Value ~= 9999 and ability.Cast.AbilityChannelTime.Value,
channel_time_scale = get_attr_scale(ability.AbilityChannelTime),
channel_time_ss = ability.Cast and get_attr_ss(ability.Cast.AbilityChannelTime),
channel_time_scale_type = get_attr_scale_type(ability.AbilityChannelTime),
channel_time_scale_multiply = get_attr_scale_multiply(ability.AbilityChannelTime),
radius = ability.Radius and ability.Radius.Value,
radius = ability.Radius and ability.Radius.Value,
radius_scale = get_attr_scale(ability.Radius),
radius_ss = get_attr_ss(ability.Radius),
radius_scale_type = get_attr_scale_type(ability.Radius),
radius_scale_multiply = get_attr_scale_multiply(ability.Radius),
range = ability.AbilityCastRange and ability.AbilityCastRange.Value,
range = ability.AbilityCastRange and ability.AbilityCastRange.Value,
range_scale = get_attr_scale(ability.AbilityCastRange),
range_ss = get_attr_ss(ability.AbilityCastRange),
range_scale_type = get_attr_scale_type(ability.AbilityCastRange),
range_scale_multiply = get_attr_scale_multiply(ability.AbilityCastRange),
duration = ability.AbilityDuration and ability.AbilityDuration.Value,
duration = ability.AbilityDuration and ability.AbilityDuration.Value,
duration_scale = get_attr_scale(ability.AbilityDuration),
duration_ss = get_attr_ss(ability.AbilityDuration),
duration_scale_type = get_attr_scale_type(ability.AbilityDuration),
duration_scale_multiply = get_attr_scale_multiply(ability.AbilityDuration),
-- ability_width = format_value_with_prepost(width_key, ability[width_key]),
-- ability_width = format_value_with_prepost(width_key, ability[width_key]),
cooldown =ability.AbilityCooldown and ability.AbilityCooldown.Value,
cooldown =ability.AbilityCooldown and ability.AbilityCooldown.Value,
cooldown_scale = get_attr_scale(ability.AbilityCooldown),
cooldown_ss = get_attr_ss(ability.AbilityCooldown),
cooldown_scale_type = get_attr_scale_type(ability.AbilityCooldown),
charge_cooldown = ability.AbilityCooldownBetweenCharge and ability.AbilityCooldownBetweenCharge.Value,
cooldown_scale_multiply = get_attr_scale_multiply(ability.AbilityCooldown),
charge_cooldown_ss = get_attr_ss(ability.AbilityCooldownBetweenCharge),
charge_cooldown = ability.AbilityCooldownBetweenCharge and ability.AbilityCooldownBetweenCharge.Value ~= -1 and ability.AbilityCooldownBetweenCharge.Value,
num_of_charges = ability.AbilityCharges and ability.AbilityCharges.Value,
charge_cooldown_scale = get_attr_scale(ability.AbilityCooldownBetweenCharge),
charge_cooldown_scale_type = get_attr_scale_type(ability.AbilityCooldownBetweenCharge),
charge_cooldown_scale_multiply = get_attr_scale_multiply(ability.AbilityCooldownBetweenCharge),
num_of_charges = ability.AbilityCharges and ability.AbilityCharges.Value or 0,
-- Info section #1 defined in "Info1" attribute
-- Info section #1 defined in "Info1" attribute
hide_info1 = infobox_is_hidden(ability.Info1) and "true" or "false",
info1_desc = info1_desc,
info1_desc = info1_desc,
info1_mainbox1 = info1_main_boxes[1],
info1_mainbox1 = info1_main_boxes[1],
info1_mainbox2 = info1_main_boxes[2],
info1_mainbox2 = info1_main_boxes[2],
info1_mainbox3 = info1_main_boxes[3],
info1_mainbox3 = info1_main_boxes[3],
info1_mainbox4 = info1_main_boxes[4],
info1_mainbox5 = info1_main_boxes[5],
info1_altbox1 =  info1_alt_boxes[1],
info1_altbox1 =  info1_alt_boxes[1],
info1_altbox2 =  info1_alt_boxes[2],
info1_altbox2 =  info1_alt_boxes[2],
Line 141: Line 165:
info1_altbox5 =  info1_alt_boxes[5],
info1_altbox5 =  info1_alt_boxes[5],
info1_altbox6 =  info1_alt_boxes[6],
info1_altbox6 =  info1_alt_boxes[6],
info1_altbox7 =  info1_alt_boxes[7],


-- Info section #2 defined in "Info2" attribute
-- Info section #2 defined in "Info2" attribute
hide_info2 = infobox_is_hidden(ability.Info2) and "true" or "false",
info2_desc = info2_desc,
info2_desc = info2_desc,
info2_mainbox1 = info2_main_boxes[1],
info2_mainbox1 = info2_main_boxes[1],
info2_mainbox2 = info2_main_boxes[2],
info2_mainbox2 = info2_main_boxes[2],
info2_mainbox3 = info2_main_boxes[3],
info2_mainbox3 = info2_main_boxes[3],
info2_mainbox4 = info2_main_boxes[4],
info2_mainbox5 = info2_main_boxes[5],
info2_altbox1 =  info2_alt_boxes[1],
info2_altbox1 =  info2_alt_boxes[1],
info2_altbox2 =  info2_alt_boxes[2],
info2_altbox2 =  info2_alt_boxes[2],
Line 157: Line 177:
info2_altbox5 =  info2_alt_boxes[5],
info2_altbox5 =  info2_alt_boxes[5],
info2_altbox6 =  info2_alt_boxes[6],
info2_altbox6 =  info2_alt_boxes[6],
info2_altbox7 =  info2_alt_boxes[7],
 
-- Info section #3 defined in "Info3" attribute
-- Info section #3 defined in "Info3" attribute
hide_info3 = infobox_is_hidden(ability.Info3) and "true" or "false",
info3_desc = info3_desc,
info3_desc = info3_desc,
info3_mainbox1 = info3_main_boxes[1],
info3_mainbox1 = info3_main_boxes[1],
info3_mainbox2 = info3_main_boxes[2],
info3_mainbox2 = info3_main_boxes[2],
info3_mainbox3 = info3_main_boxes[3],
info3_mainbox3 = info3_main_boxes[3],
info3_mainbox4 = info3_main_boxes[4],
info3_mainbox5 = info3_main_boxes[5],
info3_altbox1 =  info3_alt_boxes[1],
info3_altbox1 =  info3_alt_boxes[1],
info3_altbox2 =  info3_alt_boxes[2],
info3_altbox2 =  info3_alt_boxes[2],
Line 173: Line 189:
info3_altbox5 =  info3_alt_boxes[5],
info3_altbox5 =  info3_alt_boxes[5],
info3_altbox6 =  info3_alt_boxes[6],
info3_altbox6 =  info3_alt_boxes[6],
info3_altbox7 =  info3_alt_boxes[7],
 
-- Ability upgrades defined in "Upgrades" attribute
-- Ability upgrades defined in "Upgrades" attribute
upgrade1 = upgrades[1],
upgrade1 = upgrades[1],
Line 187: Line 202:
end
end


function infobox_is_hidden(infobox)
-- Get spirit scaling of attribute, return nil if none is found
if infobox == nil then
function get_attr_ss(attr)
return false
if not attr then  
end
return nil  
return infobox.RequiresUpgradeIndex ~= nil
end
 
-- An attribute may carry either a single scaling or a list of them.
-- Extra entries are stat-category markers (duration, cooldown, range, radius,
-- healing, charges) that always hold a value of 1 and are never displayed, so
-- return the first entry that actually renders a badge.
-- A zero-valued scaling is still returned when nothing else is displayable. The
-- card renders it as an x0 placeholder, and the ability upgrade gadget fills
-- that element in when an upgrade grants real scaling (Doorman's Doorway).
local function pick_scale(attr)
if not attr then
return nil
end
end
local scale = attr.Scale
local scale = attr.Scale
if type(scale) ~= 'table' then
return nil
end
-- A single scaling is stored directly as an object
if scale[1] == nil then
return scale
end
local placeholder = nil
for _, entry in ipairs(scale) do
if type(entry) == 'table' and Icon.isDisplayedScale(entry.Type) then
if entry.Value ~= 0 then
return entry
end
placeholder = placeholder or entry
end
end
return placeholder
end
-- Get scaling of attribute, return nil if none is found
function get_attr_scale(attr)
local scale = pick_scale(attr)
if not scale then
if not scale then
return nil
return nil
end
end
if scale.Value == 0 then
if scale.Type ~= 'spirit' then
return nil
return nil
end
return commonutils.round_to_sig_fig(scale.Value, 3)
end
-- Get scaling type of attribute, return nil if none is found
function get_attr_scale_type(attr)
local scale = pick_scale(attr)
if not scale then
return nil
end
end
Line 255: Line 221:
end
end
return scale.Type
return commonutils.round_to_sig_fig(scale.Value, 3)
end
 
-- Check if scaling should be multiplied instead of added
function get_attr_scale_multiply(attr)
local scale = pick_scale(attr)
if not scale then
return nil
end
return scale.Multiply
end
end


Line 276: Line 232:
local ability = utils.get_ability_card_data(hero_key, ability_num)
local ability = utils.get_ability_card_data(hero_key, ability_num)
if(ability == nil) then return {} end -- return empty table, not string
if(ability == nil) then return "Ability Not Found" end
local info_section = ability['Info'..info_section_num]
local info_section = ability['Info'..info_section_num]
Line 307: Line 263:
function get_main_boxes(hero_key, ability_num, info_section_num)
function get_main_boxes(hero_key, ability_num, info_section_num)
local ability = utils.get_ability_card_data(hero_key, ability_num)
local ability = utils.get_ability_card_data(hero_key, ability_num)
if(ability == nil) then return "Ability Not Found" end
if(ability == nil) then return {} end -- return empty table, not string
local info_section = ability['Info'..info_section_num]
local info_section = ability['Info'..info_section_num]
-- some abilities have no info sections
-- some abilities have no info sections
if info_section == nil then return '' end
if info_section == nil then return {} end
local main = info_section.Main
local main = info_section.Main
Line 330: Line 286:
end
end
--- A set of stat types that represent absolute values and should not have a '+' prefix.
local ABSOLUTE_VALUE_TYPES = {
damage = true,
bullet_damage = true,
tech_damage = true,
melee_damage = true,
health = true,
healing = true,
range = true,
distance = true,
duration = true,
cooldown = true,
time = true,
charges = true,
cast = true,
combat_barrier = true
}
local main_boxes = {}
local main_boxes = {}
for k, prop in ipairs(props) do  -- use ipairs for ordered iteration
for k, prop in ipairs(props) do  -- use ipairs for ordered iteration
local value = prop.Value
local value = prop.Value
local scale = pick_scale(prop)
-- If it scales with melee damage, set the value using the hero's base melee to match in game visuals
-- If it scales with melee damage, set the value using the hero's base melee to match in game visuals
if scale and scale.Type == 'melee' then
if prop.Scale and prop.Scale.Type == 'melee' then
local hero = get_hero_data(hero_key)
local hero = get_hero_data(hero_key)
value = prop.Value + hero.LightMeleeDamage * scale.Value
value = prop.Value + hero.LightMeleeDamage * prop.Scale.Value
elseif scale and scale.Type == 'heavy_melee' then
local hero = get_hero_data(hero_key)
value = prop.Value + hero.HeavyMeleeDamage * scale.Value
elseif scale and scale.Type == 'weapon_power' then
local hero = get_hero_data(hero_key)
value = hero.Weapon.BulletDamage * hero.Weapon.BulletsPerBurst * (scale.Value + 100) / 100
end
end
local icon = Icon.getAttrIcon(prop.Type)
-- Exclude 0 values
if value and value ~= 0 then
local main_box
--- Determine if the '+' sign should be excluded for this stat type by checking against the set.
if prop.StatusEffect then
local exclude_plus_flag = ABSOLUTE_VALUE_TYPES[prop.Type] or false
main_box = frame:expandTemplate{
 
title = "Ability_card_v2/Card/StatusBox",
local icon = get_icon(prop.Type)
args = {
local main_box = frame:expandTemplate{
title = prop.Title,
key = prop.Key,
value = value,
status_effect = prop.StatusEffect,
icon = icon and icon.img,
icon_link = icon and icon.link,
icon_color = icon and icon.color,
icon_size = icon and icon.size,
scale_value = scale and commonutils.round_to_sig_fig(scale.Value, 3),
scale_type =  scale and scale.Type,
-- allow nil value here as some status effects don't have a value but should not be hidden
hide = value == 0 and "true" or "false",
}
}
else
main_box = frame:expandTemplate{
title = "Ability_card_v2/Card/MainBox",
title = "Ability_card_v2/Card/MainBox",
args = {
args = {
title = prop.Title,
title = prop.Title,
key = prop.Key,
key = prop.Key,
value = value,
value = utils.format_value_with_prepost(prop.Key, value, frame, exclude_plus_flag),
icon = icon and icon.img,
icon = icon.img,
icon_link = icon and icon.link,
icon_link = icon.link,
icon_color = icon and icon.color,
icon_color = icon.color,
icon_size = icon and icon.size,
icon_size = icon.size,
scale_value = scale and commonutils.round_to_sig_fig(scale.Value, 3),
scale_value = prop.Scale and commonutils.round_to_sig_fig(prop.Scale.Value, 3),
scale_type =  scale and scale.Type,
scale_type =  prop.Scale and prop.Scale.Type
hide = (value == nil or value == 0) and "true" or "false",
}
}
}
}
table.insert(main_boxes, main_box)
end
end
table.insert(main_boxes, main_box)
end
end
Line 416: Line 365:
local alt_boxes = {}
local alt_boxes = {}
    for k, prop in ipairs(props) do
for k, prop in pairs(props) do
    -- Some props don't have values, as those come from upgrades
-- Some props don't have values, as those come from upgrades
    -- For now, we will ignore these and only show data for the base ability
-- For now, we will ignore these and only show data for the base ability
    if prop.Value then
if prop.Value and prop.Value ~= 0 then
    local icon = Icon.getAttrIcon(prop.Type)
local icon = get_icon(prop.Type)
 
local alt_box = frame:expandTemplate{
    local value = prop.Value
title = "Ability_card_v2/Card/AltBox",
    local scale = pick_scale(prop)
args = {
 
  key = prop.Key,
    if scale and scale.Type == 'weapon_power' then
  value = prop.Value,
    local hero = get_hero_data(hero_key)
  icon = icon.img,
    value = hero.Weapon.BulletDamage * hero.Weapon.BulletsPerBurst * (scale.Value + 100) / 100
  icon_link = icon.link,
    end
  icon_color = icon.color,
 
    icon_size = icon.size,
    local alt_box = frame:expandTemplate{
  scale_value = prop.Scale and commonutils.round_to_sig_fig(prop.Scale.Value, 3),
    title = "Ability_card_v2/Card/AltBox",
  scale_type = prop.Scale and prop.Scale.Type
    args = {
}
    key = prop.Key,
}
    value = value,
table.insert(alt_boxes, alt_box)
    icon = icon and icon.img,
end
    icon_link = icon and icon.link,
end
    icon_color = icon and icon.color,
    icon_size = icon and icon.size,
    scale_value = scale and commonutils.round_to_sig_fig(scale.Value, 3),
    scale_type = scale and scale.Type,
    hide = value == 0 and "true" or "false",
    }
    }
    table.insert(alt_boxes, alt_box)
    end
    end
return alt_boxes
return alt_boxes
Line 452: Line 391:


local UPGRADE_COST_MAP = {1, 2, 5}
local UPGRADE_COST_MAP = {1, 2, 5}
-- Upgrade description sizing. These are design proportions, not copies of any
-- CSS measurement: the browser supplies the box width through the container
-- query on .ac-upgrade-descwrap, so nothing here needs updating if the card's
-- widths, margins or padding change.
local UG_DESC_ASPECT = 0.357  -- max height of the text block, as a multiple of its width
local UG_LINE_HEIGHT = 1.4    -- emitted alongside the size so the two cannot drift
local UG_MAX_CQW    = 11.9  -- ceiling, as % of the text width (~16px on a full-width card)
local UG_MIN_CQW    = 6.7    -- floor (~9px)
function get_upgrade_boxes(hero_key, ability_num)
function get_upgrade_boxes(hero_key, ability_num)
local ability = utils.get_ability_card_data(hero_key, ability_num)
local ability = utils.get_ability_card_data(hero_key, ability_num)
Line 471: Line 400:
local upgrade_boxes = {}
local upgrade_boxes = {}
for k, upgrade in ipairs(upgrades) do
for k, upgrade in pairs(upgrades) do
local description = lang.get_string(upgrade.DescKey)
local description = lang.get_string(upgrade.DescKey)
-- bypass some keys if they are mistakenly left in game files
local IGNORE_DESC_KEYS = {'citadel_ability_hornet_snipe_t3_desc', 'ability_fencer_ultimate_t3_desc'}
-- Generate a description if there is no localized string
-- Generate a description if there is no localized string
if (description == nil or description == '' or commonutils.contains(IGNORE_DESC_KEYS, upgrade.DescKey)) then
if (description == nil or description == '') then
description = create_description(upgrade, frame)
description = create_description(upgrade, frame)
else
-- Replace embedded value placeholders with just the base value
-- Pattern: {'Value': X, 'Scale': {...}} -> X
description = description:gsub("{'Value':%s*([%d%.%-]+),%s*'Scale':%s*{[^}]+}}", function(value)
return commonutils.round_to_sig_fig(tonumber(value), 3)
end)
-- Pattern: {'Value': X} -> X
description = description:gsub("{'Value':%s*([%d%.%-]+)}", function(value)
return commonutils.round_to_sig_fig(tonumber(value), 3)
end)
end
end
-- Expand once, then size from the text the reader actually sees, rather
-- Vary the font size based on the number of characters to prevent overflow
-- than from a character count of the unexpanded string.
local fontsize = '1rem'
local rendered = frame:preprocess(description)
if #description > 60 and #description < 71 then
local cqw = textfit.fit(rendered, {
fontsize = '0.95rem'
aspect = UG_DESC_ASPECT,
elseif #description > 70 and #description < 91 then
lineHeight = UG_LINE_HEIGHT,
fontsize = '0.875rem'
max = UG_MAX_CQW,
elseif #description > 90 then
min = UG_MIN_CQW,
fontsize = '0.8rem'
})
end
local upgrade_scale = pick_scale(upgrade)
local upgrade_box = frame:expandTemplate{
local upgrade_box = frame:expandTemplate{
title = "Ability_card_v2/Card/UpgradeBox",
title = "Ability_card_v2/Card/UpgradeBox",
args = {
args = {
index = k,
  cost = UPGRADE_COST_MAP[k],
cost = UPGRADE_COST_MAP[k],
  description = frame:preprocess(description),
description = rendered,
  scale_value = upgrade.Scale and commonutils.round_to_sig_fig(upgrade.Scale.Value, 3),
scale_value = upgrade_scale and commonutils.round_to_sig_fig(upgrade_scale.Value, 3),
  scale_type =  upgrade.Scale and upgrade.Scale.Type,
scale_type =  upgrade_scale and upgrade_scale.Type,
  fontsize = fontsize
-- Measured against the box's own width, so the size stays correct
-- when the card renders below its 500px max-width.
fontsize = string.format('%.2fcqw', cqw),
lineheight = UG_LINE_HEIGHT
}
}
}
}
Line 522: Line 433:
return upgrade_boxes
return upgrade_boxes
end
end


function create_description(prop, frame)
function create_description(prop, frame)
     local value_lines = {}  -- normal stat changes
     local description = ''
     local scale_lines = {}  -- "Increased X scaling" notes
     local first = true
     local seen = {}
     local seen = {} -- Set to track added combinations


     for k, v in pairs(prop) do
     for k, v in pairs(prop) do
         if k == 'DescKey' then
    local value
            -- skip
         if type(v) == 'table' then
        elseif type(v) == 'table' then
        value = v.Value
            local has_value = v.Value ~= nil
        else
            local has_scale = v.Scale and v.Scale.Value and v.Scale.Value ~= 0
        value = v
    end
   
        local formatted_value = utils.format_value_with_prepost(k, value, frame)
        local attr_name = lang.get_string(k..'_label')
        local key = formatted_value .. '|' .. attr_name  -- Unique identifier


            -- Value is 0 and we have a scaling change → scaling note
        -- Skip if this combination has already been added
            if has_value and has_scale and v.Value == 0 then
        if not seen[key] then
                if not seen["scale_" .. k] then
        -- Add line break if this is not the first item
                    local scale_type = v.Scale.Type
             if not first then
                    local capitalized = scale_type:sub(1,1):upper() .. scale_type:sub(2)
              description = description .. '<br>'
                    table.insert(scale_lines, "Increased " .. capitalized .. " scaling")
                    seen["scale_" .. k] = true
                end
            -- Only a scaling change, no value → scaling note (future bot correction)
            elseif not has_value and has_scale then
                if not seen["scale_" .. k] then
                    local scale_type = v.Scale.Type
                    local capitalized = scale_type:sub(1,1):upper() .. scale_type:sub(2)
                    table.insert(scale_lines, "Increased " .. capitalized .. " scaling")
                    seen["scale_" .. k] = true
                end
             -- Normal value (0 without scale, or non‑zero with/without scale)
            elseif has_value then
                local formatted_value = utils.format_value_with_prepost(k, v.Value, frame)
                local attr_name = lang.get_string(k .. '_label')
                local key = formatted_value .. '|' .. attr_name
                if not seen[key] then
                    table.insert(value_lines, string.format('%s %s', formatted_value, attr_name))
                    seen[key] = true
                end
            end
        else
            -- Plain number/string (e.g. OutgoingDamagePenaltyPercent = -15)
            local formatted_value = utils.format_value_with_prepost(k, v, frame)
            local attr_name = lang.get_string(k .. '_label')
            local key = formatted_value .. '|' .. attr_name
            if not seen[key] then
                table.insert(value_lines, string.format('%s %s', formatted_value, attr_name))
                seen[key] = true
             end
             end
         end
         end
        description = description .. string.format('%s %s', formatted_value, attr_name)
        first = false
        seen[key] = true  -- Mark this combination as added
     end
     end
   
    return description
end


    -- Combine: value lines first, then scaling lines
p.get_attr_icon = function(frame)
    local all_lines = {}
local attr_type = frame.args[1]
    for _, line in ipairs(value_lines) do
local icon = get_icon(attr_type)
        table.insert(all_lines, line)
 
    end
return string.format('[[File:%s|%s|18px|link=%s]]', icon.img or '', icon.size or '', icon.link or '')
    for _, line in ipairs(scale_lines) do
end
        table.insert(all_lines, line)
    end


    return table.concat(all_lines, '<br>')
function get_icon(attr_type)
local mappedAttr = ATTR_TYPE_ICON_MAP[attr_type]
local img = 'GenericProperty.png'
local link = ''
local size = ''
local color = 'Grey'
if mappedAttr then
img = mappedAttr.img
link = mappedAttr.link
size = mappedAttr.size
color = mappedAttr.color or color
end
return {img=img, link=link, color=color, size=size}
end
end


Line 660: Line 564:
return mw.getCurrentFrame():expandTemplate{
return mw.getCurrentFrame():expandTemplate{
title = "Ability card v2/Card",
title = "Template:Ability card v2/Card",
args = {
args = {
hero_key = hero_key,
hero_key = hero_key,
Line 668: Line 572:
icon = lang.get_string(ability.Key, 'en') .. '.png',
icon = lang.get_string(ability.Key, 'en') .. '.png',
description = mw.getCurrentFrame():preprocess(lang.get_string(ability.DescKey)),
description = mw.getCurrentFrame():preprocess(lang.get_string(ability.DescKey)),
channel_time = ability.AbilityChannelTime and ability.AbilityChannelTime.Value ~= 9999 and ability.AbilityChannelTime.Value,
channel_time_scale = get_attr_scale(ability.AbilityChannelTime),
channel_time_scale_type = get_attr_scale_type(ability.AbilityChannelTime),
channel_time_scale_multiply = get_attr_scale_multiply(ability.AbilityChannelTime),
radius = ability.Radius and ability.Radius.Value,
radius = ability.Radius and ability.Radius.Value,
radius_scale = get_attr_scale(ability.Radius),
radius_ss = get_attr_ss(ability.Radius),
radius_scale_type = get_attr_scale_type(ability.Radius),
radius_scale_multiply = get_attr_scale_multiply(ability.Radius),
range = ability.AbilityCastRange and ability.AbilityCastRange.Value,
range = ability.AbilityCastRange and ability.AbilityCastRange.Value,
range_scale = get_attr_scale(ability.AbilityCastRange),
range_ss = get_attr_ss(ability.AbilityCastRange),
range_scale_type = get_attr_scale_type(ability.AbilityCastRange),
range_scale_multiply = get_attr_scale_multiply(ability.AbilityCastRange),
duration = ability.AbilityDuration and ability.AbilityDuration.Value,
duration = ability.AbilityDuration and ability.AbilityDuration.Value,
duration_scale = get_attr_scale(ability.AbilityDuration),
duration_ss = get_attr_ss(ability.AbilityDuration),
duration_scale_type = get_attr_scale_type(ability.AbilityDuration),
duration_scale_multiply = get_attr_scale_multiply(ability.AbilityDuration),
-- ability_width = format_value_with_prepost(width_key, ability[width_key]),
-- ability_width = format_value_with_prepost(width_key, ability[width_key]),
cooldown =ability.AbilityCooldown and ability.AbilityCooldown.Value,
cooldown =ability.AbilityCooldown and ability.AbilityCooldown.Value,
cooldown_scale = get_attr_scale(ability.AbilityCooldown),
cooldown_ss = get_attr_ss(ability.AbilityCooldown),
cooldown_scale_type = get_attr_scale_type(ability.AbilityCooldown),
charge_cooldown = ability.AbilityCooldownBetweenCharge and ability.AbilityCooldownBetweenCharge.Value,
cooldown_scale_multiply = get_attr_scale_multiply(ability.AbilityCooldown),
charge_cooldown_ss = get_attr_ss(ability.AbilityCooldownBetweenCharge),
charge_cooldown = ability.AbilityCooldownBetweenCharge and ability.AbilityCooldownBetweenCharge.Value ~= -1 and ability.AbilityCooldownBetweenCharge.Value,
num_of_charges = ability.AbilityCharges and ability.AbilityCharges.Value,
charge_cooldown_scale = get_attr_scale(ability.AbilityCooldownBetweenCharge),
notes = notes,
charge_cooldown_scale_type = get_attr_scale_type(ability.AbilityCooldownBetweenCharge),
notes_source_page = notes_source_page
charge_cooldown_scale_multiply = get_attr_scale_multiply(ability.AbilityCooldownBetweenCharge),
num_of_charges = ability.AbilityCharges and ability.AbilityCharges.Value or 0,
}
}
}
}
Please note that all contributions to The Deadlock Wiki are considered to be released under the Creative Commons Attribution-NonCommercial-ShareAlike (see Deadlock:Copyrights for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource. Do not submit copyrighted work without permission!
Cancel Editing help (opens in new window)
Preview page with this template