Module:Skill

--- -- --                             Module:Skill -- -- This module implements Template:Skill and Template:Skill progression ---

local getArgs = require('Module:Arguments').getArgs local m_util = require('Module:Util') local m_cargo = require('Module:Cargo')

local m_game = mw.loadData('Module:Game')

-- The cfg table contains all localisable strings and configuration, to make it -- easier to port this module to another wiki. local cfg = mw.loadData('Module:Skill/config')

local mwlanguage = mw.language.getContentLanguage

local i18n = cfg.i18n local tables = {} local data = {}

-- -- Helper functions -- local h = {}

function h.map_to_arg(tpl_args, frame, properties, prefix_in, map, level, set_name, set_id) if map.fields then for key, row in pairs(map.fields) do           if row.name then local val = tpl_args[prefix_in .. row.name] if row.func ~= nil then val = row.func(tpl_args, frame, val) end if val == nil and row.default ~= nil then val = row.default end if val ~= nil then if level ~= nil then if set_name then tpl_args.skill_levels[level][set_name] = tpl_args.skill_levels[level][set_name] or {} tpl_args.skill_levels[level][set_name][set_id] = tpl_args.skill_levels[level][set_name][set_id] or {} tpl_args.skill_levels[level][set_name][set_id][key] = val else tpl_args.skill_levels[level][key] = val end

-- Nuke variables since they're remapped to skill_levels tpl_args[prefix_in .. row.name] = nil else if set_name then tpl_args[set_name] = tpl_args[set_name] or {} tpl_args[set_name][set_id] = tpl_args[set_name][set_id] or {} tpl_args[set_name][set_id][key] = val

-- Nuke variables since they're remapped to [set_name] tpl_args[prefix_in .. row.name] = nil else tpl_args[key] = val end end properties[row.field] = val

-- Deprecated parameters if val and row.deprecated then tpl_args.has_deprecated_parameters = true if tpl_args.test then -- Log when testing tpl_args.deprecated_parameters = tpl_args.deprecated_parameters or {} tpl_args.deprecated_parameters[#tpl_args.deprecated_parameters+1] = {row.name, val} end end end end end end end

function h.costs(tpl_args, frame, prefix_in, level) tpl_args.skill_costs = tpl_args.skill_costs or {} for i=1, #tpl_args.skill_costs do       local cost_prefix = string.format('%s%s%d_', prefix_in, i18n.parameters.skill.cost, i) -- level _cost_ local cost = { amount = tpl_args[cost_prefix .. tables.skill_level_costs.fields.amount.name], --level _cost_amount }       if cost.amount ~= nil then local properties = { _table = tables.skill_level_costs.table, [tables.skill_level_costs.fields.set_id.field] = i,               [tables.skill_level_costs.fields.level.field] = level, }           h.map_to_arg(tpl_args, frame, properties, cost_prefix, tables.skill_level_costs, level, 'costs', i)            if not tpl_args.test then m_cargo.store(frame, properties) end end end end

function h.stats(tpl_args, frame, prefix_in, level) for i=1, cfg.max_stats_per_level do       local stat_prefix = string.format('%s%s%d_', prefix_in, i18n.parameters.skill.stat, i) -- level _stat_ local stat = { id = tpl_args[stat_prefix .. tables.skill_stats_per_level.fields.id.name], --level _stat_id value = tpl_args[stat_prefix .. tables.skill_stats_per_level.fields.value.name], --level _stat_value }       if stat.id ~= nil and stat.value ~= nil then local properties = { _table = tables.skill_stats_per_level.table, [tables.skill_stats_per_level.fields.level.field] = level, }           h.map_to_arg(tpl_args, frame, properties, stat_prefix, tables.skill_stats_per_level, level, 'stats', i)            tpl_args.skill_levels.has_stats = true if not tpl_args.test then m_cargo.store(frame, properties) end end end end

function h.int_value_or_na(tpl_args, frame, tblrow, value, tmap) value = tonumber(value) if value == nil then tblrow:node(m_util.html.td.na) else -- value = mwlanguage:formatNum(value) -- Removed for now. lang:formatNum returns a string, which causes issues for formatting if tmap.fmt ~= nil then if type(tmap.fmt) == 'string' then value = string.format(tmap.fmt, value) elseif type(tmap.fmt) == 'function' then value = string.format(tmap.fmt(tpl_args, frame) or '%s', value) end end tblrow :tag('td') :wikitext(value) :done end end

h.cast = {} function h.cast.wrap (f) return function(tpl_args, frame, value) if value == nil then return nil else return f(value) end end end

h.display = {} h.display.factory = {} function h.display.factory.value(args) return function (tpl_args, frame) args.fmt = args.fmt or tables.static.fields[args.key].fmt local value = tpl_args[args.key] if args.fmt and value then return string.format(args.fmt, value) else return value end end end

function h.display.factory.range_value(args) return function (tpl_args, frame) local value = {} if args.set_name and args.set_id then -- Guard against index errors tpl_args.skill_levels[0][args.set_name] = tpl_args.skill_levels[0][args.set_name] or {} tpl_args.skill_levels[0][args.set_name][args.set_id] = tpl_args.skill_levels[0][args.set_name][args.set_id] or {} tpl_args.skill_levels[1][args.set_name] = tpl_args.skill_levels[1][args.set_name] or {} tpl_args.skill_levels[1][args.set_name][args.set_id] = tpl_args.skill_levels[1][args.set_name][args.set_id] or {} tpl_args.skill_levels[tpl_args.max_level][args.set_name] = tpl_args.skill_levels[tpl_args.max_level][args.set_name] or {} tpl_args.skill_levels[tpl_args.max_level][args.set_name][args.set_id] = tpl_args.skill_levels[tpl_args.max_level][args.set_name][args.set_id] or {}

value.min = tpl_args.skill_levels[0][args.set_name][args.set_id][args.key] or tpl_args.skill_levels[1][args.set_name][args.set_id][args.key] value.max = tpl_args.skill_levels[0][args.set_name][args.set_id][args.key] or tpl_args.skill_levels[tpl_args.max_level][args.set_name][args.set_id][args.key] else value.min = tpl_args.skill_levels[0][args.key] or tpl_args.skill_levels[1][args.key] value.max = tpl_args.skill_levels[0][args.key] or tpl_args.skill_levels[tpl_args.max_level][args.key] end

-- property not set for this skill if value.min == nil or value.max == nil then return end

local map = args.map or tables.progression return m_util.html.format_value(tpl_args, frame, value, {           fmt=args.fmt or map.fields[args.key].fmt,            color=false,        }) end end

function h.display.factory.radius(args) return function (tpl_args, frame) local radius = tpl_args['radius' .. args.key] if radius == nil then return end local description = tpl_args[string.format('radius%s_description', args.key)] if description then return m_util.html.abbr(radius, description) else return radius end end end

-- -- Cargo tables --

tables.static = { table = 'skill', fields = { -- GrantedEffects.dat skill_id = { name = i18n.parameters.skill.skill_id, field = 'skill_id', type = 'String', func = nil, },       -- Active Skills.dat cast_time = { name = i18n.parameters.skill.cast_time, field = 'cast_time', type = 'Float', func = h.cast.wrap(m_util.cast.number), fmt = '%.2f ' .. m_game.units.seconds.short_lower, },       gem_description = { name = i18n.parameters.skill.gem_description, field = 'description', type = 'Text', func = nil, },       active_skill_name = { name = i18n.parameters.skill.active_skill_name, field = 'active_skill_name', type = 'String', func = nil, },       skill_icon = { name = i18n.parameters.skill.skill_icon, field = 'skill_icon', type = 'Page', func = function(tpl_args, frame) if tpl_args.active_skill_name then return string.format(i18n.files.skill_icon, tpl_args.active_skill_name) end end, },       item_class_id_restriction = { name = i18n.parameters.skill.item_class_id_restriction, field = 'item_class_id_restriction', type = 'List of String', func = function(tpl_args, frame, value) if value == nil then return nil end value = m_util.string.split(value, ', ') for _, v in ipairs(value) do                   if m_game.constants.item.classes[v] == nil then error(string.format(i18n.errors.skill.invalid_item_class_id, v)) end end return value end, },       item_class_restriction = { name = i18n.parameters.skill.item_class_restriction, field = 'item_class_restriction', type = 'List of String', func = function(tpl_args, frame, value) if tpl_args.item_class_id_restriction == nil then return end -- This function makes a localized list based on ids local item_classes = {} for _, v in ipairs(tpl_args.item_class_id_restriction) do                   item_classes[#item_classes+1] = m_game.constants.item.classes[v].full end return item_classes end, },       -- Projectiles.dat - manually mapped to the skills projectile_speed = { name = i18n.parameters.skill.projectile_speed, field = 'projectile_speed', type = 'Integer', func = h.cast.wrap(m_util.cast.number), },       -- Misc data derieved from stats stat_text = { name = i18n.parameters.skill.stat_text, field = 'stat_text', type = 'Text', func = nil, },       quality_stat_text = { name = i18n.parameters.skill.quality_stat_text, field = 'quality_stat_text', type = 'Text', func = nil, },       -- Misc data currently not from game data radius = { name = i18n.parameters.skill.radius, field = 'radius', type = 'Integer', func = h.cast.wrap(m_util.cast.number), },       radius_description = { name = i18n.parameters.skill.radius_description, field = 'radius_description', type = 'Text', func = h.cast.wrap(m_util.cast.text), },       radius_secondary = { name = i18n.parameters.skill.radius_secondary, field = 'radius_secondary', type = 'Integer', func = h.cast.wrap(m_util.cast.number), },       radius_secondary_description = { name = i18n.parameters.skill.radius_secondary_description, field = 'radius_secondary_description', type = 'Text', func = h.cast.wrap(m_util.cast.text), },       radius_tertiary = { -- not sure if any skill actually has 3 radius componets name = i18n.parameters.skill.radius_tertiary, field = 'radius_tertiary', type = 'Integer', func = h.cast.wrap(m_util.cast.number), },       radius_tertiary_description = { name = i18n.parameters.skill.radius_tertiary_description, field = 'radius_tertiary_description', type = 'Text', func = h.cast.wrap(m_util.cast.text), },       skill_screenshot = { name = i18n.parameters.skill.skill_screenshot, field = 'skill_screenshot', type = 'Page', func = function(tpl_args, frame) local ss               if tpl_args.skill_screenshot_file ~= nil then ss = string.format('File:%s', tpl_args.skill_screenshot_file) elseif tpl_args.skill_screenshot ~= nil then ss = string.format(i18n.files.skill_screenshot, tpl_args.skill_screenshot) elseif tpl_args.active_skill_name then -- When this parameter is set manually, we assume/expect it to be exist, but otherwise it probably doesn't and we don't need dead links in that case ss = string.format(i18n.files.skill_screenshot, tpl_args.active_skill_name) page = mw.title.new(ss) if page == nil or not page.exists then ss = nil end end return ss           end, },       -- Set programmatically max_level = { name = nil, field = 'max_level', type = 'Integer', func = h.cast.wrap(m_util.cast.number), },       html = { name = nil, field = 'html', type = 'Text', func = nil, },       -- Deprecated has_percentage_mana_cost = { name = i18n.parameters.skill.has_percentage_mana_cost, field = 'has_percentage_mana_cost', type = 'Boolean', func = h.cast.wrap(m_util.cast.boolean), default = false, deprecated = true, },       has_reservation_mana_cost = { name = i18n.parameters.skill.has_reservation_mana_cost, field = 'has_reservation_mana_cost', type = 'Boolean', func = h.cast.wrap(m_util.cast.boolean), default = false, deprecated = true, },   }, }

tables.progression = { table = 'skill_levels', fields = { level = { name = nil, field = 'level', type = 'Integer', func = nil, },       level_requirement = { name = i18n.parameters.skill.level_requirement, field = 'level_requirement', type = 'Integer', func = h.cast.wrap(m_util.cast.number), },       dexterity_requirement = { name = i18n.parameters.skill.dexterity_requirement, field = 'dexterity_requirement', type = 'Integer', func = h.cast.wrap(m_util.cast.number), },       strength_requirement = { name = i18n.parameters.skill.strength_requirement, field = 'strength_requirement', type = 'Integer', func = h.cast.wrap(m_util.cast.number), },       intelligence_requirement = { name = i18n.parameters.skill.intelligence_requirement, field = 'intelligence_requirement', type = 'Integer', func = h.cast.wrap(m_util.cast.number), },       mana_multiplier = { name = i18n.parameters.skill.mana_multiplier, field = 'mana_multiplier', type = 'Float', func = h.cast.wrap(m_util.cast.number), fmt = '%s%%', },       critical_strike_chance = { name = i18n.parameters.skill.critical_strike_chance, field = 'critical_strike_chance', type = 'Float', func = h.cast.wrap(m_util.cast.number), fmt = '%s%%', },       damage_effectiveness = { name = i18n.parameters.skill.damage_effectiveness, field = 'damage_effectiveness', type = 'Float', func = h.cast.wrap(m_util.cast.number), fmt = '%s%%', },       stored_uses = { name = i18n.parameters.skill.stored_uses, field = 'stored_uses', type = 'Integer', func = h.cast.wrap(m_util.cast.number), },       cooldown = { name = i18n.parameters.skill.cooldown, field = 'cooldown', type = 'Float', func = h.cast.wrap(m_util.cast.number), fmt = '%.2f ' .. m_game.units.seconds.short_lower, },       vaal_souls_requirement = { name = i18n.parameters.skill.vaal_souls_requirement, field = 'vaal_souls_requirement', type = 'Integer', func = h.cast.wrap(m_util.cast.number), },       vaal_stored_uses = { name = i18n.parameters.skill.vaal_stored_uses, field = 'vaal_stored_uses', type = 'Integer', func = h.cast.wrap(m_util.cast.number), header = i18n.progression.vaal_stored_uses, },       vaal_soul_gain_prevention_time = { name = i18n.parameters.skill.vaal_soul_gain_prevention_time, field = 'vaal_soul_gain_prevention_time', type = 'Float', func = h.cast.wrap(m_util.cast.number), fmt = '%i ' .. m_game.units.seconds.short_lower, },       damage_multiplier = { name = i18n.parameters.skill.damage_multiplier, field = 'damage_multiplier', type = 'Float', func = h.cast.wrap(m_util.cast.number), fmt = '%s%%', },       attack_speed_multiplier = { name = i18n.parameters.skill.attack_speed_multiplier, field = 'attack_speed_multiplier', type = 'Integer', func = h.cast.wrap(m_util.cast.number), fmt = '%s%%', },       duration = { name = i18n.parameters.skill.duration, field = 'duration', type = 'Float', func = h.cast.wrap(m_util.cast.number), fmt = '%.2f ' .. m_game.units.seconds.short_lower, },       -- from gem experience, optional experience = { name = i18n.parameters.skill.experience, field = 'experience', type = 'Integer', func = h.cast.wrap(m_util.cast.number), },       stat_text = { name = i18n.parameters.skill.stat_text, field = 'stat_text', type = 'Text', func = h.cast.wrap(m_util.cast.text), },       -- Deprecated mana_cost = { name = i18n.parameters.skill.mana_cost, field = 'mana_cost', type = 'Integer', func = h.cast.wrap(m_util.cast.number), deprecated = true, },   } }

tables.skill_costs = { table = 'skill_costs', fields = { set_id = { name = nil, field = 'set_id', type = 'Integer', func = nil, },       type = { name = i18n.parameters.skill.cost_type, field = 'type', type = 'String', func = function(tpl_args, frame, value) if value == nil then return nil end if m_game.constants.skill.cost_types[value] == nil then error(string.format(i18n.errors.skill.invalid_cost_type, value)) end return value end, },       is_reservation = { name = i18n.parameters.skill.cost_is_reservation, field = 'is_reservation', type = 'Boolean', func = h.cast.wrap(m_util.cast.boolean), default = false, },   } }

tables.skill_level_costs = { table = 'skill_level_costs', fields = { set_id = { name = nil, field = 'set_id', type = 'Integer', func = nil, },       level = { name = nil, field = 'level', type = 'Integer', func = nil, },       amount = { name = i18n.parameters.skill.cost_amount, field = 'amount', type = 'Integer', func = h.cast.wrap(m_util.cast.number), },   }, }

tables.skill_stats_per_level = { table = 'skill_stats_per_level', fields = { level = { name = nil, field = 'level', type = 'Integer', func = nil, },       id = { name = i18n.parameters.skill.stat_id, field = 'id', type = 'String', func = h.cast.wrap(m_util.cast.text), },       value = { name = i18n.parameters.skill.stat_value, field = 'value', type = 'Integer', func = h.cast.wrap(m_util.cast.number), },   }, }

tables.skill_quality = { table = 'skill_quality', fields = { set_id = { field = 'set_id', type = 'Integer', },       weight = { field = 'weight', type = 'Integer', },       stat_text = { field = 'stat_text', type = 'String', },   }, }

tables.skill_quality_stats = { table = 'skill_quality_stats', fields = { set_id = { field = 'set_id', type = 'Integer', },       id = { field = 'id', type = 'String', },       value = { field = 'value', type = 'Integer', },   }, }

-- -- Data --

data.skill_progression_table = { {       field = 'level', header = i18n.progression.level, },   {        field = 'level_requirement', header = i18n.progression.level_requirement, },   {        field = 'dexterity_requirement', header = i18n.progression.dexterity_requirement, },   {        field = 'strength_requirement', header = i18n.progression.strength_requirement, },   {        field = 'intelligence_requirement', header = i18n.progression.intelligence_requirement, },   {        field = 'mana_multiplier', header = i18n.progression.mana_multiplier, fmt = '%s%%', },   {        field = 'critical_strike_chance', header = i18n.progression.critical_strike_chance, fmt = '%s%%', },   { -- Also supports deprecated method of specifying mana cost and reservation field = 'mana_cost', header = function (tpl_args, frame) if #tpl_args.skill_data.costs == 0 and tpl_args.skill_data[tables.static.fields.has_reservation_mana_cost.name] then return i18n.progression.mana_reserved end return i18n.progression.mana_cost end, fmt = function (tpl_args, frame) if #tpl_args.skill_data.costs == 0 and tpl_args.skill_data[tables.static.fields.has_percentage_mana_cost.name] then return '%s%%' end return '%s' end, },   {        field = 'mana_percent_cost', header = i18n.progression.mana_cost, fmt = '%s%%', },   {        field = 'life_cost', header = i18n.progression.life_cost, },   {        field = 'life_percent_cost', header = i18n.progression.life_cost, fmt = '%s%%', },   {        field = 'energy_shield_cost', header = i18n.progression.energy_shield_cost, },   {        field = 'rage_cost', header = i18n.progression.rage_cost, },   {        field = 'mana_reserved', header = i18n.progression.mana_reserved, },   {        field = 'mana_percent_reserved', header = i18n.progression.mana_reserved, fmt = '%s%%', },   {        field = 'life_reserved', header = i18n.progression.life_reserved, },   {        field = 'life_percent_reserved', header = i18n.progression.life_reserved, fmt = '%s%%', },   {        field = 'damage_effectiveness', header = i18n.progression.damage_effectiveness, fmt = '%s%%', },   {        field = 'stored_uses', header = i18n.progression.stored_uses, },   {        field = 'cooldown', header = i18n.progression.cooldown, fmt = '%.2f ' .. m_game.units.seconds.short_lower, },   {        field = 'vaal_souls_requirement', header = i18n.progression.vaal_souls_requirement, },   {        field = 'vaal_stored_uses', header = i18n.progression.vaal_stored_uses, },   {        field = 'vaal_soul_gain_prevention_time', header = i18n.progression.vaal_soul_gain_prevention_time, fmt = '%i ' .. m_game.units.seconds.short_lower, },   {        field = 'damage_multiplier', header = i18n.progression.damage_multiplier, fmt = '%s%%', },   {        field = 'duration', header = i18n.progression.duration, fmt = '%.2f ' .. m_game.units.seconds.short_lower, },   {        field = 'attack_speed_multiplier', header = i18n.progression.attack_speed_multiplier, fmt = '%s%%', }, }

data.infobox_table = { {       header = i18n.infobox.active_skill_name, func = h.display.factory.value{key='active_skill_name'}, },   {        header = i18n.infobox.skill_id, func = function (tpl_args, frame) return string.format('%s', mw.title.getCurrentTitle.fullText, tpl_args.skill_id) end },   {        header = i18n.infobox.skill_icon, func = function (tpl_args, frame) if tpl_args.skill_icon then return string.format('%s', tpl_args.skill_icon) end end, },   {        header = i18n.infobox.cast_time, func = function (tpl_args, frame) local value = tpl_args.cast_time if value then if value == 0 then return i18n.infobox.instant_cast_time end return string.format('%.2f %s', value, m_game.units.seconds.short_lower) end return value end, },   {        header = i18n.infobox.item_class_restrictions, func = function (tpl_args, frame) if tpl_args.item_class_restriction == nil then return end local out = {} for _, class in ipairs(tpl_args.item_class_restriction) do               out[#out+1] = string.format('%s', class) end return table.concat(out, ' ') end, },   {        header = i18n.infobox.projectile_speed, func = h.display.factory.value{key='projectile_speed'}, },   {        header = i18n.infobox.radius, func = h.display.factory.radius{key=''}, },   {        header = i18n.infobox.radius_secondary, func = h.display.factory.radius{key='_secondary'}, },   {        header = i18n.infobox.radius_tertiary, func = h.display.factory.radius{key='_tertiary'}, },   {        header = i18n.infobox.level_requirement, func = h.display.factory.range_value{key='level_requirement'}, },   -- ingore attrbiutes? {       header = i18n.infobox.mana_multiplier, func = h.display.factory.range_value{key='mana_multiplier'}, },   {        header = i18n.infobox.critical_strike_chance, func = h.display.factory.range_value{key='critical_strike_chance'}, },   {        header = i18n.infobox.cost, func = function (tpl_args, frame) if not tpl_args.skill_costs.has_flat_cost then -- Try falling back to deprecated parameters if not tpl_args.has_reservation_mana_cost then local range = h.display.factory.range_value{key='mana_cost'}(tpl_args, frame) if range then return string.format('%s %s', range, m_game.constants.skill.cost_types.mana.long_upper) end return end return end local sets = {} for i=1, #tpl_args.skill_costs do               if not tpl_args.skill_costs[i].is_reservation then -- Only get flat costs local cost_type = tpl_args.skill_costs[i].type local range = h.display.factory.range_value{key='amount', set_name='costs', set_id=i, map=tables.skill_level_costs}(tpl_args, frame) if range then local fmt if string.find(cost_type, 'percent', 1, true) then fmt = '%s%% %s' else fmt = '%s %s' end sets[#sets+1] = string.format(fmt, range, m_game.constants.skill.cost_types[cost_type].long_upper) end end end return table.concat(sets, ', ') end, },   {        header = i18n.infobox.reservation, func = function (tpl_args, frame) if not tpl_args.skill_costs.has_reservation_cost then -- Try falling back to deprecated parameters if tpl_args.has_reservation_mana_cost then local range = h.display.factory.range_value{key='mana_cost'}(tpl_args, frame) if range then if tpl_args.has_percentage_mana_cost then return string.format('%s%% %s', range, m_game.constants.skill.cost_types.mana.long_upper) end return string.format('%s %s', range, m_game.constants.skill.cost_types.mana.long_upper) end return end return end local sets = {} for i=1, #tpl_args.skill_costs do               if tpl_args.skill_costs[i].is_reservation then -- Only get reservation costs local cost_type = tpl_args.skill_costs[i].type local range = h.display.factory.range_value{key='amount', set_name='costs', set_id=i, map=tables.skill_level_costs}(tpl_args, frame) if range then local fmt if string.find(cost_type, 'percent', 1, true) then fmt = '%s%% %s' else fmt = '%s %s' end sets[#sets+1] = string.format(fmt, range, m_game.constants.skill.cost_types[cost_type].long_upper) end end end return table.concat(sets, ', ') end, },   {        header = i18n.infobox.attack_speed_multiplier, func = h.display.factory.range_value{key='attack_speed_multiplier'}, fmt = '%s ' .. i18n.infobox.of_base_stat, },   {        header = i18n.infobox.damage_multiplier, func = h.display.factory.range_value{key='damage_multiplier'}, fmt = '%s ' .. i18n.infobox.of_base_stat, },   {        header = i18n.infobox.damage_effectiveness, func = h.display.factory.range_value{key='damage_effectiveness'}, },   {        header = i18n.infobox.stored_uses, func = h.display.factory.range_value{key='stored_uses'}, },   {        header = i18n.infobox.cooldown, func = h.display.factory.range_value{key='cooldown'}, },   {        header = i18n.infobox.vaal_souls_requirement, func = h.display.factory.range_value{key='vaal_souls_requirement'}, },   {        header = i18n.infobox.vaal_stored_uses, func = h.display.factory.range_value{key='vaal_stored_uses'}, },   {        header = i18n.infobox.vaal_soul_gain_prevention_time, func = h.display.factory.range_value{key='vaal_soul_gain_prevention_time'}, },    {        header = i18n.infobox.duration, func = h.display.factory.range_value{key='duration'}, },   {        header = nil, func = h.display.factory.value{key='gem_description'}, class = 'tc -gemdesc', },   {        header = nil, func = h.display.factory.value{key='stat_text'}, class = 'tc -mod', }, }

-- -- Invokable functions -- local p = {}

p.table_skills = m_cargo.declare_factory{data=tables.static} p.table_skill_levels = m_cargo.declare_factory{data=tables.progression} p.table_skill_costs = m_cargo.declare_factory{data=tables.skill_costs} p.table_skill_level_costs = m_cargo.declare_factory{data=tables.skill_level_costs} p.table_skill_stats_per_level = m_cargo.declare_factory{data=tables.skill_stats_per_level} p.table_skill_quality = m_cargo.declare_factory{data=tables.skill_quality} p.table_skill_quality_stats = m_cargo.declare_factory{data=tables.skill_quality_stats}

-- -- Processes skill data from tpl_args. -- Stores skill data in cargo tables. -- Attaches page to cargo tables. -- function p._skill(tpl_args, frame) frame = m_util.misc.get_frame(frame) tpl_args = tpl_args or {} tpl_args._flags = tpl_args._flags or {} tpl_args.skill_levels = { [0] = {},   }    -- Quality tpl_args.skill_quality = {} local i = 0 repeat i = i + 1 local prefix = string.format('quality_type%s', i)       local q = { _table = tables.skill_quality.table, set_id = i,           weight = tonumber(tpl_args[string.format('%s_weight', prefix)]), stat_text = tpl_args[string.format('%s_stat_text', prefix)], }       if q.stat_text then tpl_args.skill_quality[#tpl_args.skill_quality+1] = q           m_cargo.store(frame, q)            q.stats = {} q._table = nil local j = 0 repeat j = j + 1 local stat_prefix = string.format('%s_stat%s', prefix, j)               local s = { _table = tables.skill_quality_stats.table, set_id = i,                   id = tpl_args[string.format('%s_id', stat_prefix)], value = tonumber(tpl_args[string.format('%s_value', stat_prefix)]), }               if s.id and s.value then q.stats[#q.stats+1] = s                   m_cargo.store(frame, s)                end s._table = nil until s.id == nil or s.value == nil end until q.stat_text == nil if #tpl_args.skill_quality > 1 then -- Gem has alternative qualtiy tpl_args._flags.is_alt_quality_gem = true end

-- Costs for i=1, math.huge do -- repeat until no more cost sets are found local prefix = string.format('%s%d_', i18n.parameters.skill.skill_cost, i)       if tpl_args[prefix .. tables.skill_costs.fields.type.field] == nil then break end local properties = { _table = tables.skill_costs.table, [tables.skill_costs.fields.set_id.field] = i,       } h.map_to_arg(tpl_args, frame, properties, prefix, tables.skill_costs, nil, 'skill_costs', i)       if properties.is_reservation then tpl_args.skill_costs.has_reservation_cost = true else tpl_args.skill_costs.has_flat_cost = true end tpl_args.skill_costs[i] = { set_id = properties.set_id, type = properties.type, is_reservation = properties.is_reservation, }       if not tpl_args.test then m_cargo.store(frame, properties) end end -- Handle level progression local level_count = 0 for i=1, math.huge do -- repeat until no more levels are found local prefix = i18n.parameters.skill.level .. i       local level = m_util.cast.boolean(tpl_args[prefix]) if not level then break end tpl_args.skill_levels[i] = {} prefix = prefix .. '_'       level_count = i        if tpl_args[prefix .. i18n.parameters.skill.experience] ~= nil then -- For skill gems, max level is the highest level with experience. tpl_args.max_level = i       end local properties = { _table = tables.progression.table, [tables.progression.fields.level.field] = i       } h.map_to_arg(tpl_args, frame, properties, prefix, tables.progression, i)       if not tpl_args.test then m_cargo.store(frame, properties) end h.costs(tpl_args, frame, prefix, i)       h.stats(tpl_args, frame, prefix, i)    end tpl_args.max_level = tpl_args.max_level or level_count

-- handle static progression local prefix = i18n.parameters.skill.static .. '_'   do        local properties = { _table = tables.progression.table, [tables.progression.fields.level.field] = 0 }       h.map_to_arg(tpl_args, frame, properties, prefix, tables.progression, 0) if not tpl_args.test then m_cargo.store(frame, properties) end end -- Handle static arguments local properties = { _table = tables.static.table, [tables.static.fields.max_level.field] = tpl_args.max_level }   h.map_to_arg(tpl_args, frame, properties, '', tables.static) h.costs(tpl_args, frame, prefix, 0) h.stats(tpl_args, frame, prefix, 0)

-- Build infobox local infobox = mw.html.create('span') infobox:attr('class', 'skill-box') local tbl = infobox:tag('table') tbl:attr('class', 'wikitable skill-box-table') for _, infobox_data in ipairs(data.infobox_table) do       local display = infobox_data.func(tpl_args, frame) if display ~= nil and infobox_data.fmt ~= nil then if type(infobox_data.fmt) == 'string' then display = string.format(infobox_data.fmt, display) elseif type(infobox_data.fmt) == 'function' then display = string.format(infobox_data.fmt(tpl_args, frame) or '%s', display) end end if display then local tr = tbl:tag('tr') if infobox_data.header then local header_text if type(infobox_data.header) == 'function' then header_text = infobox_data.header(tpl_args, frame) else header_text = infobox_data.header end tr                   :tag('th') :wikitext(header_text) :done end local td = tr:tag('td') td:wikitext(display) td:attr('class', infobox_data.class or 'tc -value') if infobox_data.header == nil then td:attr('colspan', 2) end end end infobox = tostring(infobox)

-- Store data properties[tables.static.fields.html.field] = infobox if not tpl_args.test then m_cargo.store(frame, properties) end

-- Attach tables if not tpl_args.test then local attach_tables = { tables.static.table, tables.progression.table, }       if #tpl_args.skill_quality > 0 then attach_tables[#attach_tables+1] = tables.skill_quality.table attach_tables[#attach_tables+1] = tables.skill_quality_stats.table end if #tpl_args.skill_costs > 0 then attach_tables[#attach_tables+1] = tables.skill_costs.table attach_tables[#attach_tables+1] = tables.skill_level_costs.table end if tpl_args.skill_levels.has_stats then attach_tables[#attach_tables+1] = tables.skill_stats_per_level.table end for _, table_name in ipairs(attach_tables) do           frame:expandTemplate{ title = string.format(i18n.templates.cargo_attach, table_name), args = {} }       end end

-- Log when testing if tpl_args.test then mw.logObject(tpl_args) end

return infobox end

-- -- Template:Skill -- function p.skill(frame) --[[   Display skill infobox    Examples    =p.skill{gem_description='Icy bolts rain down over the targeted area.', active_skill_name='Icestorm', skill_id='IcestormUniqueStaff12', cast_time=0.75, required_level=1, static_mana_cost=22, static_critical_strike_chance=6, static_damage_effectiveness=30, static_damage_multiplier=100, static_stat1_id='spell_minimum_base_cold_damage_+_per_10_intelligence', static_stat1_value=1, static_stat2_id='spell_maximum_base_cold_damage_+_per_10_intelligence', static_stat2_value=3, static_stat3_id='base_skill_effect_duration', static_stat3_value=1500, static_stat4_id='fire_storm_fireball_delay_ms', static_stat4_value=100, static_stat5_id='skill_effect_duration_per_100_int', static_stat5_value=150, static_stat6_id='skill_override_pvp_scaling_time_ms', static_stat6_value=450, static_stat7_id='firestorm_drop_ground_ice_duration_ms', static_stat7_value=500, static_stat8_id='skill_art_variation', static_stat8_value=4, static_stat9_id='base_skill_show_average_damage_instead_of_dps', static_stat9_value=1, static_stat10_id='is_area_damage', static_stat10_value=1, stat_text='Deals 1 to 3 base Cold Damage per 10 Intelligence Base duration is 1.5 seconds One impact every 0.1 seconds 0.15 seconds additional Base Duration per 100 Intelligence', quality_stat_text = nil, level1=true, level1_level_requirement=1}

]]

local tpl_args = getArgs(frame, {       parentFirst = true    }) frame = m_util.misc.get_frame(frame)

-- Handle skill data and get infobox local infobox = p._skill(tpl_args, frame)

-- Container local container = mw.html.create('span') container :attr('class', 'skill-box-page-container') :wikitext(infobox) if tpl_args.skill_screenshot then container :wikitext(string.format('%s', tpl_args.skill_screenshot)) end

-- Generic messages on the page: out = {} if mw.ustring.find(tpl_args.skill_id, '_') then out[#out+1] = frame:expandTemplate{ title = i18n.templates.incorrect_title, args = {title=tpl_args.skill_id} } .. '\n\n\n' end if tpl_args.active_skill_name then out[#out+1] = string.format(           i18n.messages.intro_named_id,             tpl_args.skill_id,             tpl_args.active_skill_name        ) else out[#out+1] = string.format(           i18n.messages.intro_unnamed_id,             tpl_args.skill_id        ) end

-- Categories local cats = {i18n.categories.skill_data} if tpl_args.has_deprecated_parameters then cats[#cats+1] = i18n.categories.deprecated_parameters end return tostring(container) .. m_util.misc.add_category(cats) .. '\n' .. table.concat(out) end

function p.progression(frame) --       Displays the level progression for the skill gem.         Examples        = p.progression{page='Reave'} local tpl_args = getArgs(frame, {       parentFirst = true    }) frame = m_util.misc.get_frame(frame) -- Parse column arguments: tpl_args.stat_format = {} local param_keys = { i18n.parameters.progression.header, i18n.parameters.progression.abbr, i18n.parameters.progression.pattern_extract, i18n.parameters.progression.pattern_value, }   for i=1, math.huge do -- repeat until no more columns are found local prefix = string.format('%s%d_', i18n.parameters.progression.column, i)       if tpl_args[prefix .. param_keys[1]] == nil then break end local statfmt = {counter = 0} for _, key in ipairs(param_keys) do           local arg = prefix .. key if tpl_args[arg] == nil then error(string.format(i18n.errors.progression.argument_unspecified, arg)) end statfmt[key] = tpl_args[arg] end statfmt.header = m_util.html.abbr(statfmt.abbr, statfmt.header) statfmt.abbr = nil tpl_args.stat_format[#tpl_args.stat_format+1] = statfmt end -- Query skill data local results = {} local skill_data local fields = { '_pageName', tables.static.fields.has_reservation_mana_cost.name, tables.static.fields.has_percentage_mana_cost.name, }   local query = { groupBy = '_pageID', }   if tpl_args.skill_id then -- Query by skill id        query.where = string.format('skill_id="%s"', tpl_args.skill_id) results = m_cargo.query({tables.static.table}, fields, query) if #results == 0 then error(string.format(i18n.errors.progression.no_results_for_skill_id, tpl_args.skill_id)) end else -- Query by page name page = tpl_args.page or mw.title.getCurrentTitle.prefixedText query.where = string.format('_pageName="%s"', page) results = m_cargo.query({tables.static.table}, fields, query) if #results == 0 then error(string.format(i18n.errors.progression.no_results_for_skill_page, page)) end end skill_data = results[1] skill_data[tables.static.fields.has_reservation_mana_cost.name] = m_util.cast.boolean(skill_data[tables.static.fields.has_reservation_mana_cost.name]) skill_data[tables.static.fields.has_percentage_mana_cost.name] = m_util.cast.boolean(skill_data[tables.static.fields.has_percentage_mana_cost.name]) tpl_args.skill_data = skill_data

-- Query progression data fields = {} for _, fmap in pairs(tables.progression.fields) do       fields[#fields+1] = fmap.field end query = { where = string.format(           '_pageName="%s" AND %s > 0',            skill_data['_pageName'],            tables.progression.fields.level.field        ), groupBy = string.format(           '_pageID, %s',            tables.progression.fields.level.field        ), orderBy = string.format(           '%s ASC',            tables.progression.fields.level.field        ), }   results = m_cargo.query({tables.progression.table}, fields, query) if #results == 0 then error(i18n.errors.progression.missing_level_data) end skill_data.levels = results

-- Query cost data fields = {} for _, fmap in pairs(tables.skill_costs.fields) do       fields[#fields+1] = fmap.field end query = { where = string.format(           '_pageName="%s"',            skill_data['_pageName']        ), groupBy = string.format(           '_pageID, %s',            tables.skill_costs.fields.set_id.field        ), orderBy = string.format(           '%s ASC',            tables.skill_costs.fields.set_id.field        ), }   results = m_cargo.query({tables.skill_costs.table}, fields, query) skill_data.costs = results if #results > 0 then -- If skill has costs, query cost data by levels fields = {} for _, fmap in pairs(tables.skill_level_costs.fields) do           fields[#fields+1] = fmap.field end query = { where = string.format(               '_pageName="%s" AND %s > 0',                skill_data['_pageName'],                tables.skill_level_costs.fields.level.field            ), groupBy = string.format(               '_pageID, %s, %s',                tables.skill_level_costs.fields.set_id.field,                tables.skill_level_costs.fields.level.field            ), orderBy = string.format(               '%s ASC, %s ASC',                tables.skill_level_costs.fields.set_id.field,                tables.skill_level_costs.fields.level.field            ), }       results = m_cargo.query({tables.skill_level_costs.table}, fields, query) skill_data.costs_by_level = results

-- Interpolate cost data into level data local column for _,cdata in ipairs(skill_data.costs) do           if m_util.cast.boolean(cdata[tables.skill_costs.fields.is_reservation.field]) then column = string.format('%s_reserved', cdata[tables.skill_costs.fields.type.field]) else column = string.format('%s_cost', cdata[tables.skill_costs.fields.type.field]) end for _,ldata in ipairs(skill_data.levels) do               for _,rdata in ipairs(skill_data.costs_by_level) do                    if rdata[tables.skill_level_costs.fields.set_id.field] == cdata[tables.skill_costs.fields.set_id.field] and rdata[tables.skill_level_costs.fields.level.field] == ldata[tables.progression.fields.level.field] then ldata[column] = rdata[tables.skill_level_costs.fields.amount.field] break end end end end end -- Set up html table headers headers = {} for _, row in ipairs(skill_data.levels) do       for k, v in pairs(row) do            headers[k] = true end end local tbl = mw.html.create('table') tbl :attr('class', 'wikitable responsive-table skill-progression-table') local head = tbl:tag('tr') for _, tmap in pairs(data.skill_progression_table) do       if headers[tmap.field] then local text = type(tmap.header) == 'function' and tmap.header(tpl_args, frame) or tmap.header head :tag('th') :wikitext(text) :done end end for _, statfmt in ipairs(tpl_args.stat_format) do       head :tag('th') :wikitext(statfmt.header) :done end if headers[tables.progression.fields.experience.field] then head :tag('th') :wikitext(i18n.progression.experience) :done :tag('th') :wikitext(i18n.progression.total_experience) :done end

-- Table rows local tblrow local lastexp = 0 local experience for _, row in ipairs(skill_data.levels) do       tblrow = tbl:tag('tr') for _, tmap in pairs(data.skill_progression_table) do           if headers[tmap.field] then h.int_value_or_na(tpl_args, frame, tblrow, row[tmap.field], tmap) end end -- stats local stats = {} if row[tables.progression.fields.stat_text.field] then stats = m_util.string.split(               row[tables.progression.fields.stat_text.field],                ' '            ) end for _, statfmt in ipairs(tpl_args.stat_format) do           local match = {} for j, stat in ipairs(stats) do               match = {string.match(stat, statfmt.pattern_extract)} if #match > 0 then -- TODO maybe remove stat here to avoid testing -- against in future loops break end end if #match == 0 then tblrow:node(m_util.html.td.na) else -- used to find broken progression due to game updates -- for example: statfmt.counter = statfmt.counter + 1 tblrow :tag('td') :wikitext(string.format( statfmt.pattern_value, match[1], match[2], match[3], match[4], match[5] )                       )                        :done end end -- TODO: Quality stats, afaik no gems use this atm if headers[tables.progression.fields.experience.field] then experience = tonumber(row[tables.progression.fields.experience.field]) if experience ~= nil then h.int_value_or_na(tpl_args, frame, tblrow, experience - lastexp, {}) lastexp = experience else tblrow:node(m_util.html.td.na) end h.int_value_or_na(tpl_args, frame, tblrow, experience, {}) end end local cats = {} for _, statfmt in ipairs(tpl_args.stat_format) do       if statfmt.counter == 0 then cats = i18n.categories.broken_progression_table break end end return tostring(tbl) .. m_util.misc.add_category(cats) end

return p