Path of Exile Wiki

Wiki поддерживается сообществом, поэтому подумайте над тем, чтобы внести свой вклад.

ПОДРОБНЕЕ

Path of Exile Wiki
мНет описания правки
мНет описания правки
Строка 1: Строка 1:
  +
-- ----------------------------------------------------------------------------
local p = {}
 
  +
-- Includes
  +
-- ----------------------------------------------------------------------------
   
 
local getArgs = require('Module:Arguments').getArgs
 
local getArgs = require('Module:Arguments').getArgs
Строка 7: Строка 9:
 
local mwlanguage = mw.language.getContentLanguage()
 
local mwlanguage = mw.language.getContentLanguage()
   
  +
--- define here to avoid errors
--
 
  +
local data = {}
-- Data
 
  +
local map = {}
--
 
  +
  +
-- TODO:
  +
-- skill_id field link to data page
  +
-- aspects: % mana cost
  +
  +
-- ----------------------------------------------------------------------------
  +
-- i18n
  +
-- ----------------------------------------------------------------------------
   
 
local i18n = {
 
local i18n = {
 
skill_icon = 'File:%s skill icon.png',
 
skill_icon = 'File:%s skill icon.png',
  +
skill_screenshot = 'File:%s skill screenshot.jpg',
  +
  +
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 = m_util.html.abbr('Vaal Souls', 'Vaal Souls cost is based on the first part of the game. Multiply with 1.5 or 2 to get part 2 / end game values'),
  +
vaal_stored_uses = 'Vaal Stored Uses',
  +
damage_multiplier = 'Damage Multiplier',
  +
},
  +
  +
progression = {
  +
skill_id = 'Skill Id',
  +
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 = m_util.html.abbr('Vaal Souls', 'Vaal Souls cost is based on the first part of the game. Multiply with 1.5 or 2 to get part 2 / end game values'),
  +
vaal_stored_uses = 'Vaal Stored Uses',
  +
damage_multiplier = 'Damage Multiplier',
  +
},
 
 
 
args = {
 
args = {
Строка 35: Строка 90:
 
radius_tertiary = 'radius_tertiary',
 
radius_tertiary = 'radius_tertiary',
 
radius_tertiary_description = 'radius_tertiary_description',
 
radius_tertiary_description = 'radius_tertiary_description',
  +
skill_screenshot = 'skill_screenshot',
   
 
-- skill_levels
 
-- skill_levels
Строка 66: Строка 122:
 
}
 
}
   
  +
-- ----------------------------------------------------------------------------
--
 
  +
-- Helper functions
-- Data
 
  +
-- ----------------------------------------------------------------------------
--
 
  +
local h = {}
   
  +
function h.map_to_arg(tpl_args, frame, properties, prefix_in, map, level)
local data = {}
 
  +
if map.order then
data.cast = {}
 
  +
for _, key in ipairs(map.order) do
data.cast.wrap = function(f)
 
  +
row = map.fields[key]
  +
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
  +
tpl_args.skill_levels[level][key] = val
  +
-- Nuke variables since they're remapped to skill_levels
  +
tpl_args[prefix_in .. row.name] = nil
  +
else
  +
tpl_args[row.name] = val
  +
end
  +
properties[row.field] = val
  +
end
  +
else
  +
error(string.format('Programming error, missing field %s from order keys', key))
  +
end
  +
end
  +
end
  +
end
  +
  +
function h.stats(tpl_args, frame, prefix_in, level)
  +
for _, stat_type in ipairs(map.stats) do
  +
local type_prefix = string.format('%s%s%s', prefix_in, stat_type.prefix_in, i18n.args.infix_stat)
  +
tpl_args.skill_levels[level][stat_type.out] = {}
  +
for i=1, 8 do
  +
local stat_id_key = string.format('%s%s_%s', type_prefix, i, i18n.args.id)
  +
local stat_val_key = string.format('%s%s_%s', type_prefix, i, i18n.args.value)
  +
local stat = {
  +
id = tpl_args[stat_id_key],
  +
value = tonumber(tpl_args[stat_val_key]),
  +
}
  +
if stat.id ~= nil and stat.value ~= nil then
  +
tpl_args.skill_levels[level][stat_type.out][#tpl_args.skill_levels[level][stat_type.out]+1] = stat
  +
  +
m_util.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,
  +
})
  +
  +
-- 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
  +
  +
function h.na(tr)
  +
tr
  +
:tag('td')
  +
:attr('class', 'table-na')
  +
:wikitext('N/A')
  +
:done()
  +
end
  +
  +
function h.int_value_or_na(tpl_args, frame, tr, value, pdata)
  +
value = tonumber(value)
  +
if value == nil then
  +
h.na(tr)
  +
else
  +
value = mwlanguage:formatNum(value)
  +
if pdata.fmt ~= nil then
  +
if type(pdata.fmt) == 'string' then
  +
value = string.format(pdata.fmt, value)
  +
elseif type(pdata.fmt) == 'function' then
  +
value = string.format(pdata.fmt(tpl_args, frame), value)
  +
end
  +
end
  +
tr
  +
:tag('td')
  +
:wikitext(value)
  +
:done()
  +
end
  +
end
  +
  +
h.cast = {}
  +
function h.cast.wrap (f)
 
return function(tpl_args, frame, value)
 
return function(tpl_args, frame, value)
 
if value == nil then
 
if value == nil then
Строка 82: Строка 225:
 
end
 
end
   
local map = {}
+
h.display = {}
  +
h.display.factory = {}
  +
function h.display.factory.value(args)
  +
return function (tpl_args, frame)
  +
if args.fmt then
  +
return string.format(args.fmt, tpl_args[args.key])
  +
else
  +
return tpl_args[args.key]
  +
end
  +
end
  +
end
  +
  +
function h.display.factory.range_value(args)
  +
return function (tpl_args, frame)
  +
local value = {
  +
min = tpl_args.skill_levels[0][args.key] or tpl_args.skill_levels[1][args.key],
  +
max = tpl_args.skill_levels[0][args.key] or tpl_args.skill_levels[tpl_args.max_level][args.key],
  +
}
  +
-- property not set for this skill
  +
if value.min == nil or value.max == nil then
  +
return
  +
end
  +
  +
return m_util.html.format_value(tpl_args, frame, value, {
  +
fmt=args.fmt or map.progression.fields[args.key].fmt,
  +
no_color=true,
  +
})
  +
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
  +
  +
-- ----------------------------------------------------------------------------
  +
-- Data
  +
-- ----------------------------------------------------------------------------
  +
  +
 
map.stats = {
 
map.stats = {
 
{
 
{
Строка 98: Строка 290:
 
map.static = {
 
map.static = {
 
table = 'skill',
 
table = 'skill',
order = {'skill_id', 'cast_time', 'gem_description', 'active_skill_name', 'skill_icon', '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'},
+
order = {'skill_id', 'cast_time', 'gem_description', 'active_skill_name', 'skill_icon', '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
Строка 107: Строка 299:
 
func = nil,
 
func = nil,
 
},
 
},
--[[is_support_gem = {
 
name = i18n.args.is_support_gem,
 
field = 'is_support_gem',
 
type = 'Boolean',
 
func = data.cast.wrap(m_util.cast.boolean),
 
},
 
support_gem_letter = {
 
name = i18n.args.support_gem_letter,
 
field = 'support_gem_letter',
 
type = 'String (size=2)',
 
func = nil,
 
},-]]--
 
 
-- Active Skills.dat
 
-- Active Skills.dat
 
cast_time = {
 
cast_time = {
Строка 124: Строка 304:
 
field = 'cast_time',
 
field = 'cast_time',
 
type = 'Float',
 
type = 'Float',
func = data.cast.wrap(m_util.cast.number),
+
func = h.cast.wrap(m_util.cast.number),
 
},
 
},
 
gem_description = {
 
gem_description = {
Строка 171: Строка 351:
 
field = 'projectile_speed',
 
field = 'projectile_speed',
 
type = 'Integer',
 
type = 'Integer',
func = data.cast.wrap(m_util.cast.number),
+
func = h.cast.wrap(m_util.cast.number),
 
},
 
},
 
-- Misc data derieved from stats
 
-- Misc data derieved from stats
Строка 191: Строка 371:
 
field = 'has_percentage_mana_cost',
 
field = 'has_percentage_mana_cost',
 
type = 'Boolean',
 
type = 'Boolean',
func = data.cast.wrap(m_util.cast.boolean),
+
func = h.cast.wrap(m_util.cast.boolean),
 
default = false,
 
default = false,
 
},
 
},
Строка 198: Строка 378:
 
field = 'has_reservation_mana_cost',
 
field = 'has_reservation_mana_cost',
 
type = 'Boolean',
 
type = 'Boolean',
func = data.cast.wrap(m_util.cast.boolean),
+
func = h.cast.wrap(m_util.cast.boolean),
 
default = false,
 
default = false,
 
},
 
},
Строка 205: Строка 385:
 
field = 'radius',
 
field = 'radius',
 
type = 'Integer',
 
type = 'Integer',
func = data.cast.wrap(m_util.cast.number),
+
func = h.cast.wrap(m_util.cast.number),
 
},
 
},
 
radius_description = {
 
radius_description = {
Строка 217: Строка 397:
 
field = 'radius_secondary',
 
field = 'radius_secondary',
 
type = 'Integer',
 
type = 'Integer',
func = data.cast.wrap(m_util.cast.number),
+
func = h.cast.wrap(m_util.cast.number),
 
},
 
},
 
radius_secondary_description = {
 
radius_secondary_description = {
Строка 230: Строка 410:
 
field = 'radius_tertiary',
 
field = 'radius_tertiary',
 
type = 'Integer',
 
type = 'Integer',
func = data.cast.wrap(m_util.cast.number),
+
func = h.cast.wrap(m_util.cast.number),
 
},
 
},
 
radius_tertiary_description = {
 
radius_tertiary_description = {
Строка 242: Строка 422:
 
field = 'max_level',
 
field = 'max_level',
 
type = 'Integer',
 
type = 'Integer',
  +
},
  +
html = {
  +
field = 'html',
  +
type = 'Text',
  +
},
  +
skill_screenshot = {
  +
name = i18n.args.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.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.skill_screenshot, tpl_args.active_skill_name)
  +
page = mw.title.new(ss)
  +
if page == nil or not page.exists then
  +
ss = nil
  +
end
  +
end
  +
return ss
  +
end,
 
},
 
},
 
},
 
},
Строка 248: Строка 453:
 
map.progression = {
 
map.progression = {
 
table = 'skill_levels',
 
table = 'skill_levels',
order = {'level', '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', 'damage_multiplier', 'experience', 'stat_text', 'quality_stat_text'},
+
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', 'damage_multiplier', 'experience', 'stat_text', 'quality_stat_text'},
 
fields = {
 
fields = {
 
level = {
 
level = {
Строка 261: Строка 466:
 
field = 'level_requirement',
 
field = 'level_requirement',
 
type = 'Integer',
 
type = 'Integer',
func = data.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=|Уровень.]]', 'Required Level', 'nounderline'),
+
header = m_util.html.abbr('[[Image:Level_up_icon_small.png|link=|Lvl.]]', 'Required Level', 'nounderline'),
 
},
 
},
 
dexterity_requirement = {
 
dexterity_requirement = {
Строка 268: Строка 473:
 
field = 'dexterity_requirement',
 
field = 'dexterity_requirement',
 
type = 'Integer',
 
type = 'Integer',
func = data.cast.wrap(m_util.cast.number),
+
func = h.cast.wrap(m_util.cast.number),
header = m_util.html.abbr('[[Image:DexterityIcon_small.png|link=|Ловкость]]', 'Required Dexterity', 'nounderline'),
+
header = m_util.html.abbr('[[Image:DexterityIcon_small.png|link=|dexterity]]', 'Required Dexterity', 'nounderline'),
 
},
 
},
 
strength_requirement = {
 
strength_requirement = {
Строка 275: Строка 480:
 
field = 'strength_requirement',
 
field = 'strength_requirement',
 
type = 'Integer',
 
type = 'Integer',
func = data.cast.wrap(m_util.cast.number),
+
func = h.cast.wrap(m_util.cast.number),
header = m_util.html.abbr('[[Image:StrengthIcon_small.png|link=|Сила]]', 'Required Strength', 'nounderline'),
+
header = m_util.html.abbr('[[Image:StrengthIcon_small.png|link=|strength]]', 'Required Strength', 'nounderline'),
 
},
 
},
 
intelligence_requirement = {
 
intelligence_requirement = {
Строка 282: Строка 487:
 
field = 'intelligence_requirement',
 
field = 'intelligence_requirement',
 
type = 'Integer',
 
type = 'Integer',
func = data.cast.wrap(m_util.cast.number),
+
func = h.cast.wrap(m_util.cast.number),
header = m_util.html.abbr('[[Image:IntelligenceIcon_small.png|link=|Интелект]]', 'Required Intelligence', 'nounderline'),
+
header = m_util.html.abbr('[[Image:IntelligenceIcon_small.png|link=|intelligence]]', 'Required Intelligence', 'nounderline'),
 
},
 
},
 
mana_multiplier = {
 
mana_multiplier = {
Строка 289: Строка 494:
 
field = 'mana_multiplier',
 
field = 'mana_multiplier',
 
type = 'Float',
 
type = 'Float',
func = data.cast.wrap(m_util.cast.number),
+
func = h.cast.wrap(m_util.cast.number),
 
header = 'Mana<br>Multiplier',
 
header = 'Mana<br>Multiplier',
 
fmt = '%s%%',
 
fmt = '%s%%',
Строка 297: Строка 502:
 
field = 'critical_strike_chance',
 
field = 'critical_strike_chance',
 
type = 'Float',
 
type = 'Float',
func = data.cast.wrap(m_util.cast.number),
+
func = h.cast.wrap(m_util.cast.number),
 
header = 'Critical<br>Strike<br>Chance',
 
header = 'Critical<br>Strike<br>Chance',
 
fmt = '%s%%',
 
fmt = '%s%%',
Строка 305: Строка 510:
 
field = 'mana_cost',
 
field = 'mana_cost',
 
type = 'Integer',
 
type = 'Integer',
func = data.cast.wrap(m_util.cast.number),
+
func = h.cast.wrap(m_util.cast.number),
 
header = function (skill_data)
 
header = function (skill_data)
 
if skill_data["skill.has_reservation_mana_cost"] then
 
if skill_data["skill.has_reservation_mana_cost"] then
Строка 313: Строка 518:
 
end
 
end
 
end,
 
end,
fmt = function (tpl_args, frame, value)
+
fmt = function (tpl_args, frame)
local str
 
 
if tpl_args.has_percentage_mana_cost then
 
if tpl_args.has_percentage_mana_cost then
 
str = '%s%%'
 
str = '%s%%'
Строка 321: Строка 525:
 
end
 
end
 
 
return string.format(str, value)
+
return str
 
end,
 
end,
 
},
 
},
Строка 328: Строка 532:
 
field = 'damage_effectiveness',
 
field = 'damage_effectiveness',
 
type = 'Float',
 
type = 'Float',
func = data.cast.wrap(m_util.cast.number),
+
func = h.cast.wrap(m_util.cast.number),
 
header = 'Damage<br>Effectiveness',
 
header = 'Damage<br>Effectiveness',
 
fmt = '%s%%',
 
fmt = '%s%%',
Строка 336: Строка 540:
 
field = 'stored_uses',
 
field = 'stored_uses',
 
type = 'Integer',
 
type = 'Integer',
func = data.cast.wrap(m_util.cast.number),
+
func = h.cast.wrap(m_util.cast.number),
 
header = 'Stored<br>Uses',
 
header = 'Stored<br>Uses',
 
},
 
},
Строка 343: Строка 547:
 
field = 'cooldown',
 
field = 'cooldown',
 
type = 'Float',
 
type = 'Float',
func = data.cast.wrap(m_util.cast.number),
+
func = h.cast.wrap(m_util.cast.number),
 
header = 'Cooldown',
 
header = 'Cooldown',
 
fmt = '%ss',
 
fmt = '%ss',
Строка 351: Строка 555:
 
field = 'vaal_souls_requirement',
 
field = 'vaal_souls_requirement',
 
type = 'Integer',
 
type = 'Integer',
func = data.cast.wrap(m_util.cast.number),
+
func = h.cast.wrap(m_util.cast.number),
 
header = 'Vaal<br>souls',
 
header = 'Vaal<br>souls',
 
},
 
},
Строка 358: Строка 562:
 
field = 'vaal_stored_uses',
 
field = 'vaal_stored_uses',
 
type = 'Integer',
 
type = 'Integer',
func = data.cast.wrap(m_util.cast.number),
+
func = h.cast.wrap(m_util.cast.number),
 
header = 'Stored<br>Uses',
 
header = 'Stored<br>Uses',
 
},
 
},
Строка 365: Строка 569:
 
field = 'damage_multiplier',
 
field = 'damage_multiplier',
 
type = 'Float',
 
type = 'Float',
func = data.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'),
 
header = m_util.html.abbr('Damage<br>Multiplier', 'Deals x% of Base Damage'),
 
fmt = '%s%%',
 
fmt = '%s%%',
Строка 374: Строка 578:
 
field = 'experience',
 
field = 'experience',
 
type = 'Integer',
 
type = 'Integer',
func = data.cast.wrap(m_util.cast.number),
+
func = h.cast.wrap(m_util.cast.number),
 
hide = true,
 
hide = true,
 
},
 
},
Строка 418: Строка 622:
 
}
 
}
   
  +
data.infobox_table = {
--
 
  +
{
-- Helper functions
 
  +
header = i18n.infobox.active_skill_name,
--
 
  +
func = h.display.factory.value{key='active_skill_name'},
local h = {}
 
  +
},
 
  +
{
function h.map_to_arg(tpl_args, frame, properties, prefix_in, map, level)
 
  +
header = i18n.infobox.skill_id,
if map.order then
 
for _, key in ipairs(map.order) do
+
func = function (tpl_args, frame)
  +
return string.format('[[%s|%s]]', mw.title.getCurrentTitle().fullText, tpl_args.skill_id)
row = map.fields[key]
 
if row.name then
+
end
  +
},
local val = tpl_args[prefix_in .. row.name]
 
  +
{
if row.func ~= nil then
 
val = row.func(tpl_args, frame, val)
+
header = i18n.infobox.skill_icon,
end
+
func = function (tpl_args, frame)
if val == nil and row.default ~= nil then
+
if tpl_args.skill_icon then
val = row.default
+
return string.format('[[%s]]', tpl_args.skill_icon)
end
 
if val ~= nil then
 
if level ~= nil then
 
tpl_args.skill_levels[level][key] = val
 
-- Nuke variables since they're remapped to skill_levels
 
tpl_args[prefix_in .. row.name] = nil
 
else
 
tpl_args[row.name] = val
 
end
 
properties[row.field] = val
 
end
 
 
end
 
end
end
+
end,
end
+
},
  +
{
end
 
  +
header = i18n.infobox.cast_time,
 
  +
func = h.display.factory.value{key='cast_time'},
function h.stats(tpl_args, frame, prefix_in, level)
 
  +
},
for _, stat_type in ipairs(map.stats) do
 
  +
{
local type_prefix = string.format('%s%s%s', prefix_in, stat_type.prefix_in, i18n.args.infix_stat)
 
  +
header = i18n.infobox.item_class_restrictions,
tpl_args.skill_levels[level][stat_type.out] = {}
 
for i=1, 8 do
+
func = function (tpl_args, frame)
  +
if tpl_args.item_class_restrictions == nil then
local stat_id_key = string.format('%s%s_%s', type_prefix, i, i18n.args.id)
 
  +
return
local stat_val_key = string.format('%s%s_%s', type_prefix, i, i18n.args.value)
 
local stat = {
 
id = tpl_args[stat_id_key],
 
value = tonumber(tpl_args[stat_val_key]),
 
}
 
if stat.id ~= nil and stat.value ~= nil then
 
tpl_args.skill_levels[level][stat_type.out][#tpl_args.skill_levels[level][stat_type.out]+1] = stat
 
 
m_util.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,
 
})
 
 
-- Nuke variables since they're remapped to skill levels
 
tpl_args[stat_id_key] = nil
 
tpl_args[stat_val_key] = nil
 
 
end
 
end
  +
local ul = mw.html.create('ul')
end
 
  +
for _, class in ipairs(i18n.infobox.item_class_restrictions) do
end
 
  +
ul:tag('li')
end
 
  +
:wikitext(string.format('[[%s]]', class))
 
function h.na(tr)
 
tr
 
:tag('td')
 
:attr('class', 'table-na')
 
:wikitext('N/A')
 
:done()
 
end
 
 
function h.int_value_or_na(tpl_args, frame, tr, value, pdata)
 
value = tonumber(value)
 
if value == nil then
 
h.na(tr)
 
else
 
value = mwlanguage:formatNum(value)
 
if pdata.fmt ~= nil then
 
if type(pdata.fmt) == 'string' then
 
value = string.format(pdata.fmt, value)
 
elseif type(pdata.fmt) == 'function' then
 
value = pdata.fmt(tpl_args, frame, value)
 
 
end
 
end
end
+
return tostring(ul)
tr
+
end,
  +
},
:tag('td')
 
  +
{
:wikitext(value)
 
:done()
+
header = i18n.infobox.projectile_speed,
  +
func = h.display.factory.value{key='projectile_speed'},
end
 
  +
},
end
 
  +
{
  +
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.mana_cost,
  +
func = h.display.factory.range_value{key='mana_cost'},
  +
},
  +
{
  +
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.damage_multiplier,
  +
func = h.display.factory.range_value{key='damage_multiplier'},
  +
},
  +
{
  +
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',
  +
},
  +
}
  +
  +
-- ----------------------------------------------------------------------------
  +
-- Templates
  +
-- ----------------------------------------------------------------------------
  +
local p = {}
   
--
 
-- For Шаблон:Skill
 
--
 
 
p.table_skills = m_util.cargo.declare_factory{data=map.static}
 
p.table_skills = m_util.cargo.declare_factory{data=map.static}
 
p.table_skill_levels = m_util.cargo.declare_factory{data=map.progression}
 
p.table_skill_levels = m_util.cargo.declare_factory{data=map.progression}
 
p.table_skill_stats_per_level = m_util.cargo.declare_factory{data=map.skill_stats_per_level}
 
p.table_skill_stats_per_level = m_util.cargo.declare_factory{data=map.skill_stats_per_level}
   
  +
--
  +
-- Template:Skill
  +
--
 
function p.skill(frame, tpl_args)
 
function p.skill(frame, tpl_args)
 
if tpl_args == nil then
 
if tpl_args == nil then
Строка 533: Строка 758:
 
 
 
-- Handle level progression
 
-- Handle level progression
for i=1, 30 do
+
local i = 0
  +
repeat
  +
i = i + 1
 
local prefix = i18n.args.prefix_level .. i
 
local prefix = i18n.args.prefix_level .. i
if m_util.cast.boolean(tpl_args[prefix]) == true then
+
local level = m_util.cast.boolean(tpl_args[prefix])
  +
if level == true then
 
-- Don't need this anymore
 
-- Don't need this anymore
 
tpl_args[prefix] = nil
 
tpl_args[prefix] = nil
Строка 554: Строка 782:
 
h.stats(tpl_args, frame, prefix, i)
 
h.stats(tpl_args, frame, prefix, i)
 
end
 
end
  +
until level ~= true
end
 
  +
-- If no experience is given, assume this is a non skill gem skill.
  +
tpl_args.max_level = tpl_args.max_level or (i - 1)
 
-- handle static progression
 
-- handle static progression
 
properties = {
 
properties = {
Строка 562: Строка 792:
 
h.map_to_arg(tpl_args, frame, properties, i18n.args.prefix_static, map.progression, 0)
 
h.map_to_arg(tpl_args, frame, properties, i18n.args.prefix_static, map.progression, 0)
 
m_util.cargo.store(frame, properties)
 
m_util.cargo.store(frame, properties)
mw.logObject(properties)
 
 
 
 
-- Handle static arguments
 
-- Handle static arguments
 
properties = {
 
properties = {
 
_table = map.static.table,
 
_table = map.static.table,
  +
 
[map.static.fields.max_level.field] = tpl_args.max_level
 
[map.static.fields.max_level.field] = tpl_args.max_level
 
}
 
}
Строка 573: Строка 803:
 
h.stats(tpl_args, frame, i18n.args.prefix_static, 0)
 
h.stats(tpl_args, frame, i18n.args.prefix_static, 0)
 
 
  +
  +
  +
--
  +
-- Infobox progressing
  +
--
  +
local infobox = mw.html.create('span')
  +
infobox:attr('class', 'skill-box')
  +
  +
-- tablular sections
  +
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 then
  +
local tr = tbl:tag('tr')
  +
if infobox_data.header then
  +
tr
  +
:tag('th')
  +
:wikitext(infobox_data.header)
  +
: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[map.static.fields.html.field] = infobox
 
m_util.cargo.store(frame, properties)
 
m_util.cargo.store(frame, properties)
  +
  +
--
  +
--
  +
--
  +
local container = mw.html.create('span')
  +
container:attr('class', 'skill-box-page-container')
  +
container:wikitext(infobox)
  +
if tpl_args.skill_screenshot then
  +
container:wikitext(string.format('[[%s]]', tpl_args.skill_screenshot))
  +
end
  +
  +
return tostring(container) .. m_util.misc.add_category({'Skill data'})
 
end
 
end
   
Строка 622: Строка 900:
 
query.where = string.format('skill.skill_id="%s"', tpl_args.skill_id)
 
query.where = string.format('skill.skill_id="%s"', tpl_args.skill_id)
 
result = m_util.cargo.query({'skill'}, fields, query)
 
result = m_util.cargo.query({'skill'}, fields, query)
if #result == 0 or #result[1] == 0 then
+
if #result == 0 then
 
error('Couldn\'t find a page for the specified skill id')
 
error('Couldn\'t find a page for the specified skill id')
 
end
 
end
Строка 642: Строка 920:
 
end
 
end
 
 
skill_data["skill.has_reservation_mana_cost"] = data.cast.wrap(m_util.cast.boolean)(skill_data["skill.has_reservation_mana_cost"])
+
skill_data["skill.has_reservation_mana_cost"] = h.cast.wrap(m_util.cast.boolean)(skill_data["skill.has_reservation_mana_cost"])
 
 
 
query.where=string.format('skill_levels._pageName="%s"', skill_data['skill._pageName'])
 
query.where=string.format('skill_levels._pageName="%s"', skill_data['skill._pageName'])
Строка 661: Строка 939:
 
 
 
if #result == 0 then
 
if #result == 0 then
error('Данные о прогрессе уровня камней не найдены')
+
error('No gem level progression data found')
 
end
 
end
 
 
Строка 677: Строка 955:
 
head
 
head
 
:tag('th')
 
:tag('th')
:wikitext('Уровень')
+
:wikitext('Level')
 
:done()
 
:done()
 
 
Строка 707: Строка 985:
 
head
 
head
 
:tag('th')
 
:tag('th')
:wikitext(m_util.html.abbr('Опыт.', 'Опыт необходимый для поднятия уровня'))
+
:wikitext(m_util.html.abbr('Exp.', 'Experience Needed to Level Up'))
 
:done()
 
:done()
 
:tag('th')
 
:tag('th')
:wikitext(m_util.html.abbr('Общий <br> опыт', 'Всего опыта камня на данном уровне'))
+
:wikitext(m_util.html.abbr('Total Exp.', 'Total experience needed'))
 
:done()
 
:done()
 
end
 
end
Строка 778: Строка 1056:
 
for _, statfmt in ipairs(tpl_args.stat_format) do
 
for _, statfmt in ipairs(tpl_args.stat_format) do
 
if statfmt.counter == 0 then
 
if statfmt.counter == 0 then
empty = '[[Категория:Pages with broken skill progression tables]]'
+
empty = '[[Category:Pages with broken skill progression tables]]'
 
break
 
break
 
end
 
end
Строка 791: Строка 1069:
 
end
 
end
 
end
 
end
  +
  +
-- ----------------------------------------------------------------------------
  +
-- Debug
  +
-- ----------------------------------------------------------------------------
   
 
p._debug = {}
 
p._debug = {}

Версия от 14:56, 25 мая 2018

Template info icon Документация модуля[просмотр] [править] [история] [очистить]

Описание

Модуль для обработки умений с поддержкой Semantic MediaWiki.

Шаблоны Skill

Модуль:Item2

Все шаблоны, определенные в Модуль:Skill:

Модуль:Skill link

Все шаблоны, определенные в Модуль:Skill link:

-- ----------------------------------------------------------------------------
-- Includes
-- ----------------------------------------------------------------------------

local getArgs = require('Module:Arguments').getArgs
local m_util = require('Module:Util')
local m_game = require('Module:Game')

local mwlanguage = mw.language.getContentLanguage()

--- define here to avoid errors
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',
    
    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 = m_util.html.abbr('Vaal Souls', 'Vaal Souls cost is based on the first part of the game. Multiply with 1.5 or 2 to get part 2 / end game values'),
        vaal_stored_uses = 'Vaal Stored Uses',
        damage_multiplier = 'Damage Multiplier',
    },
    
    progression = {
        skill_id = 'Skill Id',
        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 = m_util.html.abbr('Vaal Souls', 'Vaal Souls cost is based on the first part of the game. Multiply with 1.5 or 2 to get part 2 / end game values'),
        vaal_stored_uses = 'Vaal Stored Uses',
        damage_multiplier = 'Damage Multiplier',
    },
    
    args = {
        -- skills
        skill_id = 'skill_id',
        is_support_gem = 'is_support_gem',
        support_gem_letter = 'support_gem_letter',
        cast_time = 'cast_time',
        gem_description = 'gem_description',
        active_skill_name = 'active_skill_name',
        skill_icon = 'skill_icon',
        item_class_restriction = 'item_class_restriction',
        projectile_speed = 'projectile_speed',
        stat_text = 'stat_text',
        quality_stat_text = 'quality_stat_text',
        has_percentage_mana_cost = 'has_percentage_mana_cost',
        has_reservation_mana_cost = 'has_reservation_mana_cost',
        radius = 'radius',
        radius_description = 'radius_description',
        radius_secondary = 'radius_secondary',
        radius_secondary_description = 'radius_secondary_description',
        radius_tertiary = 'radius_tertiary',
        radius_tertiary_description = 'radius_tertiary_description',
        skill_screenshot = 'skill_screenshot',

        -- skill_levels
        level_requirement = 'level_requirement',
        dexterity_requirement = 'dexterity_requirement',
        intelligence_requirement = 'intelligence_requirement',
        strength_requirement = 'strength_requirement',
        mana_multiplier = 'mana_multiplier',
        critical_strike_chance = 'critical_strike_chance',
        mana_cost = 'mana_cost',
        damage_effectiveness = 'damage_effectiveness',
        stored_uses = 'stored_uses',
        cooldown = 'cooldown',
        vaal_souls_requirement = 'vaal_souls_requirement',
        vaal_stored_uses = 'vaal_stored_uses',
        damage_multiplier = 'damage_multiplier',
        experience = 'experience',
        stat_text = 'stat_text',
        quality_stat_text = 'quality_stat_text',
        
        -- skill_stats_per_level
        id = 'id',
        value = 'value',
        
        -- prefixes
        prefix_level = 'level',
        prefix_static = 'static_',
        prefix_quality_stat = 'quality_',
        infix_stat = 'stat'
    },
}

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

function h.map_to_arg(tpl_args, frame, properties, prefix_in, map, level)
    if map.order then
        for _, key in ipairs(map.order) do
            row = map.fields[key]
            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
                        tpl_args.skill_levels[level][key] = val
                        -- Nuke variables since they're remapped to skill_levels
                        tpl_args[prefix_in .. row.name] = nil
                    else
                        tpl_args[row.name] = val
                    end
                    properties[row.field] = val
                end
            else
                error(string.format('Programming error, missing field %s from order keys', key))
            end
        end
    end
end

function h.stats(tpl_args, frame, prefix_in, level)
    for _, stat_type in ipairs(map.stats) do
        local type_prefix = string.format('%s%s%s', prefix_in, stat_type.prefix_in, i18n.args.infix_stat)
        tpl_args.skill_levels[level][stat_type.out] = {}
        for i=1, 8 do
            local stat_id_key = string.format('%s%s_%s', type_prefix, i, i18n.args.id)
            local stat_val_key = string.format('%s%s_%s', type_prefix, i, i18n.args.value)
            local stat = {
                id = tpl_args[stat_id_key],
                value = tonumber(tpl_args[stat_val_key]),
            }
            if stat.id ~= nil and stat.value ~= nil then
                tpl_args.skill_levels[level][stat_type.out][#tpl_args.skill_levels[level][stat_type.out]+1] = stat
                
                m_util.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,
                })
                
                -- 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

function h.na(tr)
    tr
        :tag('td')
            :attr('class', 'table-na')
            :wikitext('N/A')
            :done()
end

function h.int_value_or_na(tpl_args, frame, tr, value, pdata)
    value = tonumber(value)
    if value == nil then
        h.na(tr)
    else
        value = mwlanguage:formatNum(value)
        if pdata.fmt ~= nil then
            if type(pdata.fmt) == 'string' then
                value = string.format(pdata.fmt, value)
            elseif type(pdata.fmt) == 'function' then
                value = string.format(pdata.fmt(tpl_args, frame), value)
            end
        end
        tr
            :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)
        if args.fmt then
            return string.format(args.fmt, tpl_args[args.key])
        else
            return tpl_args[args.key]
        end
    end
end

function h.display.factory.range_value(args)
    return function (tpl_args, frame)
        local value = {
            min = tpl_args.skill_levels[0][args.key] or tpl_args.skill_levels[1][args.key],
            max = tpl_args.skill_levels[0][args.key] or tpl_args.skill_levels[tpl_args.max_level][args.key],
        }
        -- property not set for this skill
        if value.min == nil or value.max == nil then
            return
        end
        
        return m_util.html.format_value(tpl_args, frame, value, {
            fmt=args.fmt or map.progression.fields[args.key].fmt,
            no_color=true,
        })
    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

-- ----------------------------------------------------------------------------
-- Data
-- ----------------------------------------------------------------------------


map.stats = {
    {
        prefix_in = '',
        out = 'stats',
        quality = false,
    },
    {
        prefix_in = i18n.args.prefix_quality_stat,
        out = 'quality_stats',
        quality = true,
    },
}

map.static = {
    table = 'skill',
    order = {'skill_id', 'cast_time', 'gem_description', 'active_skill_name', 'skill_icon', '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 = {
        -- GrantedEffects.dat
        skill_id = {
            name = i18n.args.skill_id,
            field = 'skill_id',
            type = 'String',
            func = nil,
        },
        -- Active Skills.dat
        cast_time = {
            name = i18n.args.cast_time,
            field = 'cast_time',
            type = 'Float',
            func = h.cast.wrap(m_util.cast.number),
        },
        gem_description = {
            name = i18n.args.gem_description,
            field = 'description',
            type = 'Text',
            func = nil,
        },
        active_skill_name = {
            name = i18n.args.active_skill_name,
            field = 'active_skill_name',
            type = 'String',
            func = nil,
        },
        skill_icon = {
            name = i18n.args.skill_icon,
            field = 'skill_icon',
            type = 'Page',
            func = function(tpl_args, frame)
                if tpl_args.active_skill_name then
                    return string.format(i18n.skill_icon, tpl_args.active_skill_name)
                end
            end,
        },
        item_class_restriction = {
            name = i18n.args.item_class_restriction,
            field = 'item_class_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
                    local result = m_util.table.find_in_nested_array{value=v,key='full', tbl=m_game.constants.item.class}
                    if result == nil then
                        error(string.format('Invalid item class: %s', v))
                    end
                end
                return value
            end,
        },
        -- Projectiles.dat - manually mapped to the skills
        projectile_speed = {
            name = i18n.args.projectile_speed,
            field = 'projectile_speed',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
        },
        -- Misc data derieved from stats
        stat_text = {
            name = i18n.args.stat_text,
            field = 'stat_text',
            type = 'Text',
            func = nil,
        },
        quality_stat_text = {
            name = i18n.args.quality_stat_text,
            field = 'quality_stat_text',
            type = 'Text',
            func = nil,
        },
        -- Misc data currently not from game data
        has_percentage_mana_cost = {
            name = i18n.args.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 = i18n.args.has_reservation_mana_cost,
            field = 'has_reservation_mana_cost',
            type = 'Boolean',
            func = h.cast.wrap(m_util.cast.boolean),
            default = false,
        },
        radius = {
            name = i18n.args.radius,
            field = 'radius',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
        },
        radius_description = {
            name = i18n.args.radius_description,
            field = 'radius_description',
            type = 'Text',
            func = nil,
        },
        radius_secondary = {
            name = i18n.args.radius_secondary,
            field = 'radius_secondary',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
        },
        radius_secondary_description = {
            name = i18n.args.radius_secondary_description,
            field = 'radius_secondary_description',
            type = 'Text',
            func = nil,
        },
        -- not sure if any skill actually has 3 radius componets
        radius_tertiary = {
            name = i18n.args.radius_tertiary,
            field = 'radius_tertiary',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
        },
        radius_tertiary_description = {
            name = i18n.args.radius_tertiary_description,
            field = 'radius_tertiary_description',
            type = 'Text',
            func = nil,
        },
        -- Set manually
        max_level = {
            field = 'max_level',
            type = 'Integer',
        },
        html = {
            field = 'html',
            type = 'Text',
        },
        skill_screenshot = {
            name = i18n.args.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.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.skill_screenshot, tpl_args.active_skill_name)
                    page = mw.title.new(ss)
                    if page == nil or not page.exists then
                        ss = nil
                    end
                end
                return ss
            end,
        },
    },
}

map.progression = {
    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', 'damage_multiplier', 'experience', 'stat_text', 'quality_stat_text'},
    fields = {
        level = {
            name = nil,
            field = 'level',
            type = 'Integer',
            func = nil,
            header = nil,
        },
        level_requirement = {
            name = i18n.args.level_requirement,
            field = 'level_requirement',
            type = 'Integer',
            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 = {
            name = i18n.args.dexterity_requirement,
            field = 'dexterity_requirement',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
            header = m_util.html.abbr('[[Image:DexterityIcon_small.png|link=|dexterity]]', 'Required Dexterity', 'nounderline'),
        },
        strength_requirement = {
            name = i18n.args.strength_requirement,
            field = 'strength_requirement',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
            header = m_util.html.abbr('[[Image:StrengthIcon_small.png|link=|strength]]', 'Required Strength', 'nounderline'),
        },
        intelligence_requirement = {
            name = i18n.args.intelligence_requirement,
            field = 'intelligence_requirement',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
            header = m_util.html.abbr('[[Image:IntelligenceIcon_small.png|link=|intelligence]]', 'Required Intelligence', 'nounderline'),
        },
        mana_multiplier = {
            name = i18n.args.mana_multiplier,
            field = 'mana_multiplier',
            type = 'Float',
            func = h.cast.wrap(m_util.cast.number),
            header = 'Mana<br>Multiplier',
            fmt = '%s%%',
        },
        critical_strike_chance = {
            name = i18n.args.critical_strike_chance,
            field = 'critical_strike_chance',
            type = 'Float',
            func = h.cast.wrap(m_util.cast.number),
            header = 'Critical<br>Strike<br>Chance',
            fmt = '%s%%',
        },
        mana_cost = {
            name = i18n.args.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 = {
            name = i18n.args.damage_effectiveness,
            field = 'damage_effectiveness',
            type = 'Float',
            func = h.cast.wrap(m_util.cast.number),
            header = 'Damage<br>Effectiveness',
            fmt = '%s%%',
        },
        stored_uses = {
            name = i18n.args.stored_uses,
            field = 'stored_uses',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
            header = 'Stored<br>Uses',
        },
        cooldown = {
            name = i18n.args.cooldown,
            field = 'cooldown',
            type = 'Float',
            func = h.cast.wrap(m_util.cast.number),
            header = 'Cooldown',
            fmt = '%ss',
        },
        vaal_souls_requirement = {
            name = i18n.args.vaal_souls_requirement,
            field = 'vaal_souls_requirement',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
            header = 'Vaal<br>souls',
        },
        vaal_stored_uses = {
            name = i18n.args.vaal_stored_uses,
            field = 'vaal_stored_uses',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
            header = 'Stored<br>Uses',
        },
        damage_multiplier = {
            name = i18n.args.damage_multiplier,
            field = 'damage_multiplier',
            type = 'Float',
            func = h.cast.wrap(m_util.cast.number),
            header = m_util.html.abbr('Damage<br>Multiplier', 'Deals x% of Base Damage'),
            fmt = '%s%%',
        },
        -- from gem experience, optional
        experience = {
            name = i18n.args.experience,
            field = 'experience',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
            hide = true,
        },
        stat_text = {
            name = i18n.args.stat_text,
            field = 'stat_text',
            type = 'Text',
            func = nil,
            hide = true,
        },
        quality_stat_text = {
            name = i18n.args.quality_stat_text,
            field = 'quality_stat_text',
            type = 'Text',
            func = nil,
            hide = true,
        },
    }
}

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', 'damage_multiplier'}

map.skill_stats_per_level = {
    table = 'skill_stats_per_level',
    fields = {
        level = {
            field = 'level',
            type = 'Integer',
        },
        id = {
            field = 'id',
            type = 'String',
        },
        value = {
            field = 'value',
            type = 'Integer',
        },
        is_quality_stat = {
            field = 'is_quality_stat',
            type = 'Boolean',
        },
    },
}

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 = h.display.factory.value{key='cast_time'},
    },
    {
        header = i18n.infobox.item_class_restrictions,
        func = function (tpl_args, frame)
            if tpl_args.item_class_restrictions == nil then
                return
            end
            local ul = mw.html.create('ul')
            for _, class in ipairs(i18n.infobox.item_class_restrictions) do
                ul:tag('li')
                    :wikitext(string.format('[[%s]]', class))
            end
            return tostring(ul)
        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.mana_cost,
        func = h.display.factory.range_value{key='mana_cost'},
    },
    {
        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.damage_multiplier,
        func = h.display.factory.range_value{key='damage_multiplier'},
    },
    {
        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',
    },
}

-- ----------------------------------------------------------------------------
-- Templates
-- ----------------------------------------------------------------------------
local p = {}

p.table_skills = m_util.cargo.declare_factory{data=map.static}
p.table_skill_levels = m_util.cargo.declare_factory{data=map.progression}
p.table_skill_stats_per_level = m_util.cargo.declare_factory{data=map.skill_stats_per_level}

--
-- Template:Skill
--
function p.skill(frame, tpl_args)
    if tpl_args == nil then
        tpl_args = getArgs(frame, {
            parentFirst = true
        })
    end
    frame = m_util.misc.get_frame(frame)
    
    --
    -- Args
    --
    local properties
    
    tpl_args.skill_levels = {
        [0] = {},
    }
    
    -- Handle level progression
    local i = 0
    repeat 
        i = i + 1
        local prefix = i18n.args.prefix_level .. i 
        local level = m_util.cast.boolean(tpl_args[prefix])
        if level == true then
            -- Don't need this anymore
            tpl_args[prefix] = nil
            tpl_args.skill_levels[i] = {}
            prefix = prefix .. '_'
        
            if tpl_args[prefix .. i18n.args.experience] ~= nil then
                tpl_args.max_level = i
            end
            
            properties = {
                _table = map.progression.table,
                [map.progression.fields.level.field] = i
            }
            h.map_to_arg(tpl_args, frame, properties, prefix, map.progression, i)
            m_util.cargo.store(frame, properties)
            
            h.stats(tpl_args, frame, prefix, i)
        end
    until level ~= true
    -- If no experience is given, assume this is a non skill gem skill.
    tpl_args.max_level = tpl_args.max_level or (i - 1)
    -- handle static progression
    properties = {
        _table = map.progression.table,
        [map.progression.fields.level.field] = 0
    }
    h.map_to_arg(tpl_args, frame, properties, i18n.args.prefix_static, map.progression, 0)
    m_util.cargo.store(frame, properties)
    
    -- Handle static arguments
    properties = {
        _table = map.static.table,
        
        [map.static.fields.max_level.field] = tpl_args.max_level
    }
    
    h.map_to_arg(tpl_args, frame, properties, '', map.static)
    h.stats(tpl_args, frame, i18n.args.prefix_static, 0)
    
    
    
    --
    -- Infobox progressing
    --
    local infobox = mw.html.create('span')
    infobox:attr('class', 'skill-box')
    
    -- tablular sections
    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 then
            local tr = tbl:tag('tr')
            if infobox_data.header then
                tr
                    :tag('th')
                        :wikitext(infobox_data.header)
                        :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[map.static.fields.html.field] = infobox
    m_util.cargo.store(frame, properties)
    
    --
    --
    --
    local container = mw.html.create('span')
    container:attr('class', 'skill-box-page-container')
    container:wikitext(infobox)
    if tpl_args.skill_screenshot then
        container:wikitext(string.format('[[%s]]', tpl_args.skill_screenshot))
    end
    
    return tostring(container) .. m_util.misc.add_category({'Skill data'})
end

function p.progression(frame)
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
    
    --
    tpl_args.stat_format = {}
    local prefix
    for i=1, 9 do
        prefix = 'c' .. i .. '_' 
        local statfmt = {
            header = tpl_args[prefix .. 'header'],
            abbr = tpl_args[prefix .. 'abbr'],
            pattern_extract = tpl_args[prefix .. 'pattern_extract'],
            pattern_value = tpl_args[prefix .. 'pattern_value'],
            counter = 0,
        }
        if m_util.table.has_all_value(statfmt, {'header', 'abbr', 'pattern_extract', 'pattern_value'}) then
            break
        end
        
        if m_util.table.has_one_value(statfmt, {'header', 'abbr', 'pattern_extract', 'pattern_value'}) then
            error(string.format('All formatting keys must be specified for index "%s"', i))
        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
    
    
    local result
    local query = {
        groupBy = 'skill._pageID',
    }
    local skill_data
    
    local fields = {
        'skill._pageName',
        'skill.has_reservation_mana_cost',
    }
    
    if tpl_args.skill_id then
        query.where = string.format('skill.skill_id="%s"', tpl_args.skill_id) 
        result = m_util.cargo.query({'skill'}, fields, query)
        if #result == 0 then
            error('Couldn\'t find a page for the specified skill id')
        end
        skill_data = result[1]
    else
        if tpl_args.page then
            page = tpl_args.page
        else
            page = mw.title.getCurrentTitle().prefixedText
        end
        query.where = string.format('skill._pageName="%s"', page)
        
        result = m_util.cargo.query({'skill'}, fields, query)
        if #result == 0 then
            error('Couldn\'t find the queried data on the skill page')
        end
        
        skill_data = result[1]
    end
    
    skill_data["skill.has_reservation_mana_cost"] = h.cast.wrap(m_util.cast.boolean)(skill_data["skill.has_reservation_mana_cost"])
    
    query.where=string.format('skill_levels._pageName="%s"', skill_data['skill._pageName'])
    fields = {}
    for _, pdata in pairs(map.progression.fields) do
        fields[#fields+1] = string.format('skill_levels.%s', pdata.field)
    end
    
    result = m_util.cargo.query(
        {'skill_levels'}, 
        fields, 
        {
            where=string.format('skill_levels._pageName="%s" AND skill_levels.level > 0', skill_data['skill._pageName']),
            groupBy='skill_levels._pageID, skill_levels.level',
            orderBy='skill_levels.level ASC',
        }
    )
    
    if #result == 0 then
        error('No gem level progression data found')
    end
    
    headers = {}
    for i, row in ipairs(result) do
        for k, v in pairs(row) do
            headers[k] = true
        end
    end
    
    local tbl = mw.html.create('table')
    tbl:attr('class', 'wikitable skill-progression-table')
    
    local head = tbl:tag('tr')
    head
        :tag('th')
            :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
                :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['skill_levels.experience'] then
        head
            :tag('th')
                :wikitext(m_util.html.abbr('Exp.', 'Experience Needed to Level Up'))
                :done()
            :tag('th')
                :wikitext(m_util.html.abbr('Total Exp.', 'Total experience needed'))
                :done()
    end
    
    local tblrow
    local lastexp = 0
    local experience
    
    for i, row in ipairs(result) do
        tblrow = tbl:tag('tr')
        tblrow
            :tag('th')
                :wikitext(row['skill_levels.level'])
                :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
        
        -- stats
        if row['skill_levels.stat_text'] then
            stats = m_util.string.split(row['skill_levels.stat_text'], '<br>')
        else
            stats = {}
        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
                h.na(tblrow)
            else
                -- used to find broken progression (i.e. due to game updates)
                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['skill_levels.experience'] then
            experience = tonumber(row['skill_levels.experience'])
            if experience ~= nil then
                h.int_value_or_na(tpl_args, frame, tblrow, experience - lastexp, {})
                
                lastexp = experience
            else
                h.na(tblrow)
            end
            h.int_value_or_na(tpl_args, frame, tblrow, experience, {})
        end
    end
    
    local empty = ''
    
    for _, statfmt in ipairs(tpl_args.stat_format) do
        if statfmt.counter == 0 then
            empty = '[[Category:Pages with broken skill progression tables]]'
            break
        end
    end
    
    return empty .. tostring(tbl)
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

return p