[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:Item table
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',
no_results = 'Item tables without results',
},
-- 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 (1.5x in part 2, 2x in maps)'),
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'),
},
prophecy_description = {
objective = 'Objective',
reward = 'Reward',
},
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, fields, data2)
local values = {}
local fmt_values = {}
local sdata = data2.skill_levels[data['items._pageName']]
for index, field in ipairs(fields) do
local value = {
min=data[field],
max=data[field],
}
if sdata then
value.min = value.min or sdata['0'][field] or sdata['1'][field]
value.max = value.max or sdata['0'][field] or sdata[data['skill.max_level']][field]
end
if value.min then
values[#values+1] = value.max
fmt_values[#fmt_values+1] = h.format_value(nil, nil, value, args.options[index] or {})
end
end
if #values == 0 then
tr:wikitext(m_util.html.td.na())
else
local td = tr:tag('td')
td:attr('data-sort-value', table.concat(values, ', '))
td:wikitext(table.concat(fmt_values, ', '))
if args.colour then
td:attr('class', 'tc -' .. args.colour)
end
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
function h.tbl.display.factory.descriptor_value(args)
-- Arguments:
-- key
-- tbl
args = args or {}
return function (tpl_args, frame, value)
args.tbl = args.tbl or tpl_args
if args.tbl[args.key] then
value = m_util.html.abbr(value, args.tbl[args.key])
end
return value
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%%',
skill_levels = true,
},
}},
order = 4000,
options = {
[1] = {
skill_levels = true,
},
},
},
{
arg = 'cast_time',
header = i18n.item_table.cast_time,
fields = {'skill.cast_time'},
display = h.tbl.display.factory.value{options = {
}},
order = 4001,
options = {
},
},
{
arg = 'dmgeff',
header = i18n.item_table.damage_effectiveness,
fields = {'skill_levels.damage_effectiveness'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='%s%%',
skill_levels = true,
},
}},
order = 4002,
options = {
[1] = {
skill_levels = true,
},
},
},
{
arg = 'mcm',
header = i18n.item_table.mana_cost_multiplier,
fields = {'skill_levels.mana_multiplier'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='%s%%',
skill_levels = true,
},
}},
order = 5000,
options = {
[1] = {
skill_levels = true,
},
},
},
{
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, fields, data2)
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
h.tbl.display.factory.value{options = {
[1] = {
fmt='%d' .. appendix,
skill_levels = true,
},
}}(tr, data, {'skill_levels.mana_cost'}, data2)
end,
order = 5001,
options = {
[1] = {
skill_levels = true,
},
},
},
{
arg = 'vaal',
header = i18n.item_table.vaal_souls_requirement,
fields = {'skill_levels.vaal_souls_requirement'},
display = h.tbl.display.factory.value{options = {
[1] = {
skill_levels = true,
},
}},
order = 6000,
options = {
[1] = {
skill_levels = true,
},
},
},
{
arg = 'vaal',
header = i18n.item_table.stored_uses,
fields = {'skill_levels.vaal_stored_uses'},
display = h.tbl.display.factory.value{options = {
[1] = {
skill_levels = true,
},
}},
order = 6001,
options = {
[1] = {
skill_levels = true,
},
},
},
{
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(h.tbl.display.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(h.tbl.display.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(h.tbl.display.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 results2 = {
stats = {},
skill_levels = {},
}
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 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 = (results2.stats[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 = (results2.stats[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 skill_levels = {}
--
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 {}
if rowinfo.options[index].skill_levels then
skill_levels[#skill_levels+1] = field
else
fields[#fields+1] = field
tables_assoc[m_util.string.split(field, '%.')[1]] = true
end
end
end
if #skill_levels > 0 then
fields[#fields+1] = 'skill.max_level'
tables_assoc.skill = true
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 then
-- fetch skill level information
if #skill_levels > 0 then
skill_levels[#skill_levels+1] = 'skill_levels._pageName'
skill_levels[#skill_levels+1] = 'skill_levels.level'
local pages = {}
for _, row in ipairs(results) do
pages[#pages+1] = string.format('(skill_levels._pageID="%s" AND skill_levels.level IN (0, 1, %s))', row['items._pageID'], row['skill.max_level'])
end
local temp = m_util.cargo.query(
{'skill_levels'},
skill_levels,
{
where=table.concat(pages, ' OR '),
groupBy='skill_levels._pageID, skill_levels.level',
limit=5000,
}
)
-- map to results
for _, row in ipairs(temp) do
if results2.skill_levels[row['skill_levels._pageName']] == nil then
results2.skill_levels[row['skill_levels._pageName']] = {}
end
-- TODO: convert to int?
results2.skill_levels[row['skill_levels._pageName']][row['skill_levels.level']] = row
end
end
if #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 results2.stats[row['item_stats._pageName']] == nil then
results2.stats[row['item_stats._pageName']] = {[row['item_stats.id']] = stat}
else
results2.stats[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
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
local opts = rowinfo.options[index]
if opts.optional ~= true and opts.skill_levels ~= true then
display = false
break
else
row[field] = nil
end
end
end
if display then
rowinfo.display(tr, row, rowinfo.fields, results2)
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
if #results == 0 then
cats[#cats+1] = i18n.categories.no_results
end
return tostring(tbl) .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})
end
-------------------------------------------------------------------------------
-- Map item drops
-------------------------------------------------------------------------------
function p.map_item_drops(frame)
--[[
Gets the area id from the map item and activates
Template:Area_item_drops.
Examples:
= p.map_item_drops{page='Underground River Map (War for the Atlas)'}
]]
-- Get args
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle())
local results = m_util.cargo.query(
{'maps'},
{'maps.area_id'},
{
where=string.format('maps._pageName="%s" AND maps.area_id IS NOT NULL', tpl_args.page),
-- Only need each page name once
groupBy='maps._pageName',
}
)
local id = ''
if #results > 0 then
id = results[1]['maps.area_id']
end
return frame:expandTemplate{ title = 'Area item drops', args = {area_id=id} }
end
-------------------------------------------------------------------------------
-- Prophecy description
-------------------------------------------------------------------------------
function p.prophecy_description(frame)
-- Get args
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle())
local results = m_util.cargo.query(
{'prophecies'},
{'prophecies.objective', 'prophecies.reward'},
{
where=string.format('prophecies._pageName="%s"', tpl_args.page),
-- Only need each page name once
groupBy='prophecies._pageName',
}
)
results = results[1]
local out = {}
if results['prophecies.objective'] then
out[#out+1] = string.format('<h2>%s</h2>', i18n.prophecy_description.objective)
out[#out+1] = results['prophecies.objective']
end
if results['prophecies.reward'] then
out[#out+1] = string.format('<h2>%s</h2>', i18n.prophecy_description.reward)
out[#out+1] = results['prophecies.reward']
end
return table.concat(out, '\n')
end
-- ----------------------------------------------------------------------------
-- Debug stuff
-- ----------------------------------------------------------------------------
p.debug = {}
function p.debug._tbl_data(tbl)
keys = {}
for _, data in ipairs(tbl) 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