[dismiss]
The wiki is currently a work in progress. If you'd like to help out, please check the Community Portal and our getting started guide. Also, check out our sister project on poewiki.net.
Module:Skill
Module for handling skills with Cargo support.
Implemented templates
- {{Skill}}
- {{Skill progression}}
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.
-- Skill module
-- ----------------------------------------------------------------------------
-- Includes
-- ----------------------------------------------------------------------------
local getArgs = require('Module:Arguments').getArgs
local m_cargo = require('Module:Cargo')
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 tables = {}
-- 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',
attack_speed_multiplier = 'Attack Speed',
damage_effectiveness = 'Effectiveness of Added Damage',
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',
duration = 'Base duration',
},
progression = {
level_requirement = m_util.html.abbr('[[Image:Level_up_icon_small.png|link=|Lvl.]]', 'Required Level', 'nounderline'),
dexterity_requirement = m_util.html.abbr('[[Image:DexterityIcon_small.png|link=|dexterity]]', 'Required Dexterity', 'nounderline'),
strength_requirement = m_util.html.abbr('[[Image:StrengthIcon_small.png|link=|strength]]', 'Required Strength', 'nounderline'),
intelligence_requirement = m_util.html.abbr('[[Image:IntelligenceIcon_small.png|link=|intelligence]]', 'Required Intelligence', 'nounderline'),
mana_multiplier = 'Mana<br>Multiplier',
critical_strike_chance = 'Critical<br>Strike<br>Chance',
mana_cost = 'Mana<br>Cost',
mana_reserved = 'Mana<br>Reserved',
attack_speed_multiplier = 'Attack<br>Speed<br>Multiplier',
damage_effectiveness = 'Damage<br>Effectiveness',
stored_uses = 'Stored<br>Uses',
cooldown = 'Cooldown',
vaal_souls_requirement = 'Vaal<br>souls',
vaal_stored_uses = 'Stored<br>Uses',
vaal_soul_gain_prevention_time = 'Soul<br>Prevention<br>Time',
damage_multiplier = m_util.html.abbr('Damage<br>Multiplier', 'Deals x% of Base Damage'),
duration = m_util.html.abbr('Base duration', 'Base duration is x seconds'),
exp_short = 'Exp.',
exp_long = 'Experience needed to level up',
tot_exp_short = 'Total Exp.',
tot_exp_long = 'Total experience needed',
},
}
-- ----------------------------------------------------------------------------
-- 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)
local type_prefix = string.format('%s%s', prefix_in, 'stat')
tpl_args.skill_levels[level]['stats'] = {}
for i=1, 8 do
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')
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]['stats'][#tpl_args.skill_levels[level]['stats']+1] = stat
if not tpl_args.test then
m_cargo.store(frame, {
_table = tables.skill_stats_per_level.table,
[tables.skill_stats_per_level.fields.level.field] = level,
[tables.skill_stats_per_level.fields.id.field] = stat.id,
[tables.skill_stats_per_level.fields.value.field] = stat.value,
})
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
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)
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 = {
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 tables.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
-- ----------------------------------------------------------------------------
tables.static = {
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 = {
-- GrantedEffects.dat
skill_id = {
name = 'skill_id',
field = 'skill_id',
type = 'String',
func = nil,
},
-- Active Skills.dat
cast_time = {
name = 'cast_time',
field = 'cast_time',
type = 'Float',
func = h.cast.wrap(m_util.cast.number),
fmt = '%ss',
},
gem_description = {
name = 'gem_description',
field = 'description',
type = 'Text',
func = nil,
},
active_skill_name = {
name = 'active_skill_name',
field = 'active_skill_name',
type = 'String',
func = nil,
},
skill_icon = {
name = '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_id_restriction = {
name = '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('Invalid item class id: %s', v))
end
end
return value
end,
},
item_class_restriction = {
name = '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 = 'projectile_speed',
field = 'projectile_speed',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
},
-- Misc data derieved from stats
stat_text = {
name = 'stat_text',
field = 'stat_text',
type = 'Text',
func = nil,
},
quality_stat_text = {
name = 'quality_stat_text',
field = 'quality_stat_text',
type = 'Text',
func = nil,
},
-- 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 = {
name = 'radius',
field = 'radius',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
},
radius_description = {
name = 'radius_description',
field = 'radius_description',
type = 'Text',
func = nil,
},
radius_secondary = {
name = 'radius_secondary',
field = 'radius_secondary',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
},
radius_secondary_description = {
name = 'radius_secondary_description',
field = 'radius_secondary_description',
type = 'Text',
func = nil,
},
-- not sure if any skill actually has 3 radius componets
radius_tertiary = {
name = 'radius_tertiary',
field = 'radius_tertiary',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
},
radius_tertiary_description = {
name = '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 = '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,
},
},
}
tables.progression = {
table = 'skill_levels',
order = {'level_requirement', 'dexterity_requirement', 'intelligence_requirement', 'strength_requirement', 'mana_multiplier', 'critical_strike_chance', 'mana_cost', 'attack_speed_multiplier', 'damage_effectiveness', 'stored_uses', 'cooldown', 'vaal_souls_requirement', 'vaal_stored_uses', 'vaal_soul_gain_prevention_time', 'damage_multiplier', 'duration', 'experience', 'stat_text'},
fields = {
level = {
name = nil,
field = 'level',
type = 'Integer',
func = nil,
header = nil,
},
level_requirement = {
name = 'level_requirement',
field = 'level_requirement',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
header = i18n.progression.level_requirement,
},
dexterity_requirement = {
name = 'dexterity_requirement',
field = 'dexterity_requirement',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
header = i18n.progression.dexterity_requirement,
},
strength_requirement = {
name = 'strength_requirement',
field = 'strength_requirement',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
header = i18n.progression.strength_requirement,
},
intelligence_requirement = {
name = 'intelligence_requirement',
field = 'intelligence_requirement',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
header = i18n.progression.intelligence_requirement,
},
mana_multiplier = {
name = 'mana_multiplier',
field = 'mana_multiplier',
type = 'Float',
func = h.cast.wrap(m_util.cast.number),
header = i18n.progression.mana_multiplier,
fmt = '%s%%',
},
critical_strike_chance = {
name = 'critical_strike_chance',
field = 'critical_strike_chance',
type = 'Float',
func = h.cast.wrap(m_util.cast.number),
header = i18n.progression.critical_strike_chance,
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 i18n.progression.mana_reserved
else
return i18n.progression.mana_cost
end
end,
fmt = function (tpl_args, frame)
if tpl_args.has_percentage_mana_cost or (tpl_args.skill_data and tpl_args.skill_data['skill.has_percentage_mana_cost']) then
str = '%s%%'
else
str = '%s'
end
return str
end,
},
damage_effectiveness = {
name = 'damage_effectiveness',
field = 'damage_effectiveness',
type = 'Float',
func = h.cast.wrap(m_util.cast.number),
header = i18n.progression.damage_effectiveness,
fmt = '%s%%',
},
stored_uses = {
name = 'stored_uses',
field = 'stored_uses',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
header = i18n.progression.stored_uses,
},
cooldown = {
name = 'cooldown',
field = 'cooldown',
type = 'Float',
func = h.cast.wrap(m_util.cast.number),
header = i18n.progression.cooldown,
fmt = '%ss',
},
vaal_souls_requirement = {
name = 'vaal_souls_requirement',
field = 'vaal_souls_requirement',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
header = i18n.progression.vaal_souls_requirement,
},
vaal_stored_uses = {
name = '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 = 'vaal_soul_gain_prevention_time',
field = 'vaal_soul_gain_prevention_time',
type = 'Float',
func = h.cast.wrap(m_util.cast.number),
header = i18n.progression.vaal_soul_gain_prevention_time,
fmt = '%ss',
},
damage_multiplier = {
name = 'damage_multiplier',
field = 'damage_multiplier',
type = 'Float',
func = h.cast.wrap(m_util.cast.number),
header = i18n.progression.damage_multiplier,
fmt = '%s%%',
},
attack_speed_multiplier = {
name = 'attack_speed_multiplier',
field = 'attack_speed_multiplier',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
header = i18n.progression.attack_speed_multiplier,
fmt = '%s%%',
},
duration = {
name = 'duration',
field = 'duration',
type = 'Float',
func = h.cast.wrap(m_util.cast.number),
header = i18n.progression.duration,
fmt = '%ss',
},
-- from gem experience, optional
experience = {
name = 'experience',
field = 'experience',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
hide = true,
},
stat_text = {
name = 'stat_text',
field = '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', 'vaal_soul_gain_prevention_time', 'damage_multiplier', 'duration', 'attack_speed_multiplier'}
tables.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',
},
},
}
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.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_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.mana_cost,
func = h.display.factory.range_value{key='mana_cost'},
},
{
header = i18n.infobox.attack_speed_multiplier,
func = h.display.factory.range_value{key='attack_speed_multiplier'},
},
{
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.damage_multiplier,
func = h.display.factory.range_value{key='damage_multiplier'},
},
{
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',
},
}
-- ----------------------------------------------------------------------------
-- Templates
-- ----------------------------------------------------------------------------
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_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}
--
-- Template:Skill
--
function p.skill(frame, tpl_args)
--[[
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)
--
-- Args
--
local properties
tpl_args.skill_levels = {
[0] = {},
}
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
-- Handle level progression
local i = 0
repeat
i = i + 1
local 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 .. 'experience'] ~= nil then
tpl_args.max_level = i
end
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.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 = tables.progression.table,
[tables.progression.fields.level.field] = 0
}
h.map_to_arg(tpl_args, frame, properties, 'static_', tables.progression, 0)
if not tpl_args.test then
m_cargo.store(frame, properties)
end
-- Handle static arguments
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.stats(tpl_args, frame, '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[tables.static.fields.html.field] = infobox
if not tpl_args.test then
frame:expandTemplate{title=string.format('Template:Skill/cargo/attach/%s', properties._table), args={}}
m_cargo.store(frame, properties)
end
--
--
--
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 = '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.intro_named_id,
tpl_args.skill_id,
tpl_args.active_skill_name
)
else
out[#out+1] = string.format(
i18n.intro_unnamed_id,
tpl_args.skill_id
)
end
return tostring(container) .. m_util.misc.add_category({'Skill data'}) .. '\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 prefix
for i=1, 9 do
prefix = 'c' .. i .. '_'
local format_keys = {
'header',
'abbr',
'pattern_extract',
'pattern_value'
}
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
end
if m_util.table.has_one_value(statfmt, format_keys) then
error(string.format(i18n.errors.all_format_keys_specified, 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 results = {}
local query = {
groupBy = 'skill._pageID',
}
local skill_data
local fields = {
'skill._pageName',
'skill.has_reservation_mana_cost',
'skill.has_percentage_mana_cost',
}
if tpl_args.skill_id then
query.where = string.format(
'skill.skill_id="%s"',
tpl_args.skill_id
)
results = m_cargo.query({'skill'}, fields, query)
if #results == 0 then
error(i18n.errors.no_results_for_skill_id)
end
skill_data = results[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)
results = m_cargo.query({'skill'}, fields, query)
if #results == 0 then
error(i18n.errors.no_results_for_skill_page)
end
skill_data = results[1]
end
tpl_args.skill_data = skill_data
skill_data["skill.has_reservation_mana_cost"] = m_util.cast.boolean(skill_data["skill.has_reservation_mana_cost"])
skill_data['skill.has_percentage_mana_cost'] = m_util.cast.boolean(skill_data["skill.has_percentage_mana_cost"])
query.where = string.format(
'skill_levels._pageName="%s"',
skill_data['skill._pageName']
)
fields = {}
for _, pdata in pairs(tables.progression.fields) do
fields[#fields+1] = string.format('skill_levels.%s', pdata.field)
end
results = m_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 #results == 0 then
error(i18n.errors.missing_level_data)
end
headers = {}
for i, row in ipairs(results) 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')
head
:tag('th')
:wikitext('Level')
:done()
for _, key in ipairs(data.progression_display_order) do
local pdata = tables.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(
i18n.progression.exp_short,
i18n.progression.exp_long
)
)
:done()
:tag('th')
:wikitext(m_util.html.abbr(
i18n.progression.tot_exp_short,
i18n.progression.tot_exp_long
)
)
:done()
end
local tblrow
local lastexp = 0
local experience
for i, row in ipairs(results) 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 = tables.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 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['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 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
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