Module:Item table
This module is used on 4000+ pages.
To avoid major disruption and server load, do not make unnecessary edits to this module. Test changes to this module first using its /sandbox and /testcases subpages . All of the changes can then be applied to this module in a single edit.
Consider discussing changes on the talk page or on Discord before implementing them.
The item module provides functionality for creating item tables.
Implemented templates
This module implements the following templates:
The above documentation is transcluded from Module:Item table/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.
-- Item table
--
--
-- ----------------------------------------------------------------------------
-- Imports
-- ----------------------------------------------------------------------------
local m_util = require('Module:Util')
local getArgs = require('Module:Arguments').getArgs
local m_game = require('Module:Game')
local f_item_link = require('Module:Item link').item_link
local cargo = mw.ext.cargo
-- ----------------------------------------------------------------------------
-- Globals
-- ----------------------------------------------------------------------------
local c = {}
c.query_default = 50
c.query_max = 200
-- ----------------------------------------------------------------------------
-- Strings
-- ----------------------------------------------------------------------------
-- This section contains strings used by this module.
-- Add new strings here instead of in-code directly, this will help other
-- people to correct spelling mistakes easier and help with translation to
-- other PoE wikis.
local i18n = {
range = '(%s to %s)',
categories = {
-- maintenance cats
query_limit = 'Item tables hitting query limit',
query_hard_limit = 'Item tables hitting hard query limit',
},
-- Used by the item table
item_table = {
item = 'Item',
skill_gem = 'Skill gem',
physical_dps = m_util.html.abbr('pDPS', 'physical damage per second'),
fire_dps = m_util.html.abbr('Fire DPS', 'fire damage per second'),
cold_dps = m_util.html.abbr('Cold DPS', 'cold damage per second'),
lightning_dps = m_util.html.abbr('Light. DPS', 'lightning damage per second'),
chaos_dps = m_util.html.abbr('Chaos DPS', 'chaos damage per second'),
elemental_dps = m_util.html.abbr('eDPS', 'elemental damage (i.e. fire/cold/lightning) per second'),
poison_dps = m_util.html.abbr('Poison DPS', 'poison damage (i.e. physical/chaos) per second'),
dps = m_util.html.abbr('DPS', 'total damage (i.e. physical/fire/cold/lightning/chaos) per second'),
base_item = 'Base Item',
item_class = 'Item Class',
essence_level = 'Essence<br>Level',
drop_level = 'Drop<br>Level',
drop_leagues = 'Drop Leagues',
drop_areas = 'Drop Areas',
drop_text = 'Additional<br>Drop Restrictions',
stack_size = 'Stack<br>Size',
stack_size_currency_tab = m_util.html.abbr('Tab<br>Stack<br>Size', 'Stack size in the currency stash tab'),
armour = m_util.html.abbr('AR', 'Armour'),
evasion = m_util.html.abbr('EV', 'Evasion Rating'),
energy_shield = m_util.html.abbr('ES', 'Energy Shield'),
block = m_util.html.abbr('Block', 'Chance to Block'),
damage = m_util.html.abbr('Damage', 'Colour coded damage'),
attacks_per_second = m_util.html.abbr('APS', 'Attacks per second'),
local_critical_strike_chance = m_util.html.abbr('Crit', 'Local weapon critical strike chance'),
flask_life = m_util.html.abbr('Life', 'Life regenerated over the flask duration'),
flask_mana = m_util.html.abbr('Mana', 'Mana regenerated over the flask duration'),
flask_duration = 'Duration',
flask_charges_per_use = m_util.html.abbr('Usage', 'Number of charges consumed on use'),
flask_maximum_charges = m_util.html.abbr('Capacity', 'Maximum number of flask charges held'),
item_limit = 'Limit',
jewel_radius = 'Radius',
map_tier = 'Map<br>Tier',
map_level = 'Map<br>Level',
map_guild_character = m_util.html.abbr('Char', 'Character for the guild tag'),
buff_effects = 'Buff Effects',
stats = 'Stats',
effects = 'Effect(s)',
flavour_text = 'Flavour Text',
prediction_text = 'Prediction',
help_text = 'Help Text',
seal_cost = m_util.html.abbr('Seal<br>Cost', 'Silver Coin cost of sealing this prophecies into an item'),
objective = 'Objective',
reward = 'Reward',
buff_icon = 'Buff<br>Icon',
-- Skills
support_gem_letter = m_util.html.abbr('L', 'Support gem letter.'),
skill_icon = 'Icon',
description = 'Description',
skill_critical_strike_chance = m_util.html.abbr('Crit', 'Critical Strike Chance'),
cast_time = m_util.html.abbr('Cast<br>Time', 'Casting time of the skill in seconds'),
damage_effectiveness = m_util.html.abbr('Dmg.<br>Eff.', 'Damage Effectiveness'),
mana_cost_multiplier = m_util.html.abbr('MCM', 'Mana cost multiplier - missing values indicate it changes on gem level'),
mana_cost = m_util.html.abbr('Mana', 'Mana cost'),
reserves_mana_suffix = m_util.html.abbr('R', 'reserves mana'),
vaal_souls_requirement = m_util.html.abbr('Souls', 'Vaal souls requirement in Normal/Cruel/Merciless difficulty'),
stored_uses = m_util.html.abbr('Uses', 'Maximum number of stored uses'),
primary_radius = m_util.html.abbr('R1', 'Primary radius'),
secondary_radius = m_util.html.abbr('R2', 'Secondary radius'),
tertiary_radius = m_util.html.abbr('R3', 'Tertiary radius'),
},
errors = {
generic_argument_parameter = 'Unrecognized %s parameter "%s"',
invalid_item_table_mode = 'Invalid mode for item table',
},
}
-- ----------------------------------------------------------------------------
-- Helper & utility functions
-- ----------------------------------------------------------------------------
local h = {}
function h.format_value(tpl_args, frame, value, options)
-- value: table
-- min:
-- max:
-- options: table
-- fmt: formatter to use for the value instead of valfmt
-- fmt_range: formatter to use for the range values. Default: (%s to %s)
-- inline: Use this format string to insert value
-- inline_color: colour to use for the inline value; false to disable colour
-- func: Function to adjust the value with before output
-- color: colour code for m_util.html.poe_color, overrides mod colour
-- no_color: set to true to ingore colour entirely
-- return_color: also return colour
if options.no_color == nil then
if options.color then
value.color = options.color
elseif value.base ~= value.min or value.base ~= value.max then
value.color = 'mod'
else
value.color = 'value'
end
end
if options.func ~= nil then
value.min = options.func(tpl_args, frame, value.min)
value.max = options.func(tpl_args, frame, value.max)
end
if options.fmt == nil then
options.fmt = '%s'
elseif type(options.fmt) == 'function' then
options.fmt = options.fmt(tpl_args, frame)
end
if value.min == value.max then
value.out = string.format(options.fmt, value.min)
else
value.out = string.format(string.format(options.fmt_range or i18n.range, options.fmt, options.fmt), value.min, value.max)
end
if options.no_color == nil then
value.out = m_util.html.poe_color(value.color, value.out)
end
local return_color
if options.return_color ~= nil then
return_color = value.color
end
local text = options.inline
if type(text) == 'string' then
elseif type(text) == 'function' then
text = text(tpl_args, frame)
else
text = nil
end
if text and text ~= '' then
local color
if options.inline_color == nil then
color = 'default'
elseif options.inline_color ~= false then
color = color.inline_color
end
if color ~= nil then
text = m_util.html.poe_color(color, text)
end
return string.format(text, value.out), return_color
end
-- If we didn't return before, return here
return value.out, return_color
end
h.tbl = {}
function h.tbl.range_fields(field)
return function()
local fields = {}
for _, partial_field in ipairs({'maximum', 'text', 'colour'}) do
fields[#fields+1] = string.format('%s_range_%s', field, partial_field)
end
return fields
end
end
h.tbl.display = {}
function h.tbl.display.na_or_val(tr, value, data)
return h.na_or_val(tr, value)
end
function h.tbl.display.seconds(tr, value, data)
return h.na_or_val(tr, value, function(value)
return string.format('%ss', value)
end)
end
function h.tbl.display.percent(tr, value, data)
return h.na_or_val(tr, value, function(value)
return string.format('%s%%', value)
end)
end
function h.tbl.display.wikilink(tr, value, data)
return h.na_or_val(tr, value, function(value)
return string.format('[[%s]]', value)
end)
end
h.tbl.display.factory = {}
function h.tbl.display.factory.value(args)
args.options = args.options or {}
return function(tr, data, properties)
values = {}
for index, prop in ipairs(properties) do
local value = data[prop]
if args.options[index] and args.options[index].fmt then
value = string.format(args.options[index].fmt, value)
end
values[#values+1] = value
end
local td = tr:tag('td')
td:attr('data-sort-value', table.concat(values, ', '))
td:wikitext(table.concat(values, ', '))
if args.colour then
td:attr('class', 'tc -' .. args.colour)
end
end
end
function h.tbl.display.factory.range(args)
-- args: table
-- property
return function (tr, data, fields)
tr
:tag('td')
:attr('data-sort-value', data[string.format('%s_range_maximum', args.field)] or '0')
:attr('class', 'tc -' .. (data[string.format('%s_range_colour', args.field)] or 'default'))
:wikitext(data[string.format('%s_range_text', args.field)])
:done()
end
end
-- ----------------------------------------------------------------------------
-- Data mappings
-- ----------------------------------------------------------------------------
local data_map = {}
-- for sort type see:
-- https://meta.wikimedia.org/wiki/Help:Sorting
data_map.generic_item = {
{
arg = 'base_item',
header = i18n.item_table.base_item,
fields = {'items.base_item', 'items.base_item_page'},
display = function(tr, data)
tr
:tag('td')
:attr('data-sort-value', data['items.base_item'])
:wikitext(string.format('[[%s|%s]]', data['items.base_item_page'], data['items.base_item']))
end,
order = 1000,
sort_type = 'text',
},
{
arg = 'class',
header = i18n.item_table.item_class,
fields = {'items.class'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='[[%s]]',
},
}},
order = 1001,
sort_type = 'text',
},
{
arg = 'essence',
header = i18n.item_table.essence_level,
fields = {'essences.level'},
display = h.tbl.display.factory.value{},
order = 2000,
},
{
arg = {'drop', 'drop_level'},
header = i18n.item_table.drop_level,
fields = {'items.drop_level'},
display = h.tbl.display.factory.value{},
order = 3000,
},
{
arg = 'stack_size',
header = i18n.item_table.stack_size,
fields = {'stackables.stack_size'},
display = h.tbl.display.factory.value{},
order = 4000,
},
{
arg = 'stack_size_currency_tab',
header = i18n.item_table.stack_size_currency_tab,
fields = {'stackables.stack_size_currency_tab'},
display = h.tbl.display.factory.value{},
order = 4001,
},
{
arg = 'level',
header = m_game.level_requirement.icon,
fields = h.tbl.range_fields('items.required_level'),
display = h.tbl.display.factory.range{field='items.required_level'},
order = 5000,
},
{
arg = 'ar',
header = i18n.item_table.armour,
fields = h.tbl.range_fields('armours.armour'),
display = h.tbl.display.factory.range{field='armours.armour'},
order = 6000,
},
{
arg = 'ev',
header =i18n.item_table.evasion,
fields = h.tbl.range_fields('armours.evasion'),
display = h.tbl.display.factory.range{field='armours.evasion'},
order = 6001,
},
{
arg = 'es',
header = i18n.item_table.energy_shield,
fields = h.tbl.range_fields('armours.energy_shield'),
display = h.tbl.display.factory.range{field='armours.energy_shield'},
order = 6002,
},
{
arg = 'block',
header = i18n.item_table.block,
fields = h.tbl.range_fields('shields.block'),
display = h.tbl.display.factory.range{field='shields.block'},
order = 6003,
},
--[[{
arg = 'physical_damage_min',
header = m_util.html.abbr('Min', 'Local minimum weapon damage'),
fields = h.tbl.range_fields('minimum physical damage'),
display = h.tbl.display.factory.range{field='minimum physical damage'},
order = 7000,
},
{
arg = 'physical_damage_max',
header = m_util.html.abbr('Max', 'Local maximum weapon damage'),
fields = h.tbl.range_fields('maximum physical damage'),
display = h.tbl.display.factory.range{field='maximum physical damage'},
order = 7001,
},]]--
{
arg = {'weapon', 'damage'},
header = i18n.item_table.damage,
fields = {'weapons.damage_html', 'weapons.damage_avg'},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['weapons.damage_avg'])
:wikitext(data['weapons.damage_html'])
end,
order = 8000,
},
{
arg = {'weapon', 'aps'},
header = i18n.item_table.attacks_per_second,
fields = h.tbl.range_fields('weapons.attack_speed'),
display = h.tbl.display.factory.range{field='weapons.attack_speed'},
order = 8001,
},
{
arg = {'weapon', 'crit'},
header = i18n.item_table.local_critical_strike_chance,
fields = h.tbl.range_fields('weapons.critical_strike_chance'),
display = h.tbl.display.factory.range{field='weapons.critical_strike_chance'},
order = 8002,
},
{
arg = {'physical_dps'},
header = i18n.item_table.physical_dps,
fields = h.tbl.range_fields('weapons.physical_dps'),
display = h.tbl.display.factory.range{field='weapons.physical_dps'},
order = 8100,
},
{
arg = {'lightning_dps'},
header = i18n.item_table.lightning_dps,
fields = h.tbl.range_fields('weapons.lightning_dps'),
display = h.tbl.display.factory.range{field='weapons.lightning_dps'},
order = 8101,
},
{
arg = {'cold_dps'},
header = i18n.item_table.cold_dps,
fields = h.tbl.range_fields('weapons.cold_dps'),
display = h.tbl.display.factory.range{field='weapons.cold_dps'},
order = 8102,
},
{
arg = {'fire_dps'},
header = i18n.item_table.fire_dps,
fields = h.tbl.range_fields('weapons.fire_dps'),
display = h.tbl.display.factory.range{field='weapons.fire_dps'},
order = 8103,
},
{
arg = {'chaos_dps'},
header = i18n.item_table.chaos_dps,
fields = h.tbl.range_fields('weapons.chaos_dps'),
display = h.tbl.display.factory.range{field='weapons.chaos_dps'},
order = 8104,
},
{
arg = {'elemental_dps'},
header = i18n.item_table.elemental_dps,
fields = h.tbl.range_fields('weapons.elemental_dps'),
display = h.tbl.display.factory.range{field='weapons.elemental_dps'},
order = 8105,
},
{
arg = {'poison_dps'},
header = i18n.item_table.poison_dps,
fields = h.tbl.range_fields('weapons.poison_dps'),
display = h.tbl.display.factory.range{field='weapons.poison_dps'},
order = 8106,
},
{
arg = {'dps'},
header = i18n.item_table.dps,
fields = h.tbl.range_fields('weapons.dps'),
display = h.tbl.display.factory.range{field='weapons.dps'},
order = 8107,
},
{
arg = 'flask_life',
header = i18n.item_table.flask_life,
fields = h.tbl.range_fields('flasks.life'),
display = h.tbl.display.factory.range{field='flasks.life'},
order = 9000,
},
{
arg = 'flask_mana',
header = i18n.item_table.flask_mana,
fields = h.tbl.range_fields('flasks.mana'),
display = h.tbl.display.factory.range{field='flasks.mana'},
order = 9001,
},
{
arg = 'flask',
header = i18n.item_table.flask_duration,
fields = h.tbl.range_fields('flasks.duration'),
display = h.tbl.display.factory.range{field='flasks.duration'},
order = 9002,
},
{
arg = 'flask',
header = i18n.item_table.flask_charges_per_use,
fields = h.tbl.range_fields('flasks.charges_per_use'),
display = h.tbl.display.factory.range{field='flasks.charges_per_use'},
order = 9003,
},
{
arg = 'flask',
header = i18n.item_table.flask_maximum_charges,
fields = h.tbl.range_fields('flasks.charges_max'),
display = h.tbl.display.factory.range{field='flasks.charges_max'},
order = 9004,
},
{
arg = 'item_limit',
header = i18n.item_table.item_limit,
fields = {'jewels.item_limit'},
display = h.tbl.display.factory.value{},
order = 10000,
},
{
arg = 'jewel_radius',
header = i18n.item_table.jewel_radius,
fields = {'jewels.radius_html'},
display = function (tr, data)
tr
:tag('td')
:wikitext(data['jewels.radius_html'])
end,
order = 10001,
},
{
arg = 'map_tier',
header = i18n.item_table.map_tier,
fields = {'maps.tier'},
display = h.tbl.display.factory.value{},
order = 11000,
},
{
arg = 'map_level',
header = i18n.item_table.map_level,
fields = {'maps.area_level'},
display = h.tbl.display.factory.value{},
order = 11010,
},
{
arg = 'map_guild_character',
header = i18n.item_table.map_guild_character,
fields = {'maps.guild_character'},
display = h.tbl.display.factory.value{colour='value'},
order = 11020,
sort_type = 'text',
},
{
arg = 'buff',
header = i18n.item_table.buff_effects,
fields = {'item_buffs.stat_text'},
display = h.tbl.display.factory.value{colour='mod'},
order = 12000,
sort_type = 'text',
},
{
arg = 'stat',
header = i18n.item_table.stats,
fields = {'items.stat_text'},
display = h.tbl.display.factory.value{colour='mod'},
order = 12001,
sort_type = 'text',
},
{
arg = 'description',
header = i18n.item_table.effects,
fields = {'items.description'},
display = h.tbl.display.factory.value{colour='mod'},
order = 12002,
sort_type = 'text',
},
{
arg = 'flavour_text',
header = i18n.item_table.flavour_text,
fields = {'items.flavour_text'},
display = h.tbl.display.factory.value{colour='flavour'},
order = 12003,
sort_type = 'text',
},
{
arg = 'help_text',
header = i18n.item_table.help_text,
fields = {'items.help_text'},
display = h.tbl.display.factory.value{colour='help'},
order = 12005,
sort_type = 'text',
},
{
arg = {'prophecy', 'objective'},
header = i18n.item_table.objective,
fields = {'prophecies.objective'},
display = h.tbl.display.factory.value{},
order = 13002,
},
{
arg = {'prophecy', 'reward'},
header = i18n.item_table.reward,
fields = {'prophecies.reward'},
display = h.tbl.display.factory.value{},
order = 13001,
},
{
arg = {'prophecy', 'seal_cost'},
header = i18n.item_table.seal_cost,
fields = {'prophecies.seal_cost'},
display = h.tbl.display.factory.value{colour='currency'},
order = 13002,
},
{
arg = {'prediction_text'},
header = i18n.item_table.prediction_text,
fields = {'prophecies.prediction_text'},
display = h.tbl.display.factory.value{colour='value'},
order = 12004,
sort_type = 'text',
},
{
arg = 'buff_icon',
header = i18n.item_table.buff_icon,
fields = {'item_buffs.icon'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='[[%s]]',
},
}},
order = 14000,
sort_type = 'text',
},
{
arg = {'drop', 'drop_leagues'},
header = i18n.item_table.drop_leagues,
fields = {'items.drop_leagues'},
display = function (tr, data)
tr
:tag('td')
:wikitext(table.concat(m_util.string.split(data['items.drop_leagues'], ','), '<br>'))
end,
order = 15000,
},
{
arg = {'drop', 'drop_areas'},
header = i18n.item_table.drop_areas,
fields = {'items.drop_areas_html'},
display = h.tbl.display.factory.value{},
order = 15001,
},
{
arg = {'drop', 'drop_text'},
header = i18n.item_table.drop_text,
fields = {'items.drop_text'},
display = h.tbl.display.factory.value{},
order = 15002,
},
}
data_map.skill_gem_new = {
{
arg = 'icon',
header = i18n.item_table.support_gem_letter,
fields = {'skill_gems.support_gem_letter_html'},
display = h.tbl.display.factory.value{},
order = 1000,
sort_type = 'text',
},
{
arg = 'skill_icon',
header = i18n.item_table.skill_icon,
fields = {'skill.skill_icon'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='[[%s]]',
},
}},
order = 1001,
sort_type = 'text',
},
{
arg = 'description',
header = i18n.item_table.description,
fields = {'skill.description'},
display = h.tbl.display.factory.value{},
order = 2000,
sort_type = 'text',
},
{
arg = 'level',
header = m_game.level_requirement.icon,
fields = h.tbl.range_fields('items.required_level'),
display = h.tbl.display.factory.range{field='items.required_level'},
order = 3004,
},
{
arg = 'crit',
header = i18n.item_table.skill_critical_strike_chance,
fields = {'skill_levels.critical_strike_chance'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='%s%%',
},
}},
order = 4000,
},
{
arg = 'cast_time',
header = i18n.item_table.cast_time,
fields = {'skill_levels.cast_time'},
display = h.tbl.display.factory.value{},
order = 4001,
},
{
arg = 'dmgeff',
header = i18n.item_table.damage_effectiveness,
fields = {'skill_levels.damage_effectiveness'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='%s%%',
},
}},
order = 4002,
},
{
arg = 'mcm',
header = i18n.item_table.mana_cost_multiplier,
fields = {'skill_levels.mana_multiplier'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='%s%%',
},
}},
order = 5000,
},
{
arg = 'mana',
header = i18n.item_table.mana_cost,
fields = {'skill_levels.mana_cost', 'skill.has_percentage_mana_cost', 'skill.has_reservation_mana_cost'},
display = function (tr, data)
local appendix = ''
if m_util.cast.boolean(data['skill.has_percentage_mana_cost']) then
appendix = appendix .. '%'
end
if m_util.cast.boolean(data['skill.has_reservation_mana_cost']) then
appendix = appendix .. ' ' .. i18n.item_table.reserves_mana_suffix
end
tr
:tag('td')
:attr('data-sort-value', data['skill_levels.mana_cost'])
:wikitext(string.format('%d', data['skill_levels.mana_cost']) .. appendix)
end,
order = 5001,
},
{
arg = 'vaal',
header = i18n.item_table.vaal_souls_requirement,
fields = {'skill_levels.vaal_souls_requirement'},
display = function (tr, data)
local souls = tonumber(data['skill_levels.vaal_souls_requirement'])
tr
:tag('td')
:attr('data-sort-value', souls)
:wikitext(string.format('%d / %d / %d', souls, souls*1.5, souls*2))
end,
order = 6000,
},
{
arg = 'vaal',
header = i18n.item_table.stored_uses,
fields = {'skill_levels.vaal_stored_uses'},
display = h.tbl.display.factory.value{},
order = 6001,
},
{
arg = 'radius',
header = i18n.item_table.primary_radius,
fields = {'skill.radius', 'skill.radius_description'},
options = {[2] = {optional = true}},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['skill.radius'])
:wikitext(core.factory.descriptor_value{tbl=data, key='skill.radius_description'}(nil, nil, data['skill.radius']))
end,
order = 7000,
},
{
arg = 'radius',
header = i18n.item_table.secondary_radius,
fields = {'skill.radius_secondary', 'skill.radius_secondary_description'},
options = {[2] = {optional = true}},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['skill.radius_secondary'])
:wikitext(core.factory.descriptor_value{tbl=data, key='skill.radius_secondary_description'}(nil, nil, data['skill.radius_secondary']))
end,
order = 7001,
},
{
arg = 'radius',
header = i18n.item_table.tertiary_radius,
fields = {'skill.radius_tertiary', 'skill.radius_tertiary_description'},
options = {[2] = {optional = true}},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['skill.radius_tertiary'])
:wikitext(core.factory.descriptor_value{tbl=data, key='skill.radius_tertiary_description'}(nil, nil, data['skill.radius_tertiary']))
end,
order = 7002,
},
}
for i, attr in ipairs(m_game.constants.attributes) do
table.insert(data_map.generic_item, 7, {
arg = attr.short_lower,
header = attr.icon,
fields = h.tbl.range_fields(string.format('items.required_%s', attr.long_lower)),
display = h.tbl.display.factory.range{field=string.format('items.required_%s', attr.long_lower)},
order = 5000+i,
})
table.insert(data_map.skill_gem_new, 1, {
arg = attr.short_lower,
header = attr.icon,
fields = {string.format('skill_gems.%s_percent', attr.long_lower)},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data[string.format('skill_gems.%s_percent', attr.long_lower)])
:wikitext('[[File:Yes.png|yes|link=]]')
end,
order = 3000+i,
})
end
-- ----------------------------------------------------------------------------
-- Invoke callables
-- ----------------------------------------------------------------------------
local p = {}
--
-- Template:Item table
--
function p.item_table(frame)
-- args
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
if string.find(tpl_args.q_where, '%[%[') ~= nil then
error('SMW leftover in where clause')
end
local modes = {
skill = {
data = data_map.skill_gem_new,
header = i18n.item_table.skill_gem,
},
item = {
data = data_map.generic_item,
header = i18n.item_table.item,
},
}
if tpl_args.mode == nil then
tpl_args.mode = 'item'
end
if modes[tpl_args.mode] == nil then
error(i18n.errors.invalid_item_table_mode)
end
local row_infos = {}
for _, row_info in ipairs(modes[tpl_args.mode].data) do
local enabled = false
if row_info.arg == nil then
enabled = true
elseif type(row_info.arg) == 'string' and m_util.cast.boolean(tpl_args[row_info.arg]) then
enabled = true
elseif type(row_info.arg) == 'table' then
for _, argument in ipairs(row_info.arg) do
if m_util.cast.boolean(tpl_args[argument]) then
enabled = true
break
end
end
end
if enabled then
row_info.options = row_info.options or {}
row_infos[#row_infos+1] = row_info
end
end
-- Parse stat arguments
local stat_columns = {}
local query_stats = {}
local stat_results = {}
local i = 0
repeat
i = i + 1
local prefix = string.format('stat_column%s_', i)
local col_info = {
header = tpl_args[prefix .. 'header'] or tostring(i),
format = tpl_args[prefix .. 'format'],
stat_format = tpl_args[prefix .. 'stat_format'] or 'separate',
order = tonumber(tpl_args[prefix .. 'order']) or (10000000 + i),
stats = {},
options = {},
}
local j = 0
repeat
j = j +1
local stat_info = {
id = tpl_args[string.format('%sstat%s_id', prefix, j)],
}
if stat_info.id then
col_info.stats[#col_info.stats+1] = stat_info
query_stats[stat_info.id] = {}
else
-- Stop iteration entirely if this was the first index but no stat was supplied. We assume that we stop in this case.
if j == 1 then
i = nil
end
-- stop iteration
j = nil
end
until j == nil
-- Don't add this column if no stats were provided.
if #col_info.stats > 0 then
stat_columns[#stat_columns+1] = col_info
end
until i == nil
for _, col_info in ipairs(stat_columns) do
local row_info = {
--arg
header = col_info.header,
fields = {},
display = function(tr, data, properties)
if col_info.stat_format == 'separate' then
local stat_texts = {}
local num_stats = 0
local vmax = 0
for _, stat_info in ipairs(col_info.stats) do
num_stats = num_stats + 1
-- stat results from outside body
local stat = (stat_results[data['items._pageName']] or {})[stat_info.id]
if stat ~= nil then
stat_texts[#stat_texts+1] = h.format_value(tpl_args, frame, stat, {no_color=true})
vmax = vmax + stat.max
end
end
if num_stats ~= #stat_texts then
tr:wikitext(m_util.html.td.na())
else
local text
if col_info.format then
text = string.format(col_info.format, unpack(stat_texts))
else
text = table.concat(stat_texts, ', ')
end
tr:tag('td')
:attr('data-sort-value', vmax)
:attr('class', 'tc -mod')
:wikitext(text)
end
elseif col_info.stat_format == 'add' then
local total_stat = {
min = 0,
max = 0,
avg = 0,
}
for _, stat_info in ipairs(col_info.stats) do
local stat = (stat_results[data['items._pageName']] or {})[stat_info.id]
if stat ~= nil then
for k, v in pairs(total_stat) do
total_stat[k] = v + stat[k]
end
end
end
if col_info.format == nil then
col_info.format = '%s'
end
tr:tag('td')
:attr('data-sort-value', total_stat.max)
:attr('class', 'tc -mod')
:wikitext(string.format(col_info.format, h.format_value(tpl_args, frame, total_stat, {no_color=true})))
else
error(string.format(i18n.errors.generic_argument_parameter, 'stat_format', col_info.stat_format))
end
end,
order = col_info.order,
}
table.insert(row_infos, row_info)
end
-- sort the rows
table.sort(row_infos, function (a, b)
return (a.order or 0) < (b.order or 0)
end)
-- Parse query arguments
local tables_assoc = {items=true}
local fields = {
'items._pageID',
'items._pageName',
'items.name',
'items.inventory_icon',
'items.html',
'items.size_x',
'items.size_y',
}
--
local prepend = {
q_groupBy=true,
q_tables=true
}
local query = {}
for key, value in pairs(tpl_args) do
if string.sub(key, 0, 2) == 'q_' then
if prepend[key] then
value = ',' .. value
end
query[string.sub(key, 3)] = value
end
end
for _, rowinfo in ipairs(row_infos) do
if type(rowinfo.fields) == 'function' then
rowinfo.fields = rowinfo.fields()
end
for index, field in ipairs(rowinfo.fields) do
rowinfo.options[index] = rowinfo.options[index] or {}
fields[#fields+1] = field
tables_assoc[m_util.string.split(field, '%.')[1]] = true
end
end
-- reformat the fields & tables so they can be retrieved correctly
for index, field in ipairs(fields) do
fields[index] = string.format('%s=%s', field, field)
end
local tables = {}
for table_name,_ in pairs(tables_assoc) do
tables[#tables+1] = table_name
end
-- take care of required joins according to the tables
local joins = {}
for index, table_name in ipairs(tables) do
if table_name ~= 'items' then
joins[#joins+1] = string.format('items._pageID=%s._pageID', table_name)
end
end
if #joins > 0 and query.join then
query.join = table.concat(joins, ',') .. ',' .. query.join
elseif #joins > 0 and not query.join then
query.join = table.concat(joins, ',')
elseif #joins == 0 and query.join then
-- leave query.join as is
end
-- Cargo workaround: avoid duplicates using groupBy
query.groupBy = 'items._pageID' .. (query.groupBy or '')
query.limit = tonumber(query.limit)
if query.limit == nil then
query.limit = c.query_default
elseif query.limit > c.query_max then
query.limit = c.query_max
end
local results = cargo.query(
table.concat(tables,',') .. (query.tables or ''),
table.concat(fields,','),
query
)
if #results == 0 and tpl_args.default ~= nil then
return tpl_args.default
end
if #results > 0 and #stat_columns > 0 then
local pages = {}
for _, row in ipairs(results) do
pages[#pages+1] = string.format('item_stats._pageID="%s"', row['items._pageID'])
end
local query_stat_ids = {}
for stat_id, _ in pairs(query_stats) do
query_stat_ids[#query_stat_ids+1] = string.format('item_stats.id="%s"', stat_id)
end
if tpl_args.q_where then
tpl_args.q_where = string.format(' AND (%s)', tpl_args.q_where)
else
tpl_args.q_where = ''
end
local temp = m_util.cargo.query(
{'items', 'item_stats'},
{'item_stats._pageName', 'item_stats.id', 'item_stats.min', 'item_stats.max', 'item_stats.avg'},
{
where=string.format('item_stats.is_implicit IS NULL AND (%s) AND (%s)', table.concat(query_stat_ids, ' OR '), table.concat(pages, ' OR ')),
join='items._pageID=item_stats._pageID',
-- Cargo workaround: avoid duplicates using groupBy
groupBy='items._pageID, item_stats.id',
limit=5000,
}
)
for _, row in ipairs(temp) do
local stat = {
min = tonumber(row['item_stats.min']),
max = tonumber(row['item_stats.max']),
avg = tonumber(row['item_stats.avg']),
}
if stat_results[row['item_stats._pageName']] == nil then
stat_results[row['item_stats._pageName']] = {[row['item_stats.id']] = stat}
else
stat_results[row['item_stats._pageName']][row['item_stats.id']] = stat
end
end
-- Cargo doesnt support offset yet
if #temp == 5000 then
--TODO: Cargo
error('Stats > 5000')
end
end
local tbl = mw.html.create('table')
tbl:attr('class', 'wikitable sortable item-table')
-- Header
local tr = tbl:tag('tr')
tr
:tag('th')
:wikitext(modes[tpl_args.mode].header)
:done()
for _, row_info in ipairs(row_infos) do
tr
:tag('th')
:attr('data-sort-type', row_info.sort_type or 'number')
:wikitext(row_info.header)
:done()
end
for _, row in ipairs(results) do
tr = tbl:tag('tr')
local il_args = {
skip_query=true,
page=row['items._pageName'],
name=row['items.name'],
inventory_icon=row['items.inventory_icon'],
html=row['items.html'],
width=row['items.size_x'],
height=row['items.size_y'],
}
if tpl_args.large then
il_args.large = tpl_args.large
end
tr
:tag('td')
:wikitext(f_item_link(il_args))
:done()
for _, rowinfo in ipairs(row_infos) do
-- this has been cast from a function in an earlier step
local display = true
for index, field in ipairs(rowinfo.fields) do
-- this will bet set to an empty value not nil confusingly
if row[field] == '' then
if rowinfo.options[index].optional ~= true then
display = false
break
else
row[field] = nil
end
end
end
if display then
rowinfo.display(tr, row, rowinfo.fields)
else
tr:wikitext(m_util.html.td.na())
end
end
end
cats = {}
if #results == query.limit then
cats[#cats+1] = i18n.categories.query_limit
end
if #results == c.query_max then
cats[#cats+1] = i18n.categories.query_hard_limit
end
return tostring(tbl) .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})
end
-- ----------------------------------------------------------------------------
-- Debug stuff
-- ----------------------------------------------------------------------------
p.debug = {}
function p.debug._tbl_data(tbl)
keys = {}
for _, data in ipairs(data_map.generic_item) do
if type(data.arg) == 'string' then
keys[data.arg] = 1
elseif type(data.arg) == 'table' then
for _, arg in ipairs(data.arg) do
keys[arg] = 1
end
end
end
local out = {}
for key, _ in pairs(keys) do
out[#out+1] = string.format("['%s'] = '1'", key)
end
return table.concat(out, ', ')
end
function p.debug.generic_item_all()
return p.debug._tbl_data(data_map.generic_item)
end
function p.debug.skill_gem_all()
return p.debug._tbl_data(data_map.skill_gem_new)
end
-- ----------------------------------------------------------------------------
-- Return
-- ----------------------------------------------------------------------------
return p