(i18n for arguments isn't being used as it causes too much trouble adapting everything) |
(Declare variables locally) |
||
(39 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
− | -- |
+ | ------------------------------------------------------------------------------- |
− | -- |
+ | -- |
+ | -- Module:Skill |
||
− | -- ---------------------------------------------------------------------------- |
||
+ | -- |
||
+ | -- This module implements Template:Skill and Template:Skill progression |
||
+ | ------------------------------------------------------------------------------- |
||
local getArgs = require('Module:Arguments').getArgs |
local getArgs = require('Module:Arguments').getArgs |
||
+ | local m_util = require('Module:Util') |
||
local m_cargo = require('Module:Cargo') |
local m_cargo = require('Module:Cargo') |
||
+ | |||
− | local m_util = require('Module:Util') |
||
− | local m_game = |
+ | 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 mwlanguage = mw.language.getContentLanguage() |
||
+ | local i18n = cfg.i18n |
||
− | --- define here to avoid errors |
||
+ | local tables = {} |
||
local data = {} |
local data = {} |
||
− | local map = {} |
||
− | |||
− | -- TODO: |
||
− | -- skill_id field link to data page |
||
− | -- aspects: % mana cost |
||
− | |||
− | -- ---------------------------------------------------------------------------- |
||
− | -- i18n |
||
− | -- ---------------------------------------------------------------------------- |
||
− | |||
− | local i18n = { |
||
− | skill_icon = 'File:%s skill icon.png', |
||
− | skill_screenshot = 'File:%s skill screenshot.jpg', |
||
− | |||
− | -- Intro texts: |
||
− | intro_named_id = "'''%s''' is the internal id of the [[skill]] '''%s'''.\n", |
||
− | intro_unnamed_id = "'''%s''' is the internal id of an unnamed [[skill]].\n", |
||
− | |||
− | errors = { |
||
− | all_format_keys_specified = 'All formatting keys must be specified for index "%s"', |
||
− | no_results_for_skill_id = "Couldn't find a page for the specified skill id", |
||
− | no_results_for_skill_page = "Couldn't find the queried data on the skill page", |
||
− | missing_level_data = 'No gem level progression data found', |
||
− | }, |
||
− | |||
− | categories = { |
||
− | broken_progression_table = 'Pages with broken skill progression tables' |
||
− | }, |
||
− | |||
− | infobox = { |
||
− | skill_id = 'Skill Id', |
||
− | active_skill_name = 'Name', |
||
− | skill_icon = 'Icon', |
||
− | cast_time = 'Cast Time', |
||
− | item_class_restrictions = 'Item Class<br>Restrictions', |
||
− | projectile_speed = 'Projectile Speed', |
||
− | radius = 'Radius', |
||
− | radius_secondary = 'Radius 2', |
||
− | radius_tertiary = 'Radius 3', |
||
− | level_requirement = 'Level Req.', |
||
− | mana_multiplier = 'Mana Multiplier', |
||
− | critical_strike_chance = 'Critical Strike Chance', |
||
− | mana_cost = 'Mana Cost', |
||
− | mana_reserved = 'Mana Reserved', |
||
− | damage_effectiveness = 'Damage Effectiveness', |
||
− | stored_uses = 'Stored Uses', |
||
− | cooldown = 'Cooldown', |
||
− | vaal_souls_requirement = 'Vaal Souls', |
||
− | vaal_stored_uses = 'Vaal Stored Uses', |
||
− | vaal_soul_gain_prevention_time = 'Soul Gain Prevention', |
||
− | damage_multiplier = 'Damage Multiplier', |
||
− | }, |
||
− | |||
− | progression = { |
||
− | exp_short = 'Exp.', |
||
− | exp_long = 'Experience needed to level up', |
||
− | tot_exp_short = 'Total Exp.', |
||
− | tot_exp_long = 'Total experience needed', |
||
− | }, |
||
− | } |
||
-- ---------------------------------------------------------------------------- |
-- ---------------------------------------------------------------------------- |
||
Line 78: | Line 27: | ||
local h = {} |
local h = {} |
||
− | function h.map_to_arg(tpl_args, frame, properties, prefix_in, map, level) |
+ | function h.map_to_arg(tpl_args, frame, properties, prefix_in, map, level, set_name, set_id) |
− | if map. |
+ | if map.fields then |
− | for |
+ | for key, row in pairs(map.fields) do |
− | row = map.fields[key] |
||
if row.name then |
if row.name then |
||
local val = tpl_args[prefix_in .. row.name] |
local val = tpl_args[prefix_in .. row.name] |
||
Line 90: | Line 38: | ||
val = row.default |
val = row.default |
||
end |
end |
||
− | if val ~= nil then |
+ | if val ~= nil then |
if level ~= 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 |
-- Nuke variables since they're remapped to skill_levels |
||
tpl_args[prefix_in .. row.name] = nil |
tpl_args[prefix_in .. row.name] = nil |
||
else |
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 |
end |
||
properties[row.field] = val |
properties[row.field] = val |
||
+ | |||
+ | -- Deprecated parameters |
||
+ | if val and row.deprecated then |
||
+ | tpl_args._flags.has_deprecated_skill_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 |
||
− | else |
||
− | error(string.format('Programming error, missing field %s from order keys', key)) |
||
end |
end |
||
end |
end |
||
Line 107: | Line 78: | ||
end |
end |
||
− | function h. |
+ | function h.costs(tpl_args, frame, prefix_in, level) |
+ | tpl_args.skill_costs = tpl_args.skill_costs or {} |
||
− | for _, stat_type in ipairs(map.stats) do |
||
+ | for i=1, #tpl_args.skill_costs do |
||
− | local type_prefix = string.format('%s%s%s', prefix_in, stat_type.prefix_in, 'stat') |
||
+ | local cost_prefix = string.format('%s%s%d_', prefix_in, i18n.parameters.skill.cost, i) -- level<level>_cost<i>_ |
||
− | tpl_args.skill_levels[level][stat_type.out] = {} |
||
− | + | local cost = { |
|
+ | amount = tpl_args[cost_prefix .. tables.skill_level_costs.fields.amount.name], --level<level>_cost<i>_amount |
||
− | local stat_id_key = string.format('%s%s_%s', type_prefix, i, 'id') |
||
+ | } |
||
− | local stat_val_key = string.format('%s%s_%s', type_prefix, i, 'value') |
||
− | + | 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 stat.id ~= nil and stat.value ~= nil then |
||
− | + | if not tpl_args.test then |
|
− | + | m_cargo.store(frame, properties) |
|
− | if not tpl_args.test then |
||
− | m_cargo.store(frame, { |
||
− | _table = map.skill_stats_per_level.table, |
||
− | [map.skill_stats_per_level.fields.level.field] = level, |
||
− | [map.skill_stats_per_level.fields.id.field] = stat.id, |
||
− | [map.skill_stats_per_level.fields.value.field] = stat.value, |
||
− | [map.skill_stats_per_level.fields.is_quality_stat.field] = stat_type.quality, |
||
− | }) |
||
− | end |
||
− | |||
− | -- Nuke variables since they're remapped to skill levels |
||
− | tpl_args[stat_id_key] = nil |
||
− | tpl_args[stat_val_key] = nil |
||
end |
end |
||
end |
end |
||
Line 139: | Line 99: | ||
end |
end |
||
− | function h. |
+ | function h.stats(tpl_args, frame, prefix_in, level) |
+ | for i=1, cfg.max_stats_per_level do |
||
− | tr |
||
+ | local stat_prefix = string.format('%s%s%d_', prefix_in, i18n.parameters.skill.stat, i) -- level<level>_stat<i>_ |
||
− | :tag('td') |
||
− | + | local stat = { |
|
+ | id = tpl_args[stat_prefix .. tables.skill_stats_per_level.fields.id.name], --level<level>_stat<i>_id |
||
− | :wikitext('N/A') |
||
+ | value = tpl_args[stat_prefix .. tables.skill_stats_per_level.fields.value.name], --level<level>_stat<i>_value |
||
− | :done() |
||
+ | } |
||
+ | 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 |
end |
||
− | function h.int_value_or_na(tpl_args, frame, |
+ | function h.int_value_or_na(tpl_args, frame, tblrow, value, tmap) |
value = tonumber(value) |
value = tonumber(value) |
||
if value == nil then |
if value == nil then |
||
− | + | tblrow:node(m_util.html.td.na()) |
|
else |
else |
||
− | value = mwlanguage:formatNum(value) |
+ | -- value = mwlanguage:formatNum(value) -- Removed for now. lang:formatNum() returns a string, which causes issues for formatting |
− | if |
+ | if tmap.fmt ~= nil then |
− | if type( |
+ | if type(tmap.fmt) == 'string' then |
− | value = string.format( |
+ | value = string.format(tmap.fmt, value) |
− | elseif type( |
+ | elseif type(tmap.fmt) == 'function' then |
− | value = string.format( |
+ | value = string.format(tmap.fmt(tpl_args, frame) or '%s', value) |
end |
end |
||
end |
end |
||
− | + | tblrow |
|
:tag('td') |
:tag('td') |
||
:wikitext(value) |
:wikitext(value) |
||
Line 182: | Line 155: | ||
function h.display.factory.value(args) |
function h.display.factory.value(args) |
||
return function (tpl_args, frame) |
return function (tpl_args, frame) |
||
− | args.fmt = args.fmt or |
+ | args.fmt = args.fmt or tables.static.fields[args.key].fmt |
local value = tpl_args[args.key] |
local value = tpl_args[args.key] |
||
if args.fmt and value then |
if args.fmt and value then |
||
Line 194: | Line 167: | ||
function h.display.factory.range_value(args) |
function h.display.factory.range_value(args) |
||
return function (tpl_args, frame) |
return function (tpl_args, frame) |
||
− | local value = { |
+ | local value = {} |
− | + | if args.set_name and args.set_id then |
|
+ | -- Guard against index errors |
||
− | max = tpl_args.skill_levels[0][args.key] or tpl_args.skill_levels[tpl_args.max_level][args.key], |
||
+ | 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 |
-- property not set for this skill |
||
if value.min == nil or value.max == nil then |
if value.min == nil or value.max == nil then |
||
return |
return |
||
end |
end |
||
+ | |||
− | |||
+ | local map = args.map or tables.progression |
||
return m_util.html.format_value(tpl_args, frame, value, { |
return m_util.html.format_value(tpl_args, frame, value, { |
||
− | fmt=args.fmt or map |
+ | fmt=args.fmt or map.fields[args.key].fmt, |
− | + | color=false, |
|
}) |
}) |
||
end |
end |
||
Line 226: | Line 213: | ||
-- ---------------------------------------------------------------------------- |
-- ---------------------------------------------------------------------------- |
||
+ | -- Cargo tables |
||
− | -- Data |
||
-- ---------------------------------------------------------------------------- |
-- ---------------------------------------------------------------------------- |
||
+ | tables.static = { |
||
− | |||
− | map.stats = { |
||
− | { |
||
− | prefix_in = '', |
||
− | out = 'stats', |
||
− | quality = false, |
||
− | }, |
||
− | { |
||
− | prefix_in = 'quality_', |
||
− | out = 'quality_stats', |
||
− | quality = true, |
||
− | }, |
||
− | } |
||
− | |||
− | map.static = { |
||
table = 'skill', |
table = 'skill', |
||
− | order = {'skill_id', 'cast_time', 'gem_description', 'active_skill_name', 'skill_icon', 'item_class_id_restriction', 'item_class_restriction', 'projectile_speed', 'stat_text', 'quality_stat_text', 'has_percentage_mana_cost', 'has_reservation_mana_cost', 'radius', 'radius_secondary', 'radius_tertiary', 'radius_description', 'radius_secondary_description', 'radius_tertiary_description', 'skill_screenshot'}, |
||
fields = { |
fields = { |
||
-- GrantedEffects.dat |
-- GrantedEffects.dat |
||
skill_id = { |
skill_id = { |
||
− | name = |
+ | name = i18n.parameters.skill.skill_id, |
field = 'skill_id', |
field = 'skill_id', |
||
type = 'String', |
type = 'String', |
||
Line 256: | Line 228: | ||
-- Active Skills.dat |
-- Active Skills.dat |
||
cast_time = { |
cast_time = { |
||
− | name = |
+ | name = i18n.parameters.skill.cast_time, |
field = 'cast_time', |
field = 'cast_time', |
||
type = 'Float', |
type = 'Float', |
||
func = h.cast.wrap(m_util.cast.number), |
func = h.cast.wrap(m_util.cast.number), |
||
− | fmt = '% |
+ | fmt = '%.2f ' .. m_game.units.seconds.short_lower, |
}, |
}, |
||
gem_description = { |
gem_description = { |
||
− | name = |
+ | name = i18n.parameters.skill.gem_description, |
field = 'description', |
field = 'description', |
||
type = 'Text', |
type = 'Text', |
||
Line 269: | Line 241: | ||
}, |
}, |
||
active_skill_name = { |
active_skill_name = { |
||
− | name = |
+ | name = i18n.parameters.skill.active_skill_name, |
field = 'active_skill_name', |
field = 'active_skill_name', |
||
type = 'String', |
type = 'String', |
||
Line 275: | Line 247: | ||
}, |
}, |
||
skill_icon = { |
skill_icon = { |
||
− | name = |
+ | name = i18n.parameters.skill.skill_icon, |
field = 'skill_icon', |
field = 'skill_icon', |
||
type = 'Page', |
type = 'Page', |
||
func = function(tpl_args, frame) |
func = function(tpl_args, frame) |
||
if tpl_args.active_skill_name then |
if tpl_args.active_skill_name then |
||
− | return string.format(i18n.skill_icon, tpl_args.active_skill_name) |
+ | return string.format(i18n.files.skill_icon, tpl_args.active_skill_name) |
end |
end |
||
end, |
end, |
||
}, |
}, |
||
item_class_id_restriction = { |
item_class_id_restriction = { |
||
− | name = |
+ | name = i18n.parameters.skill.item_class_id_restriction, |
field = 'item_class_id_restriction', |
field = 'item_class_id_restriction', |
||
type = 'List (,) of String', |
type = 'List (,) of String', |
||
Line 295: | Line 267: | ||
for _, v in ipairs(value) do |
for _, v in ipairs(value) do |
||
if m_game.constants.item.classes[v] == nil then |
if m_game.constants.item.classes[v] == nil then |
||
− | error(string.format( |
+ | error(string.format(i18n.errors.skill.invalid_item_class_id, v)) |
end |
end |
||
end |
end |
||
Line 302: | Line 274: | ||
}, |
}, |
||
item_class_restriction = { |
item_class_restriction = { |
||
− | name = |
+ | name = i18n.parameters.skill.item_class_restriction, |
field = 'item_class_restriction', |
field = 'item_class_restriction', |
||
type = 'List (,) of String', |
type = 'List (,) of String', |
||
Line 320: | Line 292: | ||
-- Projectiles.dat - manually mapped to the skills |
-- Projectiles.dat - manually mapped to the skills |
||
projectile_speed = { |
projectile_speed = { |
||
− | name = |
+ | name = i18n.parameters.skill.projectile_speed, |
field = 'projectile_speed', |
field = 'projectile_speed', |
||
type = 'Integer', |
type = 'Integer', |
||
Line 327: | Line 299: | ||
-- Misc data derieved from stats |
-- Misc data derieved from stats |
||
stat_text = { |
stat_text = { |
||
− | name = |
+ | name = i18n.parameters.skill.stat_text, |
field = 'stat_text', |
field = 'stat_text', |
||
type = 'Text', |
type = 'Text', |
||
Line 333: | Line 305: | ||
}, |
}, |
||
quality_stat_text = { |
quality_stat_text = { |
||
− | name = |
+ | name = i18n.parameters.skill.quality_stat_text, |
field = 'quality_stat_text', |
field = 'quality_stat_text', |
||
type = 'Text', |
type = 'Text', |
||
Line 339: | Line 311: | ||
}, |
}, |
||
-- Misc data currently not from game data |
-- Misc data currently not from game data |
||
− | has_percentage_mana_cost = { |
||
− | name = 'has_percentage_mana_cost', |
||
− | field = 'has_percentage_mana_cost', |
||
− | type = 'Boolean', |
||
− | func = h.cast.wrap(m_util.cast.boolean), |
||
− | default = false, |
||
− | }, |
||
− | has_reservation_mana_cost = { |
||
− | name = 'has_reservation_mana_cost', |
||
− | field = 'has_reservation_mana_cost', |
||
− | type = 'Boolean', |
||
− | func = h.cast.wrap(m_util.cast.boolean), |
||
− | default = false, |
||
− | }, |
||
radius = { |
radius = { |
||
− | name = |
+ | name = i18n.parameters.skill.radius, |
field = 'radius', |
field = 'radius', |
||
type = 'Integer', |
type = 'Integer', |
||
Line 360: | Line 318: | ||
}, |
}, |
||
radius_description = { |
radius_description = { |
||
− | name = |
+ | name = i18n.parameters.skill.radius_description, |
field = 'radius_description', |
field = 'radius_description', |
||
type = 'Text', |
type = 'Text', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.text), |
}, |
}, |
||
radius_secondary = { |
radius_secondary = { |
||
− | name = |
+ | name = i18n.parameters.skill.radius_secondary, |
field = 'radius_secondary', |
field = 'radius_secondary', |
||
type = 'Integer', |
type = 'Integer', |
||
Line 372: | Line 330: | ||
}, |
}, |
||
radius_secondary_description = { |
radius_secondary_description = { |
||
− | name = |
+ | name = i18n.parameters.skill.radius_secondary_description, |
field = 'radius_secondary_description', |
field = 'radius_secondary_description', |
||
type = 'Text', |
type = 'Text', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.text), |
}, |
}, |
||
− | -- not sure if any skill actually has 3 radius componets |
+ | radius_tertiary = { -- not sure if any skill actually has 3 radius componets |
+ | name = i18n.parameters.skill.radius_tertiary, |
||
− | radius_tertiary = { |
||
− | name = 'radius_tertiary', |
||
field = 'radius_tertiary', |
field = 'radius_tertiary', |
||
type = 'Integer', |
type = 'Integer', |
||
Line 385: | Line 342: | ||
}, |
}, |
||
radius_tertiary_description = { |
radius_tertiary_description = { |
||
− | name = |
+ | name = i18n.parameters.skill.radius_tertiary_description, |
field = 'radius_tertiary_description', |
field = 'radius_tertiary_description', |
||
type = 'Text', |
type = 'Text', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.text), |
− | }, |
||
− | -- Set manually |
||
− | max_level = { |
||
− | field = 'max_level', |
||
− | type = 'Integer', |
||
− | }, |
||
− | html = { |
||
− | field = 'html', |
||
− | type = 'Text', |
||
}, |
}, |
||
skill_screenshot = { |
skill_screenshot = { |
||
− | name = |
+ | name = i18n.parameters.skill.skill_screenshot, |
field = 'skill_screenshot', |
field = 'skill_screenshot', |
||
type = 'Page', |
type = 'Page', |
||
Line 408: | Line 356: | ||
ss = string.format('File:%s', tpl_args.skill_screenshot_file) |
ss = string.format('File:%s', tpl_args.skill_screenshot_file) |
||
elseif tpl_args.skill_screenshot ~= nil then |
elseif tpl_args.skill_screenshot ~= nil then |
||
− | ss = string.format(i18n.skill_screenshot, tpl_args.skill_screenshot) |
+ | ss = string.format(i18n.files.skill_screenshot, tpl_args.skill_screenshot) |
elseif tpl_args.active_skill_name then |
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 |
-- 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.skill_screenshot, tpl_args.active_skill_name) |
+ | ss = string.format(i18n.files.skill_screenshot, tpl_args.active_skill_name) |
− | page = mw.title.new(ss) |
+ | local page = mw.title.new(ss) |
if page == nil or not page.exists then |
if page == nil or not page.exists then |
||
ss = nil |
ss = nil |
||
Line 419: | Line 367: | ||
return ss |
return ss |
||
end, |
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', |
table = 'skill_levels', |
||
− | order = {'level_requirement', 'dexterity_requirement', 'intelligence_requirement', 'strength_requirement', 'mana_multiplier', 'critical_strike_chance', 'mana_cost', 'damage_effectiveness', 'stored_uses', 'cooldown', 'vaal_souls_requirement', 'vaal_stored_uses', 'vaal_soul_gain_prevention_time', 'damage_multiplier', 'experience', 'stat_text', 'quality_stat_text'}, |
||
fields = { |
fields = { |
||
level = { |
level = { |
||
Line 432: | Line 409: | ||
type = 'Integer', |
type = 'Integer', |
||
func = nil, |
func = nil, |
||
− | header = nil, |
||
}, |
}, |
||
level_requirement = { |
level_requirement = { |
||
− | name = |
+ | name = i18n.parameters.skill.level_requirement, |
field = 'level_requirement', |
field = 'level_requirement', |
||
type = 'Integer', |
type = 'Integer', |
||
func = h.cast.wrap(m_util.cast.number), |
func = h.cast.wrap(m_util.cast.number), |
||
− | header = m_util.html.abbr('[[Image:Level_up_icon_small.png|link=|Lvl.]]', 'Required Level', 'nounderline'), |
||
}, |
}, |
||
dexterity_requirement = { |
dexterity_requirement = { |
||
− | name = |
+ | name = i18n.parameters.skill.dexterity_requirement, |
field = 'dexterity_requirement', |
field = 'dexterity_requirement', |
||
type = 'Integer', |
type = 'Integer', |
||
func = h.cast.wrap(m_util.cast.number), |
func = h.cast.wrap(m_util.cast.number), |
||
− | header = m_util.html.abbr('[[Image:DexterityIcon_small.png|link=|dexterity]]', 'Required Dexterity', 'nounderline'), |
||
}, |
}, |
||
strength_requirement = { |
strength_requirement = { |
||
− | name = |
+ | name = i18n.parameters.skill.strength_requirement, |
field = 'strength_requirement', |
field = 'strength_requirement', |
||
type = 'Integer', |
type = 'Integer', |
||
func = h.cast.wrap(m_util.cast.number), |
func = h.cast.wrap(m_util.cast.number), |
||
− | header = m_util.html.abbr('[[Image:StrengthIcon_small.png|link=|strength]]', 'Required Strength', 'nounderline'), |
||
}, |
}, |
||
intelligence_requirement = { |
intelligence_requirement = { |
||
− | name = |
+ | name = i18n.parameters.skill.intelligence_requirement, |
field = 'intelligence_requirement', |
field = 'intelligence_requirement', |
||
type = 'Integer', |
type = 'Integer', |
||
func = h.cast.wrap(m_util.cast.number), |
func = h.cast.wrap(m_util.cast.number), |
||
− | header = m_util.html.abbr('[[Image:IntelligenceIcon_small.png|link=|intelligence]]', 'Required Intelligence', 'nounderline'), |
||
}, |
}, |
||
mana_multiplier = { |
mana_multiplier = { |
||
− | name = |
+ | name = i18n.parameters.skill.mana_multiplier, |
field = 'mana_multiplier', |
field = 'mana_multiplier', |
||
type = 'Float', |
type = 'Float', |
||
func = h.cast.wrap(m_util.cast.number), |
func = h.cast.wrap(m_util.cast.number), |
||
− | header = 'Mana<br>Multiplier', |
||
fmt = '%s%%', |
fmt = '%s%%', |
||
}, |
}, |
||
critical_strike_chance = { |
critical_strike_chance = { |
||
− | name = |
+ | name = i18n.parameters.skill.critical_strike_chance, |
field = 'critical_strike_chance', |
field = 'critical_strike_chance', |
||
type = 'Float', |
type = 'Float', |
||
func = h.cast.wrap(m_util.cast.number), |
func = h.cast.wrap(m_util.cast.number), |
||
− | header = 'Critical<br>Strike<br>Chance', |
||
fmt = '%s%%', |
fmt = '%s%%', |
||
− | }, |
||
− | mana_cost = { |
||
− | name = 'mana_cost', |
||
− | field = 'mana_cost', |
||
− | type = 'Integer', |
||
− | func = h.cast.wrap(m_util.cast.number), |
||
− | header = function (skill_data) |
||
− | if skill_data["skill.has_reservation_mana_cost"] then |
||
− | return 'Mana<br>Reserved' |
||
− | else |
||
− | return 'Mana<br>Cost' |
||
− | end |
||
− | end, |
||
− | fmt = function (tpl_args, frame) |
||
− | if tpl_args.has_percentage_mana_cost then |
||
− | str = '%s%%' |
||
− | else |
||
− | str = '%s' |
||
− | end |
||
− | |||
− | return str |
||
− | end, |
||
}, |
}, |
||
damage_effectiveness = { |
damage_effectiveness = { |
||
− | name = |
+ | name = i18n.parameters.skill.damage_effectiveness, |
field = 'damage_effectiveness', |
field = 'damage_effectiveness', |
||
type = 'Float', |
type = 'Float', |
||
func = h.cast.wrap(m_util.cast.number), |
func = h.cast.wrap(m_util.cast.number), |
||
− | header = 'Damage<br>Effectiveness', |
||
fmt = '%s%%', |
fmt = '%s%%', |
||
}, |
}, |
||
stored_uses = { |
stored_uses = { |
||
− | name = |
+ | name = i18n.parameters.skill.stored_uses, |
field = 'stored_uses', |
field = 'stored_uses', |
||
type = 'Integer', |
type = 'Integer', |
||
func = h.cast.wrap(m_util.cast.number), |
func = h.cast.wrap(m_util.cast.number), |
||
− | header = 'Stored<br>Uses', |
||
}, |
}, |
||
cooldown = { |
cooldown = { |
||
− | name = |
+ | name = i18n.parameters.skill.cooldown, |
field = 'cooldown', |
field = 'cooldown', |
||
type = 'Float', |
type = 'Float', |
||
func = h.cast.wrap(m_util.cast.number), |
func = h.cast.wrap(m_util.cast.number), |
||
− | + | fmt = '%.2f ' .. m_game.units.seconds.short_lower, |
|
− | fmt = '%ss', |
||
}, |
}, |
||
vaal_souls_requirement = { |
vaal_souls_requirement = { |
||
− | name = |
+ | name = i18n.parameters.skill.vaal_souls_requirement, |
field = 'vaal_souls_requirement', |
field = 'vaal_souls_requirement', |
||
type = 'Integer', |
type = 'Integer', |
||
func = h.cast.wrap(m_util.cast.number), |
func = h.cast.wrap(m_util.cast.number), |
||
− | header = 'Vaal<br>souls', |
||
}, |
}, |
||
vaal_stored_uses = { |
vaal_stored_uses = { |
||
− | name = |
+ | name = i18n.parameters.skill.vaal_stored_uses, |
field = 'vaal_stored_uses', |
field = 'vaal_stored_uses', |
||
type = 'Integer', |
type = 'Integer', |
||
func = h.cast.wrap(m_util.cast.number), |
func = h.cast.wrap(m_util.cast.number), |
||
− | header = |
+ | header = i18n.progression.vaal_stored_uses, |
}, |
}, |
||
vaal_soul_gain_prevention_time = { |
vaal_soul_gain_prevention_time = { |
||
− | name = |
+ | name = i18n.parameters.skill.vaal_soul_gain_prevention_time, |
field = 'vaal_soul_gain_prevention_time', |
field = 'vaal_soul_gain_prevention_time', |
||
type = 'Float', |
type = 'Float', |
||
func = h.cast.wrap(m_util.cast.number), |
func = h.cast.wrap(m_util.cast.number), |
||
− | + | fmt = '%i ' .. m_game.units.seconds.short_lower, |
|
− | fmt = '%ss', |
||
}, |
}, |
||
damage_multiplier = { |
damage_multiplier = { |
||
− | name = |
+ | name = i18n.parameters.skill.damage_multiplier, |
field = 'damage_multiplier', |
field = 'damage_multiplier', |
||
type = 'Float', |
type = 'Float', |
||
func = h.cast.wrap(m_util.cast.number), |
func = h.cast.wrap(m_util.cast.number), |
||
− | header = m_util.html.abbr('Damage<br>Multiplier', 'Deals x% of Base Damage'), |
||
fmt = '%s%%', |
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 |
-- from gem experience, optional |
||
experience = { |
experience = { |
||
− | name = |
+ | name = i18n.parameters.skill.experience, |
field = 'experience', |
field = 'experience', |
||
type = 'Integer', |
type = 'Integer', |
||
func = h.cast.wrap(m_util.cast.number), |
func = h.cast.wrap(m_util.cast.number), |
||
− | hide = true, |
||
}, |
}, |
||
stat_text = { |
stat_text = { |
||
− | name = |
+ | name = i18n.parameters.skill.stat_text, |
field = 'stat_text', |
field = 'stat_text', |
||
type = 'Text', |
type = 'Text', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.text), |
− | hide = true, |
||
}, |
}, |
||
− | + | -- 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, |
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 = { |
||
− | data.progression_display_order = {'level_requirement', 'dexterity_requirement', 'strength_requirement', 'intelligence_requirement', 'mana_multiplier', 'critical_strike_chance', 'mana_cost', 'damage_effectiveness', 'stored_uses', 'cooldown', 'vaal_souls_requirement', 'vaal_stored_uses', 'vaal_soul_gain_prevention_time', 'damage_multiplier'} |
||
+ | 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', |
table = 'skill_stats_per_level', |
||
fields = { |
fields = { |
||
level = { |
level = { |
||
+ | name = nil, |
||
field = 'level', |
field = 'level', |
||
type = 'Integer', |
type = 'Integer', |
||
+ | func = nil, |
||
}, |
}, |
||
id = { |
id = { |
||
+ | name = i18n.parameters.skill.stat_id, |
||
field = 'id', |
field = 'id', |
||
type = 'String', |
type = 'String', |
||
+ | func = h.cast.wrap(m_util.cast.text), |
||
}, |
}, |
||
value = { |
value = { |
||
+ | name = i18n.parameters.skill.stat_value, |
||
field = 'value', |
field = 'value', |
||
type = 'Integer', |
type = 'Integer', |
||
+ | func = h.cast.wrap(m_util.cast.number), |
||
}, |
}, |
||
+ | }, |
||
− | is_quality_stat = { |
||
+ | } |
||
− | field = 'is_quality_stat', |
||
+ | |||
− | type = 'Boolean', |
||
+ | 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%%', |
||
}, |
}, |
||
} |
} |
||
Line 623: | Line 805: | ||
{ |
{ |
||
header = i18n.infobox.cast_time, |
header = i18n.infobox.cast_time, |
||
− | func = |
+ | 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, |
||
}, |
}, |
||
{ |
{ |
||
Line 668: | Line 859: | ||
}, |
}, |
||
{ |
{ |
||
− | header = i18n.infobox. |
+ | header = i18n.infobox.cost, |
− | func = |
+ | func = function (tpl_args, frame) |
+ | if not tpl_args.skill_costs.has_spending_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 spending 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, |
||
}, |
}, |
||
{ |
{ |
||
Line 696: | Line 962: | ||
}, |
}, |
||
{ |
{ |
||
− | header = i18n.infobox. |
+ | header = i18n.infobox.duration, |
− | func = h.display.factory.range_value{key=' |
+ | func = h.display.factory.range_value{key='duration'}, |
}, |
}, |
||
{ |
{ |
||
Line 712: | Line 978: | ||
-- ---------------------------------------------------------------------------- |
-- ---------------------------------------------------------------------------- |
||
+ | -- Invokable functions |
||
− | -- Templates |
||
-- ---------------------------------------------------------------------------- |
-- ---------------------------------------------------------------------------- |
||
local p = {} |
local p = {} |
||
− | p.table_skills = |
+ | p.table_skills = m_cargo.declare_factory{data=tables.static} |
− | p.table_skill_levels = |
+ | p.table_skill_levels = m_cargo.declare_factory{data=tables.progression} |
− | p. |
+ | 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. |
||
− | -- Template:Skill |
||
+ | -- Stores skill data in cargo tables. |
||
+ | -- Attaches page to cargo tables. |
||
-- |
-- |
||
− | function p. |
+ | function p._skill(tpl_args, frame) |
− | --[[ |
||
− | Creates an infobox for skills. |
||
− | |||
− | 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<br>Base duration is 1.5 seconds<br>One impact every 0.1 seconds<br>0.15 seconds additional Base Duration per 100 Intelligence', quality_stat_text = nil, level1=true, level1_level_requirement=1} |
||
− | |||
− | ]] |
||
− | |||
− | if tpl_args == nil then |
||
− | tpl_args = getArgs(frame, { |
||
− | parentFirst = true |
||
− | }) |
||
− | end |
||
frame = m_util.misc.get_frame(frame) |
frame = m_util.misc.get_frame(frame) |
||
+ | tpl_args = tpl_args or {} |
||
− | |||
+ | tpl_args._flags = tpl_args._flags or {} |
||
− | -- |
||
− | -- Args |
||
− | -- |
||
− | local properties |
||
− | |||
tpl_args.skill_levels = { |
tpl_args.skill_levels = { |
||
[0] = {}, |
[0] = {}, |
||
} |
} |
||
− | -- |
+ | -- Quality |
+ | tpl_args.skill_quality = {} |
||
local i = 0 |
local i = 0 |
||
− | repeat |
+ | repeat |
i = i + 1 |
i = i + 1 |
||
− | local prefix = ' |
+ | local prefix = string.format('quality_type%s', i) |
− | local |
+ | local q = { |
+ | _table = tables.skill_quality.table, |
||
− | if level == true then |
||
− | + | set_id = i, |
|
− | tpl_args[prefix] |
+ | weight = tonumber(tpl_args[string.format('%s_weight', prefix)]), |
+ | stat_text = tpl_args[string.format('%s_stat_text', prefix)], |
||
− | tpl_args.skill_levels[i] = {} |
||
− | + | } |
|
− | + | if q.stat_text then |
|
− | + | tpl_args.skill_quality[#tpl_args.skill_quality+1] = q |
|
− | + | m_cargo.store(frame, q) |
|
− | end |
||
− | + | q.stats = {} |
|
− | + | q._table = nil |
|
− | + | local j = 0 |
|
− | + | repeat |
|
+ | j = j + 1 |
||
− | h.map_to_arg(tpl_args, frame, properties, prefix, map.progression, i) |
||
+ | local stat_prefix = string.format('%s_stat%s', prefix, j) |
||
− | if not tpl_args.test then |
||
− | + | local s = { |
|
+ | _table = tables.skill_quality_stats.table, |
||
− | end |
||
− | + | 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 |
end |
||
− | until |
+ | until q.stat_text == nil |
+ | if #tpl_args.skill_quality > 1 then |
||
− | -- If no experience is given, assume this is a non skill gem skill. |
||
+ | -- Gem has alternative qualtiy |
||
− | tpl_args.max_level = tpl_args.max_level or (i - 1) |
||
+ | 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_spending_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 |
-- handle static progression |
||
+ | local prefix = i18n.parameters.skill.static .. '_' |
||
− | properties = { |
||
+ | do |
||
− | _table = map.progression.table, |
||
− | + | 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 |
end |
||
-- Handle static arguments |
-- Handle static arguments |
||
− | properties = { |
+ | local properties = { |
− | _table = |
+ | _table = tables.static.table, |
+ | [tables.static.fields.max_level.field] = tpl_args.max_level |
||
− | |||
− | [map.static.fields.max_level.field] = tpl_args.max_level |
||
} |
} |
||
+ | h.map_to_arg(tpl_args, frame, properties, '', tables.static) |
||
− | |||
− | h. |
+ | h.costs(tpl_args, frame, prefix, 0) |
− | h.stats(tpl_args, frame, |
+ | h.stats(tpl_args, frame, prefix, 0) |
+ | |||
− | |||
+ | -- Build infobox |
||
− | |||
− | |||
− | -- |
||
− | -- Infobox progressing |
||
− | -- |
||
local infobox = mw.html.create('span') |
local infobox = mw.html.create('span') |
||
infobox:attr('class', 'skill-box') |
infobox:attr('class', 'skill-box') |
||
− | |||
− | -- tablular sections |
||
local tbl = infobox:tag('table') |
local tbl = infobox:tag('table') |
||
tbl:attr('class', 'wikitable skill-box-table') |
tbl:attr('class', 'wikitable skill-box-table') |
||
for _, infobox_data in ipairs(data.infobox_table) do |
for _, infobox_data in ipairs(data.infobox_table) do |
||
local display = infobox_data.func(tpl_args, frame) |
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 |
if display then |
||
local tr = tbl:tag('tr') |
local tr = tbl:tag('tr') |
||
if infobox_data.header then |
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 |
tr |
||
:tag('th') |
:tag('th') |
||
− | :wikitext( |
+ | :wikitext(header_text) |
:done() |
:done() |
||
end |
end |
||
Line 828: | Line 1,157: | ||
end |
end |
||
end |
end |
||
− | |||
infobox = tostring(infobox) |
infobox = tostring(infobox) |
||
− | |||
− | -- |
||
-- Store data |
-- Store data |
||
+ | properties[tables.static.fields.html.field] = infobox |
||
− | -- |
||
− | properties[map.static.fields.html.field] = infobox |
||
if not tpl_args.test then |
if not tpl_args.test then |
||
m_cargo.store(frame, properties) |
m_cargo.store(frame, properties) |
||
end |
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<br>Base duration is 1.5 seconds<br>One impact every 0.1 seconds<br>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') |
local container = mw.html.create('span') |
||
container |
container |
||
Line 855: | Line 1,232: | ||
out = {} |
out = {} |
||
if mw.ustring.find(tpl_args.skill_id, '_') then |
if mw.ustring.find(tpl_args.skill_id, '_') then |
||
− | out[#out+1] = frame:expandTemplate{ |
+ | out[#out+1] = frame:expandTemplate{ |
− | title = |
+ | title = i18n.templates.incorrect_title, |
− | args = {title=tpl_args.skill_id} |
+ | args = {title=tpl_args.skill_id} |
} .. '\n\n\n' |
} .. '\n\n\n' |
||
end |
end |
||
if tpl_args.active_skill_name then |
if tpl_args.active_skill_name then |
||
out[#out+1] = string.format( |
out[#out+1] = string.format( |
||
− | i18n.intro_named_id, |
+ | i18n.messages.intro_named_id, |
tpl_args.skill_id, |
tpl_args.skill_id, |
||
tpl_args.active_skill_name |
tpl_args.active_skill_name |
||
Line 868: | Line 1,245: | ||
else |
else |
||
out[#out+1] = string.format( |
out[#out+1] = string.format( |
||
− | i18n.intro_unnamed_id, |
+ | i18n.messages.intro_unnamed_id, |
tpl_args.skill_id |
tpl_args.skill_id |
||
) |
) |
||
+ | end |
||
+ | |||
+ | -- Categories |
||
+ | local cats = {i18n.categories.skill_data} |
||
+ | if tpl_args._flags.has_deprecated_skill_parameters then |
||
+ | cats[#cats+1] = i18n.categories.deprecated_parameters |
||
end |
end |
||
− | return tostring(container) .. m_util.misc.add_category( |
+ | return tostring(container) .. m_util.misc.add_category(cats) .. '\n' .. table.concat(out) |
end |
end |
||
Line 892: | Line 1,275: | ||
-- Parse column arguments: |
-- Parse column arguments: |
||
tpl_args.stat_format = {} |
tpl_args.stat_format = {} |
||
− | local |
+ | local param_keys = { |
+ | i18n.parameters.progression.header, |
||
− | for i=1, 9 do |
||
− | + | i18n.parameters.progression.abbr, |
|
+ | i18n.parameters.progression.pattern_extract, |
||
− | local format_keys = { |
||
+ | i18n.parameters.progression.pattern_value, |
||
− | 'header', |
||
+ | } |
||
− | 'abbr', |
||
+ | for i=1, math.huge do -- repeat until no more columns are found |
||
− | 'pattern_extract', |
||
+ | local prefix = string.format('%s%d_', i18n.parameters.progression.column, i) |
||
− | 'pattern_value' |
||
+ | if tpl_args[prefix .. param_keys[1]] == nil then |
||
− | } |
||
− | local statfmt = {counter = 0} |
||
− | for _,v in ipairs(format_keys) do |
||
− | statfmt[v] = tpl_args[prefix .. v] |
||
− | end |
||
− | |||
− | if m_util.table.has_all_value(statfmt, format_keys) then |
||
break |
break |
||
end |
end |
||
− | + | local statfmt = {counter = 0} |
|
+ | for _, key in ipairs(param_keys) do |
||
− | if m_util.table.has_one_value(statfmt, format_keys) then |
||
− | + | 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 |
end |
||
− | |||
statfmt.header = m_util.html.abbr(statfmt.abbr, statfmt.header) |
statfmt.header = m_util.html.abbr(statfmt.abbr, statfmt.header) |
||
statfmt.abbr = nil |
statfmt.abbr = nil |
||
Line 919: | Line 1,299: | ||
end |
end |
||
+ | -- Query skill data |
||
− | |||
local results = {} |
local results = {} |
||
− | local query = { |
||
− | groupBy = 'skill._pageID', |
||
− | } |
||
local skill_data |
local skill_data |
||
− | |||
local fields = { |
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) |
||
− | query.where = string.format( |
||
− | 'skill.skill_id="%s"', |
||
− | tpl_args.skill_id |
||
− | ) |
||
− | results = m_cargo.query({'skill'}, fields, query) |
||
if #results == 0 then |
if #results == 0 then |
||
− | error(i18n.errors.no_results_for_skill_id) |
+ | error(string.format(i18n.errors.progression.no_results_for_skill_id, tpl_args.skill_id)) |
end |
end |
||
− | + | else -- Query by page name |
|
+ | page = tpl_args.page or mw.title.getCurrentTitle().prefixedText |
||
− | else |
||
+ | query.where = string.format('_pageName="%s"', page) |
||
− | if tpl_args.page then |
||
+ | results = m_cargo.query({tables.static.table}, fields, query) |
||
− | page = tpl_args.page |
||
− | else |
||
− | page = mw.title.getCurrentTitle().prefixedText |
||
− | end |
||
− | query.where = string.format('skill._pageName="%s"', page) |
||
− | |||
− | results = m_cargo.query({'skill'}, fields, query) |
||
if #results == 0 then |
if #results == 0 then |
||
− | error(i18n.errors.no_results_for_skill_page) |
+ | error(string.format(i18n.errors.progression.no_results_for_skill_page, page)) |
end |
end |
||
− | |||
− | skill_data = results[1] |
||
end |
end |
||
+ | skill_data = results[1] |
||
− | |||
− | skill_data[ |
+ | 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.where = string.format( |
||
+ | |||
− | 'skill_levels._pageName="%s"', |
||
+ | -- Query progression data |
||
− | skill_data['skill._pageName'] |
||
− | ) |
||
fields = {} |
fields = {} |
||
− | for _, |
+ | for _, fmap in pairs(tables.progression.fields) do |
− | fields[#fields+1] = |
+ | fields[#fields+1] = fmap.field |
end |
end |
||
− | + | query = { |
|
− | + | where = string.format( |
|
− | + | '_pageName="%s" AND %s > 0', |
|
− | + | skill_data['_pageName'], |
|
+ | tables.progression.fields.level.field |
||
− | { |
||
− | + | ), |
|
+ | groupBy = string.format( |
||
− | 'skill_levels._pageName="%s" AND skill_levels.level > 0', |
||
− | + | '_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 |
||
), |
), |
||
− | groupBy='skill_levels._pageID, skill_levels.level', |
||
− | orderBy='skill_levels.level ASC', |
||
} |
} |
||
+ | results = m_cargo.query({tables.skill_level_costs.table}, fields, query) |
||
− | ) |
||
+ | skill_data.costs_by_level = results |
||
− | |||
+ | |||
− | if #results == 0 then |
||
+ | -- Interpolate cost data into level data |
||
− | error(i18n.errors.missing_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 |
end |
||
+ | -- Set up html table headers |
||
headers = {} |
headers = {} |
||
− | for |
+ | for _, row in ipairs(skill_data.levels) do |
for k, v in pairs(row) do |
for k, v in pairs(row) do |
||
headers[k] = true |
headers[k] = true |
||
end |
end |
||
end |
end |
||
− | |||
local tbl = mw.html.create('table') |
local tbl = mw.html.create('table') |
||
tbl |
tbl |
||
+ | :attr('class', 'wikitable responsive-table skill-progression-table') |
||
− | :attr( |
||
− | 'class', |
||
− | 'wikitable responsive-table skill-progression-table' |
||
− | ) |
||
− | |||
local head = tbl:tag('tr') |
local head = tbl:tag('tr') |
||
+ | for _, tmap in pairs(data.skill_progression_table) do |
||
− | head |
||
− | + | if headers[tmap.field] then |
|
+ | local text = type(tmap.header) == 'function' and tmap.header(tpl_args, frame) or tmap.header |
||
− | :wikitext('Level') |
||
− | :done() |
||
− | |||
− | for _, key in ipairs(data.progression_display_order) do |
||
− | local pdata = map.progression.fields[key] |
||
− | -- TODO should be nil? |
||
− | if pdata.hide == nil and headers['skill_levels.' .. pdata.field] then |
||
− | local text |
||
− | if type(pdata.header) == 'function' then |
||
− | text = pdata.header(skill_data) |
||
− | else |
||
− | text = pdata.header |
||
− | end |
||
head |
head |
||
:tag('th') |
:tag('th') |
||
Line 1,021: | Line 1,440: | ||
end |
end |
||
end |
end |
||
− | |||
for _, statfmt in ipairs(tpl_args.stat_format) do |
for _, statfmt in ipairs(tpl_args.stat_format) do |
||
head |
head |
||
Line 1,028: | Line 1,446: | ||
:done() |
:done() |
||
end |
end |
||
+ | if headers[tables.progression.fields.experience.field] then |
||
− | |||
− | if headers['skill_levels.experience'] then |
||
head |
head |
||
:tag('th') |
:tag('th') |
||
− | :wikitext( |
+ | :wikitext(i18n.progression.experience) |
− | i18n.progression.exp_short, |
||
− | i18n.progression.exp_long |
||
− | ) |
||
− | ) |
||
:done() |
:done() |
||
:tag('th') |
:tag('th') |
||
− | :wikitext( |
+ | :wikitext(i18n.progression.total_experience) |
− | i18n.progression.tot_exp_short, |
||
− | i18n.progression.tot_exp_long |
||
− | ) |
||
− | ) |
||
:done() |
:done() |
||
end |
end |
||
+ | |||
− | |||
+ | -- Table rows |
||
local tblrow |
local tblrow |
||
local lastexp = 0 |
local lastexp = 0 |
||
local experience |
local experience |
||
+ | for _, row in ipairs(skill_data.levels) do |
||
− | |||
− | for i, row in ipairs(results) do |
||
tblrow = tbl:tag('tr') |
tblrow = tbl:tag('tr') |
||
+ | for _, tmap in pairs(data.skill_progression_table) do |
||
− | tblrow |
||
− | + | if headers[tmap.field] then |
|
− | + | h.int_value_or_na(tpl_args, frame, tblrow, row[tmap.field], tmap) |
|
− | :done() |
||
− | |||
− | for _, key in ipairs(data.progression_display_order) do |
||
− | local pdata = map.progression.fields[key] |
||
− | if pdata.hide == nil and headers['skill_levels.' .. pdata.field] then |
||
− | h.int_value_or_na( |
||
− | tpl_args, |
||
− | frame, |
||
− | tblrow, |
||
− | row['skill_levels.' .. pdata.field], |
||
− | pdata |
||
− | ) |
||
end |
end |
||
end |
end |
||
-- stats |
-- stats |
||
− | + | local stats = {} |
|
+ | if row[tables.progression.fields.stat_text.field] then |
||
stats = m_util.string.split( |
stats = m_util.string.split( |
||
− | row[ |
+ | row[tables.progression.fields.stat_text.field], |
'<br>' |
'<br>' |
||
) |
) |
||
− | else |
||
− | stats = {} |
||
end |
end |
||
for _, statfmt in ipairs(tpl_args.stat_format) do |
for _, statfmt in ipairs(tpl_args.stat_format) do |
||
Line 1,091: | Line 1,487: | ||
end |
end |
||
if #match == 0 then |
if #match == 0 then |
||
− | + | tblrow:node(m_util.html.td.na()) |
|
else |
else |
||
-- used to find broken progression due to game updates |
-- used to find broken progression due to game updates |
||
Line 1,113: | Line 1,509: | ||
-- TODO: Quality stats, afaik no gems use this atm |
-- TODO: Quality stats, afaik no gems use this atm |
||
− | if headers[ |
+ | if headers[tables.progression.fields.experience.field] then |
− | experience = tonumber(row[ |
+ | experience = tonumber(row[tables.progression.fields.experience.field]) |
if experience ~= nil then |
if experience ~= nil then |
||
− | h.int_value_or_na( |
+ | h.int_value_or_na(tpl_args, frame, tblrow, experience - lastexp, {}) |
− | tpl_args, |
||
− | frame, |
||
− | tblrow, |
||
− | experience - lastexp, |
||
− | {} |
||
− | ) |
||
− | |||
lastexp = experience |
lastexp = experience |
||
else |
else |
||
− | + | tblrow:node(m_util.html.td.na()) |
|
end |
end |
||
h.int_value_or_na(tpl_args, frame, tblrow, experience, {}) |
h.int_value_or_na(tpl_args, frame, tblrow, experience, {}) |
||
Line 1,141: | Line 1,530: | ||
return tostring(tbl) .. m_util.misc.add_category(cats) |
return tostring(tbl) .. m_util.misc.add_category(cats) |
||
− | end |
||
− | |||
− | function p.map(arg) |
||
− | for key, data in pairs(map[arg].fields) do |
||
− | mw.logObject(key) |
||
− | end |
||
− | end |
||
− | |||
− | -- ---------------------------------------------------------------------------- |
||
− | -- Debug |
||
− | -- ---------------------------------------------------------------------------- |
||
− | |||
− | p._debug = {} |
||
− | function p._debug.order(frame) |
||
− | for _, mapping in ipairs({'static', 'progression'}) do |
||
− | for _, key in ipairs(map[mapping].order) do |
||
− | if map[mapping].fields[key] == nil then |
||
− | mw.logObject(string.format('Missing key in %s.fields: %s', mapping, key)) |
||
− | end |
||
− | end |
||
− | for key, _ in pairs(map[mapping].fields) do |
||
− | local missing = true |
||
− | for _, order_key in ipairs(map[mapping].order) do |
||
− | if order_key == key then |
||
− | missing = false |
||
− | break |
||
− | end |
||
− | end |
||
− | if missing then |
||
− | mw.logObject(string.format('Missing key in %s.order: %s', mapping, key)) |
||
− | end |
||
− | end |
||
− | end |
||
end |
end |
||
Latest revision as of 11:57, 13 July 2021
This module depends on the following other modules: |
Overview
Module for handling skills with semantic media wiki support
Skill templates
Module:Item2
All templates defined in Module:Skill:
{{Skill}}
{{Skill progression}}
Module:Skill link
All templates defined in Module:Skill link:
The above documentation is transcluded from Module:Skill/doc.
Editors can experiment in this module's sandbox and testcases pages.
Subpages of this module.
Editors can experiment in this module's sandbox and testcases pages.
Subpages of this module.
-------------------------------------------------------------------------------
--
-- 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._flags.has_deprecated_skill_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<level>_cost<i>_
local cost = {
amount = tpl_args[cost_prefix .. tables.skill_level_costs.fields.amount.name], --level<level>_cost<i>_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<level>_stat<i>_
local stat = {
id = tpl_args[stat_prefix .. tables.skill_stats_per_level.fields.id.name], --level<level>_stat<i>_id
value = tpl_args[stat_prefix .. tables.skill_stats_per_level.fields.value.name], --level<level>_stat<i>_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)
local 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|%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, '<br>')
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_spending_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 spending 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_spending_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<br>Base duration is 1.5 seconds<br>One impact every 0.1 seconds<br>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._flags.has_deprecated_skill_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],
'<br>'
)
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