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.
-------------------------------------------------------------------------------
--
-- Module:Item table
--
-- This module implements Template:Item table and other templates that query
-- and display tables or lists of items.
-------------------------------------------------------------------------------
require('Module:No globals')
local m_util = require('Module:Util')
local m_cargo = require('Module:Cargo')
-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox('Item table')
local m_game = use_sandbox and mw.loadData('Module:Game/sandbox') or mw.loadData('Module:Game')
-- Lazy loading
local m_item_util -- require('Module:Item util')
local f_item_link -- require('Module:Item link').item_link
local f_skill_link -- require('Module:Skill link').skill_link
-- The cfg table contains all localisable strings and configuration, to make it
-- easier to port this module to another wiki.
local cfg = use_sandbox and mw.loadData('Module:Item table/config/sandbox') or mw.loadData('Module:Item table/config')
local i18n = cfg.i18n
-- ----------------------------------------------------------------------------
-- Helper functions
-- ----------------------------------------------------------------------------
local h = {}
h.string = {}
function h.string.format(str, vars)
--[[
Allow string replacement using named arguments.
TODO:
* Support %d ?
* Support 0.2f ?
Parameters
----------
str : String to replace.
vars : Table of arguments.
Examples
--------
= h.string.format('{foo} is {bar}.', {foo='Dinner', bar='nice'})
References
----------
http://lua-users.org/wiki/StringInterpolation
]]
if not vars then
vars = str
str = vars[1]
end
return (string.gsub(str, "({([^}]+)})",
function(whole, i)
return vars[i] or whole
end))
end
-- Lazy loading for Module:Item link
function h.item_link(args)
if not f_item_link then
f_item_link = require('Module:Item link').item_link
end
return f_item_link(args)
end
-- Lazy loading for Module:Skill link
function h.skill_link(args)
if not f_skill_link then
f_skill_link = require('Module:Skill link').skill_link
end
return f_skill_link(args)
end
function h.na_or_val(tr, value, func)
if value == nil or value == '' then
tr:wikitext(m_util.html.td.na())
else
local raw_value = value
if func ~= nil then
value = func(value)
end
tr
:tag('td')
:attr('data-sort-value', raw_value)
:wikitext(value)
:done()
end
end
h.tbl = {}
function h.tbl.range_fields(args)
local suffixes = {'maximum', 'text', 'colour'}
if args.full then
suffixes[#suffixes+1] = 'minimum'
end
return function()
local fields = {}
local inner = function (field)
for _, partial_field in ipairs(suffixes) do
fields[#fields+1] = string.format('%s_range_%s', field, partial_field)
end
end
if type(args.field) == 'table' then
for _, field in ipairs(args.field) do
inner(field)
end
else
inner(args.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],
base=data[field],
}
if sdata then --For skill data
-- Use the fixed value, or the first-level value.
value.min = value.min or sdata['0'][field] or sdata['1'][field]
-- Fall back to the fixed value, and then the max level value.
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
local opts = args.options[index] or {}
-- global colour is set, no overrides
if args.colour ~= nil then
opts.no_color = true
end
fmt_values[#fmt_values+1] = m_util.html.format_value(nil, value, opts)
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.range_composite(args)
-- division by default
if args.func == nil then
args.func = function (a, b)
if b == 0 then
return 'fail'
end
return a / b
end
end
return function(tr, data, fields)
local field = {}
for i=1, 2 do
local fieldn = args['field' .. i]
field[i] = {
min = tonumber(data[string.format('%s_range_minimum', fieldn)]) or 0,
max = tonumber(data[string.format('%s_range_maximum', fieldn)]) or 0,
color = data[string.format('%s_range_colour', fieldn)] or 'default',
}
end
field.min = args.func(field[1].min, field[2].min)
field.max = args.func(field[1].max, field[2].max)
if field.min == 'fail' or field.max == 'fail' then
field.text = ''
field.color = 'default'
else
for i=1, 2 do
if field[i].color ~= 'default' then
field.color = field[i].color
break
end
end
if field.min == field.max then
field.text = string.format('%.2f', field.min)
else
field.text = string.format('(%.2f-%.2f)', field.min, field.max)
end
end
tr
:tag('td')
:attr('data-sort-value', field.max)
:attr('class', 'tc -' .. field.color)
:wikitext(field.text)
:done()
end
end
function h.tbl.display.factory.descriptor_value(args)
-- Arguments:
-- key
-- tbl
args = args or {}
return function (value)
if args.tbl[args.key] then
value = m_util.html.abbr(value, args.tbl[args.key])
end
return value
end
end
function h.tbl.display.factory.atlas_tier(args)
args = args or {}
return function (tr, data)
for i=0,4 do
local t = tonumber(data['atlas_maps.map_tier' .. i])
if t == 0 then
tr
:tag('td')
:attr('table-sort-value', 0)
:attr('class', 'table-cell-xmark')
:wikitext('✗')
else
if args.is_level then
t = t + 67
end
tr
:tag('td')
:attr('class', 'tc -value')
:wikitext(t)
:done()
end
end
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 = 'rarity',
header = i18n.item_table.rarity,
fields = {'items.rarity'},
display = h.tbl.display.factory.value{},
order = 1002,
},
{
arg = 'rarity_id',
header = i18n.item_table.rarity_id,
fields = {'items.rarity_id'},
display = h.tbl.display.factory.value{},
order = 1003,
},
{
arg = 'metadata_id',
header = i18n.item_table.metadata_id,
fields = {'items.metadata_id'},
display = h.tbl.display.factory.value{},
order = 1004,
},
{
arg = 'essence',
header = i18n.item_table.essence_level,
fields = {'essences.level'},
display = h.tbl.display.factory.value{},
order = 1100,
},
{
arg = {'drop', 'drop_level'},
header = i18n.item_table.drop_level,
fields = {'items.drop_level'},
display = h.tbl.display.factory.value{},
order = 1200,
},
{
arg = {'drop_level_maximum'},
header = i18n.item_table.drop_level_maximum,
fields = {'items.drop_level_maximum'},
display = h.tbl.display.factory.value{},
order = 1201,
},
{
arg = 'stack_size',
header = i18n.item_table.stack_size,
fields = {'stackables.stack_size'},
display = h.tbl.display.factory.value{},
order = 1300,
},
{
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 = 1301,
},
-- Requirements
{
arg = 'level',
header = m_game.level_requirement.icon,
fields = h.tbl.range_fields{field='items.required_level'},
display = h.tbl.display.factory.range{field='items.required_level'},
order = 1400,
},
{
arg = 'str',
header = '[[File:StrengthIcon small.png|link=|alt=Required strength]]',
fields = h.tbl.range_fields{field='items.required_strength'},
display = h.tbl.display.factory.range{field='items.required_strength'},
order = 1401,
},
{
arg = 'dex',
header = '[[File:DexterityIcon small.png|link=|alt=Required dexterity]]',
fields = h.tbl.range_fields{field='items.required_dexterity'},
display = h.tbl.display.factory.range{field='items.required_dexterity'},
order = 1402,
},
{
arg = 'int',
header = '[[File:IntelligenceIcon small.png|link=|alt=Required intelligence]]',
fields = h.tbl.range_fields{field='items.required_intelligence'},
display = h.tbl.display.factory.range{field='items.required_intelligence'},
order = 1403,
},
-- Armour 15xx
{
arg = 'ar',
header = i18n.item_table.armour,
fields = h.tbl.range_fields{field='armours.armour'},
display = h.tbl.display.factory.range{field='armours.armour'},
order = 1500,
},
{
arg = 'ev',
header =i18n.item_table.evasion,
fields = h.tbl.range_fields{field='armours.evasion'},
display = h.tbl.display.factory.range{field='armours.evasion'},
order = 1501,
},
{
arg = 'es',
header = i18n.item_table.energy_shield,
fields = h.tbl.range_fields{field='armours.energy_shield'},
display = h.tbl.display.factory.range{field='armours.energy_shield'},
order = 1502,
},
{
arg = 'wd',
header = i18n.item_table.ward,
fields = h.tbl.range_fields{field='armours.ward'},
display = h.tbl.display.factory.range{field='armours.ward'},
order = 1503,
},
{
arg = 'block',
header = i18n.item_table.block,
fields = h.tbl.range_fields{field='shields.block'},
display = h.tbl.display.factory.range{field='shields.block'},
order = 1504,
},
--[[{
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,
},]]--
-- Weapons 16xx
{
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 = 1600,
},
{
arg = {'weapon', 'aps'},
header = i18n.item_table.attacks_per_second,
fields = h.tbl.range_fields{field='weapons.attack_speed'},
display = h.tbl.display.factory.range{field='weapons.attack_speed'},
order = 1601,
},
{
arg = {'weapon', 'crit'},
header = i18n.item_table.local_critical_strike_chance,
fields = h.tbl.range_fields{field='weapons.critical_strike_chance'},
display = h.tbl.display.factory.range{field='weapons.critical_strike_chance'},
order = 1602,
},
{
arg = {'physical_dps'},
header = i18n.item_table.physical_dps,
fields = h.tbl.range_fields{field='weapons.physical_dps'},
display = h.tbl.display.factory.range{field='weapons.physical_dps'},
order = 1603,
},
{
arg = {'lightning_dps'},
header = i18n.item_table.lightning_dps,
fields = h.tbl.range_fields{field='weapons.lightning_dps'},
display = h.tbl.display.factory.range{field='weapons.lightning_dps'},
order = 1604,
},
{
arg = {'cold_dps'},
header = i18n.item_table.cold_dps,
fields = h.tbl.range_fields{field='weapons.cold_dps'},
display = h.tbl.display.factory.range{field='weapons.cold_dps'},
order = 1605,
},
{
arg = {'fire_dps'},
header = i18n.item_table.fire_dps,
fields = h.tbl.range_fields{field='weapons.fire_dps'},
display = h.tbl.display.factory.range{field='weapons.fire_dps'},
order = 1606,
},
{
arg = {'chaos_dps'},
header = i18n.item_table.chaos_dps,
fields = h.tbl.range_fields{field='weapons.chaos_dps'},
display = h.tbl.display.factory.range{field='weapons.chaos_dps'},
order = 1607,
},
{
arg = {'elemental_dps'},
header = i18n.item_table.elemental_dps,
fields = h.tbl.range_fields{field='weapons.elemental_dps'},
display = h.tbl.display.factory.range{field='weapons.elemental_dps'},
order = 1608,
},
{
arg = {'poison_dps'},
header = i18n.item_table.poison_dps,
fields = h.tbl.range_fields{field='weapons.poison_dps'},
display = h.tbl.display.factory.range{field='weapons.poison_dps'},
order = 1609,
},
{
arg = {'dps'},
header = i18n.item_table.dps,
fields = h.tbl.range_fields{field='weapons.dps'},
display = h.tbl.display.factory.range{field='weapons.dps'},
order = 1610,
},
-- Flasks 17xx
{
arg = 'flask_life',
header = i18n.item_table.flask_life,
fields = h.tbl.range_fields{field='flasks.life'},
display = h.tbl.display.factory.range{field='flasks.life'},
order = 1700,
},
{
arg = 'flask_life_per_second',
header = i18n.item_table.flask_life_per_second,
fields = h.tbl.range_fields{field={'flasks.life', 'flasks.duration'}, full=true},
display = h.tbl.display.factory.range_composite{field1='flasks.life', field2='flasks.duration'},
order = 1701,
},
{
arg = 'flask_life_per_charge',
header = i18n.item_table.flask_life_per_charge,
fields = h.tbl.range_fields{field={'flasks.life', 'flasks.charges_per_use'}, full=true},
display = h.tbl.display.factory.range_composite{field1='flasks.life', field2='flasks.charges_per_use'},
order = 1702,
},
{
arg = 'flask_mana',
header = i18n.item_table.flask_mana,
fields = h.tbl.range_fields{field='flasks.mana'},
display = h.tbl.display.factory.range{field='flasks.mana'},
order = 1703,
},
{
arg = 'flask_mana_per_second',
header = i18n.item_table.flask_mana_per_second,
fields = h.tbl.range_fields{field={'flasks.mana', 'flasks.duration'}, full=true},
display = h.tbl.display.factory.range_composite{field1='flasks.mana', field2='flasks.duration'},
order = 1704,
},
{
arg = 'flask_mana_per_charge',
header = i18n.item_table.flask_mana_per_charge,
fields = h.tbl.range_fields{field={'flasks.mana', 'flasks.charges_per_use'}, full=true},
display = h.tbl.display.factory.range_composite{field1='flasks.mana', field2='flasks.charges_per_use'},
order = 1705,
},
{
arg = 'flask',
header = i18n.item_table.flask_duration,
fields = h.tbl.range_fields{field='flasks.duration'},
display = h.tbl.display.factory.range{field='flasks.duration'},
order = 1706,
},
{
arg = 'flask',
header = i18n.item_table.flask_charges_per_use,
fields = h.tbl.range_fields{field='flasks.charges_per_use'},
display = h.tbl.display.factory.range{field='flasks.charges_per_use'},
order = 1707,
},
{
arg = 'flask',
header = i18n.item_table.flask_maximum_charges,
fields = h.tbl.range_fields{field='flasks.charges_max'},
display = h.tbl.display.factory.range{field='flasks.charges_max'},
order = 1708,
},
-- Jewels 18xx
{
arg = 'jewel_limit',
header = i18n.item_table.jewel_limit,
fields = {'jewels.jewel_limit'},
display = h.tbl.display.factory.value{},
order = 1800,
},
{
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 = 1801,
},
-- Maps 19xx
{
arg = 'map_tier',
header = i18n.item_table.map_tier,
fields = {'maps.tier'},
display = h.tbl.display.factory.value{},
order = 1900,
},
{
arg = 'map_level',
header = i18n.item_table.map_level,
fields = {'maps.area_level'},
display = h.tbl.display.factory.value{},
order = 1901,
},
{
arg = 'map_guild_character',
header = i18n.item_table.map_guild_character,
fields = {'maps.guild_character'},
display = h.tbl.display.factory.value{},
order = 1902,
sort_type = 'text',
},
{
arg = 'map_series',
header = i18n.item_table.map_series,
fields = {'maps.series'},
display = h.tbl.display.factory.value{},
order = 1903,
},
{
arg = 'atlas_tier',
header = i18n.item_table.atlas_tier,
colspan = 5,
fields = {'atlas_maps.map_tier0', 'atlas_maps.map_tier1', 'atlas_maps.map_tier2', 'atlas_maps.map_tier3', 'atlas_maps.map_tier4'},
display = h.tbl.display.factory.atlas_tier{is_level=false},
order = 1904,
},
{
arg = 'atlas_level',
header = i18n.item_table.atlas_level,
colspan = 5,
fields = {'atlas_maps.map_tier0', 'atlas_maps.map_tier1', 'atlas_maps.map_tier2', 'atlas_maps.map_tier3', 'atlas_maps.map_tier4'},
display = h.tbl.display.factory.atlas_tier{is_level=true},
order = 1905,
},
-- Hideout decorations 20xx
{
arg = {'doodad', 'variation_count'},
header = i18n.item_table.variation_count,
fields = {'hideout_doodads.variation_count'},
display = h.tbl.display.factory.value{colour='mod'},
order = 2000,
},
-- Corpse items 21xx
{
arg = {'corpse', 'monster_category'},
header = i18n.item_table.monster_category,
fields = {'corpse_items.monster_category', 'corpse_items.monster_category_html'},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', m_game.constants.monster.categories[data['corpse_items.monster_category']].id)
:wikitext(data['corpse_items.monster_category_html'])
end,
order = 2100,
},
{
arg = {'corpse', 'monster_abilities'},
header = i18n.item_table.monster_abilities,
fields = {'corpse_items.monster_abilities'},
display = h.tbl.display.factory.value{colour='mod'},
order = 2101,
sort_type = 'text',
},
-- Seed data 91xx,
{
arg = {'seed', 'seed_type'},
header = i18n.item_table.seed_type,
fields = {'harvest_seeds.type', 'harvest_seeds.type_id'},
display = function (tr, data)
tr
:tag('td')
:attr('table-sort-value', data['harvest_seeds.type'])
:attr('class', 'tc -' .. data['harvest_seeds.type_id'])
:wikitext(data['harvest_seeds.type'])
end,
order = 9100,
},
{
arg = {'seed', 'seed_tier'},
header = i18n.item_table.seed_tier,
fields = {'harvest_seeds.tier'},
display = h.tbl.display.factory.value{},
order = 9101,
},
{
arg = {'seed', 'seed_growth_cycles'},
header = i18n.item_table.seed_growth_cycles,
fields = {'harvest_seeds.growth_cycles'},
display = h.tbl.display.factory.value{},
order = 9102,
},
{
arg = {'seed', 'seed_cosumed_lifeforce', 'seed_consumed_lifeforce_percentage', 'seed_consumed_primal_lifeforce_percentage'},
header = i18n.item_table.seed_consumed_primal_lifeforce_percentage,
fields = {'harvest_seeds.consumed_primal_lifeforce_percentage'},
display = h.tbl.display.factory.value{color='primal'},
order = 9110,
},
{
arg = {'seed', 'seed_cosumed_lifeforce', 'seed_consumed_lifeforce_percentage', 'seed_consumed_vivid_lifeforce_percentage'},
header = i18n.item_table.seed_consumed_vivid_lifeforce_percentage,
fields = {'harvest_seeds.consumed_vivid_lifeforce_percentage'},
display = h.tbl.display.factory.value{color='vivid'},
order = 9110,
},
{
arg = {'seed', 'seed_cosumed_lifeforce', 'seed_consumed_lifeforce_percentage', 'seed_consumed_wild_lifeforce_percentage'},
header = i18n.item_table.seed_consumed_wild_lifeforce_percentage,
fields = {'harvest_seeds.consumed_wild_lifeforce_percentage'},
display = h.tbl.display.factory.value{color='wild'},
order = 9110,
},
{
arg = {'seed', 'seed_required_nearby_seeds', 'seed_required_nearby_seed_amount'},
header = i18n.item_table.seed_required_nearby_seed_amount,
fields = {'harvest_seeds.required_nearby_seed_amount'},
display = h.tbl.display.factory.value{},
order = 9113,
},
{
arg = {'seed', 'seed_required_nearby_seeds', 'seed_required_nearby_seed_tier'},
header = i18n.item_table.seed_required_nearby_seed_tier,
fields = {'harvest_seeds.required_nearby_seed_tier'},
display = h.tbl.display.factory.value{},
order = 9114,
},
{
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 = {'seed', 'seed_effect'},
header = i18n.item_table.effects,
fields = {'harvest_seeds.effect'},
display = h.tbl.display.factory.value{colour='crafted'},
order = 12003,
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 = 12006,
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 = 12008,
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 = {'version', 'release_version'},
header = i18n.item_table.release_version,
fields = {'items.release_version'},
display = function(tr, data)
tr
:tag('td')
:wikitext(
string.format(
i18n.item_table.version_link,
data['items.release_version'],
data['items.release_version']
)
)
end,
order = 15000,
},
{
arg = {'version', 'removal_version'},
header = i18n.item_table.removal_version,
fields = {'items.removal_version'},
display = function(tr, data)
tr
:tag('td')
:wikitext(
string.format(
i18n.item_table.version_link,
data['items.removal_version'],
data['items.removal_version']
)
)
end,
order = 15001,
},
{
arg = {'drop', 'drop_enabled'},
header = i18n.item_table.drop_enabled,
fields = {'items.drop_enabled'},
display = h.tbl.display.factory.value{},
order = 15002,
},
{
arg = {'drop', 'drop_areas'},
header = i18n.item_table.drop_areas,
fields = {'items.drop_areas_html'},
display = h.tbl.display.factory.value{},
order = 15004,
sort_type = 'text',
},
{
arg = {'drop', 'drop_monsters'},
header = i18n.item_table.drop_monsters,
fields = {'items.drop_monsters'},
display = function(tr, data, na, results2)
if results2['drop_monsters_query'] == nil then
results2['drop_monsters_query'] = m_cargo.query(
{'items', 'monsters', 'main_pages'},
{
'items._pageName',
'monsters._pageName',
'monsters.name',
'main_pages._pageName',
},
{
join=[[
items.drop_monsters HOLDS monsters.metadata_id,
monsters.metadata_id=main_pages.id
]],
where=string.format([[
items._pageID IN (%s)
AND monsters.metadata_id IS NOT NULL
]],
table.concat(results2.pageIDs, ', ')
),
orderBy='items.drop_monsters',
}
)
results2['drop_monsters_query'] = m_cargo.map_results_to_id{
results=results2['drop_monsters_query'],
field='items._pageName',
}
end
local results = results2['drop_monsters_query'][data['items._pageName']] or {}
local tbl = {}
for _,v in ipairs(results) do
local page = v['main_pages._pageName'] or v['monsters._pageName'] or ''
local name = v['monsters.name'] or v['items.drop_monsters'] or ''
tbl[#tbl+1] = string.format('[[%s|%s]]', page, name)
end
h.na_or_val(tr, table.concat(tbl, '<br>'))
end,
order = 15005,
sort_type = 'text',
},
{
arg = {'drop', 'drop_text'},
header = i18n.item_table.drop_text,
fields = {'items.drop_text'},
display = h.tbl.display.factory.value{},
order = 15006,
sort_type = 'text',
},
{
arg = {'quest'},
header = i18n.item_table.quest_rewards,
fields = {'items._pageName'},
display = function(tr, data, na, results2)
if results2['quest_query'] == nil then
results2['quest_query'] = m_cargo.query(
{'items', 'quest_rewards'},
{
'quest_rewards._pageName',
'quest_rewards.classes',
'quest_rewards.act',
'quest_rewards.quest'
},
{
join='items._pageName=quest_rewards._pageName',
where=string.format(
'items._pageID IN (%s) AND quest_rewards._pageName IS NOT NULL',
table.concat(results2.pageIDs, ', ')
),
orderBy='quest_rewards.act, quest_rewards.quest',
}
)
results2['quest_query'] = m_cargo.map_results_to_id{
results=results2['quest_query'],
field='quest_rewards._pageName',
}
end
local results = results2['quest_query'][data['items._pageName']] or {}
local tbl = {}
for _, v in ipairs(results) do
local classes = table.concat(m_util.string.split(v['quest_rewards.classes'] or '', ',%s*'), ', ')
if classes == '' or classes == nil then
classes = i18n.item_table.quest_rewards_any_classes
end
tbl[#tbl+1] = string.format(
i18n.item_table.quest_rewards_row_format,
v['quest_rewards.act'],
v['quest_rewards.quest'],
classes
)
end
local value = table.concat(tbl, '<br>')
if value == nil or value == '' then
tr:wikitext(m_util.html.td.na())
else
tr
:tag('td')
:attr('style', 'text-align:left')
:wikitext(value)
end
end,
order = 16001,
sort_type = 'text',
},
{
arg = {'vendor'},
header = i18n.item_table.vendor_rewards,
fields = {'items._pageName'},
display = function(tr, data, na, results2)
if results2['vendor_query'] == nil then
results2['vendor_query'] = m_cargo.query(
{'items', 'vendor_rewards'},
{
'vendor_rewards._pageName',
'vendor_rewards.classes',
'vendor_rewards.act',
'vendor_rewards.npc',
'vendor_rewards.quest',
},
{
join='items._pageName=vendor_rewards._pageName',
where=string.format(
'items._pageID IN (%s) AND vendor_rewards._pageName IS NOT NULL',
table.concat(results2.pageIDs, ', ')
),
orderBy='vendor_rewards.act, vendor_rewards.quest',
}
)
results2['vendor_query'] = m_cargo.map_results_to_id{
results=results2['vendor_query'],
field='vendor_rewards._pageName',
}
end
local results = results2['vendor_query'][data['items._pageName']] or {}
local tbl = {}
for _, v in ipairs(results) do
local classes = table.concat(m_util.string.split(v['vendor_rewards.classes'] or '', ',%s*'), ', ')
if classes == '' or classes == nil then
classes = i18n.item_table.vendor_rewards_any_classes
end
tbl[#tbl+1] = string.format(
i18n.item_table.vendor_rewards_row_format,
v['vendor_rewards.act'],
v['vendor_rewards.quest'],
v['vendor_rewards.npc'],
classes
)
end
local value = table.concat(tbl, '<br>')
if value == nil or value == '' then
tr:wikitext(m_util.html.td.na())
else
tr
:tag('td')
:attr('style', 'text-align:left')
:wikitext(value)
end
end,
order = 17001,
sort_type = 'text',
},
{
arg = {'price', 'purchase_cost'},
header = i18n.item_table.purchase_costs,
fields = {'item_purchase_costs.name', 'item_purchase_costs.amount'},
display = function (tr, data)
-- Can purchase costs have multiple currencies and rows?
-- Just switch to the same method as in sell_price then.
local tbl = {}
if data['item_purchase_costs.name'] ~= nil then
tbl[#tbl+1] = string.format(
'%sx %s',
data['item_purchase_costs.amount'],
h.item_link{data['item_purchase_costs.name']}
)
end
h.na_or_val(tr, table.concat(tbl, '<br>'))
end,
order = 18001,
sort_type = 'text',
},
{
arg = {'price', 'sell_price'},
header = i18n.item_table.sell_price,
fields = {'item_sell_prices.name', 'item_sell_prices.amount'},
display = function(tr, data, na, results2)
if results2['sell_price_query'] == nil then
results2['sell_price_query'] = m_cargo.query(
{'items', 'item_sell_prices'},
{
'item_sell_prices.name',
'item_sell_prices.amount',
'item_sell_prices._pageID'
},
{
join='items._pageID=item_sell_prices._pageID',
where=string.format(
'items._pageID IN (%s) AND item_sell_prices._pageID IS NOT NULL',
table.concat(results2.pageIDs, ', ')
),
orderBy='item_sell_prices.name',
}
)
results2['sell_price_query'] = m_cargo.map_results_to_id{
results=results2['sell_price_query'],
field='item_sell_prices._pageID',
}
end
local results = results2['sell_price_query'][data['items._pageID']] or {}
local tbl = {}
for _,v in ipairs(results) do
tbl[#tbl+1] = string.format(
'%sx %s',
v['item_sell_prices.amount'],
h.item_link{v['item_sell_prices.name']}
)
end
h.na_or_val(tr, table.concat(tbl, '<br>'))
end,
order = 18002,
sort_type = 'text',
},
{
arg = {'boss', 'boss_name'},
header = i18n.item_table.boss_name,
fields = {'maps.area_id'},
display = function(tr, data, na, results2)
if results2['boss_query'] == nil then
results2['boss_query'] = m_cargo.query(
{'items', 'maps', 'areas', 'monsters', 'main_pages'},
{
'items._pageName',
'maps.area_id',
'areas.id',
'areas.boss_monster_ids',
'monsters._pageName',
'monsters.name',
'main_pages._pageName',
},
{
join=[[
items._pageID=maps._pageID,
maps.area_id=areas.id,
areas.boss_monster_ids HOLDS monsters.metadata_id,
monsters.metadata_id=main_pages.id
]],
where=string.format([[
items._pageID IN (%s)
AND maps.area_id IS NOT NULL
AND areas.boss_monster_ids HOLDS LIKE "%%"
]],
table.concat(results2.pageIDs, ', ')
),
orderBy='areas.boss_monster_ids',
}
)
results2['boss_query'] = m_cargo.map_results_to_id{
results=results2['boss_query'],
field='items._pageName',
}
end
local results = results2['boss_query'][data['items._pageName']] or {}
local tbl = {}
for _,v in ipairs(results) do
local page = v['main_pages._pageName'] or v['monsters._pageName'] or ''
local name = v['monsters.name'] or v['areas.boss_monster_ids'] or ''
tbl[#tbl+1] = string.format('[[%s|%s]]', page, name)
end
h.na_or_val(tr, table.concat(tbl, '<br>'))
end,
order = 19001,
sort_type = 'text',
},
{
arg = {'boss', 'boss_number'},
header = i18n.item_table.boss_number,
fields = {'maps.area_id'},
display = function(tr, data, na, results2)
if results2['boss_query'] == nil then
results2['boss_query'] = m_cargo.query(
{'items', 'maps', 'areas', 'monsters', 'main_pages'},
{
'items._pageName',
'maps.area_id',
'areas.id',
'areas.boss_monster_ids',
'monsters._pageName',
'monsters.name',
'main_pages._pageName',
},
{
join=[[
items._pageID=maps._pageID,
maps.area_id=areas.id,
areas.boss_monster_ids HOLDS monsters.metadata_id,
monsters.metadata_id=main_pages.id
]],
where=string.format([[
items._pageID IN (%s)
AND maps.area_id IS NOT NULL
AND areas.boss_monster_ids HOLDS LIKE "%%"
]],
table.concat(results2.pageIDs, ', ')
),
orderBy='areas.boss_monster_ids',
}
)
results2['boss_query'] = m_cargo.map_results_to_id{
results=results2['boss_query'],
field='items._pageName',
}
end
local results = results2['boss_query'][data['items._pageName']] or {}
local tbl = {}
for _,v in ipairs(results) do
tbl[#tbl+1] = v['areas.boss_monster_ids']
end
h.na_or_val(tr, #tbl)
end,
order = 19002,
},
{
arg = {'legacy'},
header = i18n.item_table.legacy,
fields = {'items.name'},
display = function(tr, data, na, results2)
if results2['legacy_query'] == nil then
results2['legacy_query'] = m_cargo.query(
{'items', 'legacy_variants'},
{
'items._pageID',
'items._pageName',
'items.frame_type',
'legacy_variants.removal_version',
'legacy_variants.implicit_stat_text',
'legacy_variants.explicit_stat_text',
'legacy_variants.stat_text',
'legacy_variants.base_item',
'legacy_variants.required_level'
},
{
join='items._pageID=legacy_variants._pageID',
where='legacy_variants.removal_version IS NOT NULL',
where=string.format(
'items._pageID IN (%s) AND legacy_variants.removal_version IS NOT NULL',
table.concat(results2.pageIDs, ', ')
),
orderBy='items._pageName',
}
)
results2['legacy_query'] = m_cargo.map_results_to_id{
results=results2['legacy_query'],
field='items._pageName',
}
end
local results = results2['legacy_query'][data['items._pageName']] or {}
local tbl = mw.html.create('table')
:attr('width', '100%')
for _, v in ipairs(results) do
local cell = {}
local l = {
'legacy_variants.base_item',
'legacy_variants.stat_text'
}
-- Clean up data:
for _, k in ipairs(l) do
if v[k] ~= nil then
local s = m_util.string.split(v[k], '*')
local s_flt = {}
for _, sss in ipairs(s) do
if sss ~= nil and sss ~= '' then
s_flt[#s_flt+1] = string.gsub(sss, '\n', '')
end
end
cell[#cell+1] = table.concat(s_flt, '<br>')
end
end
local sep = string.format(
'<span class="item-stat-separator -%s"></span>',
v['items.frame_type']
)
tbl
:tag('tr')
:attr('class', 'upgraded-from-set')
:tag('td')
:wikitext(
v['legacy_variants.removal_version']
)
:done()
:tag('td')
:attr('class', 'group legacy-stats plainlist')
:wikitext(table.concat(cell, sep))
:done()
:done()
end
tr
:tag('td')
:node(tbl)
end,
order = 21001,
sort_type = 'text',
},
{
arg = {'granted_skills'},
header = i18n.item_table.granted_skills,
fields = {'items.name'},
display = function(tr, data, na, results2)
if results2['granted_skills_query'] == nil then
results2['granted_skills_query'] = m_cargo.query(
{'items', 'item_mods', 'mods', 'skill', 'items=items2'},
{
'items._pageName',
'items.name',
'item_mods.id',
'mods._pageName',
'mods.id',
'mods.granted_skill',
'mods.stat_text_raw',
'skill._pageName',
'skill.skill_id',
'skill.active_skill_name',
'skill.skill_icon',
'skill.stat_text',
'items2.class',
'items2.name',
'items2.inventory_icon',
'items2.size_x',
'items2.size_y',
'items2.html',
},
{
join='items._pageID=item_mods._pageID, item_mods.id=mods.id, mods.granted_skill=skill.skill_id, skill._pageID=items2._pageID',
where=string.format(
'items._pageID IN (%s) AND mods.granted_skill IS NOT NULL',
table.concat(results2.pageIDs, ', ')
),
}
)
results2['granted_skills_query'] = m_cargo.map_results_to_id{
results=results2['granted_skills_query'],
field='items._pageName',
}
end
local results = results2['granted_skills_query'][data['items._pageName']] or {}
local tbl = {}
for _, v in ipairs(results) do
-- Check if a level for the skill is specified in the
-- mod stat text.
-- Stat ids have unreliable naming convention so using
-- the mod stat text instead.
local level = ''
local stat_text = v['mods.stat_text_raw'] or ''
local level_number = string.match(
stat_text:lower(),
h.string.format(
i18n.item_table.granted_skills_level_pattern,
{
granted_skills_level_label = i18n.item_table.granted_skills_level_label:lower()
}
)
)
-- If a level number was specified in the stat text
-- then add it to the cell:
if level_number then
level = h.string.format(
i18n.item_table.granted_skills_level_format,
{
granted_skills_level_label = i18n.item_table.granted_skills_level_label,
level_number = level_number,
}
)
end
-- Use different formats depending on if it's a gem or
-- not:
if v['items2.class'] == nil then
tbl[#tbl+1] = h.string.format(
i18n.item_table.granted_skills_skill_output_format,
{
level = level,
sl = h.skill_link{
skip_query=true,
page = v['skill.active_skill_name']
or v['skill._pageName']
or v['mods._pageName']
or '',
name = v['skill.active_skill_name']
or v['skill.stat_text']
or v['mods.granted_skill'],
icon = v['skill.skill_icon'],
},
}
)
else
local il_args = {
skip_query=true,
page=v['items2._pageName'],
name=v['items2.name'],
inventory_icon=v['items2.inventory_icon'],
width=v['items2.size_x'],
height=v['items2.size_y'],
}
-- TODO: add in tpl_args.
if no_html == nil then
il_args.html = v['items2.html']
end
tbl[#tbl+1] = h.string.format(
i18n.item_table.granted_skills_gem_output_format,
{
level = level,
il = h.item_link(il_args),
}
)
end
end
h.na_or_val(tr, table.concat(tbl, '<br>'))
end,
order = 22001,
sort_type = 'text',
},
{
arg = 'alternate_art',
header = i18n.item_table.alternate_art,
fields = {'items.alternate_art_inventory_icons'},
display = function (tr, data)
local alt_art = m_util.string.split(
data['items.alternate_art_inventory_icons'],
','
)
-- TODO: Use il instead to handle size?
-- local size = 39
local out = {}
for i,v in ipairs(alt_art) do
out[#out+1] = string.format(
'[[%s|link=|%s]]',
v,
v
)
end
tr
:tag('td')
:wikitext(table.concat(out, ''))
end,
order = 23000,
sort_type = 'text',
},
}
data_map.skill_gem = {
{
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 = {'stat', 'stat_text'},
header = i18n.item_table.stats,
fields = {'skill.stat_text'},
display = h.tbl.display.factory.value{},
order = 2000,
sort_type = 'text',
},
{
arg = {'quality', 'quality_stat_text'},
header = i18n.item_table.quality_stats,
fields = {'skill.quality_stat_text'},
display = h.tbl.display.factory.value{},
order = 2001,
sort_type = 'text',
},
{
arg = 'description',
header = i18n.item_table.description,
fields = {'skill.description'},
display = h.tbl.display.factory.value{},
order = 2100,
sort_type = 'text',
},
{
arg = 'level',
header = m_game.level_requirement.icon,
fields = h.tbl.range_fields{field='items.required_level'},
display = h.tbl.display.factory.range{field='items.required_level'},
order = 3000,
},
{
arg = 'str',
header = '[[File:StrengthIcon small.png|link=|alt=Gem has strength requirement]]',
fields = {'skill_gems.strength_percent'},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['skill_gems.strength_percent'])
:wikitext('[[File:Yes.png|yes|link=]]')
end,
order = 3001,
},
{
arg = 'dex',
header = '[[File:DexterityIcon small.png|link=|alt=Gem has dexterity requirement]]',
fields = {'skill_gems.dexterity_percent'},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['skill_gems.dexterity_percent'])
:wikitext('[[File:Yes.png|yes|link=]]')
end,
order = 3002,
},
{
arg = 'int',
header = '[[File:IntelligenceIcon small.png|link=|alt=Gem has intelligence requirement]]',
fields = {'skill_gems.intelligence_percent'},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['skill_gems.intelligence_percent'])
:wikitext('[[File:Yes.png|yes|link=]]')
end,
order = 3003,
},
{
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 = {'aspd', 'attack_speed', 'attack_speed_multiplier'},
header = i18n.item_table.attack_speed_multiplier,
fields = {'skill_levels.attack_speed_multiplier'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='%s%%',
skill_levels = true,
},
}},
order = 4002,
options = {
[1] = {
skill_levels = true,
},
},
},
{
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 = 4003,
options = {
[1] = {
skill_levels = true,
},
},
},
{
arg = {'mcm', 'cost_multiplier'},
header = i18n.item_table.cost_multiplier,
fields = {'skill_levels.cost_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.cost_amounts', 'skill_levels.mana_reservation_percent', 'skill_levels.mana_reservation_flat'},
display = function (tr, data, fields, data2)
local appendix = ''
local cost_field = ''
local sdata = data2.skill_levels[data['items._pageName']]
-- Percentage Mana reservation is in level 0 of most of the skill_levels at this point, but spellslinger and awakened blasphemy change it per-level
-- Flat Mana resservation is in real gem levels, but maybe there will be fixed flat mana reservations in the future.
-- Per-use mana costs are stored in the real gem levels if they change, or in 0 if it's fixed.
if(sdata['1']['skill_levels.mana_reservation_percent'] ~= nil or sdata['0']['skill_levels.mana_reservation_percent'] ~= nil) then
cost_field = 'skill_levels.mana_reservation_percent'
appendix = appendix .. '%%'
elseif(sdata['1']['skill_levels.mana_reservation_flat'] ~= nil or sdata['0']['skill_levels.mana_reservation_flat'] ~= nil) then
cost_field = 'skill_levels.mana_reservation_flat'
appendix = appendix .. ' ' .. i18n.item_table.reserves_mana_suffix
elseif(sdata['1']['skill_levels.cost_amounts'] ~= nil or sdata['0']['skill_levels.cost_amounts'] ~= nil) then
cost_field = 'skill_levels.cost_amounts'
end
h.tbl.display.factory.value{options = {
[1] = {
fmt='%d' .. appendix,
skill_levels = true,
},
}}(tr, data, {cost_field}, data2)
end,
order = 5001,
-- Need one set of options per field.
options = {
[1] = {
skill_levels = true,
},
[2] = {
skill_levels = true,
},
[3] = {
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'}(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'}(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'}(data['skill.radius_tertiary']))
end,
order = 7002,
},
}
-- ----------------------------------------------------------------------------
-- Main functions
-- ----------------------------------------------------------------------------
local function _item_table(args)
--[[
Creates a generic table for items.
Examples
--------
= p.item_table{
q_tables='vendor_rewards, quest_rewards, skill_gems',
q_join='items._pageID=vendor_rewards._pageID, items._pageID=quest_rewards._pageID, items._pageID=skill_gems._pageID',
q_where='(items.class="Active Skill Gems" OR items.class = "Support Skill Gems") AND (vendor_rewards.quest_id IS NOT NULL OR quest_rewards.quest_id IS NOT NULL)',
vendor=1,
}
]]
m_item_util = m_item_util or require('Module:Item util')
local t = os.clock()
args.mode = args.mode or 'item'
local modes = {
skill = {
data = data_map.skill_gem,
header = i18n.item_table.skill_gem,
},
item = {
data = data_map.generic_item,
header = i18n.item_table.item,
},
}
if modes[args.mode] == nil then
error(i18n.errors.invalid_item_table_mode)
end
local results2 = {
stats = {},
skill_levels = {},
pageIDs = {},
}
local row_infos = {}
for _, row_info in ipairs(modes[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(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(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 = {}
for i=1, math.huge do -- repeat until no more columns are found
local prefix = string.format('stat_column%s_', i)
if args[prefix .. 'stat1_id'] == nil then
-- Each column requires at least one stat id
break
end
local col_info = {
header = args[prefix .. 'header'] or tostring(i),
format = args[prefix .. 'format'],
stat_format = args[prefix .. 'stat_format'] or 'separate',
order = tonumber(args[prefix .. 'order']) or (10000000 + i),
stats = {},
}
for j=1, math.huge do
local stat_id = args[string.format('%sstat%s_id', prefix, j)]
if stat_id == nil then
break
end
table.insert(col_info.stats, {id=stat_id})
query_stats[stat_id] = true
end
table.insert(stat_columns, col_info)
end
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] = m_util.html.format_value(args, 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, m_util.html.format_value(args, 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(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
-- Namespace condition
-- This is mainly to prevent items from user pages or other testing pages
-- from being returned in the query results.
if args.namespaces ~= 'any' then
local namespaces = m_util.cast.table(args.namespaces, {callback=m_util.cast.number})
if #namespaces > 0 then
namespaces = table.concat(namespaces, ',')
else
namespaces = m_item_util.get_item_namespaces{format = 'list'}
end
query.where = string.format('(%s) AND items._pageNamespace IN (%s)', query.where, namespaces)
end
local skill_levels = {}
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 tables and fields so they can be retrieved correctly:
local tables = {}
for table_name,_ in pairs(tables_assoc) do
tables[#tables+1] = table_name
end
local tbls = table.concat(tables,',') .. (query.tables or '')
query.tables = m_util.string.split(tbls, ',')
for index, field in ipairs(fields) do
fields[index] = string.format('%s=%s', field, field)
end
query.fields = fields
-- Take care of the minimum required joins, joins from templates
-- must still be userdefined:
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
-- Needed to eliminate duplicates supplied via table joins:
query.groupBy = 'items._pageID' .. (query.groupBy or '')
-- Query results:
local results = m_cargo.query(query.tables, query.fields, query)
if #results == 0 and args.default ~= nil then
return args.default
end
if #results > 0 then
-- Create a list of found pageIDs for column specific queries:
for _,v in ipairs(results) do
results2.pageIDs[#results2.pageIDs+1] = v['items._pageID']
end
-- 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_cargo.query(
{'skill_levels'},
skill_levels,
{
where=table.concat(pages, ' OR '),
groupBy='skill_levels._pageID, skill_levels.level',
}
)
-- 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 stat_results = m_cargo.query(
{'items', 'item_stats'},
{'item_stats._pageName', 'item_stats.id', 'item_stats.min', 'item_stats.max', 'item_stats.avg'},
{
join = 'items._pageID=item_stats._pageID',
where = string.format(
'item_stats._pageID IN (%s) AND item_stats.id IN ("%s")',
table.concat(m_util.table.column(results, 'items._pageID'), ','),
table.concat(m_util.table.keys(query_stats), '","')
),
groupBy = 'items._pageID, item_stats.id',
}
)
for _, row in ipairs(stat_results) 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
end
end
--
-- Display the table
--
local tbl = mw.html.create('table')
tbl:attr('class', 'wikitable sortable item-table')
if m_util.cast.boolean(args.responsive) then
tbl:addClass('responsive-table')
end
-- Headers:
local tr = tbl:tag('tr')
tr
:tag('th')
:wikitext(modes[args.mode].header)
:done()
for _, row_info in ipairs(row_infos) do
local th = tr:tag('th')
if row_info.colspan then
th:attr('colspan', row_info.colspan)
end
th
:attr('data-sort-type', row_info.sort_type or 'number')
:wikitext(row_info.header)
end
-- Rows:
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'],
width=row['items.size_x'],
height=row['items.size_y'],
}
if args.no_html == nil then
il_args.html = row['items.html']
end
if args.large then
il_args.large = args.large
end
tr
:tag('td')
:wikitext(h.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] == nil or 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
local cats = {}
if #results == query.limit then
cats[#cats+1] = i18n.categories.query_limit
end
if #results == 0 then
cats[#cats+1] = i18n.categories.no_results
end
mw.logObject({os.clock() - t, query})
return tostring(tbl) .. m_util.misc.add_category(cats, {ignore_blacklist=args.debug})
end
-------------------------------------------------------------------------------
-- Map item drops
-------------------------------------------------------------------------------
local function _map_item_drops(args)
--[[
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)'}
]]
local tables = {'maps'}
local fields = {
'maps.area_id',
}
local query = {
-- Only need each page name once
groupBy = 'maps._pageName',
}
if args.page then
-- Join with _pageData in order to check for page redirect
tables[#tables+1] = '_pageData'
fields[#fields+1] = '_pageData._pageNameOrRedirect'
query.where = string.format(
'_pageData._pageName="%s"',
args.page
)
query.join = 'maps._pageName = _pageData._pageNameOrRedirect'
else
query.where = string.format(
'maps._pageName="%s"',
tostring(mw.title.getCurrentTitle())
)
end
query.where = query.where .. ' AND maps.area_id > ""' -- area_id must not be empty or null
local results = m_cargo.query(tables, fields, query)
local id = ''
if #results > 0 then
id = results[1]['maps.area_id']
end
return mw.getCurrentFrame():expandTemplate{ title = 'Area item drops', args = {area_id=id} }
end
-------------------------------------------------------------------------------
-- Prophecy description
-------------------------------------------------------------------------------
local function _prophecy_description(args)
args.page = args.page or tostring(mw.title.getCurrentTitle())
local results = m_cargo.query(
{'prophecies'},
{'prophecies.objective', 'prophecies.reward'},
{
where=string.format('prophecies._pageName="%s"', 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
local function _simple_item_list(args)
--[[
Creates a simple list of items.
Examples
--------
= p.simple_item_list{
q_tables='maps',
q_join='items._pageID=maps._pageID',
q_where='maps.tier=1 AND items.drop_enabled=1 AND items.rarity_id="normal"',
no_html=1,
link_from_name=1,
}
]]
local query = {}
for key, value in pairs(args) do
if string.sub(key, 0, 2) == 'q_' then
query[string.sub(key, 3)] = value
end
end
local fields = {
'items._pageName',
'items.name',
'items.class',
}
if args.no_icon == nil then
fields[#fields+1] = 'items.inventory_icon'
end
if args.no_html == nil then
fields[#fields+1] = 'items.html'
end
local tables = m_util.cast.table(args.q_tables)
table.insert(tables, 1, 'items')
query.groupBy = query.groupBy or 'items._pageID'
local results = m_cargo.query(
tables,
fields,
query
)
local out = {}
for _, row in ipairs(results) do
local page
if args.use_name_as_link ~= nil then
page = row['items.name']
else
page = row['items._pageName']
end
local link = h.item_link{
page=page,
name=row['items.name'],
inventory_icon=row['items.inventory_icon'] or '',
html=row['items.html'] or '',
skip_query=true
}
if args.format == nil then
out[#out+1] = string.format('* %s', link)
elseif args.format == 'none' then
out[#out+1] = link
elseif args.format == 'li' then
out[#out+1] = string.format('<li>%s</li>', link)
else
error(string.format(i18n.errors.generic_argument_parameter, 'format', args.format))
end
end
if args.format == nil then
return table.concat(out, '\n')
elseif args.format == 'none' then
return table.concat(out, '\n')
elseif args.format == 'li' then
return table.concat(out)
end
end
-- ----------------------------------------------------------------------------
-- Exported functions
-- ----------------------------------------------------------------------------
local p = {}
--
-- Template:Item table
--
p.item_table = m_util.misc.invoker_factory(_item_table, {
parentFirst = true,
})
p.main = p.item_table
--
-- Template:Map item drops
--
p.map_item_drops = m_util.misc.invoker_factory(_map_item_drops, {
parentFirst = true,
})
--
-- Template:Prophecy description
--
p.prophecy_description = m_util.misc.invoker_factory(_prophecy_description, {
parentFirst = true,
})
--
-- Template:Simple item list
--
p.simple_item_list = m_util.misc.invoker_factory(_simple_item_list, {
parentFirst = true,
})
-- ----------------------------------------------------------------------------
-- Debug
-- ----------------------------------------------------------------------------
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)
end
return p